/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- * vim: set ts=8 sts=4 et sw=4 tw=99: * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "builtin/RegExp.h" #include "mozilla/CheckedInt.h" #include "mozilla/TypeTraits.h" #include "jscntxt.h" #include "irregexp/RegExpParser.h" #include "jit/InlinableNatives.h" #include "vm/RegExpStatics.h" #include "vm/SelfHosting.h" #include "vm/StringBuffer.h" #include "vm/Unicode.h" #include "jsobjinlines.h" #include "vm/NativeObject-inl.h" using namespace js; using namespace js::unicode; using mozilla::ArrayLength; using mozilla::CheckedInt; using mozilla::Maybe; /* * ES 2017 draft rev 6a13789aa9e7c6de4e96b7d3e24d9e6eba6584ad 21.2.5.2.2 * steps 3, 16-25. */ bool js::CreateRegExpMatchResult(JSContext* cx, HandleString input, const MatchPairs& matches, MutableHandleValue rval) { MOZ_ASSERT(input); /* * 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 */ /* Get the templateObject that defines the shape and type of the output object */ JSObject* templateObject = cx->compartment()->regExps.getOrCreateMatchResultTemplateObject(cx); if (!templateObject) return false; size_t numPairs = matches.length(); MOZ_ASSERT(numPairs > 0); /* Step 17. */ RootedArrayObject arr(cx, NewDenseFullyAllocatedArrayWithTemplate(cx, numPairs, templateObject)); if (!arr) return false; /* Steps 22-24. * Store a Value for each pair. */ for (size_t i = 0; i < numPairs; i++) { const MatchPair& pair = matches[i]; if (pair.isUndefined()) { MOZ_ASSERT(i != 0); /* Since we had a match, first pair must be present. */ arr->setDenseInitializedLength(i + 1); arr->initDenseElement(i, UndefinedValue()); } else { JSLinearString* str = NewDependentString(cx, input, pair.start, pair.length()); if (!str) return false; arr->setDenseInitializedLength(i + 1); arr->initDenseElement(i, StringValue(str)); } } /* Step 20 (reordered). * Set the |index| property. (TemplateObject positions it in slot 0) */ arr->setSlot(0, Int32Value(matches[0].start)); /* Step 21 (reordered). * Set the |input| property. (TemplateObject positions it in slot 1) */ arr->setSlot(1, StringValue(input)); #ifdef DEBUG RootedValue test(cx); RootedId id(cx, NameToId(cx->names().index)); if (!NativeGetProperty(cx, arr, id, &test)) return false; MOZ_ASSERT(test == arr->getSlot(0)); id = NameToId(cx->names().input); if (!NativeGetProperty(cx, arr, id, &test)) return false; MOZ_ASSERT(test == arr->getSlot(1)); #endif /* Step 25. */ rval.setObject(*arr); return true; } static int32_t CreateRegExpSearchResult(JSContext* cx, const MatchPairs& matches) { /* Fit the start and limit of match into a int32_t. */ uint32_t position = matches[0].start; uint32_t lastIndex = matches[0].limit; MOZ_ASSERT(position < 0x8000); MOZ_ASSERT(lastIndex < 0x8000); return position | (lastIndex << 15); } /* * ES 2017 draft rev 6a13789aa9e7c6de4e96b7d3e24d9e6eba6584ad 21.2.5.2.2 * steps 3, 9-14, except 12.a.i, 12.c.i.1. */ static RegExpRunStatus ExecuteRegExpImpl(JSContext* cx, RegExpStatics* res, RegExpShared& re, HandleLinearString input, size_t searchIndex, MatchPairs* matches, size_t* endIndex) { RegExpRunStatus status = re.execute(cx, input, searchIndex, matches, endIndex); /* Out of spec: Update RegExpStatics. */ if (status == RegExpRunStatus_Success && res) { if (matches) { if (!res->updateFromMatchPairs(cx, input, *matches)) return RegExpRunStatus_Error; } else { res->updateLazily(cx, input, &re, searchIndex); } } return status; } /* Legacy ExecuteRegExp behavior is baked into the JSAPI. */ bool js::ExecuteRegExpLegacy(JSContext* cx, RegExpStatics* res, Handle reobj, HandleLinearString input, size_t* lastIndex, bool test, MutableHandleValue rval) { RegExpGuard shared(cx); if (!RegExpObject::getShared(cx, reobj, &shared)) return false; ScopedMatchPairs matches(&cx->tempLifoAlloc()); RegExpRunStatus status = ExecuteRegExpImpl(cx, res, *shared, input, *lastIndex, &matches, nullptr); if (status == RegExpRunStatus_Error) return false; if (status == RegExpRunStatus_Success_NotFound) { /* ExecuteRegExp() previously returned an array or null. */ rval.setNull(); return true; } *lastIndex = matches[0].limit; if (test) { /* Forbid an array, as an optimization. */ rval.setBoolean(true); return true; } return CreateRegExpMatchResult(cx, input, matches, rval); } static bool CheckPatternSyntax(JSContext* cx, HandleAtom pattern, RegExpFlag flags) { CompileOptions options(cx); frontend::TokenStream dummyTokenStream(cx, options, nullptr, 0, nullptr); return irregexp::ParsePatternSyntax(dummyTokenStream, cx->tempLifoAlloc(), pattern, flags & UnicodeFlag, flags & DotAllFlag); } enum RegExpSharedUse { UseRegExpShared, DontUseRegExpShared }; /* * ES 2016 draft Mar 25, 2016 21.2.3.2.2. * * Steps 14-15 set |obj|'s "lastIndex" property to zero. Some of * RegExpInitialize's callers have a fresh RegExp not yet exposed to script: * in these cases zeroing "lastIndex" is infallible. But others have a RegExp * whose "lastIndex" property might have been made non-writable: here, zeroing * "lastIndex" can fail. We efficiently solve this problem by completely * removing "lastIndex" zeroing from the provided function. * * CALLERS MUST HANDLE "lastIndex" ZEROING THEMSELVES! * * Because this function only ever returns a user-provided |obj| in the spec, * we omit it and just return the usual success/failure. */ static bool RegExpInitializeIgnoringLastIndex(JSContext* cx, Handle obj, HandleValue patternValue, HandleValue flagsValue, RegExpSharedUse sharedUse = DontUseRegExpShared) { RootedAtom pattern(cx); if (patternValue.isUndefined()) { /* Step 1. */ pattern = cx->names().empty; } else { /* Step 2. */ pattern = ToAtom(cx, patternValue); if (!pattern) return false; } /* Step 3. */ RegExpFlag flags = RegExpFlag(0); if (!flagsValue.isUndefined()) { /* Step 4. */ RootedString flagStr(cx, ToString(cx, flagsValue)); if (!flagStr) return false; /* Step 5. */ if (!ParseRegExpFlags(cx, flagStr, &flags)) return false; } if (sharedUse == UseRegExpShared) { /* Steps 7-8. */ RegExpGuard re(cx); if (!cx->compartment()->regExps.get(cx, pattern, flags, &re)) return false; /* Steps 9-12. */ obj->initIgnoringLastIndex(pattern, flags); obj->setShared(*re); } else { /* Steps 7-8. */ if (!CheckPatternSyntax(cx, pattern, flags)) return false; /* Steps 9-12. */ obj->initIgnoringLastIndex(pattern, flags); } return true; } /* ES 2016 draft Mar 25, 2016 21.2.3.2.3. */ bool js::RegExpCreate(JSContext* cx, HandleValue patternValue, HandleValue flagsValue, MutableHandleValue rval) { /* Step 1. */ Rooted regexp(cx, RegExpAlloc(cx)); if (!regexp) return false; /* Step 2. */ if (!RegExpInitializeIgnoringLastIndex(cx, regexp, patternValue, flagsValue, UseRegExpShared)) return false; regexp->zeroLastIndex(cx); rval.setObject(*regexp); return true; } MOZ_ALWAYS_INLINE bool IsRegExpObject(HandleValue v) { return v.isObject() && v.toObject().is(); } /* ES6 draft rc3 7.2.8. */ bool js::IsRegExp(JSContext* cx, HandleValue value, bool* result) { /* Step 1. */ if (!value.isObject()) { *result = false; return true; } RootedObject obj(cx, &value.toObject()); /* Steps 2-3. */ RootedValue isRegExp(cx); RootedId matchId(cx, SYMBOL_TO_JSID(cx->wellKnownSymbols().match)); if (!GetProperty(cx, obj, obj, matchId, &isRegExp)) return false; /* Step 4. */ if (!isRegExp.isUndefined()) { *result = ToBoolean(isRegExp); return true; } /* Steps 5-6. */ ESClass cls; if (!GetClassOfValue(cx, value, &cls)) return false; *result = cls == ESClass::RegExp; return true; } /* ES6 B.2.5.1. */ MOZ_ALWAYS_INLINE bool regexp_compile_impl(JSContext* cx, const CallArgs& args) { MOZ_ASSERT(IsRegExpObject(args.thisv())); Rooted regexp(cx, &args.thisv().toObject().as()); // Step 3. RootedValue patternValue(cx, args.get(0)); ESClass cls; if (!GetClassOfValue(cx, patternValue, &cls)) return false; if (cls == ESClass::RegExp) { // Step 3a. if (args.hasDefined(1)) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_NEWREGEXP_FLAGGED); return false; } // Beware! |patternObj| might be a proxy into another compartment, so // don't assume |patternObj.is()|. For the same reason, // don't reuse the RegExpShared below. RootedObject patternObj(cx, &patternValue.toObject()); RootedAtom sourceAtom(cx); RegExpFlag flags; { // Step 3b. RegExpGuard g(cx); if (!RegExpToShared(cx, patternObj, &g)) return false; sourceAtom = g->getSource(); flags = g->getFlags(); } // Step 5, minus lastIndex zeroing. regexp->initIgnoringLastIndex(sourceAtom, flags); } else { // Step 4. RootedValue P(cx, patternValue); RootedValue F(cx, args.get(1)); // Step 5, minus lastIndex zeroing. if (!RegExpInitializeIgnoringLastIndex(cx, regexp, P, F)) return false; } // The final niggling bit of step 5. // // |regexp| is user-exposed, but if its "lastIndex" property hasn't been // made non-writable, we can still use a fast path to zero it. if (regexp->lookupPure(cx->names().lastIndex)->writable()) { regexp->zeroLastIndex(cx); } else { RootedValue zero(cx, Int32Value(0)); if (!SetProperty(cx, regexp, cx->names().lastIndex, zero)) return false; } args.rval().setObject(*regexp); return true; } static bool regexp_compile(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); /* Steps 1-2. */ return CallNonGenericMethod(cx, args); } /* * ES 2017 draft rev 6a13789aa9e7c6de4e96b7d3e24d9e6eba6584ad 21.2.3.1. */ bool js::regexp_construct(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); // Steps 1. bool patternIsRegExp; if (!IsRegExp(cx, args.get(0), &patternIsRegExp)) return false; // We can delay step 3 and step 4a until later, during // GetPrototypeFromCallableConstructor calls. Accessing the new.target // and the callee from the stack is unobservable. if (!args.isConstructing()) { // Step 3.b. if (patternIsRegExp && !args.hasDefined(1)) { RootedObject patternObj(cx, &args[0].toObject()); // Step 3.b.i. RootedValue patternConstructor(cx); if (!GetProperty(cx, patternObj, patternObj, cx->names().constructor, &patternConstructor)) return false; // Step 3.b.ii. if (patternConstructor.isObject() && patternConstructor.toObject() == args.callee()) { args.rval().set(args[0]); return true; } } } RootedValue patternValue(cx, args.get(0)); // Step 4. ESClass cls; if (!GetClassOfValue(cx, patternValue, &cls)) return false; if (cls == ESClass::RegExp) { // Beware! |patternObj| might be a proxy into another compartment, so // don't assume |patternObj.is()|. For the same reason, // don't reuse the RegExpShared below. RootedObject patternObj(cx, &patternValue.toObject()); RootedAtom sourceAtom(cx); RegExpFlag flags; { // Step 4.a. RegExpGuard g(cx); if (!RegExpToShared(cx, patternObj, &g)) return false; sourceAtom = g->getSource(); // Step 4.b. // Get original flags in all cases, to compare with passed flags. flags = g->getFlags(); } // Step 7. RootedObject proto(cx); if (!GetPrototypeFromCallableConstructor(cx, args, &proto)) return false; Rooted regexp(cx, RegExpAlloc(cx, proto)); if (!regexp) return false; // Step 8. if (args.hasDefined(1)) { // Step 4.c / 21.2.3.2.2 RegExpInitialize step 4. RegExpFlag flagsArg = RegExpFlag(0); RootedString flagStr(cx, ToString(cx, args[1])); if (!flagStr) return false; if (!ParseRegExpFlags(cx, flagStr, &flagsArg)) return false; if (!(flags & UnicodeFlag) && flagsArg & UnicodeFlag) { // Have to check syntax again when adding 'u' flag. // ES 2017 draft rev 9b49a888e9dfe2667008a01b2754c3662059ae56 // 21.2.3.2.2 step 7. if (!CheckPatternSyntax(cx, sourceAtom, flagsArg)) return false; } flags = flagsArg; } regexp->initAndZeroLastIndex(sourceAtom, flags, cx); args.rval().setObject(*regexp); return true; } RootedValue P(cx); RootedValue F(cx); // Step 5. if (patternIsRegExp) { RootedObject patternObj(cx, &patternValue.toObject()); // Step 5.a. if (!GetProperty(cx, patternObj, patternObj, cx->names().source, &P)) return false; // Step 5.b. F = args.get(1); if (F.isUndefined()) { if (!GetProperty(cx, patternObj, patternObj, cx->names().flags, &F)) return false; } } else { // Steps 6.a-b. P = patternValue; F = args.get(1); } // Step 7. RootedObject proto(cx); if (!GetPrototypeFromCallableConstructor(cx, args, &proto)) return false; Rooted regexp(cx, RegExpAlloc(cx, proto)); if (!regexp) return false; // Step 8. if (!RegExpInitializeIgnoringLastIndex(cx, regexp, P, F)) return false; regexp->zeroLastIndex(cx); args.rval().setObject(*regexp); return true; } /* * ES 2017 draft rev 6a13789aa9e7c6de4e96b7d3e24d9e6eba6584ad 21.2.3.1 * steps 4, 7-8. */ bool js::regexp_construct_raw_flags(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); MOZ_ASSERT(args.length() == 2); MOZ_ASSERT(!args.isConstructing()); Rooted rx(cx, &args[0].toObject().as()); // Step 4.a. RootedAtom sourceAtom(cx, rx->getSource()); // Step 4.c. int32_t flags = int32_t(args[1].toNumber()); // Step 7. Rooted regexp(cx, RegExpAlloc(cx)); if (!regexp) return false; // Step 8. regexp->initAndZeroLastIndex(sourceAtom, RegExpFlag(flags), cx); args.rval().setObject(*regexp); return true; } bool js::regexp_clone(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); RootedObject from(cx, &args[0].toObject()); RootedAtom sourceAtom(cx); RegExpFlag flags; { RegExpGuard g(cx); if (!RegExpToShared(cx, from, &g)) return false; sourceAtom = g->getSource(); flags = g->getFlags(); } Rooted regexp(cx, RegExpAlloc(cx)); if (!regexp) return false; regexp->initAndZeroLastIndex(sourceAtom, flags, cx); args.rval().setObject(*regexp); return true; } MOZ_ALWAYS_INLINE bool IsRegExpInstanceOrPrototype(HandleValue v) { if (!v.isObject()) return false; return StandardProtoKeyOrNull(&v.toObject()) == JSProto_RegExp; } // ES 2017 draft 21.2.5.4. MOZ_ALWAYS_INLINE bool regexp_global_impl(JSContext* cx, const CallArgs& args) { MOZ_ASSERT(IsRegExpInstanceOrPrototype(args.thisv())); // Step 3.a. if (!IsRegExpObject(args.thisv())) { args.rval().setUndefined(); return true; } // Steps 4-6. Rooted reObj(cx, &args.thisv().toObject().as()); args.rval().setBoolean(reObj->global()); return true; } bool js::regexp_global(JSContext* cx, unsigned argc, JS::Value* vp) { // Steps 1-3. CallArgs args = CallArgsFromVp(argc, vp); return CallNonGenericMethod(cx, args); } // ES 2017 draft 21.2.5.5. MOZ_ALWAYS_INLINE bool regexp_ignoreCase_impl(JSContext* cx, const CallArgs& args) { MOZ_ASSERT(IsRegExpInstanceOrPrototype(args.thisv())); // Step 3.a if (!IsRegExpObject(args.thisv())) { args.rval().setUndefined(); return true; } // Steps 4-6. Rooted reObj(cx, &args.thisv().toObject().as()); args.rval().setBoolean(reObj->ignoreCase()); return true; } bool js::regexp_ignoreCase(JSContext* cx, unsigned argc, JS::Value* vp) { // Steps 1-3. CallArgs args = CallArgsFromVp(argc, vp); return CallNonGenericMethod(cx, args); } // ES 2017 draft 21.2.5.7. MOZ_ALWAYS_INLINE bool regexp_multiline_impl(JSContext* cx, const CallArgs& args) { MOZ_ASSERT(IsRegExpInstanceOrPrototype(args.thisv())); // Step 3.a. if (!IsRegExpObject(args.thisv())) { args.rval().setUndefined(); return true; } // Steps 4-6. Rooted reObj(cx, &args.thisv().toObject().as()); args.rval().setBoolean(reObj->multiline()); return true; } bool js::regexp_multiline(JSContext* cx, unsigned argc, JS::Value* vp) { // Steps 1-3. CallArgs args = CallArgsFromVp(argc, vp); return CallNonGenericMethod(cx, args); } // ES 2018 dotAll MOZ_ALWAYS_INLINE bool regexp_dotall_impl(JSContext* cx, const CallArgs& args) { MOZ_ASSERT(IsRegExpInstanceOrPrototype(args.thisv())); if (!IsRegExpObject(args.thisv())) { args.rval().setUndefined(); return true; } Rooted reObj(cx, &args.thisv().toObject().as()); args.rval().setBoolean(reObj->dotall()); return true; } bool js::regexp_dotall(JSContext* cx, unsigned argc, JS::Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); return CallNonGenericMethod(cx, args); } // ES 2017 draft rev32 21.2.5.10. MOZ_ALWAYS_INLINE bool regexp_source_impl(JSContext* cx, const CallArgs& args) { MOZ_ASSERT(IsRegExpInstanceOrPrototype(args.thisv())); // Step 3.a. if (!IsRegExpObject(args.thisv())) { args.rval().setString(cx->names().emptyRegExp); return true; } // Step 5. Rooted reObj(cx, &args.thisv().toObject().as()); RootedAtom src(cx, reObj->getSource()); if (!src) return false; // Step 7. RootedString str(cx, EscapeRegExpPattern(cx, src)); if (!str) return false; args.rval().setString(str); return true; } static bool regexp_source(JSContext* cx, unsigned argc, JS::Value* vp) { // Steps 1-4. CallArgs args = CallArgsFromVp(argc, vp); return CallNonGenericMethod(cx, args); } // ES 2017 draft 21.2.5.12. MOZ_ALWAYS_INLINE bool regexp_sticky_impl(JSContext* cx, const CallArgs& args) { MOZ_ASSERT(IsRegExpInstanceOrPrototype(args.thisv())); // Step 3.a. if (!IsRegExpObject(args.thisv())) { args.rval().setUndefined(); return true; } // Steps 4-6. Rooted reObj(cx, &args.thisv().toObject().as()); args.rval().setBoolean(reObj->sticky()); return true; } bool js::regexp_sticky(JSContext* cx, unsigned argc, JS::Value* vp) { // Steps 1-3. CallArgs args = CallArgsFromVp(argc, vp); return CallNonGenericMethod(cx, args); } // ES 2017 draft 21.2.5.15. MOZ_ALWAYS_INLINE bool regexp_unicode_impl(JSContext* cx, const CallArgs& args) { MOZ_ASSERT(IsRegExpInstanceOrPrototype(args.thisv())); // Step 3.a. if (!IsRegExpObject(args.thisv())) { args.rval().setUndefined(); return true; } // Steps 4-6. Rooted reObj(cx, &args.thisv().toObject().as()); args.rval().setBoolean(reObj->unicode()); return true; } bool js::regexp_unicode(JSContext* cx, unsigned argc, JS::Value* vp) { // Steps 1-3. CallArgs args = CallArgsFromVp(argc, vp); return CallNonGenericMethod(cx, args); } const JSPropertySpec js::regexp_properties[] = { JS_SELF_HOSTED_GET("flags", "RegExpFlagsGetter", 0), JS_PSG("global", regexp_global, 0), JS_PSG("ignoreCase", regexp_ignoreCase, 0), JS_PSG("multiline", regexp_multiline, 0), JS_PSG("source", regexp_source, 0), JS_PSG("sticky", regexp_sticky, 0), JS_PSG("unicode", regexp_unicode, 0), JS_PSG("dotall", regexp_dotall, 0), JS_PS_END }; const JSFunctionSpec js::regexp_methods[] = { #if JS_HAS_TOSOURCE JS_SELF_HOSTED_FN(js_toSource_str, "RegExpToString", 0, 0), #endif JS_SELF_HOSTED_FN(js_toString_str, "RegExpToString", 0, 0), JS_FN("compile", regexp_compile, 2,0), JS_SELF_HOSTED_FN("exec", "RegExp_prototype_Exec", 1,0), JS_SELF_HOSTED_FN("test", "RegExpTest" , 1,0), JS_SELF_HOSTED_SYM_FN(match, "RegExpMatch", 1,0), JS_SELF_HOSTED_SYM_FN(replace, "RegExpReplace", 2,0), JS_SELF_HOSTED_SYM_FN(search, "RegExpSearch", 1,0), JS_SELF_HOSTED_SYM_FN(split, "RegExpSplit", 2,0), JS_FS_END }; #define STATIC_PAREN_GETTER_CODE(parenNum) \ if (!res->createParen(cx, parenNum, args.rval())) \ return false; \ if (args.rval().isUndefined()) \ args.rval().setString(cx->runtime()->emptyString); \ return true /* * RegExp static properties. * * RegExp class static properties and their Perl counterparts: * * RegExp.input $_ * RegExp.lastMatch $& * RegExp.lastParen $+ * RegExp.leftContext $` * RegExp.rightContext $' */ #define DEFINE_STATIC_GETTER(name, code) \ static bool \ name(JSContext* cx, unsigned argc, Value* vp) \ { \ CallArgs args = CallArgsFromVp(argc, vp); \ RegExpStatics* res = GlobalObject::getRegExpStatics(cx, cx->global()); \ if (!res) \ return false; \ code; \ } DEFINE_STATIC_GETTER(static_input_getter, return res->createPendingInput(cx, args.rval())) DEFINE_STATIC_GETTER(static_lastMatch_getter, return res->createLastMatch(cx, args.rval())) DEFINE_STATIC_GETTER(static_lastParen_getter, return res->createLastParen(cx, args.rval())) DEFINE_STATIC_GETTER(static_leftContext_getter, return res->createLeftContext(cx, args.rval())) DEFINE_STATIC_GETTER(static_rightContext_getter, return res->createRightContext(cx, args.rval())) DEFINE_STATIC_GETTER(static_paren1_getter, STATIC_PAREN_GETTER_CODE(1)) DEFINE_STATIC_GETTER(static_paren2_getter, STATIC_PAREN_GETTER_CODE(2)) DEFINE_STATIC_GETTER(static_paren3_getter, STATIC_PAREN_GETTER_CODE(3)) DEFINE_STATIC_GETTER(static_paren4_getter, STATIC_PAREN_GETTER_CODE(4)) DEFINE_STATIC_GETTER(static_paren5_getter, STATIC_PAREN_GETTER_CODE(5)) DEFINE_STATIC_GETTER(static_paren6_getter, STATIC_PAREN_GETTER_CODE(6)) DEFINE_STATIC_GETTER(static_paren7_getter, STATIC_PAREN_GETTER_CODE(7)) DEFINE_STATIC_GETTER(static_paren8_getter, STATIC_PAREN_GETTER_CODE(8)) DEFINE_STATIC_GETTER(static_paren9_getter, STATIC_PAREN_GETTER_CODE(9)) #define DEFINE_STATIC_SETTER(name, code) \ static bool \ name(JSContext* cx, unsigned argc, Value* vp) \ { \ RegExpStatics* res = GlobalObject::getRegExpStatics(cx, cx->global()); \ if (!res) \ return false; \ code; \ return true; \ } static bool static_input_setter(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); RegExpStatics* res = GlobalObject::getRegExpStatics(cx, cx->global()); if (!res) return false; RootedString str(cx, ToString(cx, args.get(0))); if (!str) return false; res->setPendingInput(str); args.rval().setString(str); return true; } const JSPropertySpec js::regexp_static_props[] = { JS_PSGS("input", static_input_getter, static_input_setter, JSPROP_PERMANENT | JSPROP_ENUMERATE), JS_PSG("lastMatch", static_lastMatch_getter, JSPROP_PERMANENT | JSPROP_ENUMERATE), JS_PSG("lastParen", static_lastParen_getter, JSPROP_PERMANENT | JSPROP_ENUMERATE), JS_PSG("leftContext", static_leftContext_getter, JSPROP_PERMANENT | JSPROP_ENUMERATE), JS_PSG("rightContext", static_rightContext_getter, JSPROP_PERMANENT | JSPROP_ENUMERATE), JS_PSG("$1", static_paren1_getter, JSPROP_PERMANENT | JSPROP_ENUMERATE), JS_PSG("$2", static_paren2_getter, JSPROP_PERMANENT | JSPROP_ENUMERATE), JS_PSG("$3", static_paren3_getter, JSPROP_PERMANENT | JSPROP_ENUMERATE), JS_PSG("$4", static_paren4_getter, JSPROP_PERMANENT | JSPROP_ENUMERATE), JS_PSG("$5", static_paren5_getter, JSPROP_PERMANENT | JSPROP_ENUMERATE), JS_PSG("$6", static_paren6_getter, JSPROP_PERMANENT | JSPROP_ENUMERATE), JS_PSG("$7", static_paren7_getter, JSPROP_PERMANENT | JSPROP_ENUMERATE), JS_PSG("$8", static_paren8_getter, JSPROP_PERMANENT | JSPROP_ENUMERATE), JS_PSG("$9", static_paren9_getter, JSPROP_PERMANENT | JSPROP_ENUMERATE), JS_PSGS("$_", static_input_getter, static_input_setter, JSPROP_PERMANENT), JS_PSG("$&", static_lastMatch_getter, JSPROP_PERMANENT), JS_PSG("$+", static_lastParen_getter, JSPROP_PERMANENT), JS_PSG("$`", static_leftContext_getter, JSPROP_PERMANENT), JS_PSG("$'", static_rightContext_getter, JSPROP_PERMANENT), JS_SELF_HOSTED_SYM_GET(species, "RegExpSpecies", 0), JS_PS_END }; template static bool IsTrailSurrogateWithLeadSurrogateImpl(JSContext* cx, HandleLinearString input, size_t index) { JS::AutoCheckCannotGC nogc; MOZ_ASSERT(index > 0 && index < input->length()); const CharT* inputChars = input->chars(nogc); return unicode::IsTrailSurrogate(inputChars[index]) && unicode::IsLeadSurrogate(inputChars[index - 1]); } static bool IsTrailSurrogateWithLeadSurrogate(JSContext* cx, HandleLinearString input, int32_t index) { if (index <= 0 || size_t(index) >= input->length()) return false; return input->hasLatin1Chars() ? IsTrailSurrogateWithLeadSurrogateImpl(cx, input, index) : IsTrailSurrogateWithLeadSurrogateImpl(cx, input, index); } /* * ES 2017 draft rev 6a13789aa9e7c6de4e96b7d3e24d9e6eba6584ad 21.2.5.2.2 * steps 3, 9-14, except 12.a.i, 12.c.i.1. */ static RegExpRunStatus ExecuteRegExp(JSContext* cx, HandleObject regexp, HandleString string, int32_t lastIndex, MatchPairs* matches, size_t* endIndex, RegExpStaticsUpdate staticsUpdate) { /* * WARNING: Despite the presence of spec step comment numbers, this * algorithm isn't consistent with any ES6 version, draft or * otherwise. YOU HAVE BEEN WARNED. */ /* Steps 1-2 performed by the caller. */ Rooted reobj(cx, ®exp->as()); RegExpGuard re(cx); if (!RegExpObject::getShared(cx, reobj, &re)) return RegExpRunStatus_Error; RegExpStatics* res; if (staticsUpdate == UpdateRegExpStatics) { res = GlobalObject::getRegExpStatics(cx, cx->global()); if (!res) return RegExpRunStatus_Error; } else { res = nullptr; } RootedLinearString input(cx, string->ensureLinear(cx)); if (!input) return RegExpRunStatus_Error; /* Handled by caller */ MOZ_ASSERT(lastIndex >= 0 && size_t(lastIndex) <= input->length()); /* Steps 4-8 performed by the caller. */ /* Step 10. */ if (reobj->unicode()) { /* * ES 2017 draft rev 6a13789aa9e7c6de4e96b7d3e24d9e6eba6584ad * 21.2.2.2 step 2. * Let listIndex be the index into Input of the character that was * obtained from element index of str. * * In the spec, pattern match is performed with decoded Unicode code * points, but our implementation performs it with UTF-16 encoded * string. In step 2, we should decrement lastIndex (index) if it * points the trail surrogate that has corresponding lead surrogate. * * var r = /\uD83D\uDC38/ug; * r.lastIndex = 1; * var str = "\uD83D\uDC38"; * var result = r.exec(str); // pattern match starts from index 0 * print(result.index); // prints 0 * * Note: this doesn't match the current spec text and result in * different values for `result.index` under certain conditions. * However, the spec will change to match our implementation's * behavior. See https://github.com/tc39/ecma262/issues/128. */ if (IsTrailSurrogateWithLeadSurrogate(cx, input, lastIndex)) lastIndex--; } /* Steps 3, 11-14, except 12.a.i, 12.c.i.1. */ RegExpRunStatus status = ExecuteRegExpImpl(cx, res, *re, input, lastIndex, matches, endIndex); if (status == RegExpRunStatus_Error) return RegExpRunStatus_Error; /* Steps 12.a.i, 12.c.i.i, 15 are done by Self-hosted function. */ return status; } /* * ES 2017 draft rev 6a13789aa9e7c6de4e96b7d3e24d9e6eba6584ad 21.2.5.2.2 * steps 3, 9-25, except 12.a.i, 12.c.i.1, 15. */ static bool RegExpMatcherImpl(JSContext* cx, HandleObject regexp, HandleString string, int32_t lastIndex, RegExpStaticsUpdate staticsUpdate, MutableHandleValue rval) { /* Execute regular expression and gather matches. */ ScopedMatchPairs matches(&cx->tempLifoAlloc()); /* Steps 3, 9-14, except 12.a.i, 12.c.i.1. */ RegExpRunStatus status = ExecuteRegExp(cx, regexp, string, lastIndex, &matches, nullptr, staticsUpdate); if (status == RegExpRunStatus_Error) return false; /* Steps 12.a, 12.c. */ if (status == RegExpRunStatus_Success_NotFound) { rval.setNull(); return true; } /* Steps 16-25 */ return CreateRegExpMatchResult(cx, string, matches, rval); } /* * ES 2017 draft rev 6a13789aa9e7c6de4e96b7d3e24d9e6eba6584ad 21.2.5.2.2 * steps 3, 9-25, except 12.a.i, 12.c.i.1, 15. */ bool js::RegExpMatcher(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); MOZ_ASSERT(args.length() == 3); MOZ_ASSERT(IsRegExpObject(args[0])); MOZ_ASSERT(args[1].isString()); MOZ_ASSERT(args[2].isNumber()); RootedObject regexp(cx, &args[0].toObject()); RootedString string(cx, args[1].toString()); RootedValue lastIndexVal(cx, args[2]); int32_t lastIndex = 0; if (!ToInt32(cx, lastIndexVal, &lastIndex)) return false; /* Steps 3, 9-25, except 12.a.i, 12.c.i.1, 15. */ return RegExpMatcherImpl(cx, regexp, string, lastIndex, UpdateRegExpStatics, args.rval()); } /* * Separate interface for use by IonMonkey. * This code cannot re-enter Ion code. */ bool js::RegExpMatcherRaw(JSContext* cx, HandleObject regexp, HandleString input, int32_t lastIndex, MatchPairs* maybeMatches, MutableHandleValue output) { MOZ_ASSERT(lastIndex >= 0); // The MatchPairs will always be passed in, but RegExp execution was // successful only if the pairs have actually been filled in. if (maybeMatches && maybeMatches->pairsRaw()[0] >= 0) return CreateRegExpMatchResult(cx, input, *maybeMatches, output); return RegExpMatcherImpl(cx, regexp, input, lastIndex, UpdateRegExpStatics, output); } /* * ES 2017 draft rev 6a13789aa9e7c6de4e96b7d3e24d9e6eba6584ad 21.2.5.2.2 * steps 3, 9-25, except 12.a.i, 12.c.i.1, 15. * This code is inlined in CodeGenerator.cpp generateRegExpSearcherStub, * changes to this code need to get reflected in there too. */ static bool RegExpSearcherImpl(JSContext* cx, HandleObject regexp, HandleString string, int32_t lastIndex, RegExpStaticsUpdate staticsUpdate, int32_t* result) { /* Execute regular expression and gather matches. */ ScopedMatchPairs matches(&cx->tempLifoAlloc()); /* Steps 3, 9-14, except 12.a.i, 12.c.i.1. */ RegExpRunStatus status = ExecuteRegExp(cx, regexp, string, lastIndex, &matches, nullptr, staticsUpdate); if (status == RegExpRunStatus_Error) return false; /* Steps 12.a, 12.c. */ if (status == RegExpRunStatus_Success_NotFound) { *result = -1; return true; } /* Steps 16-25 */ *result = CreateRegExpSearchResult(cx, matches); return true; } /* * ES 2017 draft rev 6a13789aa9e7c6de4e96b7d3e24d9e6eba6584ad 21.2.5.2.2 * steps 3, 9-25, except 12.a.i, 12.c.i.1, 15. */ bool js::RegExpSearcher(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); MOZ_ASSERT(args.length() == 3); MOZ_ASSERT(IsRegExpObject(args[0])); MOZ_ASSERT(args[1].isString()); MOZ_ASSERT(args[2].isNumber()); RootedObject regexp(cx, &args[0].toObject()); RootedString string(cx, args[1].toString()); RootedValue lastIndexVal(cx, args[2]); int32_t lastIndex = 0; if (!ToInt32(cx, lastIndexVal, &lastIndex)) return false; /* Steps 3, 9-25, except 12.a.i, 12.c.i.1, 15. */ int32_t result = 0; if (!RegExpSearcherImpl(cx, regexp, string, lastIndex, UpdateRegExpStatics, &result)) return false; args.rval().setInt32(result); return true; } /* * Separate interface for use by IonMonkey. * This code cannot re-enter Ion code. */ bool js::RegExpSearcherRaw(JSContext* cx, HandleObject regexp, HandleString input, int32_t lastIndex, MatchPairs* maybeMatches, int32_t* result) { MOZ_ASSERT(lastIndex >= 0); // The MatchPairs will always be passed in, but RegExp execution was // successful only if the pairs have actually been filled in. if (maybeMatches && maybeMatches->pairsRaw()[0] >= 0) { *result = CreateRegExpSearchResult(cx, *maybeMatches); return true; } return RegExpSearcherImpl(cx, regexp, input, lastIndex, UpdateRegExpStatics, result); } bool js::regexp_exec_no_statics(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); MOZ_ASSERT(args.length() == 2); MOZ_ASSERT(IsRegExpObject(args[0])); MOZ_ASSERT(args[1].isString()); RootedObject regexp(cx, &args[0].toObject()); RootedString string(cx, args[1].toString()); return RegExpMatcherImpl(cx, regexp, string, 0, DontUpdateRegExpStatics, args.rval()); } /* * ES 2017 draft rev 6a13789aa9e7c6de4e96b7d3e24d9e6eba6584ad 21.2.5.2.2 * steps 3, 9-14, except 12.a.i, 12.c.i.1. */ bool js::RegExpTester(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); MOZ_ASSERT(args.length() == 3); MOZ_ASSERT(IsRegExpObject(args[0])); MOZ_ASSERT(args[1].isString()); MOZ_ASSERT(args[2].isNumber()); RootedObject regexp(cx, &args[0].toObject()); RootedString string(cx, args[1].toString()); RootedValue lastIndexVal(cx, args[2]); int32_t lastIndex = 0; if (!ToInt32(cx, lastIndexVal, &lastIndex)) return false; /* Steps 3, 9-14, except 12.a.i, 12.c.i.1. */ size_t endIndex = 0; RegExpRunStatus status = ExecuteRegExp(cx, regexp, string, lastIndex, nullptr, &endIndex, UpdateRegExpStatics); if (status == RegExpRunStatus_Error) return false; if (status == RegExpRunStatus_Success) { MOZ_ASSERT(endIndex <= INT32_MAX); args.rval().setInt32(int32_t(endIndex)); } else { args.rval().setInt32(-1); } return true; } /* * Separate interface for use by IonMonkey. * This code cannot re-enter Ion code. */ bool js::RegExpTesterRaw(JSContext* cx, HandleObject regexp, HandleString input, int32_t lastIndex, int32_t* endIndex) { MOZ_ASSERT(lastIndex >= 0); size_t endIndexTmp = 0; RegExpRunStatus status = ExecuteRegExp(cx, regexp, input, lastIndex, nullptr, &endIndexTmp, UpdateRegExpStatics); if (status == RegExpRunStatus_Success) { MOZ_ASSERT(endIndexTmp <= INT32_MAX); *endIndex = int32_t(endIndexTmp); return true; } if (status == RegExpRunStatus_Success_NotFound) { *endIndex = -1; return true; } return false; } bool js::regexp_test_no_statics(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); MOZ_ASSERT(args.length() == 2); MOZ_ASSERT(IsRegExpObject(args[0])); MOZ_ASSERT(args[1].isString()); RootedObject regexp(cx, &args[0].toObject()); RootedString string(cx, args[1].toString()); size_t ignored = 0; RegExpRunStatus status = ExecuteRegExp(cx, regexp, string, 0, nullptr, &ignored, DontUpdateRegExpStatics); args.rval().setBoolean(status == RegExpRunStatus_Success); return status != RegExpRunStatus_Error; } static void GetParen(JSLinearString* matched, const JS::Value& capture, JSSubString* out) { if (capture.isUndefined()) { out->initEmpty(matched); return; } JSLinearString& captureLinear = capture.toString()->asLinear(); out->init(&captureLinear, 0, captureLinear.length()); } template static bool InterpretDollar(JSLinearString* matched, JSLinearString* string, size_t position, size_t tailPos, MutableHandle> captures, JSLinearString* replacement, const CharT* replacementBegin, const CharT* currentDollar, const CharT* replacementEnd, JSSubString* out, size_t* skip) { MOZ_ASSERT(*currentDollar == '$'); /* If there is only a dollar, bail now. */ if (currentDollar + 1 >= replacementEnd) return false; /* ES 2016 draft Mar 25, 2016 Table 46. */ char16_t c = currentDollar[1]; if (JS7_ISDEC(c)) { /* $n, $nn */ unsigned num = JS7_UNDEC(c); if (num > captures.length()) { // The result is implementation-defined, do not substitute. return false; } const CharT* currentChar = currentDollar + 2; if (currentChar < replacementEnd && (c = *currentChar, JS7_ISDEC(c))) { unsigned tmpNum = 10 * num + JS7_UNDEC(c); // If num > captures.length(), the result is implementation-defined. // Consume next character only if num <= captures.length(). if (tmpNum <= captures.length()) { currentChar++; num = tmpNum; } } if (num == 0) { // The result is implementation-defined. // Do not substitute. return false; } *skip = currentChar - currentDollar; MOZ_ASSERT(num <= captures.length()); GetParen(matched, captures[num -1], out); return true; } *skip = 2; switch (c) { default: return false; case '$': out->init(replacement, currentDollar - replacementBegin, 1); break; case '&': out->init(matched, 0, matched->length()); break; case '+': // SpiderMonkey extension if (captures.length() == 0) out->initEmpty(matched); else GetParen(matched, captures[captures.length() - 1], out); break; case '`': out->init(string, 0, position); break; case '\'': out->init(string, tailPos, string->length() - tailPos); break; } return true; } template static bool FindReplaceLengthString(JSContext* cx, HandleLinearString matched, HandleLinearString string, size_t position, size_t tailPos, MutableHandle> captures, HandleLinearString replacement, size_t firstDollarIndex, size_t* sizep) { CheckedInt replen = replacement->length(); JS::AutoCheckCannotGC nogc; MOZ_ASSERT(firstDollarIndex < replacement->length()); const CharT* replacementBegin = replacement->chars(nogc); const CharT* currentDollar = replacementBegin + firstDollarIndex; const CharT* replacementEnd = replacementBegin + replacement->length(); do { JSSubString sub; size_t skip; if (InterpretDollar(matched, string, position, tailPos, captures, replacement, replacementBegin, currentDollar, replacementEnd, &sub, &skip)) { if (sub.length > skip) replen += sub.length - skip; else replen -= skip - sub.length; currentDollar += skip; } else { currentDollar++; } currentDollar = js_strchr_limit(currentDollar, '$', replacementEnd); } while (currentDollar); if (!replen.isValid()) { ReportAllocationOverflow(cx); return false; } *sizep = replen.value(); return true; } static bool FindReplaceLength(JSContext* cx, HandleLinearString matched, HandleLinearString string, size_t position, size_t tailPos, MutableHandle> captures, HandleLinearString replacement, size_t firstDollarIndex, size_t* sizep) { return replacement->hasLatin1Chars() ? FindReplaceLengthString(cx, matched, string, position, tailPos, captures, replacement, firstDollarIndex, sizep) : FindReplaceLengthString(cx, matched, string, position, tailPos, captures, replacement, firstDollarIndex, sizep); } /* * Precondition: |sb| already has necessary growth space reserved (as * derived from FindReplaceLength), and has been inflated to TwoByte if * necessary. */ template static void DoReplace(HandleLinearString matched, HandleLinearString string, size_t position, size_t tailPos, MutableHandle> captures, HandleLinearString replacement, size_t firstDollarIndex, StringBuffer &sb) { JS::AutoCheckCannotGC nogc; const CharT* replacementBegin = replacement->chars(nogc); const CharT* currentChar = replacementBegin; MOZ_ASSERT(firstDollarIndex < replacement->length()); const CharT* currentDollar = replacementBegin + firstDollarIndex; const CharT* replacementEnd = replacementBegin + replacement->length(); do { /* Move one of the constant portions of the replacement value. */ size_t len = currentDollar - currentChar; sb.infallibleAppend(currentChar, len); currentChar = currentDollar; JSSubString sub; size_t skip; if (InterpretDollar(matched, string, position, tailPos, captures, replacement, replacementBegin, currentDollar, replacementEnd, &sub, &skip)) { sb.infallibleAppendSubstring(sub.base, sub.offset, sub.length); currentChar += skip; currentDollar += skip; } else { currentDollar++; } currentDollar = js_strchr_limit(currentDollar, '$', replacementEnd); } while (currentDollar); sb.infallibleAppend(currentChar, replacement->length() - (currentChar - replacementBegin)); } static bool NeedTwoBytes(HandleLinearString string, HandleLinearString replacement, HandleLinearString matched, Handle> captures) { if (string->hasTwoByteChars()) return true; if (replacement->hasTwoByteChars()) return true; if (matched->hasTwoByteChars()) return true; for (size_t i = 0, len = captures.length(); i < len; i++) { Value capture = captures[i]; if (capture.isUndefined()) continue; if (capture.toString()->hasTwoByteChars()) return true; } return false; } /* ES 2016 draft Mar 25, 2016 21.1.3.14.1. */ bool js::RegExpGetSubstitution(JSContext* cx, HandleLinearString matched, HandleLinearString string, size_t position, HandleObject capturesObj, HandleLinearString replacement, size_t firstDollarIndex, MutableHandleValue rval) { MOZ_ASSERT(firstDollarIndex < replacement->length()); // Step 1 (skipped). // Step 2. size_t matchLength = matched->length(); // Steps 3-5 (skipped). // Step 6. MOZ_ASSERT(position <= string->length()); // Step 10 (reordered). uint32_t nCaptures; if (!GetLengthProperty(cx, capturesObj, &nCaptures)) return false; Rooted> captures(cx, GCVector(cx)); if (!captures.reserve(nCaptures)) return false; // Step 7. RootedValue capture(cx); for (uint32_t i = 0; i < nCaptures; i++) { if (!GetElement(cx, capturesObj, capturesObj, i, &capture)) return false; if (capture.isUndefined()) { captures.infallibleAppend(capture); continue; } MOZ_ASSERT(capture.isString()); RootedLinearString captureLinear(cx, capture.toString()->ensureLinear(cx)); if (!captureLinear) return false; captures.infallibleAppend(StringValue(captureLinear)); } // Step 8 (skipped). // Step 9. CheckedInt checkedTailPos(0); checkedTailPos += position; checkedTailPos += matchLength; if (!checkedTailPos.isValid()) { ReportAllocationOverflow(cx); return false; } uint32_t tailPos = checkedTailPos.value(); // Step 11. size_t reserveLength; if (!FindReplaceLength(cx, matched, string, position, tailPos, &captures, replacement, firstDollarIndex, &reserveLength)) { return false; } StringBuffer result(cx); if (NeedTwoBytes(string, replacement, matched, captures)) { if (!result.ensureTwoByteChars()) return false; } if (!result.reserve(reserveLength)) return false; if (replacement->hasLatin1Chars()) { DoReplace(matched, string, position, tailPos, &captures, replacement, firstDollarIndex, result); } else { DoReplace(matched, string, position, tailPos, &captures, replacement, firstDollarIndex, result); } // Step 12. JSString* resultString = result.finishString(); if (!resultString) return false; rval.setString(resultString); return true; } bool js::GetFirstDollarIndex(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); MOZ_ASSERT(args.length() == 1); RootedString str(cx, args[0].toString()); // Should be handled in different path. MOZ_ASSERT(str->length() != 0); int32_t index = -1; if (!GetFirstDollarIndexRaw(cx, str, &index)) return false; args.rval().setInt32(index); return true; } template static MOZ_ALWAYS_INLINE int GetFirstDollarIndexImpl(const TextChar* text, uint32_t textLen) { const TextChar* end = text + textLen; for (const TextChar* c = text; c != end; ++c) { if (*c == '$') return c - text; } return -1; } int32_t js::GetFirstDollarIndexRawFlat(JSLinearString* text) { uint32_t len = text->length(); JS::AutoCheckCannotGC nogc; if (text->hasLatin1Chars()) return GetFirstDollarIndexImpl(text->latin1Chars(nogc), len); return GetFirstDollarIndexImpl(text->twoByteChars(nogc), len); } bool js::GetFirstDollarIndexRaw(JSContext* cx, HandleString str, int32_t* index) { JSLinearString* text = str->ensureLinear(cx); if (!text) return false; *index = GetFirstDollarIndexRawFlat(text); return true; } bool js::RegExpPrototypeOptimizable(JSContext* cx, unsigned argc, Value* vp) { // This can only be called from self-hosted code. CallArgs args = CallArgsFromVp(argc, vp); MOZ_ASSERT(args.length() == 1); args.rval().setBoolean(RegExpPrototypeOptimizableRaw(cx, &args[0].toObject())); return true; } bool js::RegExpPrototypeOptimizableRaw(JSContext* cx, JSObject* proto) { JS::AutoCheckCannotGC nogc; AutoAssertNoPendingException aanpe(cx); if (!proto->isNative()) return false; NativeObject* nproto = static_cast(proto); Shape* shape = cx->compartment()->regExps.getOptimizableRegExpPrototypeShape(); if (shape == nproto->lastProperty()) return true; JSFunction* flagsGetter; if (!GetOwnGetterPure(cx, proto, NameToId(cx->names().flags), &flagsGetter)) return false; if (!flagsGetter) return false; if (!IsSelfHostedFunctionWithName(flagsGetter, cx->names().RegExpFlagsGetter)) return false; JSNative globalGetter; if (!GetOwnNativeGetterPure(cx, proto, NameToId(cx->names().global), &globalGetter)) return false; if (globalGetter != regexp_global) return false; JSNative ignoreCaseGetter; if (!GetOwnNativeGetterPure(cx, proto, NameToId(cx->names().ignoreCase), &ignoreCaseGetter)) return false; if (ignoreCaseGetter != regexp_ignoreCase) return false; JSNative multilineGetter; if (!GetOwnNativeGetterPure(cx, proto, NameToId(cx->names().multiline), &multilineGetter)) return false; if (multilineGetter != regexp_multiline) return false; JSNative stickyGetter; if (!GetOwnNativeGetterPure(cx, proto, NameToId(cx->names().sticky), &stickyGetter)) return false; if (stickyGetter != regexp_sticky) return false; JSNative unicodeGetter; if (!GetOwnNativeGetterPure(cx, proto, NameToId(cx->names().unicode), &unicodeGetter)) return false; if (unicodeGetter != regexp_unicode) return false; JSNative dotAllGetter; if (!GetOwnNativeGetterPure(cx, proto, NameToId(cx->names().dotall), &dotAllGetter)) return false; if (dotAllGetter != regexp_dotall) return false; // Check if @@match, @@search, and exec are own data properties, // those values should be tested in selfhosted JS. bool has = false; if (!HasOwnDataPropertyPure(cx, proto, SYMBOL_TO_JSID(cx->wellKnownSymbols().match), &has)) return false; if (!has) return false; if (!HasOwnDataPropertyPure(cx, proto, SYMBOL_TO_JSID(cx->wellKnownSymbols().search), &has)) return false; if (!has) return false; if (!HasOwnDataPropertyPure(cx, proto, NameToId(cx->names().exec), &has)) return false; if (!has) return false; cx->compartment()->regExps.setOptimizableRegExpPrototypeShape(nproto->lastProperty()); return true; } bool js::RegExpInstanceOptimizable(JSContext* cx, unsigned argc, Value* vp) { // This can only be called from self-hosted code. CallArgs args = CallArgsFromVp(argc, vp); MOZ_ASSERT(args.length() == 2); args.rval().setBoolean(RegExpInstanceOptimizableRaw(cx, &args[0].toObject(), &args[1].toObject())); return true; } bool js::RegExpInstanceOptimizableRaw(JSContext* cx, JSObject* obj, JSObject* proto) { JS::AutoCheckCannotGC nogc; AutoAssertNoPendingException aanpe(cx); RegExpObject* rx = &obj->as(); Shape* shape = cx->compartment()->regExps.getOptimizableRegExpInstanceShape(); if (shape == rx->lastProperty()) return true; if (!rx->hasStaticPrototype()) return false; if (rx->staticPrototype() != proto) return false; if (!RegExpObject::isInitialShape(rx)) return false; cx->compartment()->regExps.setOptimizableRegExpInstanceShape(rx->lastProperty()); return true; } /* * Pattern match the script to check if it is is indexing into a particular * object, e.g. 'function(a) { return b[a]; }'. Avoid calling the script in * such cases, which are used by javascript packers (particularly the popular * Dean Edwards packer) to efficiently encode large scripts. We only handle the * code patterns generated by such packers here. */ bool js::intrinsic_GetElemBaseForLambda(JSContext* cx, unsigned argc, Value* vp) { // This can only be called from self-hosted code. CallArgs args = CallArgsFromVp(argc, vp); MOZ_ASSERT(args.length() == 1); JSObject& lambda = args[0].toObject(); args.rval().setUndefined(); if (!lambda.is()) return true; RootedFunction fun(cx, &lambda.as()); if (!fun->isInterpreted() || fun->isClassConstructor()) return true; JSScript* script = JSFunction::getOrCreateScript(cx, fun); if (!script) return false; jsbytecode* pc = script->code(); /* * JSOP_GETALIASEDVAR tells us exactly where to find the base object 'b'. * Rule out the (unlikely) possibility of a function with environment * objects since it would make our environment walk off. */ if (JSOp(*pc) != JSOP_GETALIASEDVAR || fun->needsSomeEnvironmentObject()) return true; EnvironmentCoordinate ec(pc); EnvironmentObject* env = &fun->environment()->as(); for (unsigned i = 0; i < ec.hops(); ++i) env = &env->enclosingEnvironment().as(); Value b = env->aliasedBinding(ec); pc += JSOP_GETALIASEDVAR_LENGTH; /* Look for 'a' to be the lambda's first argument. */ if (JSOp(*pc) != JSOP_GETARG || GET_ARGNO(pc) != 0) return true; pc += JSOP_GETARG_LENGTH; /* 'b[a]' */ if (JSOp(*pc) != JSOP_GETELEM) return true; pc += JSOP_GETELEM_LENGTH; /* 'return b[a]' */ if (JSOp(*pc) != JSOP_RETURN) return true; /* 'b' must behave like a normal object. */ if (!b.isObject()) return true; JSObject& bobj = b.toObject(); const Class* clasp = bobj.getClass(); if (!clasp->isNative() || clasp->getOpsLookupProperty() || clasp->getOpsGetProperty()) return true; args.rval().setObject(bobj); return true; } /* * Emulates `b[a]` property access, that is detected in GetElemBaseForLambda. * It returns the property value only if the property is data property and the * propety value is a string. Otherwise it returns undefined. */ bool js::intrinsic_GetStringDataProperty(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); MOZ_ASSERT(args.length() == 2); RootedObject obj(cx, &args[0].toObject()); if (!obj->isNative()) { // The object is already checked to be native in GetElemBaseForLambda, // but it can be swapped to the other class that is non-native. // Return undefined to mark failure to get the property. args.rval().setUndefined(); return true; } RootedNativeObject nobj(cx, &obj->as()); RootedString name(cx, args[1].toString()); RootedAtom atom(cx, AtomizeString(cx, name)); if (!atom) return false; RootedValue v(cx); if (GetPropertyPure(cx, nobj, AtomToId(atom), v.address()) && v.isString()) args.rval().set(v); else args.rval().setUndefined(); return true; }