From 6dbe2de6dba7ef7c967a5a944f01bfb4c2edb79d Mon Sep 17 00:00:00 2001 From: Martok Date: Sun, 10 Nov 2024 17:05:24 +0100 Subject: [PATCH] Issue #2305 Part 3b: Create .indices and .indices.groups --- js/src/builtin/RegExp.cpp | 122 +++++++++++++++++++++++++++++------ js/src/jit/CodeGenerator.cpp | 5 ++ 2 files changed, 109 insertions(+), 18 deletions(-) diff --git a/js/src/builtin/RegExp.cpp b/js/src/builtin/RegExp.cpp index 590010b868..be6f3671e9 100644 --- a/js/src/builtin/RegExp.cpp +++ b/js/src/builtin/RegExp.cpp @@ -31,6 +31,28 @@ using mozilla::Maybe; using CapturesVector = GCVector; + +// Allocate an object for the |.groups| or |.indices.groups| property +// of a regexp match result. +static PlainObject* CreateGroupsObject(JSContext* cx, + HandlePlainObject groupsTemplate) +{ + PlainObject* result = NewObjectWithGivenProto(cx, nullptr); + result->setGroup(groupsTemplate->group()); + + return result; +} + +// Fetch a group index corresponding to a |key| from the template object +static uint32_t GetNamedCaptureIndex(JSContext* cx, HandlePlainObject groupsTemplate, HandleId key) +{ + RootedValue ival(cx); + + if (!NativeGetProperty(cx, groupsTemplate, key, &ival)) + return -1; + return ival.toInt32(); +} + /* * Implements RegExpBuiltinExec: Steps 18-35 * https://tc39.es/ecma262/#sec-regexpbuiltinexec @@ -64,17 +86,17 @@ js::CreateRegExpMatchResult(JSContext* cx, RegExpShared& re, if (!templateObject) return false; - // Step 16 + // Steps 18-19 size_t numPairs = matches.length(); MOZ_ASSERT(numPairs > 0); - /* Step 18-19. */ + // Steps 20-21: Allocate the match result object. RootedArrayObject arr(cx, NewDenseFullyAllocatedArrayWithTemplate(cx, numPairs, templateObject)); if (!arr) return false; - /* Steps 22-23 and 27 a-e - * Store a Value for each pair. */ + // Steps 28-29 and 33 a-d: Initialize the elements of the match result. + // Store a Value for each match pair. for (size_t i = 0; i < numPairs; i++) { const MatchPair& pair = matches[i]; @@ -91,15 +113,66 @@ js::CreateRegExpMatchResult(JSContext* cx, RegExpShared& re, } } - // Step 24 (reordered) + // Step 34a (reordered): Allocate and initialize the indices object if needed. + // This is an inlined implementation of MakeIndicesArray: + // https://tc39.es/ecma262/#sec-makeindicesarray + RootedArrayObject indices(cx); + RootedPlainObject indicesGroups(cx); + if (hasIndices) { + // MakeIndicesArray: step 8 + ArrayObject* indicesTemplate = + cx->compartment()->regExps.getOrCreateMatchResultTemplateObject(cx, RegExpCompartment::ResultTemplateKind::Indices); + indices = NewDenseFullyAllocatedArrayWithTemplate(cx, numPairs, indicesTemplate); + if (!indices) { + return false; + } + + // MakeIndicesArray: steps 10-12 + if (re.numNamedCaptures() > 0) { + RootedPlainObject groupsTemplate(cx, re.getGroupsTemplate()); + indicesGroups = CreateGroupsObject(cx, groupsTemplate); + if (!indicesGroups) { + return false; + } + indices->setSlot(0, ObjectValue(*indicesGroups)); + } else { + indices->setSlot(0, UndefinedValue()); + } + + // FIXME: can we merge this with step 28-29 and 33 a-d above?? + // MakeIndicesArray: step 13 a-d. (Step 13.e is implemented below.) + for (size_t i = 0; i < numPairs; i++) { + const MatchPair& pair = matches[i]; + + if (pair.isUndefined()) { + // Since we had a match, first pair must be present. + MOZ_ASSERT(i != 0); + indices->setDenseInitializedLength(i + 1); + indices->initDenseElement(i, UndefinedValue()); + } else { + RootedArrayObject indexPair(cx, NewDenseFullyAllocatedArray(cx, 2)); + if (!indexPair) { + return false; + } + indexPair->setDenseInitializedLength(2); + indexPair->initDenseElement(0, Int32Value(pair.start)); + indexPair->initDenseElement(1, Int32Value(pair.limit)); + + indices->setDenseInitializedLength(i + 1); + indices->initDenseElement(i, ObjectValue(*indexPair)); + } + } + // /FIXME + } + + // Steps 30-31 (reordered): Allocate the groups object (if needed). RootedPlainObject groups(cx); if (re.numNamedCaptures() > 0) { // construct a new object from the template saved on RegExpShared RootedPlainObject groupsTemplate(cx, re.getGroupsTemplate()); - groups = NewObjectWithGivenProto(cx, nullptr); - groups->setGroup(groupsTemplate->group()); + groups = CreateGroupsObject(cx, groupsTemplate); - // Step 27 f. + // Step 33 e-f // The groups template object stores the names of the named captures in the // the order in which they are defined. // Grab the index into the match vector from the template object and define the @@ -110,33 +183,46 @@ js::CreateRegExpMatchResult(JSContext* cx, RegExpShared& re, } MOZ_ASSERT(keys.length() == re.numNamedCaptures()); RootedId key(cx); - RootedValue ival(cx); RootedValue val(cx); for (size_t i = 0; i < keys.length(); i++) { key = keys[i]; - // fetch the group's match index... - if (!NativeGetProperty(cx, groupsTemplate, key, &ival)) - return false; - // ... and set it on groups - val = arr->getDenseElement(ival.toInt32()); + uint32_t idx = GetNamedCaptureIndex(cx, groupsTemplate, key); + MOZ_ASSERT(idx >= 0); + // Copy the matched string to |.groups| + val = arr->getDenseElement(idx); if (!NativeDefineProperty(cx, groups, key, val, nullptr, nullptr, JSPROP_ENUMERATE)) { return false; } + // MakeIndicesArray: Step 13.e (reordered) + // If we have an |.indices|, copy the information to |.indices.groups| + if (hasIndices) { + val = indices->getDenseElement(idx); + if (!NativeDefineDataProperty(cx, indicesGroups, key, val, + JSPROP_ENUMERATE)) { + return false; + } + } } } - /* Step 20 (reordered). + /* Step 22 (reordered). * Set the |index| property. (TemplateObject positions it in slot 0) */ arr->setSlot(0, Int32Value(matches[0].start)); - /* Step 21 (reordered). + /* Step 23 (reordered). * Set the |input| property. (TemplateObject positions it in slot 1) */ arr->setSlot(1, StringValue(input)); - // Steps 25-26 (reordered) + // Step 32 (reordered) // Set the |groups| property. arr->setSlot(2, groups ? ObjectValue(*groups) : UndefinedValue()); + // Step 34b (reordered) + // Set the |indices| property. + if (hasIndices) { + arr->setSlot(3, ObjectValue(*indices)); + } + #ifdef DEBUG RootedValue test(cx); RootedId id(cx, NameToId(cx->names().index)); @@ -149,7 +235,7 @@ js::CreateRegExpMatchResult(JSContext* cx, RegExpShared& re, MOZ_ASSERT(test == arr->getSlot(1)); #endif - /* Step 25. */ + /* Step 35. */ rval.setObject(*arr); return true; } diff --git a/js/src/jit/CodeGenerator.cpp b/js/src/jit/CodeGenerator.cpp index b2ef808636..b10520252c 100644 --- a/js/src/jit/CodeGenerator.cpp +++ b/js/src/jit/CodeGenerator.cpp @@ -1545,6 +1545,11 @@ JitCompartment::generateRegExpMatcherStub(JSContext* cx) ImmWord(0), &oolEntry); + // Similarly, if the |hasIndices| flag is set, fall back to the OOL stub. + masm.branchTest32(Assembler::NonZero, + Address(shared, RegExpShared::offsetOfFlags()), + Imm32(int32_t(HasIndicesFlag)), &oolEntry); + // Construct the result. Register object = temp1; Label matchResultFallback, matchResultJoin;