Bug 1475669 - Return proper result for wrapped reactions. r=jorendorff
authorAndré Bargull <andre.bargull@gmail.com>
Thu, 19 Jul 2018 12:41:34 -0700
changeset 442113 cb2f928fff562ff38b66f474ab8acd628535d985
parent 442112 2b175e8c52636117d0e635c4cf9d14d16fcefbea
child 442114 6e87ad3335ba0e7310e485d48254706cb8c21d47
push id109101
push userjorendorff@mozilla.com
push dateFri, 19 Oct 2018 15:55:18 +0000
treeherdermozilla-inbound@cb2f928fff56 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjorendorff
bugs1475669
milestone64.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 1475669 - Return proper result for wrapped reactions. r=jorendorff
js/src/builtin/Promise.cpp
js/src/jit-test/tests/debug/Object-promiseDependentPromises-realms.js
--- a/js/src/builtin/Promise.cpp
+++ b/js/src/builtin/Promise.cpp
@@ -4345,41 +4345,65 @@ PromiseObject::dependentPromises(JSConte
 
     RootedValue reactionsVal(cx, reactions());
 
     // If no reactions are pending, we don't have list and are done.
     if (reactionsVal.isNullOrUndefined()) {
         return true;
     }
 
-    RootedNativeObject reactions(cx, &reactionsVal.toObject().as<NativeObject>());
-
-    // If only a single reaction is pending, it's stored directly.
-    if (reactions->is<PromiseReactionRecord>()) {
+    RootedObject reactionsObj(cx, &reactionsVal.toObject());
+
+    // If only a single reaction exists, it's stored directly instead of in a
+    // list. In that case, `reactionsObj` might be a wrapper, which we can
+    // always safely unwrap.
+    if (IsProxy(reactionsObj)) {
+        reactionsObj = UncheckedUnwrap(reactionsObj);
+        if (JS_IsDeadWrapper(reactionsObj)) {
+            JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_DEAD_OBJECT);
+            return false;
+        }
+        MOZ_RELEASE_ASSERT(reactionsObj->is<PromiseReactionRecord>());
+    }
+
+    if (reactionsObj->is<PromiseReactionRecord>()) {
         // Not all reactions have a Promise on them.
-        RootedObject promiseObj(cx, reactions->as<PromiseReactionRecord>().promise());
+        RootedObject promiseObj(cx, reactionsObj->as<PromiseReactionRecord>().promise());
         if (!promiseObj) {
             return true;
         }
 
         if (!values.growBy(1)) {
             return false;
         }
 
         values[0].setObject(*promiseObj);
         return true;
     }
 
+    MOZ_RELEASE_ASSERT(reactionsObj->is<NativeObject>());
+    HandleNativeObject reactions = reactionsObj.as<NativeObject>();
+
     uint32_t len = reactions->getDenseInitializedLength();
     MOZ_ASSERT(len >= 2);
 
     uint32_t valuesIndex = 0;
+    Rooted<PromiseReactionRecord*> reaction(cx);
     for (uint32_t i = 0; i < len; i++) {
-        const Value& element = reactions->getDenseElement(i);
-        PromiseReactionRecord* reaction = &element.toObject().as<PromiseReactionRecord>();
+        JSObject* element = &reactions->getDenseElement(i).toObject();
+        if (IsProxy(element)) {
+            element = UncheckedUnwrap(element);
+            if (JS_IsDeadWrapper(element)) {
+                JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_DEAD_OBJECT);
+                return false;
+            }
+        }
+
+        MOZ_RELEASE_ASSERT(element->is<PromiseReactionRecord>());
+        reaction = &element->as<PromiseReactionRecord>();
 
         // Not all reactions have a Promise on them.
         RootedObject promiseObj(cx, reaction->promise());
         if (!promiseObj) {
             continue;
         }
         if (!values.growBy(1)) {
             return false;
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Object-promiseDependentPromises-realms.js
@@ -0,0 +1,17 @@
+// Bug 1475669 - Cross-compartment dependent promises.
+
+// Create a promise in realm 1.
+let g1 = newGlobal();
+let p1 = new g1.Promise((_resolve, _reject) => {});
+
+// Add a dependent promise in realm 2.
+let g2 = newGlobal();
+let p2 = g2.Promise.prototype.then.call(p1, g2.eval(`value => {}`));
+
+// Use debugger to find p2 from p1.
+let dbg = new Debugger;
+let g1w = dbg.addDebuggee(g1);
+let g2w = dbg.addDebuggee(g2);
+let dependents = g1w.makeDebuggeeValue(p1).promiseDependentPromises;
+assertEq(dependents.length, 1);
+assertEq(dependents[0], g2w.makeDebuggeeValue(p2));