author | Chris Leary <cdleary@mozilla.com> |
Fri, 07 Oct 2011 03:04:00 -0700 | |
changeset 78732 | 61dd23c012eef276495b8956cc8747e3ae4b7abd |
parent 78731 | 4b87afed1d1c92f2740d6b5965b2c47a69c1b3a1 |
child 78733 | d50bd6d5b097d2790c9125c3a060ae2773f98e53 |
push id | 21329 |
push user | eakhgari@mozilla.com |
push date | Fri, 14 Oct 2011 14:37:50 +0000 |
treeherder | mozilla-central@349f3d4b2d87 [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | mrbkap |
bugs | 673188 |
milestone | 10.0a1 |
first release with | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
--- a/js/src/builtin/RegExp.cpp +++ b/js/src/builtin/RegExp.cpp @@ -43,66 +43,180 @@ #include "builtin/RegExp.h" #include "vm/RegExpObject-inl.h" #include "vm/RegExpStatics-inl.h" using namespace js; using namespace js::types; -/* - * Return: - * - The original if no escaping need be performed. - * - A new string if escaping need be performed. - * - NULL on error. - */ -static JSString * -EscapeNakedForwardSlashes(JSContext *cx, JSString *unescaped) +class RegExpMatchBuilder +{ + JSContext * const cx; + JSObject * const array; + + bool setProperty(JSAtom *name, Value v) { + return !!js_DefineProperty(cx, array, ATOM_TO_JSID(name), &v, + JS_PropertyStub, JS_StrictPropertyStub, JSPROP_ENUMERATE); + } + + public: + RegExpMatchBuilder(JSContext *cx, JSObject *array) : cx(cx), array(array) {} + + bool append(uint32 index, Value v) { + JS_ASSERT(!array->getOps()->getElement); + return !!js_DefineElement(cx, array, index, &v, JS_PropertyStub, JS_StrictPropertyStub, + JSPROP_ENUMERATE); + } + + bool setIndex(int index) { + return setProperty(cx->runtime->atomState.indexAtom, Int32Value(index)); + } + + bool setInput(JSString *str) { + JS_ASSERT(str); + return setProperty(cx->runtime->atomState.inputAtom, StringValue(str)); + } +}; + +static bool +CreateRegExpMatchResult(JSContext *cx, JSString *input, const jschar *chars, size_t length, + MatchPairs *matchPairs, Value *rval) +{ + /* + * Create the (slow) result array for a match. + * + * Array contents: + * 0: matched string + * 1..pairCount-1: paren matches + * input: input string + * index: start index for the match + */ + JSObject *array = NewSlowEmptyArray(cx); + if (!array) + return false; + + if (!input) { + input = js_NewStringCopyN(cx, chars, length); + if (!input) + return false; + } + + RegExpMatchBuilder builder(cx, array); + + for (size_t i = 0; i < matchPairs->pairCount(); ++i) { + MatchPair pair = matchPairs->pair(i); + + JSString *captured; + if (pair.isUndefined()) { + JS_ASSERT(i != 0); /* Since we had a match, first pair must be present. */ + if (!builder.append(i, UndefinedValue())) + return false; + } else { + captured = js_NewDependentString(cx, input, pair.start, pair.length()); + if (!captured || !builder.append(i, StringValue(captured))) + return false; + } + } + + if (!builder.setIndex(matchPairs->pair(0).start) || !builder.setInput(input)) + return false; + + *rval = ObjectValue(*array); + return true; +} + +template <class T> +bool +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); + + switch (status) { + case RegExpRunStatus_Error: + return false; + case RegExpRunStatus_Success_NotFound: + *rval = NullValue(); + return true; + default: + JS_ASSERT(status == RegExpRunStatus_Success); + JS_ASSERT(matchPairs); + } + + if (res) + res->updateFromMatchPairs(cx, input, matchPairs); + + *lastIndex = matchPairs->pair(0).limit; + + if (type == RegExpTest) { + *rval = BooleanValue(true); + return true; + } + + return CreateRegExpMatchResult(cx, input, chars, length, matchPairs, rval); +} + +bool +js::ExecuteRegExp(JSContext *cx, RegExpStatics *res, RegExpPrivate *rep, JSLinearString *input, + const jschar *chars, size_t length, + size_t *lastIndex, RegExpExecType type, Value *rval) +{ + return ExecuteRegExpImpl(cx, res, rep, input, chars, length, lastIndex, type, rval); +} + +bool +js::ExecuteRegExp(JSContext *cx, RegExpStatics *res, RegExpObject *reobj, JSLinearString *input, + const jschar *chars, size_t length, + size_t *lastIndex, RegExpExecType type, Value *rval) +{ + return ExecuteRegExpImpl(cx, res, reobj, input, chars, length, lastIndex, type, rval); +} + +/* Note: returns the original if no escaping need be performed. */ +static JSLinearString * +EscapeNakedForwardSlashes(JSContext *cx, JSLinearString *unescaped) { size_t oldLen = unescaped->length(); - const jschar *oldChars = unescaped->getChars(cx); - if (!oldChars) - return NULL; + const jschar *oldChars = unescaped->chars(); + JS::Anchor<JSString *> anchor(unescaped); - js::Vector<jschar, 128> newChars(cx); + /* We may never need to use |sb|. Start using it lazily. */ + StringBuffer sb(cx); + for (const jschar *it = oldChars; it < oldChars + oldLen; ++it) { if (*it == '/' && (it == oldChars || it[-1] != '\\')) { - if (!newChars.length()) { - if (!newChars.reserve(oldLen + 1)) + /* There's a forward slash that needs escaping. */ + if (sb.empty()) { + /* This is the first one we've seen, copy everything up to this point. */ + if (!sb.reserve(oldLen + 1)) return NULL; - newChars.infallibleAppend(oldChars, size_t(it - oldChars)); + sb.infallibleAppend(oldChars, size_t(it - oldChars)); } - if (!newChars.append('\\')) + if (!sb.append('\\')) return NULL; } - if (!newChars.empty() && !newChars.append(*it)) + if (!sb.empty() && !sb.append(*it)) return NULL; } - if (newChars.empty()) - return unescaped; - - size_t len = newChars.length(); - if (!newChars.append('\0')) - return NULL; - jschar *chars = newChars.extractRawBuffer(); - JSString *escaped = js_NewString(cx, chars, len); - if (!escaped) - cx->free_(chars); - return escaped; + return sb.empty() ? unescaped : sb.finishString(); } static bool ResetRegExpObjectWithStatics(JSContext *cx, RegExpObject *reobj, - JSString *str, RegExpFlag flags = RegExpFlag(0)) + JSLinearString *str, RegExpFlag flags = RegExpFlag(0)) { flags = RegExpFlag(flags | cx->regExpStatics()->getFlags()); - return ResetRegExpObject(cx, reobj, str, flags); + return reobj->reset(cx, str, flags); } /* * Compile a new |RegExpPrivate| for the |RegExpObject|. * * Per ECMAv5 15.10.4.1, we act on combinations of (pattern, flags) as * arguments: * @@ -120,61 +234,62 @@ CompileRegExpObject(JSContext *cx, RegEx *rval = ObjectValue(*obj); return true; } Value sourceValue = argv[0]; if (ValueIsRegExp(sourceValue)) { /* * If we get passed in a |RegExpObject| source we return a new - * object with the same |RegExpPrivate|. + * object with the same source/flags. * * Note: the regexp static flags are not taken into consideration here. */ JSObject &sourceObj = sourceValue.toObject(); if (argc >= 2 && !argv[1].isUndefined()) { JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_NEWREGEXP_FLAGGED); return false; } - RegExpPrivate *rep = sourceObj.asRegExp()->getPrivate(); - if (!rep) - return false; - - rep->incref(cx); - if (!ResetRegExpObject(cx, obj, AlreadyIncRefed<RegExpPrivate>(rep))) + if (!obj->reset(cx, sourceObj.asRegExp())) return false; *rval = ObjectValue(*obj); return true; } - JSString *sourceStr; + JSLinearString *sourceStr; if (sourceValue.isUndefined()) { sourceStr = cx->runtime->emptyString; } else { /* Coerce to string and compile. */ - sourceStr = js_ValueToString(cx, sourceValue); + JSString *str = js_ValueToString(cx, sourceValue); + if (!str) + return false; + sourceStr = str->ensureLinear(cx); if (!sourceStr) return false; } RegExpFlag flags = RegExpFlag(0); if (argc > 1 && !argv[1].isUndefined()) { JSString *flagStr = js_ValueToString(cx, argv[1]); if (!flagStr) return false; argv[1].setString(flagStr); if (!ParseRegExpFlags(cx, flagStr, &flags)) return false; } - JSString *escapedSourceStr = EscapeNakedForwardSlashes(cx, sourceStr); + JSLinearString *escapedSourceStr = EscapeNakedForwardSlashes(cx, sourceStr); if (!escapedSourceStr) return false; + if (!RegExpPrivateCode::checkSyntax(cx, NULL, escapedSourceStr)) + return false; + if (!ResetRegExpObjectWithStatics(cx, obj, escapedSourceStr, flags)) return false; *rval = ObjectValue(*obj); return true; } static JSBool regexp_compile(JSContext *cx, uintN argc, Value *vp) @@ -182,19 +297,17 @@ regexp_compile(JSContext *cx, uintN argc CallArgs args = CallArgsFromVp(argc, vp); bool ok; JSObject *obj = NonGenericMethodGuard(cx, args, regexp_compile, &RegExpClass, &ok); if (!obj) return ok; RegExpObject *reobj = obj->asRegExp(); - ok = CompileRegExpObject(cx, reobj, args.length(), args.array(), &args.rval()); - JS_ASSERT_IF(ok, reobj->getPrivate()); - return ok; + return CompileRegExpObject(cx, reobj, args.length(), args.array(), &args.rval()); } static JSBool regexp_construct(JSContext *cx, uintN argc, Value *vp) { Value *argv = JS_ARGV(cx, vp); if (!IsConstructing(vp)) { @@ -208,26 +321,20 @@ regexp_construct(JSContext *cx, uintN ar return true; } } JSObject *obj = NewBuiltinClassInstance(cx, &RegExpClass); if (!obj) return false; - PreInitRegExpObject pireo(obj); - RegExpObject *reobj = pireo.get(); + if (!CompileRegExpObject(cx, obj->asRegExp(), argc, argv, &JS_RVAL(cx, vp))) + return false; - if (!CompileRegExpObject(cx, reobj, argc, argv, &JS_RVAL(cx, vp))) { - pireo.fail(); - return false; - } - - pireo.succeed(); - *vp = ObjectValue(*reobj); + *vp = ObjectValue(*obj); return true; } static JSBool regexp_toString(JSContext *cx, uintN argc, Value *vp) { CallArgs args = CallArgsFromVp(argc, vp); @@ -351,40 +458,19 @@ js_InitRegExpClass(JSContext *cx, JSObje JS_ASSERT(obj->isNative()); GlobalObject *global = obj->asGlobal(); JSObject *proto = global->createBlankPrototype(cx, &RegExpClass); if (!proto) return NULL; - { - AlreadyIncRefed<RegExpPrivate> rep = - RegExpPrivate::create(cx, cx->runtime->emptyString, RegExpFlag(0), NULL); - if (!rep) - return NULL; - - /* - * Associate the empty regular expression with |RegExp.prototype|, and define - * the initial non-method properties of any regular expression instance. - * These must be added before methods to preserve slot layout. - */ -#ifdef DEBUG - assertSameCompartment(cx, proto, rep->compartment); -#endif - - PreInitRegExpObject pireo(proto); - RegExpObject *reproto = pireo.get(); - if (!ResetRegExpObject(cx, reproto, rep)) { - pireo.fail(); - return NULL; - } - - pireo.succeed(); - } + RegExpObject *reproto = proto->asRegExp(); + if (!reproto->reset(cx, cx->runtime->emptyString, RegExpFlag(0))) + return NULL; if (!DefinePropertiesAndBrand(cx, proto, NULL, regexp_methods)) return NULL; JSFunction *ctor = global->createConstructor(cx, regexp_construct, &RegExpClass, CLASS_ATOM(cx, RegExp), 2); if (!ctor) return NULL; @@ -414,73 +500,78 @@ js_InitRegExpClass(JSContext *cx, JSObje } /* * ES5 15.10.6.2 (and 15.10.6.3, which calls 15.10.6.2). * * RegExp.prototype.test doesn't need to create a results array, and we use * |execType| to perform this optimization. */ -static JSBool +static bool ExecuteRegExp(JSContext *cx, Native native, uintN argc, Value *vp) { CallArgs args = CallArgsFromVp(argc, vp); /* Step 1. */ bool ok; JSObject *obj = NonGenericMethodGuard(cx, args, native, &RegExpClass, &ok); if (!obj) return ok; RegExpObject *reobj = obj->asRegExp(); - RegExpPrivate *re = reobj->getPrivate(); - if (!re) + RegExpPrivate *rep = reobj->getOrCreatePrivate(cx); + if (!rep) return true; /* * Code execution under this call could swap out the guts of |reobj|, so we * have to take a defensive refcount here. */ - AutoRefCount<RegExpPrivate> arc(cx, NeedsIncRef<RegExpPrivate>(re)); + AutoRefCount<RegExpPrivate> arc(cx, NeedsIncRef<RegExpPrivate>(rep)); RegExpStatics *res = cx->regExpStatics(); /* Step 2. */ - JSString *input = js_ValueToString(cx, args.length() > 0 ? args[0] : UndefinedValue()); + JSString *input = js_ValueToString(cx, args.length() > 0 ? args[0] : UndefinedValue()); if (!input) return false; - + /* Step 3. */ + JSLinearString *linearInput = input->ensureLinear(cx); + const jschar *chars = linearInput->chars(); size_t length = input->length(); /* Step 4. */ const Value &lastIndex = reobj->getLastIndex(); /* Step 5. */ jsdouble i; if (!ToInteger(cx, lastIndex, &i)) return false; /* Steps 6-7 (with sticky extension). */ - if (!re->global() && !re->sticky()) + if (!rep->global() && !rep->sticky()) i = 0; /* Step 9a. */ if (i < 0 || i > length) { reobj->zeroLastIndex(); args.rval() = NullValue(); return true; } /* Steps 8-21. */ + RegExpExecType execType = native == regexp_test ? RegExpTest : RegExpExec; size_t lastIndexInt(i); - if (!re->execute(cx, res, input, &lastIndexInt, native == regexp_test, &args.rval())) + if (!ExecuteRegExp(cx, res, rep, linearInput, chars, length, &lastIndexInt, execType, + &args.rval())) { return false; + } /* Step 11 (with sticky extension). */ - if (re->global() || (!args.rval().isNull() && re->sticky())) { + if (rep->global() || (!args.rval().isNull() && rep->sticky())) { if (args.rval().isNull()) reobj->zeroLastIndex(); else reobj->setLastIndex(lastIndexInt); } return true; }
--- a/js/src/builtin/RegExp.h +++ b/js/src/builtin/RegExp.h @@ -48,16 +48,31 @@ js_InitRegExpClass(JSContext *cx, JSObje /* * The following builtin natives are extern'd for pointer comparison in * other parts of the engine. */ namespace js { +/* + * |res| may be null if the |RegExpStatics| are not to be updated. + * |input| may be null if there is no |JSString| corresponding to + * |chars| and |length|. + */ +bool +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, RegExpPrivate *rep, JSLinearString *input, + const jschar *chars, size_t length, + size_t *lastIndex, RegExpExecType type, Value *rval); + extern JSBool regexp_exec(JSContext *cx, uintN argc, Value *vp); extern JSBool regexp_test(JSContext *cx, uintN argc, Value *vp); } /* namespace js */
--- a/js/src/ds/LifoAlloc.h +++ b/js/src/ds/LifoAlloc.h @@ -319,16 +319,20 @@ class LifoAllocScope { mark = lifoAlloc->mark(); } ~LifoAllocScope() { if (shouldRelease) lifoAlloc->release(mark); } + LifoAlloc &alloc() { + return *lifoAlloc; + } + void releaseEarly() { JS_ASSERT(shouldRelease); lifoAlloc->release(mark); shouldRelease = false; } }; } /* namespace js */
--- a/js/src/jsapi.cpp +++ b/js/src/jsapi.cpp @@ -5956,26 +5956,19 @@ JS_ClearRegExpStatics(JSContext *cx, JSO } JS_PUBLIC_API(JSBool) JS_ExecuteRegExp(JSContext *cx, JSObject *obj, JSObject *reobj, jschar *chars, size_t length, size_t *indexp, JSBool test, jsval *rval) { CHECK_REQUEST(cx); - RegExpPrivate *rep = reobj->asRegExp()->getPrivate(); - if (!rep) - return false; - - JSString *str = js_NewStringCopyN(cx, chars, length); - if (!str) - return false; - RegExpStatics *res = obj->asGlobal()->getRegExpStatics(); - return rep->execute(cx, res, str, indexp, test, rval); + return ExecuteRegExp(cx, res, reobj->asRegExp(), NULL, chars, length, + indexp, test ? RegExpTest : RegExpExec, rval); } JS_PUBLIC_API(JSObject *) JS_NewRegExpObjectNoStatics(JSContext *cx, char *bytes, size_t length, uintN flags) { CHECK_REQUEST(cx); jschar *chars = InflateString(cx, bytes, &length); if (!chars) @@ -5993,25 +5986,18 @@ JS_NewUCRegExpObjectNoStatics(JSContext } JS_PUBLIC_API(JSBool) JS_ExecuteRegExpNoStatics(JSContext *cx, JSObject *obj, jschar *chars, size_t length, size_t *indexp, JSBool test, jsval *rval) { CHECK_REQUEST(cx); - RegExpPrivate *rep = obj->asRegExp()->getPrivate(); - if (!rep) - return false; - - JSString *str = js_NewStringCopyN(cx, chars, length); - if (!str) - return false; - - return rep->executeNoStatics(cx, str, indexp, test, rval); + return ExecuteRegExp(cx, NULL, obj->asRegExp(), NULL, chars, length, indexp, + test ? RegExpTest : RegExpExec, rval); } JS_PUBLIC_API(JSBool) JS_ObjectIsRegExp(JSContext *cx, JSObject *obj) { JS_ASSERT(obj); return obj->isRegExp(); }
--- a/js/src/jsprvtd.h +++ b/js/src/jsprvtd.h @@ -123,27 +123,35 @@ class JSAtom; struct JSDefinition; class JSWrapper; namespace js { struct ArgumentsData; struct Class; +class RegExpObject; class RegExpPrivate; class RegExpStatics; +class MatchPairs; enum RegExpFlag { IgnoreCaseFlag = JS_BIT(0), GlobalFlag = JS_BIT(1), MultilineFlag = JS_BIT(2), StickyFlag = JS_BIT(3) }; +enum RegExpExecType +{ + RegExpExec, + RegExpTest +}; + class AutoStringRooter; class ExecuteArgsGuard; class InvokeFrameGuard; class InvokeArgsGuard; class StringBuffer; class TraceRecorder; struct TraceMonitor;
--- a/js/src/jsstr.cpp +++ b/js/src/jsstr.cpp @@ -67,16 +67,17 @@ #include "jsnum.h" #include "jsobj.h" #include "jsopcode.h" #include "jsprobes.h" #include "jsscope.h" #include "jsstr.h" #include "jsversion.h" +#include "builtin/RegExp.h" #include "vm/GlobalObject.h" #include "vm/RegExpObject.h" #include "jsinferinlines.h" #include "jsobjinlines.h" #include "jsautooplen.h" // generated headers last #include "vm/RegExpObject-inl.h" @@ -512,31 +513,31 @@ js_str_toString(JSContext *cx, uintN arg args.rval().setString(str); return true; } /* * Java-like string native methods. */ - + JS_ALWAYS_INLINE bool ValueToIntegerRange(JSContext *cx, const Value &v, int32 *out) { if (v.isInt32()) { *out = v.toInt32(); } else { double d; if (!ToInteger(cx, v, &d)) return false; if (d > INT32_MAX) *out = INT32_MAX; else if (d < INT32_MIN) *out = INT32_MIN; - else + else *out = int32(d); } return true; } static JSBool str_substring(JSContext *cx, uintN argc, Value *vp) @@ -1248,53 +1249,59 @@ class FlatMatch const jschar *pat; size_t patlen; int32 match_; friend class RegExpGuard; public: FlatMatch() : patstr(NULL) {} /* Old GCC wants this initialization. */ - JSString *pattern() const { return patstr; } + JSLinearString *pattern() const { return patstr; } size_t patternLength() const { return patlen; } /* * Note: The match is -1 when the match is performed successfully, * but no match is found. */ int32 match() const { return match_; } }; -/* A regexp and optional associated object. */ class RegExpPair { AutoRefCount<RegExpPrivate> rep_; - JSObject *reobj_; + RegExpObject *reobj_; explicit RegExpPair(RegExpPair &); + void operator=(const RegExpPair &); public: explicit RegExpPair(JSContext *cx) : rep_(cx) {} - void reset(JSObject &obj) { - reobj_ = &obj; - RegExpPrivate *rep = reobj_->asRegExp()->getPrivate(); - JS_ASSERT(rep); + bool resetWithObject(JSContext *cx, RegExpObject *reobj) { + reobj_ = reobj; + RegExpPrivate *rep = reobj_->asRegExp()->getOrCreatePrivate(cx); + if (!rep) + return false; rep_.reset(NeedsIncRef<RegExpPrivate>(rep)); + return true; } - void reset(AlreadyIncRefed<RegExpPrivate> rep) { + void resetWithPrivate(AlreadyIncRefed<RegExpPrivate> rep) { reobj_ = NULL; rep_.reset(rep); } - /* Note: May be null. */ - JSObject *reobj() const { return reobj_; } - bool hasRegExp() const { return !rep_.null(); } - RegExpPrivate &re() const { JS_ASSERT(hasRegExp()); return *rep_; } + bool null() const { return rep_.null(); } + + RegExpObject *reobj() const { return reobj_; } + + RegExpPrivate *getPrivate() const { + JS_ASSERT(!null()); + return rep_.get(); + } }; /* * RegExpGuard factors logic out of String regexp operations. * * @param optarg Indicates in which argument position RegExp * flags will be found, if present. This is a Mozilla * extension and not part of any ECMA spec. @@ -1309,17 +1316,17 @@ class RegExpGuard FlatMatch fm; /* * Upper bound on the number of characters we are willing to potentially * waste on searching for RegExp meta-characters. */ static const size_t MAX_FLAT_PAT_LEN = 256; - static JSString *flattenPattern(JSContext *cx, JSLinearString *patstr) { + static JSLinearString *flattenPattern(JSContext *cx, JSLinearString *patstr) { StringBuffer sb(cx); if (!sb.reserve(patstr->length())) return NULL; static const jschar ESCAPE_CHAR = '\\'; const jschar *chars = patstr->chars(); size_t len = patstr->length(); for (const jschar *it = chars; it != chars + len; ++it) { @@ -1338,23 +1345,23 @@ class RegExpGuard explicit RegExpGuard(JSContext *cx) : cx(cx), rep(cx) {} ~RegExpGuard() {} /* init must succeed in order to call tryFlatMatch or normalizeRegExp. */ bool init(uintN argc, Value *vp, bool convertVoid = false) { if (argc != 0 && ValueIsRegExp(vp[2])) { - rep.reset(vp[2].toObject()); + rep.resetWithObject(cx, vp[2].toObject().asRegExp()); } else { if (convertVoid && (argc == 0 || vp[2].isUndefined())) { fm.patstr = cx->runtime->emptyString; return true; } - + fm.patstr = ArgToRootedString(cx, argc, vp, 0); if (!fm.patstr) return false; } return true; } /* @@ -1365,17 +1372,17 @@ class RegExpGuard * @return Whether flat matching could be used. * * N.B. tryFlatMatch returns NULL on OOM, so the caller must check cx->isExceptionPending(). */ const FlatMatch * tryFlatMatch(JSContext *cx, JSString *textstr, uintN optarg, uintN argc, bool checkMetaChars = true) { - if (rep.hasRegExp()) + if (!rep.null()) return NULL; fm.pat = fm.patstr->chars(); fm.patlen = fm.patstr->length(); if (optarg < argc) return NULL; @@ -1398,56 +1405,56 @@ class RegExpGuard } return &fm; } /* If the pattern is not already a regular expression, make it so. */ const RegExpPair * normalizeRegExp(bool flat, uintN optarg, uintN argc, Value *vp) { - if (rep.hasRegExp()) + if (!rep.null()) return &rep; /* Build RegExp from pattern string. */ JSString *opt; if (optarg < argc) { opt = js_ValueToString(cx, vp[2 + optarg]); if (!opt) return NULL; } else { opt = NULL; } - JSString *patstr; + JSLinearString *patstr; if (flat) { patstr = flattenPattern(cx, fm.patstr); if (!patstr) return NULL; } else { patstr = fm.patstr; } JS_ASSERT(patstr); - AlreadyIncRefed<RegExpPrivate> re = RegExpPrivate::createFlagged(cx, patstr, opt, NULL); + AlreadyIncRefed<RegExpPrivate> re = RegExpPrivate::create(cx, patstr, opt, NULL); if (!re) return NULL; - rep.reset(re); + rep.resetWithPrivate(re); return &rep; } #if DEBUG - bool hasRegExpPair() const { return rep.hasRegExp(); } + bool hasRegExpPair() const { return !rep.null(); } #endif }; -/* js_ExecuteRegExp indicates success in two ways, based on the 'test' flag. */ +/* ExecuteRegExp indicates success in two ways, based on the 'test' flag. */ static JS_ALWAYS_INLINE bool -Matched(bool test, const Value &v) +Matched(RegExpExecType type, const Value &v) { - return test ? v.isTrue() : !v.isNull(); + return type == RegExpTest ? v.isTrue() : !v.isNull(); } typedef bool (*DoMatchCallback)(JSContext *cx, RegExpStatics *res, size_t count, void *data); /* * BitOR-ing these flags allows the DoMatch caller to control when how the * RegExp engine is called and when callbacks are fired. */ @@ -1458,43 +1465,50 @@ enum MatchControlFlags { MATCH_ARGS = TEST_GLOBAL_BIT, MATCHALL_ARGS = CALLBACK_ON_SINGLE_BIT, REPLACE_ARGS = TEST_GLOBAL_BIT | TEST_SINGLE_BIT | CALLBACK_ON_SINGLE_BIT }; /* Factor out looping and matching logic. */ static bool -DoMatch(JSContext *cx, RegExpStatics *res, JSString *str, const RegExpPair &rep, +DoMatch(JSContext *cx, RegExpStatics *res, JSString *str, const RegExpPair ®ExpPair, DoMatchCallback callback, void *data, MatchControlFlags flags, Value *rval) { - RegExpPrivate &re = rep.re(); - if (re.global()) { + RegExpPrivate *rep = regExpPair.getPrivate(); + JSLinearString *linearStr = str->ensureLinear(cx); + if (!linearStr) + return false; + const jschar *chars = linearStr->chars(); + size_t length = linearStr->length(); + + if (rep->global()) { /* global matching ('g') */ - bool testGlobal = flags & TEST_GLOBAL_BIT; - if (rep.reobj()) - rep.reobj()->asRegExp()->zeroLastIndex(); + RegExpExecType type = flags & TEST_GLOBAL_BIT ? RegExpTest : RegExpExec; + if (RegExpObject *reobj = regExpPair.reobj()) + reobj->zeroLastIndex(); + for (size_t count = 0, i = 0, length = str->length(); i <= length; ++count) { - if (!re.execute(cx, res, str, &i, testGlobal, rval)) + if (!ExecuteRegExp(cx, res, rep, linearStr, chars, length, &i, type, rval)) return false; - if (!Matched(testGlobal, *rval)) + if (!Matched(type, *rval)) break; if (!callback(cx, res, count, data)) return false; if (!res->matched()) ++i; } } else { /* single match */ - bool testSingle = !!(flags & TEST_SINGLE_BIT), - callbackOnSingle = !!(flags & CALLBACK_ON_SINGLE_BIT); + RegExpExecType type = (flags & TEST_SINGLE_BIT) ? RegExpTest : RegExpExec; + bool callbackOnSingle = !!(flags & CALLBACK_ON_SINGLE_BIT); size_t i = 0; - if (!re.execute(cx, res, str, &i, testSingle, rval)) + if (!ExecuteRegExp(cx, res, rep, linearStr, chars, length, &i, type, rval)) return false; - if (callbackOnSingle && Matched(testSingle, *rval) && !callback(cx, res, 0, data)) + if (callbackOnSingle && Matched(type, *rval) && !callback(cx, res, 0, data)) return false; } return true; } static bool BuildFlatMatchArray(JSContext *cx, JSString *textstr, const FlatMatch &fm, Value *vp) { @@ -1539,17 +1553,17 @@ MatchCallback(JSContext *cx, RegExpStati } JSBool js::str_match(JSContext *cx, uintN argc, Value *vp) { JSString *str = ThisToStringForStringProto(cx, vp); if (!str) return false; - + RegExpGuard g(cx); if (!g.init(argc, vp, true)) return false; if (const FlatMatch *fm = g.tryFlatMatch(cx, str, 1, argc)) return BuildFlatMatchArray(cx, str, *fm, vp); if (cx->isExceptionPending()) /* from tryFlatMatch */ return false; @@ -1559,17 +1573,17 @@ js::str_match(JSContext *cx, uintN argc, AutoObjectRooter array(cx); MatchArgType arg = array.addr(); RegExpStatics *res = cx->regExpStatics(); Value rval; if (!DoMatch(cx, res, str, *rep, MatchCallback, arg, MATCH_ARGS, &rval)) return false; - if (rep->re().global()) + if (rep->getPrivate()->global()) vp->setObjectOrNull(array.object()); else *vp = rval; return true; } JSBool js::str_search(JSContext *cx, uintN argc, Value *vp) @@ -1582,24 +1596,30 @@ js::str_search(JSContext *cx, uintN argc if (!g.init(argc, vp, true)) return false; if (const FlatMatch *fm = g.tryFlatMatch(cx, str, 1, argc)) { vp->setInt32(fm->match()); return true; } if (cx->isExceptionPending()) /* from tryFlatMatch */ return false; - + const RegExpPair *rep = g.normalizeRegExp(false, 1, argc, vp); if (!rep) return false; + JSLinearString *linearStr = str->ensureLinear(cx); + if (!linearStr) + return false; + const jschar *chars = linearStr->chars(); + size_t length = linearStr->length(); RegExpStatics *res = cx->regExpStatics(); + /* Per ECMAv5 15.5.4.12 (5) The last index property is ignored and left unchanged. */ size_t i = 0; - if (!rep->re().execute(cx, res, str, &i, true, vp)) + if (!ExecuteRegExp(cx, res, rep->getPrivate(), linearStr, chars, length, &i, RegExpTest, vp)) return false; if (vp->isTrue()) vp->setInt32(res->matchStart()); else vp->setInt32(-1); return true; } @@ -1652,17 +1672,17 @@ InterpretDollar(JSContext *cx, RegExpSta } if (num == 0) return false; *skip = cp - dp; JS_ASSERT(num <= res->parenCount()); - /* + /* * Note: we index to get the paren with the (1-indexed) pair * number, as opposed to a (0-indexed) paren number. */ res->getParen(num, out); return true; } *skip = 2; @@ -1810,17 +1830,17 @@ FindReplaceLength(JSContext *cx, RegExpS } else { dp++; } } *sizep = replen; return true; } -/* +/* * Precondition: |rdata.sb| already has necessary growth space reserved (as * derived from FindReplaceLength). */ static void DoReplace(JSContext *cx, RegExpStatics *res, ReplaceData &rdata) { JSLinearString *repstr = rdata.repstr; const jschar *cp; @@ -2207,17 +2227,17 @@ js::str_replace(JSContext *cx, uintN arg if (fm->match() < 0) { vp->setString(rdata.str); return true; } if (rdata.lambda) return str_replace_flat_lambda(cx, argc, vp, rdata, *fm); - /* + /* * Note: we could optimize the text.length == pattern.length case if we wanted, * even in the presence of dollar metachars. */ if (rdata.dollar) return BuildDollarReplacement(cx, rdata.str, rdata.repstr, rdata.dollar, *fm, vp); return BuildFlatReplacement(cx, rdata.str, rdata.repstr, *fm, vp); } @@ -2378,30 +2398,32 @@ SplitHelper(JSContext *cx, JSLinearStrin * The SplitMatch operation from ES5 15.5.4.14 is implemented using different * matchers 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. */ class SplitRegExpMatcher { RegExpStatics *res; - RegExpPrivate *re; + RegExpPrivate *rep; public: static const bool returnsCaptures = true; - SplitRegExpMatcher(RegExpPrivate *re, RegExpStatics *res) : res(res), re(re) {} + SplitRegExpMatcher(RegExpPrivate *rep, RegExpStatics *res) : res(res), rep(rep) {} inline bool operator()(JSContext *cx, JSLinearString *str, size_t index, SplitMatchResult *result) { Value rval #ifdef __GNUC__ /* quell GCC overwarning */ = UndefinedValue() #endif ; - if (!re->execute(cx, res, str, &index, true, &rval)) + const jschar *chars = str->chars(); + size_t length = str->length(); + if (!ExecuteRegExp(cx, res, rep, str, chars, length, &index, RegExpTest, &rval)) return false; if (!rval.isTrue()) { result->setFailure(); return true; } JSSubString sep; res->getLastMatch(&sep); @@ -2460,17 +2482,19 @@ js::str_split(JSContext *cx, uintN argc, } /* Step 8. */ RegExpPrivate *re = NULL; JSLinearString *sepstr = NULL; bool sepUndefined = (argc == 0 || vp[2].isUndefined()); if (!sepUndefined) { if (ValueIsRegExp(vp[2])) { - re = vp[2].toObject().asRegExp()->getPrivate(); + re = vp[2].toObject().asRegExp()->getOrCreatePrivate(cx); + if (!re) + return false; } else { JSString *sep = js_ValueToString(cx, vp[2]); if (!sep) return false; vp[2].setString(sep); sepstr = sep->ensureLinear(cx); if (!sepstr) @@ -2541,17 +2565,17 @@ str_substr(JSContext *cx, uintN argc, Va begin += length; /* length + INT_MIN will always be less then 0 */ if (begin < 0) begin = 0; } if (argc == 1 || vp[3].isUndefined()) { len = length - begin; } else { - if (!ValueToIntegerRange(cx, vp[3], &len)) + if (!ValueToIntegerRange(cx, vp[3], &len)) return false; if (len <= 0) { str = cx->runtime->emptyString; goto out; } if (uint32(length) < uint32(begin + len))
--- a/js/src/methodjit/Compiler.cpp +++ b/js/src/methodjit/Compiler.cpp @@ -6588,19 +6588,19 @@ mjit::Compiler::jsop_regexp() types::OBJECT_FLAG_REGEXP_FLAGS_SET)) { prepareStubCall(Uses(0)); masm.move(ImmPtr(obj), Registers::ArgReg1); INLINE_STUBCALL(stubs::RegExp, REJOIN_FALLTHROUGH); frame.pushSynced(JSVAL_TYPE_OBJECT); return; } - RegExpPrivate *regexp = static_cast<RegExpObject *>(obj)->getPrivate(); - - DebugOnly<uint32> origFlags = regexp->getFlags(); + RegExpObject *reobj = obj->asRegExp(); + + DebugOnly<uint32> origFlags = reobj->getFlags(); DebugOnly<uint32> staticsFlags = res->getFlags(); JS_ASSERT((origFlags & staticsFlags) == staticsFlags); /* * JS semantics require regular expression literals to create different * objects every time they execute. We only need to do this cloning if the * script could actually observe the effect of such cloning, by getting * or setting properties on it. Particular RegExp and String natives take @@ -6642,20 +6642,16 @@ mjit::Compiler::jsop_regexp() Jump emptyFreeList = masm.getNewObject(cx, result, obj); stubcc.linkExit(emptyFreeList, Uses(0)); stubcc.leave(); stubcc.masm.move(ImmPtr(obj), Registers::ArgReg1); OOL_STUBCALL(stubs::RegExp, REJOIN_FALLTHROUGH); - /* Bump the refcount on the wrapped RegExp. */ - size_t *refcount = regexp->addressOfRefCount(); - masm.add32(Imm32(1), AbsoluteAddress(refcount)); - frame.pushTypedPayload(JSVAL_TYPE_OBJECT, result); stubcc.rejoin(Changes(1)); } bool mjit::Compiler::startLoop(jsbytecode *head, Jump entry, jsbytecode *entryTarget) {
new file mode 100644 --- /dev/null +++ b/js/src/vm/MatchPairs.h @@ -0,0 +1,125 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: set ts=8 sw=4 et tw=99 ft=cpp: + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla SpiderMonkey JavaScript code. + * + * The Initial Developer of the Original Code is + * the Mozilla Foundation. + * Portions created by the Initial Developer are Copyright (C) 2011 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Chris Leary <cdleary@mozilla.com> + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#ifndef MatchPairs_h__ +#define MatchPairs_h__ + +/* + * RegExp match results are succinctly represented by pairs of integer + * indices delimiting (start, limit] segments of the input string. + * + * The pair count for a given RegExp match is the capturing parentheses + * count plus one for the "0 capturing paren" whole text match. + */ + +namespace js { + +struct MatchPair +{ + int start; + int limit; + + MatchPair(int start, int limit) : start(start), limit(limit) {} + + size_t length() const { + JS_ASSERT(!isUndefined()); + return limit - start; + } + + bool isUndefined() const { + return start == -1; + } + + void check() const { + JS_ASSERT(limit >= start); + JS_ASSERT_IF(!isUndefined(), start >= 0); + } +}; + +class MatchPairs +{ + size_t pairCount_; + int buffer_[1]; + + explicit MatchPairs(size_t pairCount) : pairCount_(pairCount) { + initPairValues(); + } + + void initPairValues() { + for (int *it = buffer_; it < buffer_ + 2 * pairCount_; ++it) + *it = -1; + } + + static size_t calculateSize(size_t backingPairCount) { + return sizeof(MatchPairs) - sizeof(int) + sizeof(int) * backingPairCount; + } + + int *buffer() { return buffer_; } + + friend class RegExpPrivate; + + public: + /* + * |backingPairCount| is necessary because PCRE uses extra space + * after the actual results in the buffer. + */ + static MatchPairs *create(LifoAlloc &alloc, size_t pairCount, size_t backingPairCount); + + size_t pairCount() const { return pairCount_; } + + MatchPair pair(size_t i) { + JS_ASSERT(i < pairCount()); + return MatchPair(buffer_[2 * i], buffer_[2 * i + 1]); + } + + void displace(size_t amount) { + if (!amount) + return; + + for (int *it = buffer_; it < buffer_ + 2 * pairCount_; ++it) + *it = *it < 0 ? -1 : *it + amount; + } + + inline void checkAgainst(size_t length); +}; + +} /* namespace js */ + +#endif
--- a/js/src/vm/RegExpObject-inl.h +++ b/js/src/vm/RegExpObject-inl.h @@ -47,59 +47,21 @@ #include "jsobjinlines.h" #include "jsstrinlines.h" #include "RegExpStatics-inl.h" inline js::RegExpObject * JSObject::asRegExp() { JS_ASSERT(isRegExp()); - js::RegExpObject *reobj = static_cast<js::RegExpObject *>(this); - JS_ASSERT(reobj->getPrivate()); - return reobj; + return static_cast<js::RegExpObject *>(this); } namespace js { -/* - * Maintains the post-initialization invariant of having a RegExpPrivate. - * - * N.B. If initialization fails, the |RegExpPrivate| will be null, so - * finalization must consider that as a possibility. - */ -class PreInitRegExpObject -{ - RegExpObject *reobj; - DebugOnly<bool> gotResult; - - public: - explicit PreInitRegExpObject(JSObject *obj) { - JS_ASSERT(obj->isRegExp()); - reobj = static_cast<RegExpObject *>(obj); - gotResult = false; - } - - ~PreInitRegExpObject() { - JS_ASSERT(gotResult); - } - - RegExpObject *get() { return reobj; } - - void succeed() { - JS_ASSERT(!gotResult); - JS_ASSERT(reobj->getPrivate()); - gotResult = true; - } - - void fail() { - JS_ASSERT(!gotResult); - gotResult = true; - } -}; - inline bool ValueIsRegExp(const Value &v) { return !v.isPrimitive() && v.toObject().isRegExp(); } inline bool IsRegExpMetaChar(jschar c) @@ -120,78 +82,80 @@ HasRegExpMetaChars(const jschar *chars, { for (size_t i = 0; i < length; ++i) { if (IsRegExpMetaChar(chars[i])) return true; } return false; } -inline bool -ResetRegExpObject(JSContext *cx, RegExpObject *reobj, JSString *str, RegExpFlag flags) -{ - AlreadyIncRefed<RegExpPrivate> rep = RegExpPrivate::create(cx, str, flags, NULL); - if (!rep) - return false; - - return reobj->reset(cx, rep); -} - -inline bool -ResetRegExpObject(JSContext *cx, RegExpObject *reobj, AlreadyIncRefed<RegExpPrivate> rep) -{ - return reobj->reset(cx, rep); -} - inline RegExpObject * RegExpObject::create(JSContext *cx, RegExpStatics *res, const jschar *chars, size_t length, - RegExpFlag flags, TokenStream *ts) + RegExpFlag flags, TokenStream *tokenStream) { RegExpFlag staticsFlags = res->getFlags(); - return createNoStatics(cx, chars, length, RegExpFlag(flags | staticsFlags), ts); + return createNoStatics(cx, chars, length, RegExpFlag(flags | staticsFlags), tokenStream); } inline RegExpObject * RegExpObject::createNoStatics(JSContext *cx, const jschar *chars, size_t length, - RegExpFlag flags, TokenStream *ts) + RegExpFlag flags, TokenStream *tokenStream) { - JSString *str = js_NewStringCopyN(cx, chars, length); - if (!str) + JSLinearString *source = js_NewStringCopyN(cx, chars, length); + if (!source) return NULL; - /* - * |NewBuiltinClassInstance| can GC before we store |re| in the - * private field of the object. At that point the only reference to - * the source string could be from the malloc-allocated GC-invisible - * |re|. So we must anchor. - */ - JS::Anchor<JSString *> anchor(str); - AlreadyIncRefed<RegExpPrivate> rep = RegExpPrivate::create(cx, str, flags, ts); - if (!rep) + /* |NewBuiltinClassInstance| can GC. */ + JS::Anchor<JSString *> anchor(source); + + if (!RegExpPrivateCode::checkSyntax(cx, tokenStream, source)) return NULL; JSObject *obj = NewBuiltinClassInstance(cx, &RegExpClass); if (!obj) return NULL; - PreInitRegExpObject pireo(obj); - RegExpObject *reobj = pireo.get(); - if (!ResetRegExpObject(cx, reobj, rep)) { + RegExpObject *reobj = obj->asRegExp(); + return reobj->reset(cx, source, flags) ? reobj : NULL; +} + +inline void +RegExpObject::finalize(JSContext *cx) +{ + if (RegExpPrivate *rep = getPrivate()) rep->decref(cx); - pireo.fail(); - return NULL; - } - - pireo.succeed(); - return reobj; +#ifdef DEBUG + setPrivate((void *) 0x1); /* Non-null but still in the zero page. */ +#endif } inline bool RegExpObject::reset(JSContext *cx, AlreadyIncRefed<RegExpPrivate> rep) { + if (!reset(cx, rep->getSource(), rep->getFlags())) + return false; + + setPrivate(rep.get()); + return true; +} + +inline bool +RegExpObject::reset(JSContext *cx, RegExpObject *other) +{ + if (RegExpPrivate *rep = other->getPrivate()) { + rep->incref(cx); + return reset(cx, AlreadyIncRefed<RegExpPrivate>(rep)); + } + + return reset(cx, other->getSource(), other->getFlags()); +} + +inline bool +RegExpObject::reset(JSContext *cx, JSLinearString *source, RegExpFlag flags) +{ if (nativeEmpty()) { const js::Shape **shapep = &cx->compartment->initialRegExpShape; if (!*shapep) { *shapep = assignInitialShape(cx); if (!*shapep) return false; } setLastProperty(*shapep); @@ -203,114 +167,30 @@ RegExpObject::reset(JSContext *cx, Alrea 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); - setPrivate(rep.get()); zeroLastIndex(); - setSource(rep->getSource()); - setGlobal(rep->global()); - setIgnoreCase(rep->ignoreCase()); - setMultiline(rep->multiline()); - setSticky(rep->sticky()); + setPrivate(NULL); + setSource(source); + setGlobal(flags & GlobalFlag); + setIgnoreCase(flags & IgnoreCaseFlag); + setMultiline(flags & MultilineFlag); + setSticky(flags & StickyFlag); return true; } /* RegExpPrivate inlines. */ -class RegExpMatchBuilder -{ - JSContext * const cx; - JSObject * const array; - - bool setProperty(JSAtom *name, Value v) { - return !!js_DefineProperty(cx, array, ATOM_TO_JSID(name), &v, - JS_PropertyStub, JS_StrictPropertyStub, JSPROP_ENUMERATE); - } - - public: - RegExpMatchBuilder(JSContext *cx, JSObject *array) : cx(cx), array(array) {} - - bool append(uint32 index, Value v) { - JS_ASSERT(!array->getOps()->getElement); - return !!js_DefineElement(cx, array, index, &v, JS_PropertyStub, JS_StrictPropertyStub, - JSPROP_ENUMERATE); - } - - bool setIndex(int index) { - return setProperty(cx->runtime->atomState.indexAtom, Int32Value(index)); - } - - bool setInput(JSString *str) { - JS_ASSERT(str); - return setProperty(cx->runtime->atomState.inputAtom, StringValue(str)); - } -}; - -inline void -RegExpPrivate::checkMatchPairs(JSString *input, int *buf, size_t matchItemCount) -{ -#if DEBUG - size_t inputLength = input->length(); - for (size_t i = 0; i < matchItemCount; i += 2) { - int start = buf[i]; - int limit = buf[i + 1]; - JS_ASSERT(limit >= start); /* Limit index must be larger than the start index. */ - if (start == -1) - continue; - JS_ASSERT(start >= 0); - JS_ASSERT(size_t(limit) <= inputLength); - } -#endif -} - -inline JSObject * -RegExpPrivate::createResult(JSContext *cx, JSString *input, int *buf, size_t matchItemCount) -{ - /* - * Create the result array for a match. Array contents: - * 0: matched string - * 1..pairCount-1: paren matches - */ - JSObject *array = NewSlowEmptyArray(cx); - if (!array) - return NULL; - - RegExpMatchBuilder builder(cx, array); - for (size_t i = 0; i < matchItemCount; i += 2) { - int start = buf[i]; - int end = buf[i + 1]; - - JSString *captured; - if (start >= 0) { - JS_ASSERT(start <= end); - JS_ASSERT(unsigned(end) <= input->length()); - captured = js_NewDependentString(cx, input, start, end - start); - if (!captured || !builder.append(i / 2, StringValue(captured))) - return NULL; - } else { - /* Missing parenthesized match. */ - JS_ASSERT(i != 0); /* Since we had a match, first pair must be present. */ - if (!builder.append(i / 2, UndefinedValue())) - return NULL; - } - } - - if (!builder.setIndex(buf[0]) || !builder.setInput(input)) - return NULL; - - return array; -} - inline AlreadyIncRefed<RegExpPrivate> -RegExpPrivate::create(JSContext *cx, JSString *source, RegExpFlag flags, TokenStream *ts) +RegExpPrivate::create(JSContext *cx, JSLinearString *source, RegExpFlag flags, TokenStream *ts) { typedef AlreadyIncRefed<RegExpPrivate> RetType; JSLinearString *flatSource = source->ensureLinear(cx); if (!flatSource) return RetType(NULL); RegExpPrivate *self = cx->new_<RegExpPrivate>(flatSource, flags, cx->compartment); @@ -411,43 +291,43 @@ RegExpPrivate::compile(JSContext *cx, To sb.infallibleAppend(postfix, JS_ARRAY_LENGTH(postfix)); JSLinearString *fakeySource = sb.finishString(); if (!fakeySource) return false; return code.compile(cx, *fakeySource, ts, &parenCount, getFlags()); } -inline RegExpPrivateCode::ExecuteResult -RegExpPrivateCode::execute(JSContext *cx, const jschar *chars, size_t start, size_t length, +inline RegExpRunStatus +RegExpPrivateCode::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 Success_NotFound; + return RegExpRunStatus_Success_NotFound; #if !ENABLE_YARR_JIT if (result < 0) { reportPCREError(cx, result); - return Error; + return RegExpRunStatus_Error; } #endif JS_ASSERT(result >= 0); - return Success; + return RegExpRunStatus_Success; } inline void RegExpPrivate::incref(JSContext *cx) { #ifdef DEBUG assertSameCompartment(cx, compartment); #endif @@ -460,16 +340,25 @@ RegExpPrivate::decref(JSContext *cx) #ifdef DEBUG assertSameCompartment(cx, compartment); #endif if (--refCount == 0) cx->delete_(this); } inline RegExpPrivate * +RegExpObject::getOrCreatePrivate(JSContext *cx) +{ + if (RegExpPrivate *rep = getPrivate()) + return rep; + + return makePrivate(cx) ? getPrivate() : NULL; +} + +inline RegExpPrivate * RegExpObject::getPrivate() const { RegExpPrivate *rep = static_cast<RegExpPrivate *>(JSObject::getPrivate()); #ifdef DEBUG if (rep) CompartmentChecker::check(compartment(), rep->compartment); #endif return rep;
--- a/js/src/vm/RegExpObject.cpp +++ b/js/src/vm/RegExpObject.cpp @@ -36,16 +36,17 @@ * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ #include "jsscan.h" #include "vm/RegExpStatics.h" +#include "vm/MatchPairs.h" #include "jsobjinlines.h" #include "jsstrinlines.h" #include "vm/RegExpObject-inl.h" #ifdef JS_TRACER #include "jstracer.h" @@ -54,100 +55,109 @@ using namespace nanojit; using namespace js; JS_STATIC_ASSERT(IgnoreCaseFlag == JSREG_FOLD); JS_STATIC_ASSERT(GlobalFlag == JSREG_GLOB); JS_STATIC_ASSERT(MultilineFlag == JSREG_MULTILINE); JS_STATIC_ASSERT(StickyFlag == JSREG_STICKY); -bool -RegExpPrivate::executeInternal(JSContext *cx, RegExpStatics *res, JSString *inputstr, - size_t *lastIndex, bool test, Value *rval) +MatchPairs * +MatchPairs::create(LifoAlloc &alloc, size_t pairCount, size_t backingPairCount) +{ + void *mem = alloc.alloc(calculateSize(backingPairCount)); + if (!mem) + return NULL; + + return new (mem) MatchPairs(pairCount); +} + +inline void +MatchPairs::checkAgainst(size_t inputLength) { - const size_t pairCount = parenCount + 1; - const size_t matchItemCount = pairCount * 2; - const size_t bufCount = RegExpPrivateCode::getOutputSize(pairCount); +#if DEBUG + for (size_t i = 0; i < pairCount(); ++i) { + MatchPair p = pair(i); + p.check(); + if (p.isUndefined()) + continue; + JS_ASSERT(size_t(p.limit) <= inputLength); + } +#endif +} - LifoAllocScope las(&cx->tempLifoAlloc()); - int *buf = cx->tempLifoAlloc().newArray<int>(bufCount); - if (!buf) - return false; +RegExpRunStatus +RegExpPrivate::execute(JSContext *cx, const jschar *chars, size_t length, size_t *lastIndex, + LifoAllocScope &allocScope, MatchPairs **output) +{ + const size_t origLength = length; + size_t backingPairCount = RegExpPrivateCode::getOutputSize(pairCount()); + + MatchPairs *matchPairs = MatchPairs::create(allocScope.alloc(), pairCount(), backingPairCount); + if (!matchPairs) + return RegExpRunStatus_Error; /* - * The JIT regexp procedure doesn't always initialize matchPair values. - * Maybe we can make this faster by ensuring it does? + * |displacement| emulates sticky mode by matching from this offset + * into the char buffer and subtracting the delta off at the end. */ - for (int *it = buf; it != buf + matchItemCount; ++it) - *it = -1; - - JSLinearString *input = inputstr->ensureLinear(cx); - if (!input) - return false; - - JS::Anchor<JSString *> anchor(input); - size_t len = input->length(); - const jschar *chars = input->chars(); - - /* - * inputOffset emulates sticky mode by matching from this offset into the char buf and - * subtracting the delta off at the end. - */ - size_t inputOffset = 0; + size_t start = *lastIndex; + size_t displacement = 0; if (sticky()) { - /* Sticky matches at the last index for the regexp object. */ - chars += *lastIndex; - len -= *lastIndex; - inputOffset = *lastIndex; + displacement = *lastIndex; + chars += displacement; + length -= displacement; + start = 0; } - size_t start = *lastIndex - inputOffset; - RegExpPrivateCode::ExecuteResult result = code.execute(cx, chars, start, len, buf, bufCount); + RegExpRunStatus status = code.execute(cx, chars, length, start, + matchPairs->buffer(), backingPairCount); - switch (result) { - case RegExpPrivateCode::Error: - return false; - case RegExpPrivateCode::Success_NotFound: - *rval = NullValue(); - return true; + switch (status) { + case RegExpRunStatus_Error: + return status; + case RegExpRunStatus_Success_NotFound: + *output = matchPairs; + return status; default: - JS_ASSERT(result == RegExpPrivateCode::Success); + JS_ASSERT(status == RegExpRunStatus_Success); } - /* - * Adjust buf for the inputOffset. Use of sticky is rare and the matchItemCount is small, so - * just do another pass. - */ - if (JS_UNLIKELY(inputOffset)) { - for (size_t i = 0; i < matchItemCount; ++i) - buf[i] = buf[i] < 0 ? -1 : buf[i] + inputOffset; - } + matchPairs->displace(displacement); + matchPairs->checkAgainst(origLength); - /* Make sure the populated contents of |buf| are sane values against |input|. */ - checkMatchPairs(input, buf, matchItemCount); + *lastIndex = matchPairs->pair(0).limit; + *output = matchPairs; - if (res) - res->updateFromMatch(cx, input, buf, matchItemCount); - - *lastIndex = buf[1]; + return RegExpRunStatus_Success; +} - if (test) { - *rval = BooleanValue(true); - return true; - } - - JSObject *array = createResult(cx, input, buf, matchItemCount); - if (!array) +bool +RegExpObject::makePrivate(JSContext *cx) +{ + JS_ASSERT(!getPrivate()); + AlreadyIncRefed<RegExpPrivate> rep = RegExpPrivate::create(cx, getSource(), getFlags(), NULL); + if (!rep) return false; - *rval = ObjectValue(*array); + setPrivate(rep.get()); return true; } +RegExpRunStatus +RegExpObject::execute(JSContext *cx, const jschar *chars, size_t length, size_t *lastIndex, + LifoAllocScope &allocScope, MatchPairs **output) +{ + if (!getPrivate() && !makePrivate(cx)) + return RegExpRunStatus_Error; + + return getPrivate()->execute(cx, chars, length, lastIndex, allocScope, output); +} + const Shape * RegExpObject::assignInitialShape(JSContext *cx) { JS_ASSERT(!cx->compartment->initialRegExpShape); JS_ASSERT(isRegExp()); JS_ASSERT(nativeEmpty()); JS_STATIC_ASSERT(LAST_INDEX_SLOT == 0); @@ -221,18 +231,17 @@ js_XDRRegExpObject(JSXDRState *xdr, JSOb #define js_XDRRegExpObject NULL #endif /* !JS_HAS_XDR */ static void regexp_finalize(JSContext *cx, JSObject *obj) { - if (RegExpPrivate *rep = static_cast<RegExpObject *>(obj)->getPrivate()) - rep->decref(cx); + obj->asRegExp()->finalize(cx); } 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 */ @@ -289,19 +298,19 @@ RegExpPrivateCode::reportYarrError(JSCon void RegExpPrivateCode::reportPCREError(JSContext *cx, int error) { #define REPORT(msg_) \ JS_ReportErrorFlagsAndNumberUC(cx, JSREPORT_ERROR, js_GetErrorMessage, NULL, msg_); \ return switch (error) { case -2: REPORT(JSMSG_REGEXP_TOO_COMPLEX); - case 0: JS_NOT_REACHED("Precondition violation: an error must have occurred."); + case 0: JS_NOT_REACHED("Precondition violation: an error must have occurred."); case 1: REPORT(JSMSG_TRAILING_SLASH); - case 2: REPORT(JSMSG_TRAILING_SLASH); + case 2: REPORT(JSMSG_TRAILING_SLASH); case 3: REPORT(JSMSG_REGEXP_TOO_COMPLEX); case 4: REPORT(JSMSG_BAD_QUANTIFIER); case 5: REPORT(JSMSG_BAD_QUANTIFIER); case 6: REPORT(JSMSG_BAD_CLASS_RANGE); case 7: REPORT(JSMSG_REGEXP_TOO_COMPLEX); case 8: REPORT(JSMSG_BAD_CLASS_RANGE); case 9: REPORT(JSMSG_BAD_QUANTIFIER); case 10: REPORT(JSMSG_UNMATCHED_RIGHT_PAREN); @@ -312,16 +321,17 @@ RegExpPrivateCode::reportPCREError(JSCon case 15: REPORT(JSMSG_BAD_BACKREF); case 16: REPORT(JSMSG_REGEXP_TOO_COMPLEX); case 17: REPORT(JSMSG_REGEXP_TOO_COMPLEX); default: JS_NOT_REACHED("Precondition violation: unknown PCRE error code."); } #undef REPORT } + #endif /* ENABLE_YARR_JIT */ bool js::ParseRegExpFlags(JSContext *cx, JSString *flagStr, RegExpFlag *flagsOut) { size_t n = flagStr->length(); const jschar *s = flagStr->getChars(cx); if (!s) @@ -352,74 +362,68 @@ js::ParseRegExpFlags(JSContext *cx, JSSt } } #undef HANDLE_FLAG } return true; } AlreadyIncRefed<RegExpPrivate> -RegExpPrivate::createFlagged(JSContext *cx, JSString *str, JSString *opt, TokenStream *ts) +RegExpPrivate::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<RegExpPrivate>(NULL); return create(cx, str, flags, ts); } RegExpObject * -RegExpObject::clone(JSContext *cx, RegExpObject *obj, RegExpObject *proto) +RegExpObject::clone(JSContext *cx, RegExpObject *reobj, RegExpObject *proto) { JSObject *clone = NewNativeClassInstance(cx, &RegExpClass, proto, proto->getParent()); if (!clone) return NULL; /* * This clone functionality does not duplicate the JIT'd code blob, * which is necessary for cross-compartment cloning functionality. */ - assertSameCompartment(cx, obj, clone); + assertSameCompartment(cx, reobj, clone); RegExpStatics *res = cx->regExpStatics(); + RegExpObject *reclone = clone->asRegExp(); - /* + /* * Check that the RegExpPrivate for the original is okay to use in - * the clone -- if the RegExpStatics provides more flags we'll need - * a different RegExpPrivate. + * the clone -- if the |RegExpStatics| provides more flags we'll + * need a different |RegExpPrivate|. */ - AlreadyIncRefed<RegExpPrivate> rep(NULL); - { - RegExpFlag origFlags = obj->getFlags(); - RegExpFlag staticsFlags = res->getFlags(); - if ((origFlags & staticsFlags) != staticsFlags) { - RegExpFlag newFlags = RegExpFlag(origFlags | staticsFlags); - rep = RegExpPrivate::create(cx, obj->getSource(), newFlags, NULL); - if (!rep) - return NULL; - } else { - RegExpPrivate *toShare = obj->getPrivate(); - toShare->incref(cx); - rep = AlreadyIncRefed<RegExpPrivate>(toShare); - } + RegExpFlag origFlags = reobj->getFlags(); + RegExpFlag staticsFlags = res->getFlags(); + if ((origFlags & staticsFlags) != staticsFlags) { + RegExpFlag newFlags = RegExpFlag(origFlags | staticsFlags); + return reclone->reset(cx, reobj->getSource(), newFlags) ? reclone : NULL; } - JS_ASSERT(rep); - - PreInitRegExpObject pireo(clone); - RegExpObject *reclone = pireo.get(); - if (!ResetRegExpObject(cx, reclone, rep)) { - pireo.fail(); - return NULL; + RegExpPrivate *toShare = reobj->getPrivate(); + if (toShare) { + toShare->incref(cx); + if (!reclone->reset(cx, AlreadyIncRefed<RegExpPrivate>(toShare))) { + toShare->decref(cx); + return NULL; + } + } else { + if (!reclone->reset(cx, reobj)) + return NULL; } - pireo.succeed(); return reclone; } JSObject * JS_FASTCALL js_CloneRegExpObject(JSContext *cx, JSObject *obj, JSObject *proto) { JS_ASSERT(obj->isRegExp()); JS_ASSERT(proto->isRegExp()); @@ -430,35 +434,31 @@ js_CloneRegExpObject(JSContext *cx, JSOb #ifdef JS_TRACER JS_DEFINE_CALLINFO_3(extern, OBJECT, js_CloneRegExpObject, CONTEXT, OBJECT, OBJECT, 0, ACCSET_STORE_ANY) #endif JSFlatString * RegExpObject::toString(JSContext *cx) const { - RegExpPrivate *rep = getPrivate(); - if (!rep) - return cx->runtime->emptyString; - - JSLinearString *src = rep->getSource(); + 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 (rep->global() && !sb.append('g')) + if (global() && !sb.append('g')) return NULL; - if (rep->ignoreCase() && !sb.append('i')) + if (ignoreCase() && !sb.append('i')) return NULL; - if (rep->multiline() && !sb.append('m')) + if (multiline() && !sb.append('m')) return NULL; - if (rep->sticky() && !sb.append('y')) + if (sticky() && !sb.append('y')) return NULL; return sb.finishString(); }
--- a/js/src/vm/RegExpObject.h +++ b/js/src/vm/RegExpObject.h @@ -44,23 +44,29 @@ #include <stddef.h> #include "jsobj.h" #include "js/TemplateLib.h" #include "yarr/Yarr.h" #if ENABLE_YARR_JIT #include "yarr/YarrJIT.h" +#include "yarr/YarrSyntaxChecker.h" #else #include "yarr/pcre/pcre.h" #endif namespace js { -class RegExpPrivate; +enum RegExpRunStatus +{ + RegExpRunStatus_Error, + RegExpRunStatus_Success, + RegExpRunStatus_Success_NotFound +}; class RegExpObject : public ::JSObject { static const uintN LAST_INDEX_SLOT = 0; static const uintN SOURCE_SLOT = 1; static const uintN GLOBAL_FLAG_SLOT = 2; static const uintN IGNORE_CASE_FLAG_SLOT = 3; static const uintN MULTILINE_FLAG_SLOT = 4; @@ -82,79 +88,107 @@ class RegExpObject : public ::JSObject createNoStatics(JSContext *cx, const jschar *chars, size_t length, RegExpFlag flags, TokenStream *ts); static RegExpObject *clone(JSContext *cx, RegExpObject *obj, RegExpObject *proto); /* Note: fallible. */ JSFlatString *toString(JSContext *cx) const; + /* + * Run the regular expression over the input text. + * + * Results are placed in |output| as integer pairs. For eaxmple, + * |output[0]| and |output[1]| represent the text indices that make + * up the "0" (whole match) pair. Capturing parens will result in + * more output. + * + * 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); + /* Accessors. */ const Value &getLastIndex() const { return getSlot(LAST_INDEX_SLOT); } void setLastIndex(const Value &v) { setSlot(LAST_INDEX_SLOT, v); } void setLastIndex(double d) { setSlot(LAST_INDEX_SLOT, NumberValue(d)); } void zeroLastIndex() { setSlot(LAST_INDEX_SLOT, Int32Value(0)); } - JSString *getSource() const { - return getSlot(SOURCE_SLOT).toString(); + JSLinearString *getSource() const { + return &getSlot(SOURCE_SLOT).toString()->asLinear(); } - void setSource(JSString *source) { + void setSource(JSLinearString *source) { setSlot(SOURCE_SLOT, StringValue(source)); } RegExpFlag getFlags() const { uintN flags = 0; - flags |= getSlot(GLOBAL_FLAG_SLOT).toBoolean() ? GlobalFlag : 0; - flags |= getSlot(IGNORE_CASE_FLAG_SLOT).toBoolean() ? IgnoreCaseFlag : 0; - flags |= getSlot(MULTILINE_FLAG_SLOT).toBoolean() ? MultilineFlag : 0; - flags |= getSlot(STICKY_FLAG_SLOT).toBoolean() ? StickyFlag : 0; + flags |= global() ? GlobalFlag : 0; + flags |= ignoreCase() ? IgnoreCaseFlag : 0; + flags |= multiline() ? MultilineFlag : 0; + flags |= sticky() ? StickyFlag : 0; return RegExpFlag(flags); } - inline RegExpPrivate *getPrivate() const; /* Flags. */ void setIgnoreCase(bool enabled) { setSlot(IGNORE_CASE_FLAG_SLOT, BooleanValue(enabled)); } void setGlobal(bool enabled) { setSlot(GLOBAL_FLAG_SLOT, BooleanValue(enabled)); } void setMultiline(bool enabled) { setSlot(MULTILINE_FLAG_SLOT, BooleanValue(enabled)); } void setSticky(bool enabled) { setSlot(STICKY_FLAG_SLOT, BooleanValue(enabled)); } + bool ignoreCase() const { return getSlot(IGNORE_CASE_FLAG_SLOT).toBoolean(); } + bool global() const { return getSlot(GLOBAL_FLAG_SLOT).toBoolean(); } + bool multiline() const { return getSlot(MULTILINE_FLAG_SLOT).toBoolean(); } + bool sticky() const { return getSlot(STICKY_FLAG_SLOT).toBoolean(); } + + /* + * N.B. |RegExpObject|s can be mutated in place because of |RegExp.prototype.compile|, hence + * |reset| for re-initialization. + */ + + inline bool reset(JSContext *cx, RegExpObject *other); + inline bool reset(JSContext *cx, AlreadyIncRefed<RegExpPrivate> rep); + inline bool reset(JSContext *cx, JSLinearString *source, RegExpFlag flags); + + inline RegExpPrivate *getOrCreatePrivate(JSContext *cx); + inline void finalize(JSContext *cx); private: - /* N.B. |RegExpObject|s can be mutated in place because of |RegExp.prototype.compile|. */ - inline bool reset(JSContext *cx, AlreadyIncRefed<RegExpPrivate> rep); + /* The |RegExpPrivate| is lazily created at the time of use. */ + inline RegExpPrivate *getPrivate() const; - friend bool ResetRegExpObject(JSContext *, RegExpObject *, JSString *, RegExpFlag); + /* + * Precondition: the syntax for |source| has already been validated. + * Side effect: sets the private field. + */ + bool makePrivate(JSContext *cx); + + friend bool ResetRegExpObject(JSContext *, RegExpObject *, JSLinearString *, RegExpFlag); friend bool ResetRegExpObject(JSContext *, RegExpObject *, AlreadyIncRefed<RegExpPrivate>); /* * Compute the initial shape to associate with fresh RegExp objects, * encoding their initial properties. Return the shape after * changing this regular expression object's last property to it. */ const Shape *assignInitialShape(JSContext *cx); RegExpObject(); RegExpObject &operator=(const RegExpObject &reo); }; /* class RegExpObject */ -inline bool -ResetRegExpObject(JSContext *cx, RegExpObject *reobj, JSString *str, RegExpFlag flags); - -/* N.B. On failure, caller must decref |rep|. */ -inline bool -ResetRegExpObject(JSContext *cx, AlreadyIncRefed<RegExpPrivate> rep); - /* Abstracts away the gross |RegExpPrivate| backend details. */ class RegExpPrivateCode { #if ENABLE_YARR_JIT typedef JSC::Yarr::BytecodePattern BytecodePattern; typedef JSC::Yarr::ErrorCode ErrorCode; typedef JSC::Yarr::JSGlobalData JSGlobalData; typedef JSC::Yarr::YarrCodeBlock YarrCodeBlock; @@ -184,30 +218,41 @@ class RegExpPrivateCode if (byteCode) Foreground::delete_<BytecodePattern>(byteCode); #else if (compiled) jsRegExpFree(compiled); #endif } + static bool checkSyntax(JSContext *cx, TokenStream *tokenStream, JSLinearString *source) { +#if ENABLE_YARR_JIT + ErrorCode error = JSC::Yarr::checkSyntax(*source); + if (error == JSC::Yarr::NoError) + return true; + + reportYarrError(cx, tokenStream, error); + return false; +#else +# error "Syntax checking not implemented for !ENABLE_YARR_JIT" +#endif + } + #if ENABLE_YARR_JIT static inline bool isJITRuntimeEnabled(JSContext *cx); - void reportYarrError(JSContext *cx, TokenStream *ts, JSC::Yarr::ErrorCode error); + static void reportYarrError(JSContext *cx, TokenStream *ts, JSC::Yarr::ErrorCode error); #else - void reportPCREError(JSContext *cx, int error); + static void reportPCREError(JSContext *cx, int error); #endif inline bool compile(JSContext *cx, JSLinearString &pattern, TokenStream *ts, uintN *parenCount, RegExpFlag flags); - enum ExecuteResult { Error, Success, Success_NotFound }; - - inline ExecuteResult execute(JSContext *cx, const jschar *chars, size_t start, size_t length, - int *output, size_t outputCount); + inline RegExpRunStatus execute(JSContext *cx, const jschar *chars, size_t length, size_t start, + int *output, size_t outputCount); static size_t getOutputSize(size_t pairCount) { #if ENABLE_YARR_JIT return pairCount * 2; #else return pairCount * 3; /* Should be x2, but PCRE has... needs. */ #endif } @@ -242,66 +287,49 @@ class RegExpPrivate RegExpPrivate(JSLinearString *source, RegExpFlag flags, JSCompartment *compartment) : source(source), refCount(1), parenCount(0), flags(flags), compartment(compartment) { } 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 JSObject *createResult(JSContext *cx, JSString *input, int *buf, size_t matchItemCount); - bool executeInternal(JSContext *cx, RegExpStatics *res, JSString *input, - size_t *lastIndex, bool test, Value *rval); public: - /* - * Execute regexp on |input| at |*lastIndex|. - * - * On match: Update |*lastIndex| and RegExp class statics. - * Return true if test is true. Place an array in |*rval| if test is false. - * On mismatch: Make |*rval| null. - */ - bool execute(JSContext *cx, RegExpStatics *res, JSString *input, size_t *lastIndex, bool test, - Value *rval) { - JS_ASSERT(res); - return executeInternal(cx, res, input, lastIndex, test, rval); - } + static AlreadyIncRefed<RegExpPrivate> + create(JSContext *cx, JSLinearString *source, RegExpFlag flags, TokenStream *ts); - bool executeNoStatics(JSContext *cx, JSString *input, size_t *lastIndex, bool test, - Value *rval) { - return executeInternal(cx, NULL, input, lastIndex, test, rval); - } - - /* Factories */ + static AlreadyIncRefed<RegExpPrivate> + create(JSContext *cx, JSLinearString *source, JSString *flags, TokenStream *ts); - static AlreadyIncRefed<RegExpPrivate> create(JSContext *cx, JSString *source, RegExpFlag flags, - TokenStream *ts); - - /* Would overload |create|, but |0| resolves ambiguously against pointer and uint. */ - static AlreadyIncRefed<RegExpPrivate> createFlagged(JSContext *cx, JSString *source, - JSString *flags, TokenStream *ts); + 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; } /* Accessors */ - JSLinearString *getSource() const { return source; } - size_t getParenCount() const { return parenCount; } - RegExpFlag getFlags() const { return flags; } + 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; } -}; /* class RegExpPrivate */ +}; /* * Parse regexp flags. Report an error and return false if an invalid * sequence of flags is encountered (repeat/invalid flag). * * N.B. flagStr must be rooted. */ bool
--- a/js/src/vm/RegExpStatics.h +++ b/js/src/vm/RegExpStatics.h @@ -40,22 +40,24 @@ #ifndef RegExpStatics_h__ #define RegExpStatics_h__ #include "jscntxt.h" #include "js/Vector.h" +#include "vm/MatchPairs.h" + namespace js { class RegExpStatics { - typedef Vector<int, 20, SystemAllocPolicy> MatchPairs; - MatchPairs matchPairs; + typedef Vector<int, 20, SystemAllocPolicy> Pairs; + Pairs matchPairs; /* The input that was used to produce matchPairs. */ JSLinearString *matchPairsInput; /* The input last set on the statics. */ JSString *pendingInput; RegExpFlag flags; RegExpStatics *bufferLink; bool copied; @@ -157,27 +159,30 @@ class RegExpStatics public: RegExpStatics() : bufferLink(NULL), copied(false) { clear(); } static JSObject *create(JSContext *cx, GlobalObject *parent); /* Mutators. */ - bool updateFromMatch(JSContext *cx, JSLinearString *input, int *buf, size_t matchItemCount) { + bool updateFromMatchPairs(JSContext *cx, JSLinearString *input, MatchPairs *newPairs) { + JS_ASSERT(input); aboutToWrite(); pendingInput = input; - if (!matchPairs.resizeUninitialized(matchItemCount)) { + if (!matchPairs.resizeUninitialized(2 * newPairs->pairCount())) { js_ReportOutOfMemory(cx); return false; } - for (size_t i = 0; i < matchItemCount; ++i) - matchPairs[i] = buf[i]; + for (size_t i = 0; i < newPairs->pairCount(); ++i) { + matchPairs[2 * i] = newPairs->pair(i).start; + matchPairs[2 * i + 1] = newPairs->pair(i).limit; + } matchPairsInput = input; return true; } inline void setMultiline(JSContext *cx, bool enabled); void clear() {