mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Bug 724748 - simplify RegExpShared lifetime management (r=cdleary)
This commit is contained in:
parent
11d130df7f
commit
330f1dd2d0
@ -38,7 +38,7 @@
|
||||
*
|
||||
* ***** END LICENSE BLOCK ***** */
|
||||
|
||||
#include "jsinfer.h"
|
||||
#include "jscntxt.h"
|
||||
|
||||
#include "builtin/RegExp.h"
|
||||
|
||||
@ -126,13 +126,13 @@ CreateRegExpMatchResult(JSContext *cx, JSString *input, const jschar *chars, siz
|
||||
|
||||
template <class T>
|
||||
bool
|
||||
ExecuteRegExpImpl(JSContext *cx, RegExpStatics *res, T *re, JSLinearString *input,
|
||||
ExecuteRegExpImpl(JSContext *cx, RegExpStatics *res, T &re, JSLinearString *input,
|
||||
const jschar *chars, size_t length,
|
||||
size_t *lastIndex, RegExpExecType type, Value *rval)
|
||||
{
|
||||
LifoAllocScope allocScope(&cx->tempLifoAlloc());
|
||||
MatchPairs *matchPairs = NULL;
|
||||
RegExpRunStatus status = re->execute(cx, chars, length, lastIndex, allocScope, &matchPairs);
|
||||
RegExpRunStatus status = re.execute(cx, chars, length, lastIndex, &matchPairs);
|
||||
|
||||
switch (status) {
|
||||
case RegExpRunStatus_Error:
|
||||
@ -159,15 +159,15 @@ ExecuteRegExpImpl(JSContext *cx, RegExpStatics *res, T *re, JSLinearString *inpu
|
||||
}
|
||||
|
||||
bool
|
||||
js::ExecuteRegExp(JSContext *cx, RegExpStatics *res, const RegExpMatcher &matcher, JSLinearString *input,
|
||||
js::ExecuteRegExp(JSContext *cx, RegExpStatics *res, RegExpShared &shared, JSLinearString *input,
|
||||
const jschar *chars, size_t length,
|
||||
size_t *lastIndex, RegExpExecType type, Value *rval)
|
||||
{
|
||||
return ExecuteRegExpImpl(cx, res, &matcher, input, chars, length, lastIndex, type, rval);
|
||||
return ExecuteRegExpImpl(cx, res, shared, input, chars, length, lastIndex, type, rval);
|
||||
}
|
||||
|
||||
bool
|
||||
js::ExecuteRegExp(JSContext *cx, RegExpStatics *res, RegExpObject *reobj, JSLinearString *input,
|
||||
js::ExecuteRegExp(JSContext *cx, RegExpStatics *res, RegExpObject &reobj, JSLinearString *input,
|
||||
const jschar *chars, size_t length,
|
||||
size_t *lastIndex, RegExpExecType type, Value *rval)
|
||||
{
|
||||
@ -175,8 +175,8 @@ js::ExecuteRegExp(JSContext *cx, RegExpStatics *res, RegExpObject *reobj, JSLine
|
||||
}
|
||||
|
||||
/* Note: returns the original if no escaping need be performed. */
|
||||
static JSLinearString *
|
||||
EscapeNakedForwardSlashes(JSContext *cx, JSLinearString *unescaped)
|
||||
static JSAtom *
|
||||
EscapeNakedForwardSlashes(JSContext *cx, JSAtom *unescaped)
|
||||
{
|
||||
size_t oldLen = unescaped->length();
|
||||
const jschar *oldChars = unescaped->chars();
|
||||
@ -203,7 +203,7 @@ EscapeNakedForwardSlashes(JSContext *cx, JSLinearString *unescaped)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return sb.empty() ? unescaped : sb.finishString();
|
||||
return sb.empty() ? unescaped : sb.finishAtom();
|
||||
}
|
||||
|
||||
/*
|
||||
@ -230,25 +230,46 @@ CompileRegExpObject(JSContext *cx, RegExpObjectBuilder &builder, CallArgs args)
|
||||
}
|
||||
|
||||
Value sourceValue = args[0];
|
||||
|
||||
/*
|
||||
* If we get passed in an object whose internal [[Class]] property is
|
||||
* "RegExp", return a new object with the same source/flags.
|
||||
*/
|
||||
if (IsObjectWithClass(sourceValue, ESClass_RegExp, cx)) {
|
||||
/*
|
||||
* If we get passed in a |RegExpObject| source we return a new
|
||||
* object with the same source/flags.
|
||||
*
|
||||
* Note: the regexp static flags are not taken into consideration here.
|
||||
* Beware, sourceObj may be a (transparent) proxy to a RegExp, so only
|
||||
* use generic (proxyable) operations on sourceObj that do not assume
|
||||
* sourceObj.isRegExp().
|
||||
*/
|
||||
JSObject &sourceObj = sourceValue.toObject();
|
||||
|
||||
if (args.length() >= 2 && !args[1].isUndefined()) {
|
||||
JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_NEWREGEXP_FLAGGED);
|
||||
return false;
|
||||
}
|
||||
|
||||
RegExpShared *shared = RegExpToShared(cx, sourceObj);
|
||||
if (!shared)
|
||||
/*
|
||||
* Only extract the 'flags' out of sourceObj; do not reuse the
|
||||
* RegExpShared since it may be from a different compartment.
|
||||
*/
|
||||
RegExpFlag flags;
|
||||
{
|
||||
RegExpShared *shared = RegExpToShared(cx, sourceObj);
|
||||
if (!shared)
|
||||
return false;
|
||||
|
||||
flags = shared->getFlags();
|
||||
}
|
||||
|
||||
/*
|
||||
* 'toSource' is a permanent read-only property, so this is equivalent
|
||||
* to executing RegExpObject::getSource on the unwrapped object.
|
||||
*/
|
||||
Value v;
|
||||
if (!sourceObj.getProperty(cx, cx->runtime->atomState.sourceAtom, &v))
|
||||
return false;
|
||||
|
||||
shared->incref(cx);
|
||||
RegExpObject *reobj = builder.build(AlreadyIncRefed<RegExpShared>(shared));
|
||||
RegExpObject *reobj = builder.build(&v.toString()->asAtom(), flags);
|
||||
if (!reobj)
|
||||
return false;
|
||||
|
||||
@ -256,16 +277,17 @@ CompileRegExpObject(JSContext *cx, RegExpObjectBuilder &builder, CallArgs args)
|
||||
return true;
|
||||
}
|
||||
|
||||
JSLinearString *sourceStr;
|
||||
JSAtom *source;
|
||||
if (sourceValue.isUndefined()) {
|
||||
sourceStr = cx->runtime->emptyString;
|
||||
source = cx->runtime->emptyString;
|
||||
} else {
|
||||
/* Coerce to string and compile. */
|
||||
JSString *str = ToString(cx, sourceValue);
|
||||
if (!str)
|
||||
return false;
|
||||
sourceStr = str->ensureLinear(cx);
|
||||
if (!sourceStr)
|
||||
|
||||
source = js_AtomizeString(cx, str);
|
||||
if (!source)
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -279,11 +301,11 @@ CompileRegExpObject(JSContext *cx, RegExpObjectBuilder &builder, CallArgs args)
|
||||
return false;
|
||||
}
|
||||
|
||||
JSLinearString *escapedSourceStr = EscapeNakedForwardSlashes(cx, sourceStr);
|
||||
JSAtom *escapedSourceStr = EscapeNakedForwardSlashes(cx, source);
|
||||
if (!escapedSourceStr)
|
||||
return false;
|
||||
|
||||
if (!CheckRegExpSyntax(cx, escapedSourceStr))
|
||||
if (!js::detail::RegExpCode::checkSyntax(cx, NULL, escapedSourceStr))
|
||||
return false;
|
||||
|
||||
RegExpStatics *res = cx->regExpStatics();
|
||||
@ -500,6 +522,39 @@ js_InitRegExpClass(JSContext *cx, JSObject *obj)
|
||||
return proto;
|
||||
}
|
||||
|
||||
|
||||
static const jschar GreedyStarChars[] = {'.', '*'};
|
||||
|
||||
static inline bool
|
||||
StartsWithGreedyStar(JSAtom *source)
|
||||
{
|
||||
return false;
|
||||
|
||||
#if 0
|
||||
if (source->length() < 3)
|
||||
return false;
|
||||
|
||||
const jschar *chars = source->chars();
|
||||
return chars[0] == GreedyStarChars[0] &&
|
||||
chars[1] == GreedyStarChars[1] &&
|
||||
chars[2] != '?';
|
||||
#endif
|
||||
}
|
||||
|
||||
static inline RegExpShared *
|
||||
GetSharedForGreedyStar(JSContext *cx, JSAtom *source, RegExpFlag flags)
|
||||
{
|
||||
if (RegExpShared *hit = cx->compartment->regExps.lookupHack(cx, source, flags))
|
||||
return hit;
|
||||
|
||||
JSAtom *hackedSource = js_AtomizeChars(cx, source->chars() + ArrayLength(GreedyStarChars),
|
||||
source->length() - ArrayLength(GreedyStarChars));
|
||||
if (!hackedSource)
|
||||
return NULL;
|
||||
|
||||
return cx->compartment->regExps.getHack(cx, source, hackedSource, flags);
|
||||
}
|
||||
|
||||
/*
|
||||
* ES5 15.10.6.2 (and 15.10.6.3, which calls 15.10.6.2).
|
||||
*
|
||||
@ -519,17 +574,16 @@ ExecuteRegExp(JSContext *cx, Native native, uintN argc, Value *vp)
|
||||
|
||||
RegExpObject &reobj = obj->asRegExp();
|
||||
|
||||
RegExpMatcher matcher(cx);
|
||||
if (reobj.startsWithAtomizedGreedyStar()) {
|
||||
if (!matcher.initWithTestOptimized(reobj))
|
||||
return false;
|
||||
} else {
|
||||
RegExpShared *shared = reobj.getShared(cx);
|
||||
if (!shared)
|
||||
return false;
|
||||
matcher.init(NeedsIncRef<RegExpShared>(shared));
|
||||
}
|
||||
RegExpShared *shared;
|
||||
if (StartsWithGreedyStar(reobj.getSource()))
|
||||
shared = GetSharedForGreedyStar(cx, reobj.getSource(), reobj.getFlags());
|
||||
else
|
||||
shared = reobj.getShared(cx);
|
||||
|
||||
if (!shared)
|
||||
return false;
|
||||
|
||||
RegExpShared::Guard re(*shared);
|
||||
RegExpStatics *res = cx->regExpStatics();
|
||||
|
||||
/* Step 2. */
|
||||
@ -553,7 +607,7 @@ ExecuteRegExp(JSContext *cx, Native native, uintN argc, Value *vp)
|
||||
return false;
|
||||
|
||||
/* Steps 6-7 (with sticky extension). */
|
||||
if (!matcher.global() && !matcher.sticky())
|
||||
if (!re->global() && !re->sticky())
|
||||
i = 0;
|
||||
|
||||
/* Step 9a. */
|
||||
@ -566,13 +620,13 @@ ExecuteRegExp(JSContext *cx, Native native, uintN argc, Value *vp)
|
||||
/* Steps 8-21. */
|
||||
RegExpExecType execType = (native == regexp_test) ? RegExpTest : RegExpExec;
|
||||
size_t lastIndexInt(i);
|
||||
if (!ExecuteRegExp(cx, res, matcher, linearInput, chars, length, &lastIndexInt, execType,
|
||||
if (!ExecuteRegExp(cx, res, *re, linearInput, chars, length, &lastIndexInt, execType,
|
||||
&args.rval())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Step 11 (with sticky extension). */
|
||||
if (matcher.global() || (!args.rval().isNull() && matcher.sticky())) {
|
||||
if (re->global() || (!args.rval().isNull() && re->sticky())) {
|
||||
if (args.rval().isNull())
|
||||
reobj.zeroLastIndex();
|
||||
else
|
||||
|
@ -59,12 +59,12 @@ namespace js {
|
||||
* |chars| and |length|.
|
||||
*/
|
||||
bool
|
||||
ExecuteRegExp(JSContext *cx, RegExpStatics *res, RegExpObject *reobj,
|
||||
ExecuteRegExp(JSContext *cx, RegExpStatics *res, RegExpObject &reobj,
|
||||
JSLinearString *input, const jschar *chars, size_t length,
|
||||
size_t *lastIndex, RegExpExecType type, Value *rval);
|
||||
|
||||
bool
|
||||
ExecuteRegExp(JSContext *cx, RegExpStatics *res, const RegExpMatcher &matcher,
|
||||
ExecuteRegExp(JSContext *cx, RegExpStatics *res, RegExpShared &shared,
|
||||
JSLinearString *input, const jschar *chars, size_t length,
|
||||
size_t *lastIndex, RegExpExecType type, Value *rval);
|
||||
|
||||
|
@ -702,7 +702,6 @@ JSRuntime::JSRuntime()
|
||||
tempLifoAlloc(TEMP_LIFO_ALLOC_PRIMARY_CHUNK_SIZE),
|
||||
execAlloc_(NULL),
|
||||
bumpAlloc_(NULL),
|
||||
reCache_(NULL),
|
||||
nativeStackBase(0),
|
||||
nativeStackQuota(0),
|
||||
interpreterFrames(NULL),
|
||||
@ -850,7 +849,6 @@ JSRuntime::~JSRuntime()
|
||||
|
||||
delete_<JSC::ExecutableAllocator>(execAlloc_);
|
||||
delete_<WTF::BumpPointerAllocator>(bumpAlloc_);
|
||||
JS_ASSERT(!reCache_);
|
||||
|
||||
#ifdef DEBUG
|
||||
/* Don't hurt everyone in leaky ol' Mozilla with a fatal JS_ASSERT! */
|
||||
@ -6361,7 +6359,7 @@ JS_ExecuteRegExp(JSContext *cx, JSObject *obj, JSObject *reobj, jschar *chars, s
|
||||
CHECK_REQUEST(cx);
|
||||
|
||||
RegExpStatics *res = obj->asGlobal().getRegExpStatics();
|
||||
return ExecuteRegExp(cx, res, &reobj->asRegExp(), NULL, chars, length,
|
||||
return ExecuteRegExp(cx, res, reobj->asRegExp(), NULL, chars, length,
|
||||
indexp, test ? RegExpTest : RegExpExec, rval);
|
||||
}
|
||||
|
||||
@ -6393,7 +6391,7 @@ JS_ExecuteRegExpNoStatics(JSContext *cx, JSObject *obj, jschar *chars, size_t le
|
||||
AssertNoGC(cx);
|
||||
CHECK_REQUEST(cx);
|
||||
|
||||
return ExecuteRegExp(cx, NULL, &obj->asRegExp(), NULL, chars, length, indexp,
|
||||
return ExecuteRegExp(cx, NULL, obj->asRegExp(), NULL, chars, length, indexp,
|
||||
test ? RegExpTest : RegExpExec, rval);
|
||||
}
|
||||
|
||||
|
@ -148,23 +148,6 @@ JSRuntime::createBumpPointerAllocator(JSContext *cx)
|
||||
return bumpAlloc_;
|
||||
}
|
||||
|
||||
RegExpCache *
|
||||
JSRuntime::createRegExpCache(JSContext *cx)
|
||||
{
|
||||
JS_ASSERT(!reCache_);
|
||||
JS_ASSERT(cx->runtime == this);
|
||||
|
||||
RegExpCache *newCache = new_<RegExpCache>(this);
|
||||
if (!newCache || !newCache->init()) {
|
||||
js_ReportOutOfMemory(cx);
|
||||
delete_<RegExpCache>(newCache);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
reCache_ = newCache;
|
||||
return reCache_;
|
||||
}
|
||||
|
||||
JSScript *
|
||||
js_GetCurrentScript(JSContext *cx)
|
||||
{
|
||||
@ -1170,9 +1153,6 @@ JSRuntime::purge(JSContext *cx)
|
||||
|
||||
/* FIXME: bug 506341 */
|
||||
propertyCache.purge(cx);
|
||||
|
||||
delete_<RegExpCache>(reCache_);
|
||||
reCache_ = NULL;
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -96,11 +96,6 @@ class InterpreterFrames;
|
||||
class ScriptOpcodeCounts;
|
||||
struct ScriptOpcodeCountsPair;
|
||||
|
||||
typedef HashMap<JSAtom *,
|
||||
detail::RegExpCacheValue,
|
||||
DefaultHasher<JSAtom *>,
|
||||
RuntimeAllocPolicy> RegExpCache;
|
||||
|
||||
/*
|
||||
* GetSrcNote cache to avoid O(n^2) growth in finding a source note for a
|
||||
* given pc in a script. We use the script->code pointer to tag the cache,
|
||||
@ -220,11 +215,9 @@ struct JSRuntime : js::RuntimeFriendFields
|
||||
*/
|
||||
JSC::ExecutableAllocator *execAlloc_;
|
||||
WTF::BumpPointerAllocator *bumpAlloc_;
|
||||
js::RegExpCache *reCache_;
|
||||
|
||||
JSC::ExecutableAllocator *createExecutableAllocator(JSContext *cx);
|
||||
WTF::BumpPointerAllocator *createBumpPointerAllocator(JSContext *cx);
|
||||
js::RegExpCache *createRegExpCache(JSContext *cx);
|
||||
|
||||
public:
|
||||
JSC::ExecutableAllocator *getExecutableAllocator(JSContext *cx) {
|
||||
@ -233,12 +226,6 @@ struct JSRuntime : js::RuntimeFriendFields
|
||||
WTF::BumpPointerAllocator *getBumpPointerAllocator(JSContext *cx) {
|
||||
return bumpAlloc_ ? bumpAlloc_ : createBumpPointerAllocator(cx);
|
||||
}
|
||||
js::RegExpCache *maybeRegExpCache() {
|
||||
return reCache_;
|
||||
}
|
||||
js::RegExpCache *getRegExpCache(JSContext *cx) {
|
||||
return reCache_ ? reCache_ : createRegExpCache(cx);
|
||||
}
|
||||
|
||||
/* Base address of the native stack for the current thread. */
|
||||
uintptr_t nativeStackBase;
|
||||
|
@ -85,6 +85,7 @@ JSCompartment::JSCompartment(JSRuntime *rt)
|
||||
#ifdef JS_METHODJIT
|
||||
jaegerCompartment_(NULL),
|
||||
#endif
|
||||
regExps(rt),
|
||||
propertyTree(thisForCtor()),
|
||||
emptyTypeObject(NULL),
|
||||
debugModeBits(rt->debugMode ? DebugFromC : 0),
|
||||
@ -121,6 +122,9 @@ JSCompartment::init(JSContext *cx)
|
||||
if (!crossCompartmentWrappers.init())
|
||||
return false;
|
||||
|
||||
if (!regExps.init(cx))
|
||||
return false;
|
||||
|
||||
if (!scriptFilenameTable.init())
|
||||
return false;
|
||||
|
||||
@ -549,6 +553,7 @@ void
|
||||
JSCompartment::purge(JSContext *cx)
|
||||
{
|
||||
arenas.purge();
|
||||
regExps.purge();
|
||||
dtoaCache.purge();
|
||||
|
||||
/*
|
||||
|
@ -50,6 +50,7 @@
|
||||
#include "jsobj.h"
|
||||
#include "jsscope.h"
|
||||
#include "vm/GlobalObject.h"
|
||||
#include "vm/RegExpObject.h"
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning(push)
|
||||
@ -244,6 +245,8 @@ struct JSCompartment
|
||||
size_t sizeOfMjitCode() const;
|
||||
#endif
|
||||
|
||||
js::RegExpCompartment regExps;
|
||||
|
||||
size_t sizeOfShapeTable(JSMallocSizeOfFun mallocSizeOf);
|
||||
void sizeOfTypeInferenceData(JSContext *cx, JS::TypeInferenceSizes *stats,
|
||||
JSMallocSizeOfFun mallocSizeOf);
|
||||
|
@ -2882,7 +2882,7 @@ BEGIN_CASE(JSOP_REGEXP)
|
||||
JSObject *proto = regs.fp()->scopeChain().global().getOrCreateRegExpPrototype(cx);
|
||||
if (!proto)
|
||||
goto error;
|
||||
JSObject *obj = js_CloneRegExpObject(cx, script->getRegExp(index), proto);
|
||||
JSObject *obj = CloneRegExpObject(cx, script->getRegExp(index), proto);
|
||||
if (!obj)
|
||||
goto error;
|
||||
PUSH_OBJECT(*obj);
|
||||
|
@ -124,18 +124,12 @@ struct ArgumentsData;
|
||||
struct Class;
|
||||
|
||||
class RegExpObject;
|
||||
class RegExpMatcher;
|
||||
class RegExpObjectBuilder;
|
||||
class RegExpShared;
|
||||
class RegExpStatics;
|
||||
class MatchPairs;
|
||||
|
||||
namespace detail {
|
||||
|
||||
class RegExpCode;
|
||||
class RegExpCacheValue;
|
||||
|
||||
} /* namespace detail */
|
||||
namespace detail { class RegExpCode; }
|
||||
|
||||
enum RegExpFlag
|
||||
{
|
||||
|
@ -2820,7 +2820,7 @@ ASTSerializer::literal(ParseNode *pn, Value *dst)
|
||||
if (!js_GetClassPrototype(cx, &cx->fp()->scopeChain(), JSProto_RegExp, &proto))
|
||||
return false;
|
||||
|
||||
JSObject *re2 = js_CloneRegExpObject(cx, re1, proto);
|
||||
JSObject *re2 = CloneRegExpObject(cx, re1, proto);
|
||||
if (!re2)
|
||||
return false;
|
||||
|
||||
|
172
js/src/jsstr.cpp
172
js/src/jsstr.cpp
@ -1332,10 +1332,10 @@ str_trimRight(JSContext *cx, uintN argc, Value *vp)
|
||||
/* Result of a successfully performed flat match. */
|
||||
class FlatMatch
|
||||
{
|
||||
JSLinearString *patstr;
|
||||
const jschar *pat;
|
||||
size_t patlen;
|
||||
int32_t match_;
|
||||
JSAtom *patstr;
|
||||
const jschar *pat;
|
||||
size_t patlen;
|
||||
int32_t match_;
|
||||
|
||||
friend class RegExpGuard;
|
||||
|
||||
@ -1351,6 +1351,30 @@ class FlatMatch
|
||||
int32_t match() const { return match_; }
|
||||
};
|
||||
|
||||
static inline bool
|
||||
IsRegExpMetaChar(jschar c)
|
||||
{
|
||||
switch (c) {
|
||||
/* Taken from the PatternCharacter production in 15.10.1. */
|
||||
case '^': case '$': case '\\': case '.': case '*': case '+':
|
||||
case '?': case '(': case ')': case '[': case ']': case '{':
|
||||
case '}': case '|':
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static inline bool
|
||||
HasRegExpMetaChars(const jschar *chars, size_t length)
|
||||
{
|
||||
for (size_t i = 0; i < length; ++i) {
|
||||
if (IsRegExpMetaChar(chars[i]))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* RegExpGuard factors logic out of String regexp operations.
|
||||
*
|
||||
@ -1362,9 +1386,8 @@ class RegExpGuard
|
||||
RegExpGuard(const RegExpGuard &) MOZ_DELETE;
|
||||
void operator=(const RegExpGuard &) MOZ_DELETE;
|
||||
|
||||
JSContext *cx;
|
||||
RegExpMatcher matcher;
|
||||
FlatMatch fm;
|
||||
RegExpShared::Guard re_;
|
||||
FlatMatch fm;
|
||||
|
||||
/*
|
||||
* Upper bound on the number of characters we are willing to potentially
|
||||
@ -1372,7 +1395,9 @@ class RegExpGuard
|
||||
*/
|
||||
static const size_t MAX_FLAT_PAT_LEN = 256;
|
||||
|
||||
static JSLinearString *flattenPattern(JSContext *cx, JSLinearString *patstr) {
|
||||
static JSAtom *
|
||||
flattenPattern(JSContext *cx, JSAtom *patstr)
|
||||
{
|
||||
StringBuffer sb(cx);
|
||||
if (!sb.reserve(patstr->length()))
|
||||
return NULL;
|
||||
@ -1389,30 +1414,31 @@ class RegExpGuard
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
return sb.finishString();
|
||||
return sb.finishAtom();
|
||||
}
|
||||
|
||||
public:
|
||||
explicit RegExpGuard(JSContext *cx) : cx(cx), matcher(cx) {}
|
||||
~RegExpGuard() {}
|
||||
RegExpGuard() {}
|
||||
|
||||
/* init must succeed in order to call tryFlatMatch or normalizeRegExp. */
|
||||
bool
|
||||
init(CallArgs args, bool convertVoid = false)
|
||||
bool init(JSContext *cx, CallArgs args, bool convertVoid = false)
|
||||
{
|
||||
if (args.length() != 0 && IsObjectWithClass(args[0], ESClass_RegExp, cx)) {
|
||||
RegExpShared *shared = RegExpToShared(cx, args[0].toObject());
|
||||
if (!shared)
|
||||
return false;
|
||||
|
||||
matcher.init(NeedsIncRef<RegExpShared>(shared));
|
||||
re_.init(*shared);
|
||||
} else {
|
||||
if (convertVoid && (args.length() == 0 || args[0].isUndefined())) {
|
||||
fm.patstr = cx->runtime->emptyString;
|
||||
return true;
|
||||
}
|
||||
|
||||
fm.patstr = ArgToRootedString(cx, args, 0);
|
||||
JSString *arg = ArgToRootedString(cx, args, 0);
|
||||
if (!arg)
|
||||
return false;
|
||||
|
||||
fm.patstr = js_AtomizeString(cx, arg);
|
||||
if (!fm.patstr)
|
||||
return false;
|
||||
}
|
||||
@ -1433,7 +1459,7 @@ class RegExpGuard
|
||||
tryFlatMatch(JSContext *cx, JSString *textstr, uintN optarg, uintN argc,
|
||||
bool checkMetaChars = true)
|
||||
{
|
||||
if (matcher.initialized())
|
||||
if (re_.initialized())
|
||||
return NULL;
|
||||
|
||||
fm.pat = fm.patstr->chars();
|
||||
@ -1463,41 +1489,40 @@ class RegExpGuard
|
||||
}
|
||||
|
||||
/* If the pattern is not already a regular expression, make it so. */
|
||||
const RegExpMatcher *
|
||||
normalizeRegExp(bool flat, uintN optarg, CallArgs args)
|
||||
bool normalizeRegExp(JSContext *cx, bool flat, uintN optarg, CallArgs args)
|
||||
{
|
||||
if (matcher.initialized())
|
||||
return &matcher;
|
||||
if (re_.initialized())
|
||||
return true;
|
||||
|
||||
/* Build RegExp from pattern string. */
|
||||
JSString *opt;
|
||||
if (optarg < args.length()) {
|
||||
opt = ToString(cx, args[optarg]);
|
||||
if (!opt)
|
||||
return NULL;
|
||||
return false;
|
||||
} else {
|
||||
opt = NULL;
|
||||
}
|
||||
|
||||
JSLinearString *patstr;
|
||||
JSAtom *patstr;
|
||||
if (flat) {
|
||||
patstr = flattenPattern(cx, fm.patstr);
|
||||
if (!patstr)
|
||||
return NULL;
|
||||
return false;
|
||||
} else {
|
||||
patstr = fm.patstr;
|
||||
}
|
||||
JS_ASSERT(patstr);
|
||||
|
||||
if (!matcher.init(patstr, opt))
|
||||
return NULL;
|
||||
RegExpShared *re = cx->compartment->regExps.get(cx, patstr, opt);
|
||||
if (!re)
|
||||
return false;
|
||||
|
||||
return &matcher;
|
||||
re_.init(*re);
|
||||
return true;
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
bool matcherInitialized() const { return matcher.initialized(); }
|
||||
#endif
|
||||
RegExpShared ®Exp() { return *re_; }
|
||||
};
|
||||
|
||||
/* ExecuteRegExp indicates success in two ways, based on the 'test' flag. */
|
||||
@ -1525,7 +1550,7 @@ enum MatchControlFlags {
|
||||
|
||||
/* Factor out looping and matching logic. */
|
||||
static bool
|
||||
DoMatch(JSContext *cx, RegExpStatics *res, JSString *str, const RegExpMatcher &matcher,
|
||||
DoMatch(JSContext *cx, RegExpStatics *res, JSString *str, RegExpShared &re,
|
||||
DoMatchCallback callback, void *data, MatchControlFlags flags, Value *rval)
|
||||
{
|
||||
JSLinearString *linearStr = str->ensureLinear(cx);
|
||||
@ -1535,10 +1560,10 @@ DoMatch(JSContext *cx, RegExpStatics *res, JSString *str, const RegExpMatcher &m
|
||||
const jschar *chars = linearStr->chars();
|
||||
size_t length = linearStr->length();
|
||||
|
||||
if (matcher.global()) {
|
||||
if (re.global()) {
|
||||
RegExpExecType type = (flags & TEST_GLOBAL_BIT) ? RegExpTest : RegExpExec;
|
||||
for (size_t count = 0, i = 0, length = str->length(); i <= length; ++count) {
|
||||
if (!ExecuteRegExp(cx, res, matcher, linearStr, chars, length, &i, type, rval))
|
||||
if (!ExecuteRegExp(cx, res, re, linearStr, chars, length, &i, type, rval))
|
||||
return false;
|
||||
if (!Matched(type, *rval))
|
||||
break;
|
||||
@ -1551,7 +1576,7 @@ DoMatch(JSContext *cx, RegExpStatics *res, JSString *str, const RegExpMatcher &m
|
||||
RegExpExecType type = (flags & TEST_SINGLE_BIT) ? RegExpTest : RegExpExec;
|
||||
bool callbackOnSingle = !!(flags & CALLBACK_ON_SINGLE_BIT);
|
||||
size_t i = 0;
|
||||
if (!ExecuteRegExp(cx, res, matcher, linearStr, chars, length, &i, type, rval))
|
||||
if (!ExecuteRegExp(cx, res, re, linearStr, chars, length, &i, type, rval))
|
||||
return false;
|
||||
if (callbackOnSingle && Matched(type, *rval) && !callback(cx, res, 0, data))
|
||||
return false;
|
||||
@ -1613,29 +1638,28 @@ js::str_match(JSContext *cx, uintN argc, Value *vp)
|
||||
if (!str)
|
||||
return false;
|
||||
|
||||
RegExpGuard g(cx);
|
||||
if (!g.init(args, true))
|
||||
RegExpGuard g;
|
||||
if (!g.init(cx, args, true))
|
||||
return false;
|
||||
|
||||
if (const FlatMatch *fm = g.tryFlatMatch(cx, str, 1, args.length()))
|
||||
return BuildFlatMatchArray(cx, str, *fm, &args);
|
||||
|
||||
/* Return if there was an error in tryFlatMatch. */
|
||||
if (cx->isExceptionPending()) /* from tryFlatMatch */
|
||||
if (cx->isExceptionPending())
|
||||
return false;
|
||||
|
||||
const RegExpMatcher *matcher = g.normalizeRegExp(false, 1, args);
|
||||
if (!matcher)
|
||||
if (!g.normalizeRegExp(cx, false, 1, args))
|
||||
return false;
|
||||
|
||||
JSObject *array = NULL;
|
||||
MatchArgType arg = &array;
|
||||
RegExpStatics *res = cx->regExpStatics();
|
||||
Value rval;
|
||||
if (!DoMatch(cx, res, str, *matcher, MatchCallback, arg, MATCH_ARGS, &rval))
|
||||
if (!DoMatch(cx, res, str, g.regExp(), MatchCallback, arg, MATCH_ARGS, &rval))
|
||||
return false;
|
||||
|
||||
if (matcher->global())
|
||||
if (g.regExp().global())
|
||||
args.rval() = ObjectOrNullValue(array);
|
||||
else
|
||||
args.rval() = rval;
|
||||
@ -1650,8 +1674,8 @@ js::str_search(JSContext *cx, uintN argc, Value *vp)
|
||||
if (!str)
|
||||
return false;
|
||||
|
||||
RegExpGuard g(cx);
|
||||
if (!g.init(args, true))
|
||||
RegExpGuard g;
|
||||
if (!g.init(cx, args, true))
|
||||
return false;
|
||||
if (const FlatMatch *fm = g.tryFlatMatch(cx, str, 1, args.length())) {
|
||||
args.rval() = Int32Value(fm->match());
|
||||
@ -1661,8 +1685,7 @@ js::str_search(JSContext *cx, uintN argc, Value *vp)
|
||||
if (cx->isExceptionPending()) /* from tryFlatMatch */
|
||||
return false;
|
||||
|
||||
const RegExpMatcher *matcher = g.normalizeRegExp(false, 1, args);
|
||||
if (!matcher)
|
||||
if (!g.normalizeRegExp(cx, false, 1, args))
|
||||
return false;
|
||||
|
||||
JSLinearString *linearStr = str->ensureLinear(cx);
|
||||
@ -1676,7 +1699,7 @@ js::str_search(JSContext *cx, uintN argc, Value *vp)
|
||||
/* Per ECMAv5 15.5.4.12 (5) The last index property is ignored and left unchanged. */
|
||||
size_t i = 0;
|
||||
Value result;
|
||||
if (!ExecuteRegExp(cx, res, *matcher, linearStr, chars, length, &i, RegExpTest, &result))
|
||||
if (!ExecuteRegExp(cx, res, g.regExp(), linearStr, chars, length, &i, RegExpTest, &result))
|
||||
return false;
|
||||
|
||||
if (result.isTrue())
|
||||
@ -1689,7 +1712,7 @@ js::str_search(JSContext *cx, uintN argc, Value *vp)
|
||||
struct ReplaceData
|
||||
{
|
||||
ReplaceData(JSContext *cx)
|
||||
: g(cx), sb(cx)
|
||||
: sb(cx)
|
||||
{}
|
||||
|
||||
JSString *str; /* 'this' parameter object as a string */
|
||||
@ -2117,16 +2140,17 @@ BuildDollarReplacement(JSContext *cx, JSString *textstrArg, JSLinearString *reps
|
||||
static inline bool
|
||||
str_replace_regexp(JSContext *cx, CallArgs args, ReplaceData &rdata)
|
||||
{
|
||||
const RegExpMatcher *matcher = rdata.g.normalizeRegExp(true, 2, args);
|
||||
if (!matcher)
|
||||
if (!rdata.g.normalizeRegExp(cx, true, 2, args))
|
||||
return false;
|
||||
|
||||
rdata.leftIndex = 0;
|
||||
rdata.calledBack = false;
|
||||
|
||||
RegExpStatics *res = cx->regExpStatics();
|
||||
RegExpShared &re = rdata.g.regExp();
|
||||
|
||||
Value tmp;
|
||||
if (!DoMatch(cx, res, rdata.str, *matcher, ReplaceRegExpCallback, &rdata, REPLACE_ARGS, &tmp))
|
||||
if (!DoMatch(cx, res, rdata.str, re, ReplaceRegExpCallback, &rdata, REPLACE_ARGS, &tmp))
|
||||
return false;
|
||||
|
||||
if (!rdata.calledBack) {
|
||||
@ -2211,7 +2235,7 @@ js::str_replace(JSContext *cx, uintN argc, Value *vp)
|
||||
if (!rdata.str)
|
||||
return false;
|
||||
|
||||
if (!rdata.g.init(args))
|
||||
if (!rdata.g.init(cx, args))
|
||||
return false;
|
||||
|
||||
/* Extract replacement string/function. */
|
||||
@ -2283,7 +2307,6 @@ js::str_replace(JSContext *cx, uintN argc, Value *vp)
|
||||
if (!fm) {
|
||||
if (cx->isExceptionPending()) /* oom in RopeMatch in tryFlatMatch */
|
||||
return false;
|
||||
JS_ASSERT_IF(!rdata.g.matcherInitialized(), args.length() > ReplaceOptArg);
|
||||
return str_replace_regexp(cx, args, rdata);
|
||||
}
|
||||
|
||||
@ -2459,25 +2482,27 @@ SplitHelper(JSContext *cx, JSLinearString *str, uint32_t limit, Matcher splitMat
|
||||
|
||||
/*
|
||||
* The SplitMatch operation from ES5 15.5.4.14 is implemented using different
|
||||
* matchers for regular expression and string separators.
|
||||
* paths for regular expression and string separators.
|
||||
*
|
||||
* The algorithm differs from the spec in that the matchers return the next
|
||||
* index at which a match happens.
|
||||
* The algorithm differs from the spec in that the we return the next index at
|
||||
* which a match happens.
|
||||
*/
|
||||
class SplitRegExpMatcher {
|
||||
class SplitRegExpMatcher
|
||||
{
|
||||
RegExpShared &re;
|
||||
RegExpStatics *res;
|
||||
RegExpMatcher &matcher;
|
||||
|
||||
public:
|
||||
static const bool returnsCaptures = true;
|
||||
SplitRegExpMatcher(RegExpMatcher &matcher, RegExpStatics *res) : res(res), matcher(matcher) {}
|
||||
SplitRegExpMatcher(RegExpShared &re, RegExpStatics *res) : re(re), res(res) {}
|
||||
|
||||
inline bool operator()(JSContext *cx, JSLinearString *str, size_t index,
|
||||
SplitMatchResult *result) {
|
||||
static const bool returnsCaptures = true;
|
||||
|
||||
bool operator()(JSContext *cx, JSLinearString *str, size_t index, SplitMatchResult *result)
|
||||
{
|
||||
Value rval = UndefinedValue();
|
||||
const jschar *chars = str->chars();
|
||||
size_t length = str->length();
|
||||
if (!ExecuteRegExp(cx, res, matcher, str, chars, length, &index, RegExpTest, &rval))
|
||||
if (!ExecuteRegExp(cx, res, re, str, chars, length, &index, RegExpTest, &rval))
|
||||
return false;
|
||||
if (!rval.isTrue()) {
|
||||
result->setFailure();
|
||||
@ -2491,19 +2516,21 @@ class SplitRegExpMatcher {
|
||||
}
|
||||
};
|
||||
|
||||
class SplitStringMatcher {
|
||||
class SplitStringMatcher
|
||||
{
|
||||
const jschar *sepChars;
|
||||
size_t sepLength;
|
||||
|
||||
public:
|
||||
static const bool returnsCaptures = false;
|
||||
SplitStringMatcher(JSLinearString *sep) {
|
||||
sepChars = sep->chars();
|
||||
sepLength = sep->length();
|
||||
}
|
||||
|
||||
inline bool operator()(JSContext *cx, JSLinearString *str, size_t index,
|
||||
SplitMatchResult *res) {
|
||||
static const bool returnsCaptures = false;
|
||||
|
||||
bool operator()(JSContext *cx, JSLinearString *str, size_t index, SplitMatchResult *res)
|
||||
{
|
||||
JS_ASSERT(index == 0 || index < str->length());
|
||||
const jschar *chars = str->chars();
|
||||
jsint match = StringMatch(chars + index, str->length() - index, sepChars, sepLength);
|
||||
@ -2543,7 +2570,7 @@ js::str_split(JSContext *cx, uintN argc, Value *vp)
|
||||
}
|
||||
|
||||
/* Step 8. */
|
||||
RegExpMatcher matcher(cx);
|
||||
RegExpShared::Guard re;
|
||||
JSLinearString *sepstr = NULL;
|
||||
bool sepUndefined = (args.length() == 0 || args[0].isUndefined());
|
||||
if (!sepUndefined) {
|
||||
@ -2551,7 +2578,7 @@ js::str_split(JSContext *cx, uintN argc, Value *vp)
|
||||
RegExpShared *shared = RegExpToShared(cx, args[0].toObject());
|
||||
if (!shared)
|
||||
return false;
|
||||
matcher.init(NeedsIncRef<RegExpShared>(shared));
|
||||
re.init(*shared);
|
||||
} else {
|
||||
sepstr = ArgToRootedString(cx, args, 0);
|
||||
if (!sepstr)
|
||||
@ -2585,13 +2612,10 @@ js::str_split(JSContext *cx, uintN argc, Value *vp)
|
||||
|
||||
/* Steps 11-15. */
|
||||
JSObject *aobj;
|
||||
if (!matcher.initialized()) {
|
||||
// NB: sepstr is anchored through its storage in args[0].
|
||||
if (!re.initialized())
|
||||
aobj = SplitHelper(cx, strlin, limit, SplitStringMatcher(sepstr), type);
|
||||
} else {
|
||||
aobj = SplitHelper(cx, strlin, limit,
|
||||
SplitRegExpMatcher(matcher, cx->regExpStatics()), type);
|
||||
}
|
||||
else
|
||||
aobj = SplitHelper(cx, strlin, limit, SplitRegExpMatcher(*re, cx->regExpStatics()), type);
|
||||
if (!aobj)
|
||||
return false;
|
||||
|
||||
|
@ -192,96 +192,6 @@ class AutoScopedAssign
|
||||
~AutoScopedAssign() { *addr = old; }
|
||||
};
|
||||
|
||||
template <class RefCountable>
|
||||
class AlreadyIncRefed
|
||||
{
|
||||
typedef RefCountable *****ConvertibleToBool;
|
||||
|
||||
RefCountable *obj;
|
||||
|
||||
public:
|
||||
explicit AlreadyIncRefed(RefCountable *obj = NULL) : obj(obj) {}
|
||||
|
||||
bool null() const { return obj == NULL; }
|
||||
operator ConvertibleToBool() const { return (ConvertibleToBool)obj; }
|
||||
|
||||
RefCountable *operator->() const { JS_ASSERT(!null()); return obj; }
|
||||
RefCountable &operator*() const { JS_ASSERT(!null()); return *obj; }
|
||||
RefCountable *get() const { return obj; }
|
||||
};
|
||||
|
||||
template <class RefCountable>
|
||||
class NeedsIncRef
|
||||
{
|
||||
typedef RefCountable *****ConvertibleToBool;
|
||||
|
||||
RefCountable *obj;
|
||||
|
||||
public:
|
||||
explicit NeedsIncRef(RefCountable *obj = NULL) : obj(obj) {}
|
||||
|
||||
bool null() const { return obj == NULL; }
|
||||
operator ConvertibleToBool() const { return (ConvertibleToBool)obj; }
|
||||
|
||||
RefCountable *operator->() const { JS_ASSERT(!null()); return obj; }
|
||||
RefCountable &operator*() const { JS_ASSERT(!null()); return *obj; }
|
||||
RefCountable *get() const { return obj; }
|
||||
};
|
||||
|
||||
template <class RefCountable>
|
||||
class AutoRefCount
|
||||
{
|
||||
typedef RefCountable *****ConvertibleToBool;
|
||||
|
||||
JSContext *const cx;
|
||||
RefCountable *obj;
|
||||
|
||||
AutoRefCount(const AutoRefCount &other) MOZ_DELETE;
|
||||
void operator=(const AutoRefCount &other) MOZ_DELETE;
|
||||
|
||||
public:
|
||||
explicit AutoRefCount(JSContext *cx)
|
||||
: cx(cx), obj(NULL)
|
||||
{}
|
||||
|
||||
AutoRefCount(JSContext *cx, NeedsIncRef<RefCountable> aobj)
|
||||
: cx(cx), obj(aobj.get())
|
||||
{
|
||||
if (obj)
|
||||
obj->incref(cx);
|
||||
}
|
||||
|
||||
AutoRefCount(JSContext *cx, AlreadyIncRefed<RefCountable> aobj)
|
||||
: cx(cx), obj(aobj.get())
|
||||
{}
|
||||
|
||||
~AutoRefCount() {
|
||||
if (obj)
|
||||
obj->decref(cx);
|
||||
}
|
||||
|
||||
void reset(NeedsIncRef<RefCountable> aobj) {
|
||||
if (obj)
|
||||
obj->decref(cx);
|
||||
obj = aobj.get();
|
||||
if (obj)
|
||||
obj->incref(cx);
|
||||
}
|
||||
|
||||
void reset(AlreadyIncRefed<RefCountable> aobj) {
|
||||
if (obj)
|
||||
obj->decref(cx);
|
||||
obj = aobj.get();
|
||||
}
|
||||
|
||||
bool null() const { return obj == NULL; }
|
||||
operator ConvertibleToBool() const { return (ConvertibleToBool)obj; }
|
||||
|
||||
RefCountable *operator->() const { JS_ASSERT(!null()); return obj; }
|
||||
RefCountable &operator*() const { JS_ASSERT(!null()); return *obj; }
|
||||
RefCountable *get() const { return obj; }
|
||||
};
|
||||
|
||||
template <class T>
|
||||
JS_ALWAYS_INLINE static void
|
||||
PodZero(T *t)
|
||||
|
@ -226,7 +226,7 @@ JS_XDRFindClassById(JSXDRState *xdr, uint32_t id);
|
||||
* and saved versions. If deserialization fails, the data should be
|
||||
* invalidated if possible.
|
||||
*/
|
||||
#define JSXDR_BYTECODE_VERSION (0xb973c0de - 106)
|
||||
#define JSXDR_BYTECODE_VERSION (0xb973c0de - 107)
|
||||
|
||||
/*
|
||||
* Library-private functions.
|
||||
|
@ -6939,10 +6939,10 @@ mjit::Compiler::jsop_regexp()
|
||||
}
|
||||
|
||||
/*
|
||||
* Force creation of the RegExpPrivate in the script's RegExpObject
|
||||
* Force creation of the RegExpShared in the script's RegExpObject
|
||||
* so that we grab it in the getNewObject template copy. Note that
|
||||
* JIT code is discarded on every GC, which permits us to burn in
|
||||
* the pointer to the RegExpPrivate refcount.
|
||||
* the pointer to the RegExpShared.
|
||||
*/
|
||||
if (!reobj->getShared(cx))
|
||||
return false;
|
||||
@ -6956,10 +6956,6 @@ mjit::Compiler::jsop_regexp()
|
||||
stubcc.masm.move(ImmPtr(obj), Registers::ArgReg1);
|
||||
OOL_STUBCALL(stubs::RegExp, REJOIN_FALLTHROUGH);
|
||||
|
||||
/* Bump the refcount on the wrapped RegExp. */
|
||||
size_t *refcount = reobj->addressOfPrivateRefCount();
|
||||
masm.add32(Imm32(1), AbsoluteAddress(refcount));
|
||||
|
||||
frame.pushTypedPayload(JSVAL_TYPE_OBJECT, result);
|
||||
|
||||
stubcc.rejoin(Changes(1));
|
||||
|
@ -1104,7 +1104,7 @@ stubs::RegExp(VMFrame &f, JSObject *regex)
|
||||
if (!proto)
|
||||
THROW();
|
||||
JS_ASSERT(proto);
|
||||
JSObject *obj = js_CloneRegExpObject(f.cx, regex, proto);
|
||||
JSObject *obj = CloneRegExpObject(f.cx, regex, proto);
|
||||
if (!obj)
|
||||
THROW();
|
||||
f.regs.sp[0].setObject(*obj);
|
||||
|
@ -59,150 +59,25 @@ JSObject::asRegExp()
|
||||
|
||||
namespace js {
|
||||
|
||||
inline bool
|
||||
IsRegExpMetaChar(jschar c)
|
||||
inline RegExpShared &
|
||||
RegExpObject::shared() const
|
||||
{
|
||||
switch (c) {
|
||||
/* Taken from the PatternCharacter production in 15.10.1. */
|
||||
case '^': case '$': case '\\': case '.': case '*': case '+':
|
||||
case '?': case '(': case ')': case '[': case ']': case '{':
|
||||
case '}': case '|':
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
JS_ASSERT(JSObject::getPrivate() != NULL);
|
||||
return *static_cast<RegExpShared *>(JSObject::getPrivate());
|
||||
}
|
||||
|
||||
inline bool
|
||||
HasRegExpMetaChars(const jschar *chars, size_t length)
|
||||
inline RegExpShared *
|
||||
RegExpObject::maybeShared()
|
||||
{
|
||||
for (size_t i = 0; i < length; ++i) {
|
||||
if (IsRegExpMetaChar(chars[i]))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
return static_cast<RegExpShared *>(JSObject::getPrivate());
|
||||
}
|
||||
|
||||
inline bool
|
||||
RegExpObject::startsWithAtomizedGreedyStar() const
|
||||
inline RegExpShared *
|
||||
RegExpObject::getShared(JSContext *cx)
|
||||
{
|
||||
JSLinearString *source = getSource();
|
||||
|
||||
if (!source->isAtom())
|
||||
return false;
|
||||
|
||||
if (source->length() < 3)
|
||||
return false;
|
||||
|
||||
const jschar *chars = source->chars();
|
||||
return chars[0] == detail::GreedyStarChars[0] &&
|
||||
chars[1] == detail::GreedyStarChars[1] &&
|
||||
chars[2] != '?';
|
||||
}
|
||||
|
||||
inline size_t *
|
||||
RegExpObject::addressOfPrivateRefCount() const
|
||||
{
|
||||
return shared().addressOfRefCount();
|
||||
}
|
||||
|
||||
inline RegExpObject *
|
||||
RegExpObject::create(JSContext *cx, RegExpStatics *res, const jschar *chars, size_t length,
|
||||
RegExpFlag flags, TokenStream *tokenStream)
|
||||
{
|
||||
RegExpFlag staticsFlags = res->getFlags();
|
||||
return createNoStatics(cx, chars, length, RegExpFlag(flags | staticsFlags), tokenStream);
|
||||
}
|
||||
|
||||
inline RegExpObject *
|
||||
RegExpObject::createNoStatics(JSContext *cx, const jschar *chars, size_t length,
|
||||
RegExpFlag flags, TokenStream *tokenStream)
|
||||
{
|
||||
JSAtom *source = js_AtomizeChars(cx, chars, length);
|
||||
if (!source)
|
||||
return NULL;
|
||||
|
||||
return createNoStatics(cx, source, flags, tokenStream);
|
||||
}
|
||||
|
||||
inline RegExpObject *
|
||||
RegExpObject::createNoStatics(JSContext *cx, JSAtom *source, RegExpFlag flags,
|
||||
TokenStream *tokenStream)
|
||||
{
|
||||
if (!RegExpCode::checkSyntax(cx, tokenStream, source))
|
||||
return NULL;
|
||||
|
||||
RegExpObjectBuilder builder(cx);
|
||||
return builder.build(source, flags);
|
||||
}
|
||||
|
||||
inline void
|
||||
RegExpObject::purge(JSContext *cx)
|
||||
{
|
||||
if (RegExpShared *shared = maybeShared()) {
|
||||
shared->decref(cx);
|
||||
JSObject::setPrivate(NULL);
|
||||
}
|
||||
}
|
||||
|
||||
inline void
|
||||
RegExpObject::finalize(JSContext *cx)
|
||||
{
|
||||
purge(cx);
|
||||
#ifdef DEBUG
|
||||
JSObject::setPrivate((void *) 0x1); /* Non-null but still in the zero page. */
|
||||
#endif
|
||||
}
|
||||
|
||||
inline bool
|
||||
RegExpObject::init(JSContext *cx, JSLinearString *source, RegExpFlag flags)
|
||||
{
|
||||
if (nativeEmpty()) {
|
||||
if (isDelegate()) {
|
||||
if (!assignInitialShape(cx))
|
||||
return false;
|
||||
} else {
|
||||
Shape *shape = assignInitialShape(cx);
|
||||
if (!shape)
|
||||
return false;
|
||||
EmptyShape::insertInitialShape(cx, shape, getProto());
|
||||
}
|
||||
JS_ASSERT(!nativeEmpty());
|
||||
}
|
||||
|
||||
DebugOnly<JSAtomState *> atomState = &cx->runtime->atomState;
|
||||
JS_ASSERT(nativeLookup(cx, ATOM_TO_JSID(atomState->lastIndexAtom))->slot() == LAST_INDEX_SLOT);
|
||||
JS_ASSERT(nativeLookup(cx, ATOM_TO_JSID(atomState->sourceAtom))->slot() == SOURCE_SLOT);
|
||||
JS_ASSERT(nativeLookup(cx, ATOM_TO_JSID(atomState->globalAtom))->slot() == GLOBAL_FLAG_SLOT);
|
||||
JS_ASSERT(nativeLookup(cx, ATOM_TO_JSID(atomState->ignoreCaseAtom))->slot() ==
|
||||
IGNORE_CASE_FLAG_SLOT);
|
||||
JS_ASSERT(nativeLookup(cx, ATOM_TO_JSID(atomState->multilineAtom))->slot() ==
|
||||
MULTILINE_FLAG_SLOT);
|
||||
JS_ASSERT(nativeLookup(cx, ATOM_TO_JSID(atomState->stickyAtom))->slot() == STICKY_FLAG_SLOT);
|
||||
|
||||
JS_ASSERT(!maybeShared());
|
||||
zeroLastIndex();
|
||||
setSource(source);
|
||||
setGlobal(flags & GlobalFlag);
|
||||
setIgnoreCase(flags & IgnoreCaseFlag);
|
||||
setMultiline(flags & MultilineFlag);
|
||||
setSticky(flags & StickyFlag);
|
||||
return true;
|
||||
}
|
||||
|
||||
inline void
|
||||
RegExpMatcher::init(NeedsIncRef<RegExpShared> shared)
|
||||
{
|
||||
JS_ASSERT(!shared_);
|
||||
shared_.reset(shared);
|
||||
}
|
||||
|
||||
inline bool
|
||||
RegExpMatcher::init(JSLinearString *patstr, JSString *opt)
|
||||
{
|
||||
JS_ASSERT(!shared_);
|
||||
shared_.reset(RegExpShared::create(cx_, patstr, opt, NULL));
|
||||
return !!shared_;
|
||||
if (RegExpShared *shared = maybeShared())
|
||||
return shared;
|
||||
return createShared(cx);
|
||||
}
|
||||
|
||||
inline void
|
||||
@ -224,7 +99,7 @@ RegExpObject::zeroLastIndex()
|
||||
}
|
||||
|
||||
inline void
|
||||
RegExpObject::setSource(JSLinearString *source)
|
||||
RegExpObject::setSource(JSAtom *source)
|
||||
{
|
||||
setSlot(SOURCE_SLOT, StringValue(source));
|
||||
}
|
||||
@ -253,97 +128,6 @@ RegExpObject::setSticky(bool enabled)
|
||||
setSlot(STICKY_FLAG_SLOT, BooleanValue(enabled));
|
||||
}
|
||||
|
||||
/* RegExpShared inlines. */
|
||||
|
||||
inline bool
|
||||
RegExpShared::cacheLookup(JSContext *cx, JSAtom *atom, RegExpFlag flags,
|
||||
RegExpCacheKind targetKind,
|
||||
AlreadyIncRefed<RegExpShared> *result)
|
||||
{
|
||||
RegExpCache *cache = cx->runtime->getRegExpCache(cx);
|
||||
if (!cache)
|
||||
return false;
|
||||
|
||||
if (RegExpCache::Ptr p = cache->lookup(atom)) {
|
||||
RegExpCacheValue &cacheValue = p->value;
|
||||
if (cacheValue.kind() == targetKind && cacheValue.shared().getFlags() == flags) {
|
||||
cacheValue.shared().incref(cx);
|
||||
*result = AlreadyIncRefed<RegExpShared>(&cacheValue.shared());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
JS_ASSERT(result->null());
|
||||
return true;
|
||||
}
|
||||
|
||||
inline bool
|
||||
RegExpShared::cacheInsert(JSContext *cx, JSAtom *atom, RegExpCacheKind kind,
|
||||
RegExpShared &shared)
|
||||
{
|
||||
/*
|
||||
* Note: allocation performed since lookup may cause a garbage collection,
|
||||
* so we have to re-lookup the cache (and inside the cache) after the
|
||||
* allocation is performed.
|
||||
*/
|
||||
RegExpCache *cache = cx->runtime->getRegExpCache(cx);
|
||||
if (!cache)
|
||||
return false;
|
||||
|
||||
if (RegExpCache::AddPtr addPtr = cache->lookupForAdd(atom)) {
|
||||
/* We clobber existing entries with the same source (but different flags or kind). */
|
||||
JS_ASSERT(addPtr->value.shared().getFlags() != shared.getFlags() ||
|
||||
addPtr->value.kind() != kind);
|
||||
addPtr->value.reset(shared, kind);
|
||||
} else {
|
||||
if (!cache->add(addPtr, atom, RegExpCacheValue(shared, kind))) {
|
||||
js_ReportOutOfMemory(cx);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
inline AlreadyIncRefed<RegExpShared>
|
||||
RegExpShared::create(JSContext *cx, JSLinearString *source, RegExpFlag flags, TokenStream *ts)
|
||||
{
|
||||
typedef AlreadyIncRefed<RegExpShared> RetType;
|
||||
|
||||
/*
|
||||
* We choose to only cache |RegExpShared|s who have atoms as
|
||||
* sources, under the unverified premise that non-atoms will have a
|
||||
* low hit rate (both hit ratio and absolute number of hits).
|
||||
*/
|
||||
bool cacheable = source->isAtom();
|
||||
if (!cacheable)
|
||||
return RetType(RegExpShared::createUncached(cx, source, flags, ts));
|
||||
|
||||
/*
|
||||
* Refcount note: not all |RegExpShared|s are cached so we need to keep a
|
||||
* refcount. The cache holds a "weak ref", where the |RegExpShared|'s
|
||||
* deallocation decref will first cause it to remove itself from the cache.
|
||||
*/
|
||||
|
||||
JSAtom *sourceAtom = &source->asAtom();
|
||||
|
||||
AlreadyIncRefed<RegExpShared> cached;
|
||||
if (!cacheLookup(cx, sourceAtom, flags, detail::RegExpCache_ExecCapable, &cached))
|
||||
return RetType(NULL);
|
||||
|
||||
if (cached)
|
||||
return cached;
|
||||
|
||||
RegExpShared *shared = RegExpShared::createUncached(cx, source, flags, ts);
|
||||
if (!shared)
|
||||
return RetType(NULL);
|
||||
|
||||
if (!cacheInsert(cx, sourceAtom, detail::RegExpCache_ExecCapable, *shared))
|
||||
return RetType(NULL);
|
||||
|
||||
return RetType(shared);
|
||||
}
|
||||
|
||||
/* This function should be deleted once bad Android platforms phase out. See bug 604774. */
|
||||
inline bool
|
||||
detail::RegExpCode::isJITRuntimeEnabled(JSContext *cx)
|
||||
@ -355,151 +139,6 @@ detail::RegExpCode::isJITRuntimeEnabled(JSContext *cx)
|
||||
#endif
|
||||
}
|
||||
|
||||
inline bool
|
||||
detail::RegExpCode::compile(JSContext *cx, JSLinearString &pattern, TokenStream *ts,
|
||||
uintN *parenCount, RegExpFlag flags)
|
||||
{
|
||||
#if ENABLE_YARR_JIT
|
||||
/* Parse the pattern. */
|
||||
ErrorCode yarrError;
|
||||
YarrPattern yarrPattern(pattern, bool(flags & IgnoreCaseFlag), bool(flags & MultilineFlag),
|
||||
&yarrError);
|
||||
if (yarrError) {
|
||||
reportYarrError(cx, ts, yarrError);
|
||||
return false;
|
||||
}
|
||||
*parenCount = yarrPattern.m_numSubpatterns;
|
||||
|
||||
/*
|
||||
* The YARR JIT compiler attempts to compile the parsed pattern. If
|
||||
* it cannot, it informs us via |codeBlock.isFallBack()|, in which
|
||||
* case we have to bytecode compile it.
|
||||
*/
|
||||
|
||||
#ifdef JS_METHODJIT
|
||||
if (isJITRuntimeEnabled(cx) && !yarrPattern.m_containsBackreferences) {
|
||||
JSC::ExecutableAllocator *execAlloc = cx->runtime->getExecutableAllocator(cx);
|
||||
if (!execAlloc) {
|
||||
js_ReportOutOfMemory(cx);
|
||||
return false;
|
||||
}
|
||||
|
||||
JSGlobalData globalData(execAlloc);
|
||||
jitCompile(yarrPattern, &globalData, codeBlock);
|
||||
if (!codeBlock.isFallBack())
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
WTF::BumpPointerAllocator *bumpAlloc = cx->runtime->getBumpPointerAllocator(cx);
|
||||
if (!bumpAlloc) {
|
||||
js_ReportOutOfMemory(cx);
|
||||
return false;
|
||||
}
|
||||
|
||||
codeBlock.setFallBack(true);
|
||||
byteCode = byteCompile(yarrPattern, bumpAlloc).get();
|
||||
return true;
|
||||
#else /* !defined(ENABLE_YARR_JIT) */
|
||||
int error = 0;
|
||||
compiled = jsRegExpCompile(pattern.chars(), pattern.length(),
|
||||
ignoreCase() ? JSRegExpIgnoreCase : JSRegExpDoNotIgnoreCase,
|
||||
multiline() ? JSRegExpMultiline : JSRegExpSingleLine,
|
||||
parenCount, &error);
|
||||
if (error) {
|
||||
reportPCREError(cx, error);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
|
||||
inline bool
|
||||
RegExpShared::compile(JSContext *cx, TokenStream *ts)
|
||||
{
|
||||
if (!sticky())
|
||||
return code.compile(cx, *source, ts, &parenCount, getFlags());
|
||||
|
||||
/*
|
||||
* The sticky case we implement hackily by prepending a caret onto the front
|
||||
* and relying on |::execute| to pseudo-slice the string when it sees a sticky regexp.
|
||||
*/
|
||||
static const jschar prefix[] = {'^', '(', '?', ':'};
|
||||
static const jschar postfix[] = {')'};
|
||||
|
||||
using mozilla::ArrayLength;
|
||||
StringBuffer sb(cx);
|
||||
if (!sb.reserve(ArrayLength(prefix) + source->length() + ArrayLength(postfix)))
|
||||
return false;
|
||||
sb.infallibleAppend(prefix, ArrayLength(prefix));
|
||||
sb.infallibleAppend(source->chars(), source->length());
|
||||
sb.infallibleAppend(postfix, ArrayLength(postfix));
|
||||
|
||||
JSLinearString *fakeySource = sb.finishString();
|
||||
if (!fakeySource)
|
||||
return false;
|
||||
return code.compile(cx, *fakeySource, ts, &parenCount, getFlags());
|
||||
}
|
||||
|
||||
inline RegExpRunStatus
|
||||
detail::RegExpCode::execute(JSContext *cx, const jschar *chars, size_t length, size_t start,
|
||||
int *output, size_t outputCount)
|
||||
{
|
||||
int result;
|
||||
#if ENABLE_YARR_JIT
|
||||
(void) cx; /* Unused. */
|
||||
if (codeBlock.isFallBack())
|
||||
result = JSC::Yarr::interpret(byteCode, chars, start, length, output);
|
||||
else
|
||||
result = JSC::Yarr::execute(codeBlock, chars, start, length, output);
|
||||
#else
|
||||
result = jsRegExpExecute(cx, compiled, chars, length, start, output, outputCount);
|
||||
#endif
|
||||
|
||||
if (result == -1)
|
||||
return RegExpRunStatus_Success_NotFound;
|
||||
|
||||
#if !ENABLE_YARR_JIT
|
||||
if (result < 0) {
|
||||
reportPCREError(cx, result);
|
||||
return RegExpRunStatus_Error;
|
||||
}
|
||||
#endif
|
||||
|
||||
JS_ASSERT(result >= 0);
|
||||
return RegExpRunStatus_Success;
|
||||
}
|
||||
|
||||
inline void
|
||||
RegExpShared::incref(JSContext *cx)
|
||||
{
|
||||
++refCount;
|
||||
}
|
||||
|
||||
inline void
|
||||
RegExpShared::decref(JSContext *cx)
|
||||
{
|
||||
if (--refCount != 0)
|
||||
return;
|
||||
|
||||
if (RegExpCache *cache = cx->runtime->maybeRegExpCache()) {
|
||||
if (source->isAtom()) {
|
||||
if (RegExpCache::Ptr p = cache->lookup(&source->asAtom())) {
|
||||
if (&p->value.shared() == this)
|
||||
cache->remove(p);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef DEBUG
|
||||
this->~RegExpShared();
|
||||
memset(this, 0xcd, sizeof(*this));
|
||||
cx->free_(this);
|
||||
#else
|
||||
cx->delete_(this);
|
||||
#endif
|
||||
}
|
||||
|
||||
inline RegExpShared *
|
||||
RegExpToShared(JSContext *cx, JSObject &obj)
|
||||
{
|
||||
|
@ -56,30 +56,15 @@ JS_STATIC_ASSERT(GlobalFlag == JSREG_GLOB);
|
||||
JS_STATIC_ASSERT(MultilineFlag == JSREG_MULTILINE);
|
||||
JS_STATIC_ASSERT(StickyFlag == JSREG_STICKY);
|
||||
|
||||
/* RegExpMatcher */
|
||||
|
||||
bool
|
||||
RegExpMatcher::initWithTestOptimized(RegExpObject &reobj)
|
||||
{
|
||||
JS_ASSERT(reobj.startsWithAtomizedGreedyStar());
|
||||
JS_ASSERT(!shared_);
|
||||
|
||||
JSAtom *source = &reobj.getSource()->asAtom();
|
||||
shared_.reset(RegExpShared::createTestOptimized(cx_, source, reobj.getFlags()));
|
||||
if (!shared_)
|
||||
return false;
|
||||
|
||||
/*
|
||||
* Create a dummy RegExpObject to persist this RegExpShared until the next GC.
|
||||
* Note that we give the ref we have to this new object.
|
||||
*/
|
||||
RegExpObjectBuilder builder(cx_);
|
||||
shared_->incref(cx_);
|
||||
return !!builder.build(AlreadyIncRefed<RegExpShared>(shared_.get()));
|
||||
}
|
||||
|
||||
/* RegExpObjectBuilder */
|
||||
|
||||
RegExpObjectBuilder::RegExpObjectBuilder(JSContext *cx, RegExpObject *reobj)
|
||||
: cx(cx), reobj_(reobj)
|
||||
{
|
||||
if (reobj_)
|
||||
reobj_->setPrivate(NULL);
|
||||
}
|
||||
|
||||
bool
|
||||
RegExpObjectBuilder::getOrCreate()
|
||||
{
|
||||
@ -110,30 +95,24 @@ RegExpObjectBuilder::getOrCreateClone(RegExpObject *proto)
|
||||
}
|
||||
|
||||
RegExpObject *
|
||||
RegExpObjectBuilder::build(AlreadyIncRefed<RegExpShared> shared)
|
||||
{
|
||||
if (!getOrCreate()) {
|
||||
shared->decref(cx);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
reobj_->purge(cx);
|
||||
if (!reobj_->init(cx, shared->getSource(), shared->getFlags())) {
|
||||
shared->decref(cx);
|
||||
return NULL;
|
||||
}
|
||||
reobj_->setPrivate(shared.get());
|
||||
|
||||
return reobj_;
|
||||
}
|
||||
|
||||
RegExpObject *
|
||||
RegExpObjectBuilder::build(JSLinearString *source, RegExpFlag flags)
|
||||
RegExpObjectBuilder::build(JSAtom *source, RegExpShared &shared)
|
||||
{
|
||||
if (!getOrCreate())
|
||||
return NULL;
|
||||
|
||||
if (!reobj_->init(cx, source, shared.getFlags()))
|
||||
return NULL;
|
||||
|
||||
reobj_->setPrivate(&shared);
|
||||
return reobj_;
|
||||
}
|
||||
|
||||
RegExpObject *
|
||||
RegExpObjectBuilder::build(JSAtom *source, RegExpFlag flags)
|
||||
{
|
||||
if (!getOrCreate())
|
||||
return NULL;
|
||||
|
||||
reobj_->purge(cx);
|
||||
return reobj_->init(cx, source, flags) ? reobj_ : NULL;
|
||||
}
|
||||
|
||||
@ -160,8 +139,7 @@ RegExpObjectBuilder::clone(RegExpObject *other, RegExpObject *proto)
|
||||
if (!toShare)
|
||||
return NULL;
|
||||
|
||||
toShare->incref(cx);
|
||||
return build(AlreadyIncRefed<RegExpShared>(toShare));
|
||||
return build(other->getSource(), *toShare);
|
||||
}
|
||||
|
||||
/* MatchPairs */
|
||||
@ -190,188 +168,7 @@ MatchPairs::checkAgainst(size_t inputLength)
|
||||
#endif
|
||||
}
|
||||
|
||||
RegExpRunStatus
|
||||
RegExpShared::execute(JSContext *cx, const jschar *chars, size_t length, size_t *lastIndex,
|
||||
LifoAllocScope &allocScope, MatchPairs **output)
|
||||
{
|
||||
const size_t origLength = length;
|
||||
size_t backingPairCount = RegExpCode::getOutputSize(pairCount());
|
||||
|
||||
MatchPairs *matchPairs = MatchPairs::create(allocScope.alloc(), pairCount(), backingPairCount);
|
||||
if (!matchPairs)
|
||||
return RegExpRunStatus_Error;
|
||||
|
||||
/*
|
||||
* |displacement| emulates sticky mode by matching from this offset
|
||||
* into the char buffer and subtracting the delta off at the end.
|
||||
*/
|
||||
size_t start = *lastIndex;
|
||||
size_t displacement = 0;
|
||||
|
||||
if (sticky()) {
|
||||
displacement = *lastIndex;
|
||||
chars += displacement;
|
||||
length -= displacement;
|
||||
start = 0;
|
||||
}
|
||||
|
||||
RegExpRunStatus status = code.execute(cx, chars, length, start,
|
||||
matchPairs->buffer(), backingPairCount);
|
||||
|
||||
switch (status) {
|
||||
case RegExpRunStatus_Error:
|
||||
return status;
|
||||
case RegExpRunStatus_Success_NotFound:
|
||||
*output = matchPairs;
|
||||
return status;
|
||||
default:
|
||||
JS_ASSERT(status == RegExpRunStatus_Success);
|
||||
}
|
||||
|
||||
matchPairs->displace(displacement);
|
||||
matchPairs->checkAgainst(origLength);
|
||||
|
||||
*lastIndex = matchPairs->pair(0).limit;
|
||||
*output = matchPairs;
|
||||
|
||||
return RegExpRunStatus_Success;
|
||||
}
|
||||
|
||||
RegExpShared *
|
||||
RegExpObject::createShared(JSContext *cx)
|
||||
{
|
||||
JS_ASSERT(!maybeShared());
|
||||
AlreadyIncRefed<RegExpShared> shared = RegExpShared::create(cx, getSource(), getFlags(), NULL);
|
||||
if (!shared)
|
||||
return NULL;
|
||||
|
||||
setPrivate(shared.get());
|
||||
return shared.get();
|
||||
}
|
||||
|
||||
RegExpRunStatus
|
||||
RegExpObject::execute(JSContext *cx, const jschar *chars, size_t length, size_t *lastIndex,
|
||||
LifoAllocScope &allocScope, MatchPairs **output)
|
||||
{
|
||||
RegExpShared *shared = getShared(cx);
|
||||
if (!shared)
|
||||
return RegExpRunStatus_Error;
|
||||
return shared->execute(cx, chars, length, lastIndex, allocScope, output);
|
||||
}
|
||||
|
||||
Shape *
|
||||
RegExpObject::assignInitialShape(JSContext *cx)
|
||||
{
|
||||
JS_ASSERT(isRegExp());
|
||||
JS_ASSERT(nativeEmpty());
|
||||
|
||||
JS_STATIC_ASSERT(LAST_INDEX_SLOT == 0);
|
||||
JS_STATIC_ASSERT(SOURCE_SLOT == LAST_INDEX_SLOT + 1);
|
||||
JS_STATIC_ASSERT(GLOBAL_FLAG_SLOT == SOURCE_SLOT + 1);
|
||||
JS_STATIC_ASSERT(IGNORE_CASE_FLAG_SLOT == GLOBAL_FLAG_SLOT + 1);
|
||||
JS_STATIC_ASSERT(MULTILINE_FLAG_SLOT == IGNORE_CASE_FLAG_SLOT + 1);
|
||||
JS_STATIC_ASSERT(STICKY_FLAG_SLOT == MULTILINE_FLAG_SLOT + 1);
|
||||
|
||||
/* The lastIndex property alone is writable but non-configurable. */
|
||||
if (!addDataProperty(cx, ATOM_TO_JSID(cx->runtime->atomState.lastIndexAtom),
|
||||
LAST_INDEX_SLOT, JSPROP_PERMANENT))
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Remaining instance properties are non-writable and non-configurable. */
|
||||
if (!addDataProperty(cx, ATOM_TO_JSID(cx->runtime->atomState.sourceAtom),
|
||||
SOURCE_SLOT, JSPROP_PERMANENT | JSPROP_READONLY) ||
|
||||
!addDataProperty(cx, ATOM_TO_JSID(cx->runtime->atomState.globalAtom),
|
||||
GLOBAL_FLAG_SLOT, JSPROP_PERMANENT | JSPROP_READONLY) ||
|
||||
!addDataProperty(cx, ATOM_TO_JSID(cx->runtime->atomState.ignoreCaseAtom),
|
||||
IGNORE_CASE_FLAG_SLOT, JSPROP_PERMANENT | JSPROP_READONLY) ||
|
||||
!addDataProperty(cx, ATOM_TO_JSID(cx->runtime->atomState.multilineAtom),
|
||||
MULTILINE_FLAG_SLOT, JSPROP_PERMANENT | JSPROP_READONLY))
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return addDataProperty(cx, ATOM_TO_JSID(cx->runtime->atomState.stickyAtom),
|
||||
STICKY_FLAG_SLOT, JSPROP_PERMANENT | JSPROP_READONLY);
|
||||
}
|
||||
|
||||
#if JS_HAS_XDR
|
||||
|
||||
#include "jsxdrapi.h"
|
||||
|
||||
JSBool
|
||||
js_XDRRegExpObject(JSXDRState *xdr, JSObject **objp)
|
||||
{
|
||||
JSString *source = 0;
|
||||
uint32_t flagsword = 0;
|
||||
|
||||
if (xdr->mode == JSXDR_ENCODE) {
|
||||
JS_ASSERT(objp);
|
||||
RegExpObject &reobj = (*objp)->asRegExp();
|
||||
source = reobj.getSource();
|
||||
flagsword = reobj.getFlags();
|
||||
}
|
||||
if (!JS_XDRString(xdr, &source) || !JS_XDRUint32(xdr, &flagsword))
|
||||
return false;
|
||||
if (xdr->mode == JSXDR_DECODE) {
|
||||
JSAtom *atom = js_AtomizeString(xdr->cx, source);
|
||||
if (!atom)
|
||||
return false;
|
||||
RegExpObject *reobj = RegExpObject::createNoStatics(xdr->cx, atom, RegExpFlag(flagsword),
|
||||
NULL);
|
||||
if (!reobj)
|
||||
return false;
|
||||
|
||||
if (!reobj->clearParent(xdr->cx))
|
||||
return false;
|
||||
if (!reobj->clearType(xdr->cx))
|
||||
return false;
|
||||
*objp = reobj;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
#else /* !JS_HAS_XDR */
|
||||
|
||||
#define js_XDRRegExpObject NULL
|
||||
|
||||
#endif /* !JS_HAS_XDR */
|
||||
|
||||
static void
|
||||
regexp_finalize(JSContext *cx, JSObject *obj)
|
||||
{
|
||||
obj->asRegExp().finalize(cx);
|
||||
}
|
||||
|
||||
static void
|
||||
regexp_trace(JSTracer *trc, JSObject *obj)
|
||||
{
|
||||
if (trc->runtime->gcRunning)
|
||||
obj->asRegExp().purge(trc->context);
|
||||
}
|
||||
|
||||
Class js::RegExpClass = {
|
||||
js_RegExp_str,
|
||||
JSCLASS_HAS_PRIVATE |
|
||||
JSCLASS_HAS_RESERVED_SLOTS(RegExpObject::RESERVED_SLOTS) |
|
||||
JSCLASS_HAS_CACHED_PROTO(JSProto_RegExp),
|
||||
JS_PropertyStub, /* addProperty */
|
||||
JS_PropertyStub, /* delProperty */
|
||||
JS_PropertyStub, /* getProperty */
|
||||
JS_StrictPropertyStub, /* setProperty */
|
||||
JS_EnumerateStub, /* enumerate */
|
||||
JS_ResolveStub,
|
||||
JS_ConvertStub,
|
||||
regexp_finalize,
|
||||
NULL, /* reserved0 */
|
||||
NULL, /* checkAccess */
|
||||
NULL, /* call */
|
||||
NULL, /* construct */
|
||||
js_XDRRegExpObject,
|
||||
NULL, /* hasInstance */
|
||||
regexp_trace
|
||||
};
|
||||
/* detail::RegExpCode */
|
||||
|
||||
#if ENABLE_YARR_JIT
|
||||
void
|
||||
@ -441,6 +238,468 @@ RegExpCode::reportPCREError(JSContext *cx, int error)
|
||||
|
||||
#endif /* ENABLE_YARR_JIT */
|
||||
|
||||
bool
|
||||
RegExpCode::compile(JSContext *cx, JSLinearString &pattern, uintN *parenCount, RegExpFlag flags)
|
||||
{
|
||||
#if ENABLE_YARR_JIT
|
||||
/* Parse the pattern. */
|
||||
ErrorCode yarrError;
|
||||
YarrPattern yarrPattern(pattern, bool(flags & IgnoreCaseFlag), bool(flags & MultilineFlag),
|
||||
&yarrError);
|
||||
if (yarrError) {
|
||||
reportYarrError(cx, NULL, yarrError);
|
||||
return false;
|
||||
}
|
||||
*parenCount = yarrPattern.m_numSubpatterns;
|
||||
|
||||
/*
|
||||
* The YARR JIT compiler attempts to compile the parsed pattern. If
|
||||
* it cannot, it informs us via |codeBlock.isFallBack()|, in which
|
||||
* case we have to bytecode compile it.
|
||||
*/
|
||||
|
||||
#ifdef JS_METHODJIT
|
||||
if (isJITRuntimeEnabled(cx) && !yarrPattern.m_containsBackreferences) {
|
||||
JSC::ExecutableAllocator *execAlloc = cx->runtime->getExecutableAllocator(cx);
|
||||
if (!execAlloc) {
|
||||
js_ReportOutOfMemory(cx);
|
||||
return false;
|
||||
}
|
||||
|
||||
JSGlobalData globalData(execAlloc);
|
||||
jitCompile(yarrPattern, &globalData, codeBlock);
|
||||
if (!codeBlock.isFallBack())
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
WTF::BumpPointerAllocator *bumpAlloc = cx->runtime->getBumpPointerAllocator(cx);
|
||||
if (!bumpAlloc) {
|
||||
js_ReportOutOfMemory(cx);
|
||||
return false;
|
||||
}
|
||||
|
||||
codeBlock.setFallBack(true);
|
||||
byteCode = byteCompile(yarrPattern, bumpAlloc).get();
|
||||
return true;
|
||||
#else /* !defined(ENABLE_YARR_JIT) */
|
||||
int error = 0;
|
||||
compiled = jsRegExpCompile(pattern.chars(), pattern.length(),
|
||||
ignoreCase() ? JSRegExpIgnoreCase : JSRegExpDoNotIgnoreCase,
|
||||
multiline() ? JSRegExpMultiline : JSRegExpSingleLine,
|
||||
parenCount, &error);
|
||||
if (error) {
|
||||
reportPCREError(cx, error);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
|
||||
RegExpRunStatus
|
||||
RegExpCode::execute(JSContext *cx, const jschar *chars, size_t length, size_t start,
|
||||
int *output, size_t outputCount)
|
||||
{
|
||||
int result;
|
||||
#if ENABLE_YARR_JIT
|
||||
(void) cx; /* Unused. */
|
||||
if (codeBlock.isFallBack())
|
||||
result = JSC::Yarr::interpret(byteCode, chars, start, length, output);
|
||||
else
|
||||
result = JSC::Yarr::execute(codeBlock, chars, start, length, output);
|
||||
#else
|
||||
result = jsRegExpExecute(cx, compiled, chars, length, start, output, outputCount);
|
||||
#endif
|
||||
|
||||
if (result == -1)
|
||||
return RegExpRunStatus_Success_NotFound;
|
||||
|
||||
#if !ENABLE_YARR_JIT
|
||||
if (result < 0) {
|
||||
reportPCREError(cx, result);
|
||||
return RegExpRunStatus_Error;
|
||||
}
|
||||
#endif
|
||||
|
||||
JS_ASSERT(result >= 0);
|
||||
return RegExpRunStatus_Success;
|
||||
}
|
||||
|
||||
/* RegExpObject */
|
||||
|
||||
static void
|
||||
regexp_trace(JSTracer *trc, JSObject *obj)
|
||||
{
|
||||
if (trc->runtime->gcRunning)
|
||||
obj->setPrivate(NULL);
|
||||
}
|
||||
|
||||
Class js::RegExpClass = {
|
||||
js_RegExp_str,
|
||||
JSCLASS_HAS_PRIVATE |
|
||||
JSCLASS_HAS_RESERVED_SLOTS(RegExpObject::RESERVED_SLOTS) |
|
||||
JSCLASS_HAS_CACHED_PROTO(JSProto_RegExp),
|
||||
JS_PropertyStub, /* addProperty */
|
||||
JS_PropertyStub, /* delProperty */
|
||||
JS_PropertyStub, /* getProperty */
|
||||
JS_StrictPropertyStub, /* setProperty */
|
||||
JS_EnumerateStub, /* enumerate */
|
||||
JS_ResolveStub,
|
||||
JS_ConvertStub,
|
||||
NULL, /* finalize */
|
||||
NULL, /* reserved0 */
|
||||
NULL, /* checkAccess */
|
||||
NULL, /* call */
|
||||
NULL, /* construct */
|
||||
#if JS_HAS_XDR
|
||||
js_XDRRegExpObject,
|
||||
#else
|
||||
NULL
|
||||
#endif
|
||||
NULL, /* hasInstance */
|
||||
regexp_trace
|
||||
};
|
||||
|
||||
RegExpShared::RegExpShared(RegExpFlag flags)
|
||||
: parenCount(0), flags(flags), activeUseCount(0)
|
||||
{}
|
||||
|
||||
RegExpObject *
|
||||
RegExpObject::create(JSContext *cx, RegExpStatics *res, const jschar *chars, size_t length,
|
||||
RegExpFlag flags, TokenStream *tokenStream)
|
||||
{
|
||||
RegExpFlag staticsFlags = res->getFlags();
|
||||
return createNoStatics(cx, chars, length, RegExpFlag(flags | staticsFlags), tokenStream);
|
||||
}
|
||||
|
||||
RegExpObject *
|
||||
RegExpObject::createNoStatics(JSContext *cx, const jschar *chars, size_t length, RegExpFlag flags,
|
||||
TokenStream *tokenStream)
|
||||
{
|
||||
JSAtom *source = js_AtomizeChars(cx, chars, length);
|
||||
if (!source)
|
||||
return NULL;
|
||||
|
||||
return createNoStatics(cx, source, flags, tokenStream);
|
||||
}
|
||||
|
||||
RegExpObject *
|
||||
RegExpObject::createNoStatics(JSContext *cx, JSAtom *source, RegExpFlag flags,
|
||||
TokenStream *tokenStream)
|
||||
{
|
||||
if (!RegExpCode::checkSyntax(cx, tokenStream, source))
|
||||
return NULL;
|
||||
|
||||
RegExpObjectBuilder builder(cx);
|
||||
return builder.build(source, flags);
|
||||
}
|
||||
|
||||
RegExpShared *
|
||||
RegExpObject::createShared(JSContext *cx)
|
||||
{
|
||||
JS_ASSERT(!maybeShared());
|
||||
RegExpShared *shared = cx->compartment->regExps.get(cx, getSource(), getFlags());
|
||||
if (!shared)
|
||||
return NULL;
|
||||
|
||||
setPrivate(shared);
|
||||
return shared;
|
||||
}
|
||||
|
||||
Shape *
|
||||
RegExpObject::assignInitialShape(JSContext *cx)
|
||||
{
|
||||
JS_ASSERT(isRegExp());
|
||||
JS_ASSERT(nativeEmpty());
|
||||
|
||||
JS_STATIC_ASSERT(LAST_INDEX_SLOT == 0);
|
||||
JS_STATIC_ASSERT(SOURCE_SLOT == LAST_INDEX_SLOT + 1);
|
||||
JS_STATIC_ASSERT(GLOBAL_FLAG_SLOT == SOURCE_SLOT + 1);
|
||||
JS_STATIC_ASSERT(IGNORE_CASE_FLAG_SLOT == GLOBAL_FLAG_SLOT + 1);
|
||||
JS_STATIC_ASSERT(MULTILINE_FLAG_SLOT == IGNORE_CASE_FLAG_SLOT + 1);
|
||||
JS_STATIC_ASSERT(STICKY_FLAG_SLOT == MULTILINE_FLAG_SLOT + 1);
|
||||
|
||||
/* The lastIndex property alone is writable but non-configurable. */
|
||||
if (!addDataProperty(cx, ATOM_TO_JSID(cx->runtime->atomState.lastIndexAtom),
|
||||
LAST_INDEX_SLOT, JSPROP_PERMANENT))
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Remaining instance properties are non-writable and non-configurable. */
|
||||
if (!addDataProperty(cx, ATOM_TO_JSID(cx->runtime->atomState.sourceAtom),
|
||||
SOURCE_SLOT, JSPROP_PERMANENT | JSPROP_READONLY) ||
|
||||
!addDataProperty(cx, ATOM_TO_JSID(cx->runtime->atomState.globalAtom),
|
||||
GLOBAL_FLAG_SLOT, JSPROP_PERMANENT | JSPROP_READONLY) ||
|
||||
!addDataProperty(cx, ATOM_TO_JSID(cx->runtime->atomState.ignoreCaseAtom),
|
||||
IGNORE_CASE_FLAG_SLOT, JSPROP_PERMANENT | JSPROP_READONLY) ||
|
||||
!addDataProperty(cx, ATOM_TO_JSID(cx->runtime->atomState.multilineAtom),
|
||||
MULTILINE_FLAG_SLOT, JSPROP_PERMANENT | JSPROP_READONLY))
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return addDataProperty(cx, ATOM_TO_JSID(cx->runtime->atomState.stickyAtom),
|
||||
STICKY_FLAG_SLOT, JSPROP_PERMANENT | JSPROP_READONLY);
|
||||
}
|
||||
|
||||
inline bool
|
||||
RegExpObject::init(JSContext *cx, JSAtom *source, RegExpFlag flags)
|
||||
{
|
||||
if (nativeEmpty()) {
|
||||
if (isDelegate()) {
|
||||
if (!assignInitialShape(cx))
|
||||
return false;
|
||||
} else {
|
||||
Shape *shape = assignInitialShape(cx);
|
||||
if (!shape)
|
||||
return false;
|
||||
EmptyShape::insertInitialShape(cx, shape, getProto());
|
||||
}
|
||||
JS_ASSERT(!nativeEmpty());
|
||||
}
|
||||
|
||||
DebugOnly<JSAtomState *> atomState = &cx->runtime->atomState;
|
||||
JS_ASSERT(nativeLookup(cx, ATOM_TO_JSID(atomState->lastIndexAtom))->slot() == LAST_INDEX_SLOT);
|
||||
JS_ASSERT(nativeLookup(cx, ATOM_TO_JSID(atomState->sourceAtom))->slot() == SOURCE_SLOT);
|
||||
JS_ASSERT(nativeLookup(cx, ATOM_TO_JSID(atomState->globalAtom))->slot() == GLOBAL_FLAG_SLOT);
|
||||
JS_ASSERT(nativeLookup(cx, ATOM_TO_JSID(atomState->ignoreCaseAtom))->slot() ==
|
||||
IGNORE_CASE_FLAG_SLOT);
|
||||
JS_ASSERT(nativeLookup(cx, ATOM_TO_JSID(atomState->multilineAtom))->slot() ==
|
||||
MULTILINE_FLAG_SLOT);
|
||||
JS_ASSERT(nativeLookup(cx, ATOM_TO_JSID(atomState->stickyAtom))->slot() == STICKY_FLAG_SLOT);
|
||||
|
||||
JS_ASSERT(!maybeShared());
|
||||
zeroLastIndex();
|
||||
setSource(source);
|
||||
setGlobal(flags & GlobalFlag);
|
||||
setIgnoreCase(flags & IgnoreCaseFlag);
|
||||
setMultiline(flags & MultilineFlag);
|
||||
setSticky(flags & StickyFlag);
|
||||
return true;
|
||||
}
|
||||
|
||||
RegExpRunStatus
|
||||
RegExpObject::execute(JSContext *cx, const jschar *chars, size_t length, size_t *lastIndex,
|
||||
MatchPairs **output)
|
||||
{
|
||||
RegExpShared *shared = getShared(cx);
|
||||
if (!shared)
|
||||
return RegExpRunStatus_Error;
|
||||
return shared->execute(cx, chars, length, lastIndex, output);
|
||||
}
|
||||
|
||||
JSFlatString *
|
||||
RegExpObject::toString(JSContext *cx) const
|
||||
{
|
||||
JSAtom *src = getSource();
|
||||
StringBuffer sb(cx);
|
||||
if (size_t len = src->length()) {
|
||||
if (!sb.reserve(len + 2))
|
||||
return NULL;
|
||||
sb.infallibleAppend('/');
|
||||
sb.infallibleAppend(src->chars(), len);
|
||||
sb.infallibleAppend('/');
|
||||
} else {
|
||||
if (!sb.append("/(?:)/"))
|
||||
return NULL;
|
||||
}
|
||||
if (global() && !sb.append('g'))
|
||||
return NULL;
|
||||
if (ignoreCase() && !sb.append('i'))
|
||||
return NULL;
|
||||
if (multiline() && !sb.append('m'))
|
||||
return NULL;
|
||||
if (sticky() && !sb.append('y'))
|
||||
return NULL;
|
||||
|
||||
return sb.finishString();
|
||||
}
|
||||
|
||||
/* RegExpShared */
|
||||
|
||||
bool
|
||||
RegExpShared::compile(JSContext *cx, JSAtom *source)
|
||||
{
|
||||
if (!sticky())
|
||||
return code.compile(cx, *source, &parenCount, getFlags());
|
||||
|
||||
/*
|
||||
* The sticky case we implement hackily by prepending a caret onto the front
|
||||
* and relying on |::execute| to pseudo-slice the string when it sees a sticky regexp.
|
||||
*/
|
||||
static const jschar prefix[] = {'^', '(', '?', ':'};
|
||||
static const jschar postfix[] = {')'};
|
||||
|
||||
using mozilla::ArrayLength;
|
||||
StringBuffer sb(cx);
|
||||
if (!sb.reserve(ArrayLength(prefix) + source->length() + ArrayLength(postfix)))
|
||||
return false;
|
||||
sb.infallibleAppend(prefix, ArrayLength(prefix));
|
||||
sb.infallibleAppend(source->chars(), source->length());
|
||||
sb.infallibleAppend(postfix, ArrayLength(postfix));
|
||||
|
||||
JSAtom *fakeySource = sb.finishAtom();
|
||||
if (!fakeySource)
|
||||
return false;
|
||||
return code.compile(cx, *fakeySource, &parenCount, getFlags());
|
||||
}
|
||||
|
||||
RegExpRunStatus
|
||||
RegExpShared::execute(JSContext *cx, const jschar *chars, size_t length, size_t *lastIndex,
|
||||
MatchPairs **output)
|
||||
{
|
||||
const size_t origLength = length;
|
||||
size_t backingPairCount = RegExpCode::getOutputSize(pairCount());
|
||||
|
||||
LifoAlloc &alloc = cx->tempLifoAlloc();
|
||||
MatchPairs *matchPairs = MatchPairs::create(alloc, pairCount(), backingPairCount);
|
||||
if (!matchPairs)
|
||||
return RegExpRunStatus_Error;
|
||||
|
||||
/*
|
||||
* |displacement| emulates sticky mode by matching from this offset
|
||||
* into the char buffer and subtracting the delta off at the end.
|
||||
*/
|
||||
size_t start = *lastIndex;
|
||||
size_t displacement = 0;
|
||||
|
||||
if (sticky()) {
|
||||
displacement = *lastIndex;
|
||||
chars += displacement;
|
||||
length -= displacement;
|
||||
start = 0;
|
||||
}
|
||||
|
||||
RegExpRunStatus status = code.execute(cx, chars, length, start,
|
||||
matchPairs->buffer(), backingPairCount);
|
||||
|
||||
switch (status) {
|
||||
case RegExpRunStatus_Error:
|
||||
return status;
|
||||
case RegExpRunStatus_Success_NotFound:
|
||||
*output = matchPairs;
|
||||
return status;
|
||||
default:
|
||||
JS_ASSERT(status == RegExpRunStatus_Success);
|
||||
}
|
||||
|
||||
matchPairs->displace(displacement);
|
||||
matchPairs->checkAgainst(origLength);
|
||||
|
||||
*lastIndex = matchPairs->pair(0).limit;
|
||||
*output = matchPairs;
|
||||
|
||||
return RegExpRunStatus_Success;
|
||||
}
|
||||
|
||||
/* RegExpCompartment */
|
||||
|
||||
RegExpCompartment::RegExpCompartment(JSRuntime *rt)
|
||||
: map_(rt)
|
||||
{}
|
||||
|
||||
RegExpCompartment::~RegExpCompartment()
|
||||
{
|
||||
map_.empty();
|
||||
}
|
||||
|
||||
bool
|
||||
RegExpCompartment::init(JSContext *cx)
|
||||
{
|
||||
if (!map_.init()) {
|
||||
js_ReportOutOfMemory(cx);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
RegExpCompartment::purge()
|
||||
{
|
||||
for (Map::Enum e(map_); !e.empty(); e.popFront()) {
|
||||
RegExpShared *shared = e.front().value;
|
||||
if (shared->activeUseCount == 0) {
|
||||
Foreground::delete_(shared);
|
||||
e.removeFront();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inline RegExpShared *
|
||||
RegExpCompartment::get(JSContext *cx, JSAtom *keyAtom, JSAtom *source, RegExpFlag flags, Type type)
|
||||
{
|
||||
DebugOnly<size_t> gcNumberBefore = cx->runtime->gcNumber;
|
||||
|
||||
Key key(keyAtom, flags, type);
|
||||
Map::AddPtr p = map_.lookupForAdd(key);
|
||||
if (p)
|
||||
return p->value;
|
||||
|
||||
RegExpShared *shared = cx->runtime->new_<RegExpShared>(flags);
|
||||
if (!shared || !shared->compile(cx, source))
|
||||
goto error;
|
||||
|
||||
/*
|
||||
* The compilation path only mallocs so cannot GC. Thus, it is safe to add
|
||||
* the regexp directly.
|
||||
*/
|
||||
JS_ASSERT(cx->runtime->gcNumber == gcNumberBefore);
|
||||
|
||||
if (!map_.add(p, key, shared))
|
||||
goto error;
|
||||
|
||||
return shared;
|
||||
|
||||
error:
|
||||
Foreground::delete_(shared);
|
||||
js_ReportOutOfMemory(cx);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
RegExpShared *
|
||||
RegExpCompartment::get(JSContext *cx, JSAtom *source, RegExpFlag flags)
|
||||
{
|
||||
return get(cx, source, source, flags, Normal);
|
||||
}
|
||||
|
||||
RegExpShared *
|
||||
RegExpCompartment::getHack(JSContext *cx, JSAtom *source, JSAtom *hackedSource, RegExpFlag flags)
|
||||
{
|
||||
return get(cx, source, hackedSource, flags, Hack);
|
||||
}
|
||||
|
||||
RegExpShared *
|
||||
RegExpCompartment::lookupHack(JSContext *cx, JSAtom *source, RegExpFlag flags)
|
||||
{
|
||||
if (Map::Ptr p = map_.lookup(Key(source, flags, Hack)))
|
||||
return p->value;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
RegExpShared *
|
||||
RegExpCompartment::get(JSContext *cx, JSAtom *atom, JSString *opt)
|
||||
{
|
||||
RegExpFlag flags = RegExpFlag(0);
|
||||
if (opt && !ParseRegExpFlags(cx, opt, &flags))
|
||||
return NULL;
|
||||
|
||||
return get(cx, atom, flags);
|
||||
}
|
||||
|
||||
/* Functions */
|
||||
|
||||
JSObject *
|
||||
js::CloneRegExpObject(JSContext *cx, JSObject *obj, JSObject *proto)
|
||||
{
|
||||
JS_ASSERT(obj->isRegExp());
|
||||
JS_ASSERT(proto->isRegExp());
|
||||
|
||||
RegExpObjectBuilder builder(cx);
|
||||
return builder.clone(&obj->asRegExp(), &proto->asRegExp());
|
||||
}
|
||||
|
||||
bool
|
||||
js::ParseRegExpFlags(JSContext *cx, JSString *flagStr, RegExpFlag *flagsOut)
|
||||
{
|
||||
@ -478,101 +737,36 @@ js::ParseRegExpFlags(JSContext *cx, JSString *flagStr, RegExpFlag *flagsOut)
|
||||
return true;
|
||||
}
|
||||
|
||||
RegExpShared *
|
||||
RegExpShared::createUncached(JSContext *cx, JSLinearString *source, RegExpFlag flags,
|
||||
TokenStream *tokenStream)
|
||||
{
|
||||
RegExpShared *shared = cx->new_<RegExpShared>(source, flags);
|
||||
if (!shared)
|
||||
return NULL;
|
||||
#if JS_HAS_XDR
|
||||
# include "jsxdrapi.h"
|
||||
|
||||
if (!shared->compile(cx, tokenStream)) {
|
||||
Foreground::delete_(shared);
|
||||
return NULL;
|
||||
JSBool
|
||||
js_XDRRegExpObject(JSXDRState *xdr, JSObject **objp)
|
||||
{
|
||||
JSAtom *source = 0;
|
||||
uint32_t flagsword = 0;
|
||||
|
||||
if (xdr->mode == JSXDR_ENCODE) {
|
||||
JS_ASSERT(objp);
|
||||
RegExpObject &reobj = (*objp)->asRegExp();
|
||||
source = reobj.getSource();
|
||||
flagsword = reobj.getFlags();
|
||||
}
|
||||
if (!js_XDRAtom(xdr, &source) || !JS_XDRUint32(xdr, &flagsword))
|
||||
return false;
|
||||
if (xdr->mode == JSXDR_DECODE) {
|
||||
RegExpFlag flags = RegExpFlag(flagsword);
|
||||
RegExpObject *reobj = RegExpObject::createNoStatics(xdr->cx, source, flags, NULL);
|
||||
if (!reobj)
|
||||
return false;
|
||||
|
||||
return shared;
|
||||
}
|
||||
|
||||
AlreadyIncRefed<RegExpShared>
|
||||
RegExpShared::createTestOptimized(JSContext *cx, JSAtom *cacheKey, RegExpFlag flags)
|
||||
{
|
||||
using namespace detail;
|
||||
typedef AlreadyIncRefed<RegExpShared> RetType;
|
||||
|
||||
RetType cached;
|
||||
if (!cacheLookup(cx, cacheKey, flags, RegExpCache_TestOptimized, &cached))
|
||||
return RetType(NULL);
|
||||
|
||||
if (cached)
|
||||
return cached;
|
||||
|
||||
/* Strip off the greedy star characters, create a new RegExpShared, and cache. */
|
||||
JS_ASSERT(cacheKey->length() > JS_ARRAY_LENGTH(GreedyStarChars));
|
||||
JSDependentString *stripped =
|
||||
JSDependentString::new_(cx, cacheKey, cacheKey->chars() + JS_ARRAY_LENGTH(GreedyStarChars),
|
||||
cacheKey->length() - JS_ARRAY_LENGTH(GreedyStarChars));
|
||||
if (!stripped)
|
||||
return RetType(NULL);
|
||||
|
||||
RegExpShared *shared = createUncached(cx, cacheKey, flags, NULL);
|
||||
if (!shared)
|
||||
return RetType(NULL);
|
||||
|
||||
if (!cacheInsert(cx, cacheKey, RegExpCache_TestOptimized, *shared)) {
|
||||
shared->decref(cx);
|
||||
return RetType(NULL);
|
||||
if (!reobj->clearParent(xdr->cx))
|
||||
return false;
|
||||
if (!reobj->clearType(xdr->cx))
|
||||
return false;
|
||||
*objp = reobj;
|
||||
}
|
||||
|
||||
return RetType(shared);
|
||||
return true;
|
||||
}
|
||||
#endif /* !JS_HAS_XDR */
|
||||
|
||||
AlreadyIncRefed<RegExpShared>
|
||||
RegExpShared::create(JSContext *cx, JSLinearString *str, JSString *opt, TokenStream *ts)
|
||||
{
|
||||
if (!opt)
|
||||
return create(cx, str, RegExpFlag(0), ts);
|
||||
|
||||
RegExpFlag flags = RegExpFlag(0);
|
||||
if (!ParseRegExpFlags(cx, opt, &flags))
|
||||
return AlreadyIncRefed<RegExpShared>(NULL);
|
||||
|
||||
return create(cx, str, flags, ts);
|
||||
}
|
||||
|
||||
JSObject * JS_FASTCALL
|
||||
js_CloneRegExpObject(JSContext *cx, JSObject *obj, JSObject *proto)
|
||||
{
|
||||
JS_ASSERT(obj->isRegExp());
|
||||
JS_ASSERT(proto->isRegExp());
|
||||
|
||||
RegExpObjectBuilder builder(cx);
|
||||
return builder.clone(&obj->asRegExp(), &proto->asRegExp());
|
||||
}
|
||||
|
||||
JSFlatString *
|
||||
RegExpObject::toString(JSContext *cx) const
|
||||
{
|
||||
JSLinearString *src = getSource();
|
||||
StringBuffer sb(cx);
|
||||
if (size_t len = src->length()) {
|
||||
if (!sb.reserve(len + 2))
|
||||
return NULL;
|
||||
sb.infallibleAppend('/');
|
||||
sb.infallibleAppend(src->chars(), len);
|
||||
sb.infallibleAppend('/');
|
||||
} else {
|
||||
if (!sb.append("/(?:)/"))
|
||||
return NULL;
|
||||
}
|
||||
if (global() && !sb.append('g'))
|
||||
return NULL;
|
||||
if (ignoreCase() && !sb.append('i'))
|
||||
return NULL;
|
||||
if (multiline() && !sb.append('m'))
|
||||
return NULL;
|
||||
if (sticky() && !sb.append('y'))
|
||||
return NULL;
|
||||
|
||||
return sb.finishString();
|
||||
}
|
||||
|
@ -56,6 +56,26 @@
|
||||
#include "yarr/pcre/pcre.h"
|
||||
#endif
|
||||
|
||||
/*
|
||||
* JavaScript Regular Expressions
|
||||
*
|
||||
* There are several engine concepts associated with a single logical regexp:
|
||||
*
|
||||
* RegExpObject - The JS-visible object whose .[[Class]] equals "RegExp"
|
||||
*
|
||||
* RegExpShared - The compiled representation of the regexp.
|
||||
*
|
||||
* RegExpCode - The low-level implementation jit details.
|
||||
*
|
||||
* RegExpCompartment - Owns all RegExpShared instances in a compartment.
|
||||
*
|
||||
* To save memory, a RegExpShared is not created for a RegExpObject until it is
|
||||
* needed for execution. When a RegExpShared needs to be created, it is looked
|
||||
* up in a per-compartment table to allow reuse between objects. Lastly, on
|
||||
* GC, every RegExpShared (that is not active on the callstack) is discarded.
|
||||
* Because of the last point, any code using a RegExpShared (viz., by executing
|
||||
* a regexp) must indicate the RegExpShared is active via RegExpShared::Guard.
|
||||
*/
|
||||
namespace js {
|
||||
|
||||
enum RegExpRunStatus
|
||||
@ -84,19 +104,16 @@ class RegExpObject : public JSObject
|
||||
* so this function is really meant for object creation during code
|
||||
* execution, as opposed to during something like XDR.
|
||||
*/
|
||||
static inline RegExpObject *
|
||||
create(JSContext *cx, RegExpStatics *res, const jschar *chars, size_t length, RegExpFlag flags,
|
||||
TokenStream *tokenStream);
|
||||
static RegExpObject *
|
||||
create(JSContext *cx, RegExpStatics *res, const jschar *chars, size_t length,
|
||||
RegExpFlag flags, TokenStream *ts);
|
||||
|
||||
static inline RegExpObject *
|
||||
static RegExpObject *
|
||||
createNoStatics(JSContext *cx, const jschar *chars, size_t length, RegExpFlag flags,
|
||||
TokenStream *tokenStream);
|
||||
TokenStream *ts);
|
||||
|
||||
static inline RegExpObject *
|
||||
createNoStatics(JSContext *cx, JSAtom *atom, RegExpFlag flags, TokenStream *tokenStream);
|
||||
|
||||
/* Note: fallible. */
|
||||
JSFlatString *toString(JSContext *cx) const;
|
||||
static RegExpObject *
|
||||
createNoStatics(JSContext *cx, JSAtom *atom, RegExpFlag flags, TokenStream *ts);
|
||||
|
||||
/*
|
||||
* Run the regular expression over the input text.
|
||||
@ -109,8 +126,9 @@ class RegExpObject : public JSObject
|
||||
* N.B. it's the responsibility of the caller to hook the |output|
|
||||
* into the |RegExpStatics| appropriately, if necessary.
|
||||
*/
|
||||
RegExpRunStatus execute(JSContext *cx, const jschar *chars, size_t length, size_t *lastIndex,
|
||||
LifoAllocScope &allocScope, MatchPairs **output);
|
||||
RegExpRunStatus
|
||||
execute(JSContext *cx, const jschar *chars, size_t length, size_t *lastIndex,
|
||||
MatchPairs **output);
|
||||
|
||||
/* Accessors. */
|
||||
|
||||
@ -121,10 +139,12 @@ class RegExpObject : public JSObject
|
||||
inline void setLastIndex(double d);
|
||||
inline void zeroLastIndex();
|
||||
|
||||
JSLinearString *getSource() const {
|
||||
return &getSlot(SOURCE_SLOT).toString()->asLinear();
|
||||
JSFlatString *toString(JSContext *cx) const;
|
||||
|
||||
JSAtom *getSource() const {
|
||||
return &getSlot(SOURCE_SLOT).toString()->asAtom();
|
||||
}
|
||||
inline void setSource(JSLinearString *source);
|
||||
inline void setSource(JSAtom *source);
|
||||
|
||||
RegExpFlag getFlags() const {
|
||||
uintN flags = 0;
|
||||
@ -135,12 +155,6 @@ class RegExpObject : public JSObject
|
||||
return RegExpFlag(flags);
|
||||
}
|
||||
|
||||
inline bool startsWithAtomizedGreedyStar() const;
|
||||
|
||||
/* JIT only. */
|
||||
|
||||
inline size_t *addressOfPrivateRefCount() const;
|
||||
|
||||
/* Flags. */
|
||||
|
||||
inline void setIgnoreCase(bool enabled);
|
||||
@ -152,40 +166,12 @@ class RegExpObject : public JSObject
|
||||
bool multiline() const { return getSlot(MULTILINE_FLAG_SLOT).toBoolean(); }
|
||||
bool sticky() const { return getSlot(STICKY_FLAG_SLOT).toBoolean(); }
|
||||
|
||||
inline void finalize(JSContext *cx);
|
||||
|
||||
/* Clear out lazy |RegExpShared|. */
|
||||
inline void purge(JSContext *x);
|
||||
|
||||
RegExpShared &shared() const {
|
||||
JS_ASSERT(JSObject::getPrivate() != NULL);
|
||||
return *static_cast<RegExpShared *>(JSObject::getPrivate());
|
||||
}
|
||||
|
||||
RegExpShared *maybeShared() {
|
||||
return static_cast<RegExpShared *>(JSObject::getPrivate());
|
||||
}
|
||||
|
||||
RegExpShared *getShared(JSContext *cx) {
|
||||
if (RegExpShared *shared = maybeShared())
|
||||
return shared;
|
||||
return createShared(cx);
|
||||
}
|
||||
inline RegExpShared &shared() const;
|
||||
inline RegExpShared *maybeShared();
|
||||
inline RegExpShared *getShared(JSContext *cx);
|
||||
|
||||
private:
|
||||
friend class RegExpObjectBuilder;
|
||||
friend class RegExpMatcher;
|
||||
|
||||
inline bool init(JSContext *cx, JSLinearString *source, RegExpFlag flags);
|
||||
|
||||
/*
|
||||
* Precondition: the syntax for |source| has already been validated.
|
||||
* Side effect: sets the private field.
|
||||
*/
|
||||
RegExpShared *createShared(JSContext *cx);
|
||||
|
||||
friend bool ResetRegExpObject(JSContext *, RegExpObject *, JSLinearString *, RegExpFlag);
|
||||
friend bool ResetRegExpObject(JSContext *, RegExpObject *, AlreadyIncRefed<RegExpShared>);
|
||||
|
||||
/*
|
||||
* Compute the initial shape to associate with fresh RegExp objects,
|
||||
@ -194,11 +180,18 @@ class RegExpObject : public JSObject
|
||||
*/
|
||||
Shape *assignInitialShape(JSContext *cx);
|
||||
|
||||
inline bool init(JSContext *cx, JSAtom *source, RegExpFlag flags);
|
||||
|
||||
/*
|
||||
* Precondition: the syntax for |source| has already been validated.
|
||||
* Side effect: sets the private field.
|
||||
*/
|
||||
RegExpShared *createShared(JSContext *cx);
|
||||
|
||||
RegExpObject() MOZ_DELETE;
|
||||
RegExpObject &operator=(const RegExpObject &reo) MOZ_DELETE;
|
||||
}; /* class RegExpObject */
|
||||
};
|
||||
|
||||
/* Either builds a new RegExpObject or re-initializes an existing one. */
|
||||
class RegExpObjectBuilder
|
||||
{
|
||||
JSContext *cx;
|
||||
@ -207,27 +200,23 @@ class RegExpObjectBuilder
|
||||
bool getOrCreate();
|
||||
bool getOrCreateClone(RegExpObject *proto);
|
||||
|
||||
friend class RegExpMatcher;
|
||||
|
||||
public:
|
||||
RegExpObjectBuilder(JSContext *cx, RegExpObject *reobj = NULL)
|
||||
: cx(cx), reobj_(reobj)
|
||||
{ }
|
||||
RegExpObjectBuilder(JSContext *cx, RegExpObject *reobj = NULL);
|
||||
|
||||
RegExpObject *reobj() { return reobj_; }
|
||||
|
||||
RegExpObject *build(JSLinearString *str, RegExpFlag flags);
|
||||
RegExpObject *build(AlreadyIncRefed<RegExpShared> shared);
|
||||
RegExpObject *build(JSAtom *source, RegExpFlag flags);
|
||||
RegExpObject *build(JSAtom *source, RegExpShared &shared);
|
||||
|
||||
/* Perform a VM-internal clone. */
|
||||
RegExpObject *clone(RegExpObject *other, RegExpObject *proto);
|
||||
};
|
||||
|
||||
JSObject *
|
||||
CloneRegExpObject(JSContext *cx, JSObject *obj, JSObject *proto);
|
||||
|
||||
namespace detail {
|
||||
|
||||
static const jschar GreedyStarChars[] = {'.', '*'};
|
||||
|
||||
/* Abstracts away the gross |RegExpShared| backend details. */
|
||||
class RegExpCode
|
||||
{
|
||||
#if ENABLE_YARR_JIT
|
||||
@ -294,168 +283,138 @@ class RegExpCode
|
||||
#endif
|
||||
}
|
||||
|
||||
inline bool compile(JSContext *cx, JSLinearString &pattern, TokenStream *ts, uintN *parenCount,
|
||||
RegExpFlag flags);
|
||||
bool compile(JSContext *cx, JSLinearString &pattern, uintN *parenCount, RegExpFlag flags);
|
||||
|
||||
|
||||
inline RegExpRunStatus execute(JSContext *cx, const jschar *chars, size_t length, size_t start,
|
||||
int *output, size_t outputCount);
|
||||
RegExpRunStatus
|
||||
execute(JSContext *cx, const jschar *chars, size_t length, size_t start,
|
||||
int *output, size_t outputCount);
|
||||
};
|
||||
|
||||
enum RegExpCacheKind
|
||||
{
|
||||
RegExpCache_TestOptimized,
|
||||
RegExpCache_ExecCapable
|
||||
};
|
||||
} /* namespace detail */
|
||||
|
||||
class RegExpCacheValue
|
||||
{
|
||||
union {
|
||||
RegExpShared *shared_;
|
||||
uintptr_t bits;
|
||||
};
|
||||
|
||||
public:
|
||||
RegExpCacheValue() : shared_(NULL) {}
|
||||
|
||||
RegExpCacheValue(RegExpShared &shared, RegExpCacheKind kind) {
|
||||
reset(shared, kind);
|
||||
}
|
||||
|
||||
RegExpCacheKind kind() const {
|
||||
return (bits & 0x1)
|
||||
? RegExpCache_TestOptimized
|
||||
: RegExpCache_ExecCapable;
|
||||
}
|
||||
|
||||
RegExpShared &shared() {
|
||||
return *reinterpret_cast<RegExpShared *>(bits & ~uintptr_t(1));
|
||||
}
|
||||
|
||||
void reset(RegExpShared &shared, RegExpCacheKind kind) {
|
||||
shared_ = &shared;
|
||||
if (kind == RegExpCache_TestOptimized)
|
||||
bits |= 0x1;
|
||||
JS_ASSERT(this->kind() == kind);
|
||||
}
|
||||
};
|
||||
|
||||
} /* namespace detail */
|
||||
|
||||
/*
|
||||
* The "meat" of the builtin regular expression objects: it contains the
|
||||
* mini-program that represents the source of the regular expression.
|
||||
* Excepting refcounts, this is an immutable datastructure after
|
||||
* compilation.
|
||||
*
|
||||
* Note: refCount cannot overflow because that would require more
|
||||
* referring regexp objects than there is space for in addressable
|
||||
* memory.
|
||||
*/
|
||||
/* The compiled representation of a regexp. */
|
||||
class RegExpShared
|
||||
{
|
||||
typedef detail::RegExpCode RegExpCode;
|
||||
typedef detail::RegExpCacheKind RegExpCacheKind;
|
||||
typedef detail::RegExpCacheValue RegExpCacheValue;
|
||||
friend class RegExpCompartment;
|
||||
|
||||
RegExpCode code;
|
||||
JSLinearString *source;
|
||||
size_t refCount;
|
||||
uintN parenCount;
|
||||
RegExpFlag flags;
|
||||
detail::RegExpCode code;
|
||||
uintN parenCount;
|
||||
RegExpFlag flags;
|
||||
size_t activeUseCount;
|
||||
|
||||
private:
|
||||
RegExpShared(JSLinearString *source, RegExpFlag flags)
|
||||
: source(source), refCount(1), parenCount(0), flags(flags)
|
||||
{ }
|
||||
bool compile(JSContext *cx, JSAtom *source);
|
||||
|
||||
RegExpShared(RegExpFlag flags);
|
||||
JS_DECLARE_ALLOCATION_FRIENDS_FOR_PRIVATE_CONSTRUCTOR;
|
||||
|
||||
bool compile(JSContext *cx, TokenStream *ts);
|
||||
static inline void checkMatchPairs(JSString *input, int *buf, size_t matchItemCount);
|
||||
|
||||
static RegExpShared *
|
||||
createUncached(JSContext *cx, JSLinearString *source, RegExpFlag flags,
|
||||
TokenStream *tokenStream);
|
||||
|
||||
static bool cacheLookup(JSContext *cx, JSAtom *atom, RegExpFlag flags,
|
||||
RegExpCacheKind kind, AlreadyIncRefed<RegExpShared> *result);
|
||||
static bool cacheInsert(JSContext *cx, JSAtom *atom,
|
||||
RegExpCacheKind kind, RegExpShared &shared);
|
||||
|
||||
public:
|
||||
static AlreadyIncRefed<RegExpShared>
|
||||
create(JSContext *cx, JSLinearString *source, RegExpFlag flags, TokenStream *ts);
|
||||
/*
|
||||
* Extend the lifetime of a given RegExpShared to at least the lifetime of
|
||||
* the Guard object. See Regular Expression comment at the top.
|
||||
*/
|
||||
class Guard {
|
||||
RegExpShared *re_;
|
||||
Guard(const Guard &) MOZ_DELETE;
|
||||
void operator=(const Guard &) MOZ_DELETE;
|
||||
public:
|
||||
Guard() : re_(NULL) {}
|
||||
Guard(RegExpShared &re) : re_(&re) {
|
||||
re_->activeUseCount++;
|
||||
}
|
||||
void init(RegExpShared &re) {
|
||||
JS_ASSERT(!re_);
|
||||
re_ = &re;
|
||||
re_->activeUseCount++;
|
||||
}
|
||||
~Guard() {
|
||||
if (re_) {
|
||||
JS_ASSERT(re_->activeUseCount > 0);
|
||||
re_->activeUseCount--;
|
||||
}
|
||||
}
|
||||
bool initialized() const { return !!re_; }
|
||||
RegExpShared *operator->() { JS_ASSERT(initialized()); return re_; }
|
||||
RegExpShared &operator*() { JS_ASSERT(initialized()); return *re_; }
|
||||
};
|
||||
|
||||
static AlreadyIncRefed<RegExpShared>
|
||||
create(JSContext *cx, JSLinearString *source, JSString *flags, TokenStream *ts);
|
||||
/* Primary interface: run this regular expression on the given string. */
|
||||
|
||||
static AlreadyIncRefed<RegExpShared>
|
||||
createTestOptimized(JSContext *cx, JSAtom *originalSource, RegExpFlag flags);
|
||||
|
||||
RegExpRunStatus execute(JSContext *cx, const jschar *chars, size_t length, size_t *lastIndex,
|
||||
LifoAllocScope &allocScope, MatchPairs **output);
|
||||
|
||||
/* Mutators */
|
||||
|
||||
void incref(JSContext *cx);
|
||||
void decref(JSContext *cx);
|
||||
|
||||
/* For JIT access. */
|
||||
size_t *addressOfRefCount() { return &refCount; }
|
||||
RegExpRunStatus
|
||||
execute(JSContext *cx, const jschar *chars, size_t length, size_t *lastIndex,
|
||||
MatchPairs **output);
|
||||
|
||||
/* Accessors */
|
||||
|
||||
JSLinearString *getSource() const { return source; }
|
||||
size_t getParenCount() const { return parenCount; }
|
||||
|
||||
/* Accounts for the "0" (whole match) pair. */
|
||||
size_t pairCount() const { return parenCount + 1; }
|
||||
|
||||
RegExpFlag getFlags() const { return flags; }
|
||||
bool ignoreCase() const { return flags & IgnoreCaseFlag; }
|
||||
bool global() const { return flags & GlobalFlag; }
|
||||
bool multiline() const { return flags & MultilineFlag; }
|
||||
bool sticky() const { return flags & StickyFlag; }
|
||||
bool ignoreCase() const { return flags & IgnoreCaseFlag; }
|
||||
bool global() const { return flags & GlobalFlag; }
|
||||
bool multiline() const { return flags & MultilineFlag; }
|
||||
bool sticky() const { return flags & StickyFlag; }
|
||||
};
|
||||
|
||||
/*
|
||||
* Wraps the RegExpObject's internals in a recount-safe interface for
|
||||
* use in RegExp execution. This is used in situations where we'd like
|
||||
* to avoid creating a full-fledged RegExpObject. This interface is
|
||||
* provided in lieu of exposing the RegExpShared directly.
|
||||
*
|
||||
* Note: this exposes precisely the execute interface of a RegExpObject.
|
||||
*/
|
||||
class RegExpMatcher
|
||||
class RegExpCompartment
|
||||
{
|
||||
JSContext *cx_;
|
||||
AutoRefCount<RegExpShared> shared_;
|
||||
enum Type { Normal = 0x0, Hack = 0x1 };
|
||||
|
||||
struct Key {
|
||||
JSAtom *atom;
|
||||
uint16_t flag;
|
||||
uint16_t type;
|
||||
Key() {}
|
||||
Key(JSAtom *atom, RegExpFlag flag, Type type)
|
||||
: atom(atom), flag(flag), type(type) {}
|
||||
typedef Key Lookup;
|
||||
static HashNumber hash(const Lookup &l) {
|
||||
return DefaultHasher<JSAtom *>::hash(l.atom) ^ (l.flag << 1) ^ l.type;
|
||||
}
|
||||
static bool match(Key l, Key r) {
|
||||
return l.atom == r.atom && l.flag == r.flag && l.type == r.type;
|
||||
}
|
||||
};
|
||||
|
||||
typedef HashMap<Key, RegExpShared *, Key, RuntimeAllocPolicy> Map;
|
||||
Map map_;
|
||||
|
||||
RegExpShared *get(JSContext *cx, JSAtom *key, JSAtom *source, RegExpFlag flags, Type type);
|
||||
|
||||
public:
|
||||
explicit RegExpMatcher(JSContext *cx) : cx_(cx), shared_(cx) {
|
||||
JS_ASSERT(!initialized());
|
||||
}
|
||||
RegExpCompartment(JSRuntime *rt);
|
||||
~RegExpCompartment();
|
||||
|
||||
bool initialized() const {
|
||||
return !shared_.null();
|
||||
}
|
||||
bool global() const {
|
||||
return shared_->global();
|
||||
}
|
||||
bool sticky() const {
|
||||
return shared_->sticky();
|
||||
}
|
||||
bool init(JSContext *cx);
|
||||
void purge();
|
||||
|
||||
inline void init(NeedsIncRef<RegExpShared> shared);
|
||||
inline bool init(JSLinearString *patstr, JSString *opt);
|
||||
bool initWithTestOptimized(RegExpObject &reobj);
|
||||
/* Return a regexp corresponding to the given (source, flags) pair. */
|
||||
RegExpShared *get(JSContext *cx, JSAtom *source, RegExpFlag flags);
|
||||
|
||||
RegExpRunStatus execute(JSContext *cx, const jschar *chars, size_t length, size_t *lastIndex,
|
||||
LifoAllocScope &allocScope, MatchPairs **output) const {
|
||||
JS_ASSERT(initialized());
|
||||
return shared_->execute(cx, chars, length, lastIndex, allocScope, output);
|
||||
}
|
||||
/* Like 'get', but compile 'maybeOpt' (if non-null). */
|
||||
RegExpShared *get(JSContext *cx, JSAtom *source, JSString *maybeOpt);
|
||||
|
||||
/*
|
||||
* A 'hacked' RegExpShared is one where the input 'source' doesn't match
|
||||
* what is actually compiled in the regexp. To compile a hacked regexp,
|
||||
* getHack may be called providing both the original 'source' and the
|
||||
* 'hackedSource' which should actually be compiled. For a given 'source'
|
||||
* there may only ever be one corresponding 'hackedSource'. Thus, we assume
|
||||
* there is some single pure function mapping 'source' to 'hackedSource'
|
||||
* that is always respected in calls to getHack. Note that this restriction
|
||||
* only applies to 'getHack': a single 'source' value may be passed to both
|
||||
* 'get' and 'getHack'.
|
||||
*/
|
||||
RegExpShared *getHack(JSContext *cx, JSAtom *source, JSAtom *hackedSource, RegExpFlag flags);
|
||||
|
||||
/*
|
||||
* To avoid atomizing 'hackedSource', callers may call 'lookupHack',
|
||||
* passing only the original 'source'. Due to the abovementioned unique
|
||||
* mapping property, 'hackedSource' is unambiguous.
|
||||
*/
|
||||
RegExpShared *lookupHack(JSContext *cx, JSAtom *source, RegExpFlag flags);
|
||||
};
|
||||
|
||||
/*
|
||||
@ -467,23 +426,19 @@ class RegExpMatcher
|
||||
bool
|
||||
ParseRegExpFlags(JSContext *cx, JSString *flagStr, RegExpFlag *flagsOut);
|
||||
|
||||
inline bool
|
||||
IsRegExpMetaChar(jschar c);
|
||||
|
||||
inline bool
|
||||
CheckRegExpSyntax(JSContext *cx, JSLinearString *str)
|
||||
{
|
||||
return detail::RegExpCode::checkSyntax(cx, NULL, str);
|
||||
}
|
||||
|
||||
/*
|
||||
* Assuming ObjectClassIs(obj, ESClass_RegExp), return obj's RegExpShared.
|
||||
*
|
||||
* Beware: this RegExpShared can be owned by a compartment other than
|
||||
* cx->compartment. Normal RegExpShared::Guard (which is necessary anyways)
|
||||
* will protect the object but it is important not to assign the return value
|
||||
* to be the private of any RegExpObject.
|
||||
*/
|
||||
inline RegExpShared *
|
||||
RegExpToShared(JSContext *cx, JSObject &obj);
|
||||
|
||||
} /* namespace js */
|
||||
|
||||
extern JS_FRIEND_API(JSObject *) JS_FASTCALL
|
||||
js_CloneRegExpObject(JSContext *cx, JSObject *obj, JSObject *proto);
|
||||
|
||||
JSBool
|
||||
js_XDRRegExpObject(JSXDRState *xdr, JSObject **objp);
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user