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 343634 33ad12d6ff452fa3f5f12f623a8837024706ab52
parent 343633 cf7722889ed96e7deaaaa9eef4b8b0caf8421d7d
child 343635 87c4e3921c4c419001c3ae554ab4249d3ee13c0a
push id13661
push userbmo:mh+mozilla@glandium.org
push dateWed, 23 Mar 2016 00:49:41 +0000
reviewersefaust
bugs911216
milestone48.0a1
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),