Bug 911216 - Part 9: Properly handle rejecting wrapped promises in the face of xray wrappers. r=efaust,f=bz
authorTill Schneidereit <till@tillschneidereit.net>
Tue, 22 Mar 2016 16:18:47 +0100
changeset 289861 33ad12d6ff452fa3f5f12f623a8837024706ab52
parent 289860 cf7722889ed96e7deaaaa9eef4b8b0caf8421d7d
child 289862 87c4e3921c4c419001c3ae554ab4249d3ee13c0a
push id74020
push usertschneidereit@gmail.com
push dateTue, 22 Mar 2016 23:02:59 +0000
treeherdermozilla-inbound@e947c9941fe1 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersefaust
bugs911216
milestone48.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 911216 - Part 9: Properly handle rejecting wrapped promises in the face of xray wrappers. r=efaust,f=bz
js/src/builtin/Promise.js
js/src/js.msg
js/src/vm/SelfHosting.cpp
--- a/js/src/builtin/Promise.js
+++ b/js/src/builtin/Promise.js
@@ -43,18 +43,17 @@ function CreateResolvingFunctions(promis
         // We know |promise| is an object, so using strict equality instead of
         // SameValue is fine.
         if (resolution === promise) {
             // Step 6.a.
             let selfResolutionError = GetTypeError(JSMSG_CANNOT_RESOLVE_PROMISE_WITH_ITSELF);
 
             // Step 6.b.
             if (unwrap) {
-                return callFunction(CallPromiseMethodIfWrapped, promise, selfResolutionError,
-                                    "RejectUnwrappedPromise");
+                return RejectUnwrappedPromise(promise, selfResolutionError);
             }
             return RejectPromise(promise, selfResolutionError);
         }
 
         // Step 7.
         if (!IsObject(resolution)) {
             if (unwrap) {
                 return callFunction(CallPromiseMethodIfWrapped, promise, resolution,
@@ -64,18 +63,17 @@ function CreateResolvingFunctions(promis
         }
 
         // Steps 8-9.
         let then;
         try {
             then = resolution.then;
         } catch (e) {
             if (unwrap) {
-                return callFunction(CallPromiseMethodIfWrapped, promise, e,
-                                    "RejectUnwrappedPromise");
+                return RejectUnwrappedPromise(promise, e);
             }
             return RejectPromise(promise, e);
         }
 
         // Step 10 (implicit).
 
         // Step 11.
         if (!IsCallable(then)) {
@@ -102,18 +100,17 @@ function CreateResolvingFunctions(promis
         if (alreadyResolved)
             return undefined;
 
         // Step 5.
         alreadyResolved = true;
 
         // Step 6.
         if (unwrap) {
-            return callFunction(CallPromiseMethodIfWrapped, promise, reason,
-                                "RejectUnwrappedPromise");
+            return RejectUnwrappedPromise(promise, reason);
         }
         return RejectPromise(promise, reason);
     }
 
     // Return an array instead of an object with resolve/reject properties
     // to make value extraction from C++ easier.
     return [resolve, reject];
 }
@@ -204,20 +201,16 @@ function NewPromiseCapability(C) {
 
 // ES6, 25.4.1.6. is implemented as an intrinsic in SelfHosting.cpp.
 
 // ES6, 25.4.1.7.
 function RejectPromise(promise, reason) {
     return ResolvePromise(promise, reason, PROMISE_REJECT_REACTIONS_SLOT, PROMISE_STATE_REJECTED);
 }
 
-function RejectUnwrappedPromise(reason) {
-    return ResolvePromise(this, reason, PROMISE_REJECT_REACTIONS_SLOT, PROMISE_STATE_REJECTED);
-}
-
 // ES6, 25.4.1.8.
 function TriggerPromiseReactions(reactions, argument) {
     // Step 1.
     for (var i = 0, len = reactions.length; i < len; i++)
         EnqueuePromiseReactionJob(reactions[i], argument);
     // Step 2 (implicit).
 }
 
--- a/js/src/js.msg
+++ b/js/src/js.msg
@@ -521,8 +521,9 @@ MSG_DEF(JSMSG_AMBIGUOUS_IMPORT,         
 MSG_DEF(JSMSG_MISSING_NAMESPACE_EXPORT,  0, JSEXN_SYNTAXERR, "export not found for namespace")
 MSG_DEF(JSMSG_MISSING_EXPORT,            1, JSEXN_SYNTAXERR, "local binding for export '{0}' not found")
 
 // Promise
 MSG_DEF(JSMSG_CANNOT_RESOLVE_PROMISE_WITH_ITSELF,       0, JSEXN_TYPEERR, "A promise cannot be resolved with itself.")
 MSG_DEF(JSMSG_PROMISE_CAPABILITY_HAS_SOMETHING_ALREADY, 0, JSEXN_TYPEERR, "GetCapabilitiesExecutor function already invoked with non-undefined values.")
 MSG_DEF(JSMSG_PROMISE_RESOLVE_FUNCTION_NOT_CALLABLE,    0, JSEXN_TYPEERR, "A Promise subclass passed a non-callable value as the resolve function.")
 MSG_DEF(JSMSG_PROMISE_REJECT_FUNCTION_NOT_CALLABLE,     0, JSEXN_TYPEERR, "A Promise subclass passed a non-callable value as the reject function.")
+MSG_DEF(JSMSG_PROMISE_ERROR_IN_WRAPPED_REJECTION_REASON,0, JSEXN_INTERNALERR, "Promise rejection value is a non-unwrappable cross-compartment wrapper.")
--- a/js/src/vm/SelfHosting.cpp
+++ b/js/src/vm/SelfHosting.cpp
@@ -1716,16 +1716,62 @@ intrinsic_OriginalPromiseConstructor(JSC
     if (!obj)
         return false;
 
     args.rval().setObject(*obj);
     return true;
 }
 
 static bool
+intrinsic_RejectUnwrappedPromise(JSContext* cx, unsigned argc, Value* vp)
+{
+    CallArgs args = CallArgsFromVp(argc, vp);
+    MOZ_ASSERT(args.length() == 2);
+
+    RootedObject obj(cx, &args[0].toObject());
+    MOZ_ASSERT(IsWrapper(obj));
+    Rooted<PromiseObject*> promise(cx, &UncheckedUnwrap(obj)->as<PromiseObject>());
+    AutoCompartment ac(cx, promise);
+    RootedValue reasonVal(cx, args[1]);
+
+    // The rejection reason might've been created in a compartment with higher
+    // privileges than the Promise's. In that case, object-type rejection
+    // values might be wrapped into a wrapper that throws whenever the
+    // Promise's reaction handler wants to do anything useful with it. To
+    // avoid that situation, we synthesize a generic error that doesn't
+    // expose any privileged information but can safely be used in the
+    // rejection handler.
+    if (!promise->compartment()->wrap(cx, &reasonVal))
+        return false;
+    if (reasonVal.isObject() && !CheckedUnwrap(&reasonVal.toObject())) {
+        JS_ReportErrorNumber(cx, GetErrorMessage, nullptr,
+                             JSMSG_PROMISE_ERROR_IN_WRAPPED_REJECTION_REASON);
+        if (!GetAndClearException(cx, &reasonVal))
+            return false;
+    }
+
+    RootedAtom atom(cx, Atomize(cx, "RejectPromise", strlen("RejectPromise")));
+    if (!atom)
+        return false;
+    RootedPropertyName name(cx, atom->asPropertyName());
+
+    InvokeArgs args2(cx);
+    if (!args2.init(2))
+        return false;
+    args2[0].setObject(*promise);
+    args2[1].set(reasonVal);
+
+    if (!CallSelfHostedFunction(cx, name, args2))
+        return false;
+
+    args.rval().set(args2.rval());
+    return true;
+}
+
+static bool
 intrinsic_IsWrappedPromiseObject(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     MOZ_ASSERT(args.length() == 1);
 
     RootedObject obj(cx, &args[0].toObject());
     MOZ_ASSERT(!obj->is<PromiseObject>(),
                "Unwrapped promises should be filtered out in inlineable code");
@@ -2125,16 +2171,17 @@ static const JSFunctionSpec intrinsic_fu
     JS_FN("IsWeakSet", intrinsic_IsInstanceOfBuiltin<WeakSetObject>, 1,0),
     JS_FN("CallWeakSetMethodIfWrapped",
           CallNonGenericSelfhostedMethod<Is<WeakSetObject>>, 2, 0),
 
     JS_FN("IsPromise",                      intrinsic_IsInstanceOfBuiltin<PromiseObject>, 1,0),
     JS_FN("IsWrappedPromise",               intrinsic_IsWrappedPromiseObject,     1, 0),
     JS_FN("_EnqueuePromiseJob",             intrinsic_EnqueuePromiseJob,          1, 0),
     JS_FN("_GetOriginalPromiseConstructor", intrinsic_OriginalPromiseConstructor, 0, 0),
+    JS_FN("RejectUnwrappedPromise",         intrinsic_RejectUnwrappedPromise,     2, 0),
     JS_FN("CallPromiseMethodIfWrapped",
           CallNonGenericSelfhostedMethod<Is<PromiseObject>>,      2,0),
 
     // See builtin/TypedObject.h for descriptors of the typedobj functions.
     JS_FN("NewOpaqueTypedObject",           js::NewOpaqueTypedObject, 1, 0),
     JS_FN("NewDerivedTypedObject",          js::NewDerivedTypedObject, 3, 0),
     JS_FN("TypedObjectBuffer",              TypedObject::GetBuffer, 1, 0),
     JS_FN("TypedObjectByteOffset",          TypedObject::GetByteOffset, 1, 0),