Bug 1568903 - Part 7: Add function to check if Promise combinator element function was already called. r=jorendorff
☠☠ backed out by cfe990fab350 ☠ ☠
authorAndré Bargull <andre.bargull@gmail.com>
Tue, 12 Nov 2019 11:14:08 +0000
changeset 502143 3f4aee7f2893f625f0e88dbd623ac9cb41824e97
parent 502142 595accbef95e27f253a0c4539a31c6858f1cae7c
child 502144 379d0f2de211fb18470dfe40f41199dcc8b44cb4
push id114172
push userdluca@mozilla.com
push dateTue, 19 Nov 2019 11:31:10 +0000
treeherdermozilla-inbound@b5c5ba07d3db [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjorendorff
bugs1568903
milestone72.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 1568903 - Part 7: Add function to check if Promise combinator element function was already called. r=jorendorff The first five steps in each Promise combinator element function are always the same. Add a helper function for this task to reduce more code duplication. Differential Revision: https://phabricator.services.mozilla.com/D51656
js/src/builtin/Promise.cpp
--- a/js/src/builtin/Promise.cpp
+++ b/js/src/builtin/Promise.cpp
@@ -2900,16 +2900,57 @@ static JSFunction* NewPromiseCombinatorE
 
   fn->setExtendedSlot(PromiseCombinatorElementFunctionSlot_Data,
                       ObjectValue(*dataHolder));
   fn->setExtendedSlot(PromiseCombinatorElementFunctionSlot_ElementIndex,
                       Int32Value(index));
   return fn;
 }
 
