Bug 1362154: Part 5: Create groups property on result object r=mgaudet
authorIain Ireland <iireland@mozilla.com>
Wed, 20 May 2020 21:04:47 +0000
changeset 531650 00d2e220a57535825b59e92df5201119109f55e4
parent 531649 1322042730b68d46674cc39feea638a22efb81f3
child 531651 9bad8ab13f064e49d2a01d8fe0343712322ba44e
push id37441
push userapavel@mozilla.com
push dateFri, 22 May 2020 21:38:53 +0000
treeherdermozilla-central@d6abd35b54ad [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmgaudet
bugs1362154
milestone78.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
Bug 1362154: Part 5: Create groups property on result object r=mgaudet When a regexp has named captures, the result object has an extra `groups` property, which is an object that in turn has a string property per named capture. This patch creates the `groups` object and attaches it to the result object. The changes here correspond to this section of the spec proposal, which conveniently highlights the changes: https://tc39.es/proposal-regexp-named-groups/#sec-regexpbuiltinexec Differential Revision: https://phabricator.services.mozilla.com/D76037
js/src/builtin/RegExp.cpp
js/src/builtin/RegExp.h
js/src/jit/CodeGenerator.cpp
js/src/vm/CommonPropertyNames.h
js/src/vm/RegExpObject.cpp
js/src/vm/RegExpShared.h
--- a/js/src/builtin/RegExp.cpp
+++ b/js/src/builtin/RegExp.cpp
@@ -25,65 +25,78 @@
 #include "vm/JSContext.h"
 #include "vm/RegExpStatics.h"
 #include "vm/SelfHosting.h"
 
 #include "vm/EnvironmentObject-inl.h"
 #include "vm/JSObject-inl.h"
 #include "vm/NativeObject-inl.h"
 #include "vm/ObjectOperations-inl.h"
+#include "vm/PlainObject-inl.h"
 
 using namespace js;
 
 using mozilla::AssertedCast;
 using mozilla::CheckedInt;
 using mozilla::IsAsciiDigit;
 
 using JS::CompileOptions;
 using JS::RegExpFlag;
 using JS::RegExpFlags;
 
 /*
- * ES 2017 draft rev 6a13789aa9e7c6de4e96b7d3e24d9e6eba6584ad 21.2.5.2.2
- * steps 3, 16-25.
+ * ES 2021 draft 21.2.5.2.2: Steps 16-28
+ * https://tc39.es/ecma262/#sec-regexpbuiltinexec
  */
-bool js::CreateRegExpMatchResult(JSContext* cx, HandleString input,
-                                 const MatchPairs& matches,
+bool js::CreateRegExpMatchResult(JSContext* cx, HandleRegExpShared re,
+                                 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
+   *  groups:         named capture groups for the match
    */
 
   // Get the templateObject that defines the shape and type of the output
   // object.
   ArrayObject* templateObject =
       cx->realm()->regExps.getOrCreateMatchResultTemplateObject(cx);
   if (!templateObject) {
     return false;
   }
 
+  // Step 16
   size_t numPairs = matches.length();
   MOZ_ASSERT(numPairs > 0);
 
-  // Step 17.
+  // Steps 18-19
   RootedArrayObject arr(cx, NewDenseFullyAllocatedArrayWithTemplate(
                                 cx, numPairs, templateObject));
   if (!arr) {
     return false;
   }
 
-  // Steps 22-24.
+#ifdef ENABLE_NEW_REGEXP
+  // Step 24 (reordered)
+  RootedPlainObject groups(cx);
+  if (re->numNamedCaptures() > 0) {
+    RootedPlainObject groupsTemplate(cx, re->getGroupsTemplate());
+    JS_TRY_VAR_OR_RETURN_FALSE(
+        cx, groups, PlainObject::createWithTemplate(cx, groupsTemplate));
+  }
+#endif
+
+  // Steps 22-23 and 27 a-e.
   // 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());
@@ -93,40 +106,55 @@ bool js::CreateRegExpMatchResult(JSConte
       if (!str) {
         return false;
       }
       arr->setDenseInitializedLength(i + 1);
       arr->initDenseElement(i, StringValue(str));
     }
   }
 
+#ifdef ENABLE_NEW_REGEXP
+  // Step 27 f.
+  for (uint32_t i = 0; i < re->numNamedCaptures(); i++) {
+    uint32_t idx = re->getNamedCaptureIndex(i);
+    groups->setSlot(i, arr->getDenseElement(idx));
+  }
+#endif
+
   // Step 20 (reordered).
   // Set the |index| property.
   arr->setSlot(RegExpRealm::MatchResultObjectIndexSlot,
                Int32Value(matches[0].start));
 
   // Step 21 (reordered).
   // Set the |input| property.
   arr->setSlot(RegExpRealm::MatchResultObjectInputSlot, StringValue(input));
 
+#ifdef ENABLE_NEW_REGEXP
+  // Steps 25-26 (reordered)
+  // Set the |groups| property.
+  arr->setSlot(RegExpRealm::MatchResultObjectGroupsSlot,
+               groups ? ObjectValue(*groups) : UndefinedValue());
+#endif
+
 #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(RegExpRealm::MatchResultObjectIndexSlot));
   id = NameToId(cx->names().input);
   if (!NativeGetProperty(cx, arr, id, &test)) {
     return false;
   }
   MOZ_ASSERT(test == arr->getSlot(RegExpRealm::MatchResultObjectInputSlot));
 #endif
 
-  // Step 25.
+  // Step 28.
   rval.setObject(*arr);
   return true;
 }
 
 static int32_t CreateRegExpSearchResult(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;
@@ -183,17 +211,17 @@ bool js::ExecuteRegExpLegacy(JSContext* 
   *lastIndex = matches[0].limit;
 
   if (test) {
     /* Forbid an array, as an optimization. */
     rval.setBoolean(true);
     return true;
   }
 
-  return CreateRegExpMatchResult(cx, input, matches, rval);
+  return CreateRegExpMatchResult(cx, shared, input, matches, rval);
 }
 
 static bool CheckPatternSyntaxSlow(JSContext* cx, HandleAtom pattern,
                                    RegExpFlags flags) {
   LifoAllocScope allocScope(&cx->tempLifoAlloc());
   CompileOptions options(cx);
   frontend::DummyTokenStream dummyTokenStream(cx, options);
 #ifdef ENABLE_NEW_REGEXP
@@ -1042,17 +1070,19 @@ static bool RegExpMatcherImpl(JSContext*
 
   /* Steps 12.a, 12.c. */
   if (status == RegExpRunStatus_Success_NotFound) {
     rval.setNull();
     return true;
   }
 
   /* Steps 16-25 */
-  return CreateRegExpMatchResult(cx, string, matches, rval);
+  Handle<RegExpObject*> reobj = regexp.as<RegExpObject>();
+  RootedRegExpShared shared(cx, RegExpObject::getShared(cx, reobj));
+  return CreateRegExpMatchResult(cx, shared, 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);
@@ -1076,17 +1106,19 @@ bool js::RegExpMatcher(JSContext* cx, un
  * This code cannot re-enter Ion code.
  */
 bool js::RegExpMatcherRaw(JSContext* cx, HandleObject regexp,
                           HandleString input, int32_t maybeLastIndex,
                           MatchPairs* maybeMatches, MutableHandleValue output) {
   // 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] > MatchPair::NoMatch) {
-    return CreateRegExpMatchResult(cx, input, *maybeMatches, output);
+    Handle<RegExpObject*> reobj = regexp.as<RegExpObject>();
+    RootedRegExpShared shared(cx, RegExpObject::getShared(cx, reobj));
+    return CreateRegExpMatchResult(cx, shared, input, *maybeMatches, output);
   }
 
   // |maybeLastIndex| only contains a valid value when the RegExp execution
   // was not successful.
   MOZ_ASSERT(maybeLastIndex >= 0);
   return RegExpMatcherImpl(cx, regexp, input, maybeLastIndex, output);
 }
 
--- a/js/src/builtin/RegExp.h
+++ b/js/src/builtin/RegExp.h
@@ -27,17 +27,18 @@ JSObject* InitRegExpClass(JSContext* cx,
  */
 MOZ_MUST_USE bool ExecuteRegExpLegacy(JSContext* cx, RegExpStatics* res,
                                       Handle<RegExpObject*> reobj,
                                       HandleLinearString input,
                                       size_t* lastIndex, bool test,
                                       MutableHandleValue rval);
 
 // Translation from MatchPairs to a JS array in regexp_exec()'s output format.
-MOZ_MUST_USE bool CreateRegExpMatchResult(JSContext* cx, HandleString input,
+MOZ_MUST_USE bool CreateRegExpMatchResult(JSContext* cx, HandleRegExpShared re,
+                                          HandleString input,
                                           const MatchPairs& matches,
                                           MutableHandleValue rval);
 
 extern MOZ_MUST_USE bool RegExpMatcher(JSContext* cx, unsigned argc, Value* vp);
 
 extern MOZ_MUST_USE bool RegExpMatcherRaw(JSContext* cx, HandleObject regexp,
                                           HandleString input,
                                           int32_t maybeLastIndex,
--- a/js/src/jit/CodeGenerator.cpp
+++ b/js/src/jit/CodeGenerator.cpp
@@ -2880,23 +2880,33 @@ JitCode* JitRealm::generateRegExpMatcher
 
   // Construct the result.
   Register object = temp1;
   Label matchResultFallback, matchResultJoin;
   masm.createGCObject(object, temp2, templateObj, gc::DefaultHeap,
                       &matchResultFallback);
   masm.bind(&matchResultJoin);
 
-  // Initialize slots of result object.
+#ifdef ENABLE_NEW_REGEXP
+  MOZ_ASSERT(nativeTemplateObj.numFixedSlots() == 0);
+  MOZ_ASSERT(nativeTemplateObj.numDynamicSlots() == 3);
+  static_assert(RegExpRealm::MatchResultObjectIndexSlot == 0,
+                "First slot holds the 'index' property");
+  static_assert(RegExpRealm::MatchResultObjectInputSlot == 1,
+                "Second slot holds the 'input' property");
+  static_assert(RegExpRealm::MatchResultObjectGroupsSlot == 2,
+                "Third slot holds the 'groups' property");
+#else
   MOZ_ASSERT(nativeTemplateObj.numFixedSlots() == 0);
   MOZ_ASSERT(nativeTemplateObj.numDynamicSlots() == 2);
   static_assert(RegExpRealm::MatchResultObjectIndexSlot == 0,
                 "First slot holds the 'index' property");
   static_assert(RegExpRealm::MatchResultObjectInputSlot == 1,
                 "Second slot holds the 'input' property");
+#endif
 
   masm.loadPtr(Address(object, NativeObject::offsetOfSlots()), temp2);
   masm.storeValue(
       nativeTemplateObj.getSlot(RegExpRealm::MatchResultObjectIndexSlot),
       Address(temp2, 0));
   masm.storeValue(
       nativeTemplateObj.getSlot(RegExpRealm::MatchResultObjectInputSlot),
       Address(temp2, sizeof(Value)));
--- a/js/src/vm/CommonPropertyNames.h
+++ b/js/src/vm/CommonPropertyNames.h
@@ -191,16 +191,17 @@
   MACRO(getOwnPropertyNames, getOwnPropertyNames, "getOwnPropertyNames")       \
   MACRO(getPrefix, getPrefix, "get ")                                          \
   MACRO(getPropertySuper, getPropertySuper, "getPropertySuper")                \
   MACRO(getPrototypeOf, getPrototypeOf, "getPrototypeOf")                      \
   MACRO(GetTypeError, GetTypeError, "GetTypeError")                            \
   MACRO(global, global, "global")                                              \
   MACRO(globalThis, globalThis, "globalThis")                                  \
   MACRO(group, group, "group")                                                 \
+  MACRO(groups, groups, "groups")                                              \
   MACRO(Handle, Handle, "Handle")                                              \
   MACRO(has, has, "has")                                                       \
   MACRO(hasOwn, hasOwn, "hasOwn")                                              \
   MACRO(hasOwnProperty, hasOwnProperty, "hasOwnProperty")                      \
   MACRO(highWaterMark, highWaterMark, "highWaterMark")                         \
   MACRO(hour, hour, "hour")                                                    \
   MACRO(hourCycle, hourCycle, "hourCycle")                                     \
   MACRO(if, if_, "if")                                                         \
--- a/js/src/vm/RegExpObject.cpp
+++ b/js/src/vm/RegExpObject.cpp
@@ -1454,48 +1454,65 @@ RegExpRealm::RegExpRealm()
 ArrayObject* RegExpRealm::createMatchResultTemplateObject(JSContext* cx) {
   MOZ_ASSERT(!matchResultTemplateObject_);
 
   /* Create template array object */
   RootedArrayObject templateObject(
       cx, NewDenseUnallocatedArray(cx, RegExpObject::MaxPairCount, nullptr,
                                    TenuredObject));
   if (!templateObject) {
-    return matchResultTemplateObject_;  // = nullptr
+    return nullptr;
   }
 
   // Create a new group for the template.
   Rooted<TaggedProto> proto(cx, templateObject->taggedProto());
   ObjectGroup* group = ObjectGroupRealm::makeGroup(
       cx, templateObject->realm(), templateObject->getClass(), proto);
   if (!group) {
-    return matchResultTemplateObject_;  // = nullptr
+    return nullptr;
   }
   templateObject->setGroup(group);
 
   /* Set dummy index property */
   RootedValue index(cx, Int32Value(0));
   if (!NativeDefineDataProperty(cx, templateObject, cx->names().index, index,
                                 JSPROP_ENUMERATE)) {
-    return matchResultTemplateObject_;  // = nullptr
+    return nullptr;
   }
 
   /* Set dummy input property */
   RootedValue inputVal(cx, StringValue(cx->runtime()->emptyString));
   if (!NativeDefineDataProperty(cx, templateObject, cx->names().input, inputVal,
                                 JSPROP_ENUMERATE)) {
-    return matchResultTemplateObject_;  // = nullptr
+    return nullptr;
   }
 
+#ifdef ENABLE_NEW_REGEXP
+  /* Set dummy groups property */
+  RootedValue groupsVal(cx, UndefinedValue());
+  if (!NativeDefineDataProperty(cx, templateObject, cx->names().groups,
+                                groupsVal, JSPROP_ENUMERATE)) {
+    return nullptr;
+  }
+  AddTypePropertyId(cx, templateObject, NameToId(cx->names().groups),
+                    TypeSet::AnyObjectType());
+
   // Make sure that the properties are in the right slots.
-  DebugOnly<Shape*> shape = templateObject->lastProperty();
-  MOZ_ASSERT(shape->previous()->slot() == MatchResultObjectIndexSlot &&
-             shape->previous()->propidRef() == NameToId(cx->names().index));
-  MOZ_ASSERT(shape->slot() == MatchResultObjectInputSlot &&
-             shape->propidRef() == NameToId(cx->names().input));
+#  ifdef DEBUG
+  Shape* groupsShape = templateObject->lastProperty();
+  MOZ_ASSERT(groupsShape->slot() == MatchResultObjectGroupsSlot &&
+             groupsShape->propidRef() == NameToId(cx->names().groups));
+  Shape* inputShape = groupsShape->previous().get();
+  MOZ_ASSERT(inputShape->slot() == MatchResultObjectInputSlot &&
+             inputShape->propidRef() == NameToId(cx->names().input));
+  Shape* indexShape = inputShape->previous().get();
+  MOZ_ASSERT(indexShape->slot() == MatchResultObjectIndexSlot &&
+             indexShape->propidRef() == NameToId(cx->names().index));
+#  endif
+#endif
 
   // Make sure type information reflects the indexed properties which might
   // be added.
   AddTypePropertyId(cx, templateObject, JSID_VOID, TypeSet::StringType());
   AddTypePropertyId(cx, templateObject, JSID_VOID, TypeSet::UndefinedType());
 
   matchResultTemplateObject_.set(templateObject);
 
--- a/js/src/vm/RegExpShared.h
+++ b/js/src/vm/RegExpShared.h
@@ -386,16 +386,19 @@ class RegExpRealm {
 
  public:
   explicit RegExpRealm();
 
   void traceWeak(JSTracer* trc);
 
   static const size_t MatchResultObjectIndexSlot = 0;
   static const size_t MatchResultObjectInputSlot = 1;
+#ifdef ENABLE_NEW_REGEXP
+  static const size_t MatchResultObjectGroupsSlot = 2;
+#endif
 
   /* Get or create template object used to base the result of .exec() on. */
   ArrayObject* getOrCreateMatchResultTemplateObject(JSContext* cx) {
     if (matchResultTemplateObject_) {
       return matchResultTemplateObject_;
     }
     return createMatchResultTemplateObject(cx);
   }