+// ES2020 draft rev e97c95d064750fb949b6778584702dd658cf5624
+// 25.6.4.1.2 Promise.all Resolve Element Functions
+// 25.6.4.2.2 Promise.allSettled Resolve Element Functions
+// 25.6.4.2.3 Promise.allSettled Reject Element Functions
+//
+// Common implementation for Promise combinator element functions to check if
+// they've already been called.
+static bool PromiseCombinatorElementFunctionAlreadyCalled(
+    const CallArgs& args, MutableHandle<PromiseCombinatorDataHolder*> data,
+    uint32_t* index) {
+  // Step 1.
+  JSFunction* fn = &args.callee().as<JSFunction>();
+
+  // Step 2.
+  const Value& dataVal =
+      fn->getExtendedSlot(PromiseCombinatorElementFunctionSlot_Data);
+
+  // Step 3.
+  // We use the existence of the data holder as a signal for whether the Promise
+  // combinator element function was already called. Upon resolution, it's reset
+  // to `undefined`.
+  if (dataVal.isUndefined()) {
+    return true;
+  }
+
+  data.set(&dataVal.toObject().as<PromiseCombinatorDataHolder>());
+
+  // Step 4.
+  fn->setExtendedSlot(PromiseCombinatorElementFunctionSlot_Data,
+                      UndefinedValue());
+
+  // Step 5.
+  int32_t idx =
+      fn->getExtendedSlot(PromiseCombinatorElementFunctionSlot_ElementIndex)
+          .toInt32();
+  MOZ_ASSERT(idx >= 0);
+  *index = uint32_t(idx);
+
+  return false;
+}
+
 // ES2020 draft rev dc1e21c454bd316810be1c0e7af0131a2d7f38e9
 // 25.6.4.1.1 PerformPromiseAll (iteratorRecord, constructor, resultCapability)
 static MOZ_MUST_USE bool PerformPromiseAll(
     JSContext* cx, PromiseForOfIterator& iterator, HandleObject C,
     Handle<PromiseCapability> resultCapability, bool* done) {
   *done = false;
 
   // Step 1.
@@ -3032,46 +3073,26 @@ static MOZ_MUST_USE bool PerformPromiseA
 
   return true;
 }
 
 // ES2016, 25.4.4.1.2.
 static bool PromiseAllResolveElementFunction(JSContext* cx, unsigned argc,
                                              Value* vp) {
   CallArgs args = CallArgsFromVp(argc, vp);
-
-  JSFunction* resolve = &args.callee().as<JSFunction>();
   RootedValue xVal(cx, args.get(0));
 
-  // Step 1.
-  const Value& dataVal =
-      resolve->getExtendedSlot(PromiseCombinatorElementFunctionSlot_Data);
-
-  // Step 2.
-  // We use the existence of the data holder as a signal for whether the
-  // Promise was already resolved. Upon resolution, it's reset to
-  // `undefined`.
-  if (dataVal.isUndefined()) {
+  // Steps 1-5.
+  Rooted<PromiseCombinatorDataHolder*> data(cx);
+  uint32_t index;
+  if (PromiseCombinatorElementFunctionAlreadyCalled(args, &data, &index)) {
     args.rval().setUndefined();
     return true;
   }
 
-  Rooted<PromiseCombinatorDataHolder*> data(
-      cx, &dataVal.toObject().as<PromiseCombinatorDataHolder>());
-
-  // Step 3.
-  resolve->setExtendedSlot(PromiseCombinatorElementFunctionSlot_Data,
-                           UndefinedValue());
-
-  // Step 4.
-  int32_t index =
-      resolve
-          ->getExtendedSlot(PromiseCombinatorElementFunctionSlot_ElementIndex)
-          .toInt32();
-
   // Step 5.
   RootedValue valuesVal(cx, data->valuesArray());
   RootedObject valuesObj(cx, &valuesVal.toObject());
   if (IsProxy(valuesObj)) {
     // See comment for PerformPromiseAll, step 3 for why we unwrap here.
     valuesObj = UncheckedUnwrap(valuesObj);
 
     if (JS_IsDeadWrapper(valuesObj)) {
@@ -3309,30 +3330,23 @@ static MOZ_MUST_USE bool PerformPromiseA
 // Promise.allSettled Resolve Element Functions
 // Promise.allSettled Reject Element Functions
 template <PromiseAllSettledElementFunctionKind Kind>
 static bool PromiseAllSettledElementFunction(JSContext* cx, unsigned argc,
                                              Value* vp) {
   CallArgs args = CallArgsFromVp(argc, vp);
   HandleValue valueOrReason = args.get(0);
 
-  // Step 1.
-  JSFunction* resolve = &args.callee().as<JSFunction>();
-  Rooted<PromiseCombinatorDataHolder*> data(
-      cx, &resolve->getExtendedSlot(PromiseCombinatorElementFunctionSlot_Data)
-               .toObject()
-               .as<PromiseCombinatorDataHolder>());
-
-  // Steps 2-4 (moved below).
-
-  // Step 5.
-  int32_t index =
-      resolve
-          ->getExtendedSlot(PromiseCombinatorElementFunctionSlot_ElementIndex)
-          .toInt32();
+  // Steps 1-5.
+  Rooted<PromiseCombinatorDataHolder*> data(cx);
+  uint32_t index;
+  if (PromiseCombinatorElementFunctionAlreadyCalled(args, &data, &index)) {
+    args.rval().setUndefined();
+    return true;
+  }
 
   // Step 6.
   RootedValue valuesVal(cx, data->valuesArray());
   RootedObject valuesObj(cx, &valuesVal.toObject());
   bool needsWrapping = false;
   if (IsProxy(valuesObj)) {
     // See comment for PerformPromiseAllSettled, step 3 for why we unwrap here.
     valuesObj = UncheckedUnwrap(valuesObj);
@@ -3343,16 +3357,19 @@ static bool PromiseAllSettledElementFunc
       return false;
     }
 
     needsWrapping = true;
   }
   HandleNativeObject values = valuesObj.as<NativeObject>();
 
   // Steps 2-3.
+  // The already-called check above only handles the case when |this| function
+  // is called repeatedly, so we still need to check if the other pair of this
+  // resolving function was already called:
   // We use the element value as a signal for whether the Promise was already
   // fulfilled. Upon resolution, it's set to the result object created below.
   if (!values->getDenseElement(index).isUndefined()) {
     args.rval().setUndefined();
     return true;
   }
 
   // Steps 7-8 (moved below).