Bug 1339999 - Properly handle OOM during exception throwing in all Promise code. r=arai
☠☠ backed out by 4dbbd735c27d ☠ ☠
authorTill Schneidereit <till@tillschneidereit.net>
Tue, 28 Feb 2017 15:58:32 +0100
changeset 493421 cfd2fd77ff046cfbe2d1693858f093df9af41d74
parent 493420 17aafd546d57dfac9a403e1cfc78a89a82cec01a
child 493422 b9f1821ed5111b515912051c692d98bace3b2943
push id47755
push userbmo:dkeeler@mozilla.com
push dateFri, 03 Mar 2017 22:32:10 +0000
reviewersarai
bugs1339999
milestone54.0a1
Bug 1339999 - Properly handle OOM during exception throwing in all Promise code. r=arai MozReview-Commit-ID: 2S5uosso0wN
js/src/builtin/Promise.cpp
js/src/jit-test/tests/promise/handling-oom-during-exception-throwing.js
--- a/js/src/builtin/Promise.cpp
+++ b/js/src/builtin/Promise.cpp
@@ -126,34 +126,44 @@ NewPromiseAllDataHolder(JSContext* cx, H
 
     dataHolder->setFixedSlot(PromiseAllDataHolderSlot_Promise, ObjectValue(*resultPromise));
     dataHolder->setFixedSlot(PromiseAllDataHolderSlot_RemainingElements, Int32Value(1));
     dataHolder->setFixedSlot(PromiseAllDataHolderSlot_ValuesArray, valuesArray);
     dataHolder->setFixedSlot(PromiseAllDataHolderSlot_ResolveFunction, ObjectValue(*resolve));
     return dataHolder;
 }
 
+/**
+ * Wrapper for GetAndClearException that handles cases where no exception is
+ * pending, but an error occurred. This can be the case if an OOM was
+ * encountered while throwing the error.
+ */
+static bool
+MaybeGetAndClearException(JSContext* cx, MutableHandleValue rval)
+{
+    if (!cx->isExceptionPending())
+        return false;
+
+    return GetAndClearException(cx, rval);
+}
+
 static MOZ_MUST_USE bool RunResolutionFunction(JSContext *cx, HandleObject resolutionFun,
                                                HandleValue result, ResolutionMode mode,
                                                HandleObject promiseObj);
 
 // ES2016, 25.4.1.1.1, Steps 1.a-b.
 // Extracting all of this internal spec algorithm into a helper function would
 // be tedious, so the check in step 1 and the entirety of step 2 aren't
 // included.
 static bool
 AbruptRejectPromise(JSContext *cx, CallArgs& args, HandleObject promiseObj, HandleObject reject)
 {
-    // Not much we can do about uncatchable exceptions, so just bail.
-    if (!cx->isExceptionPending())
-        return false;
-
     // Step 1.a.
     RootedValue reason(cx);
-    if (!GetAndClearException(cx, &reason))
+    if (!MaybeGetAndClearException(cx, &reason))
         return false;
 
     if (!RunResolutionFunction(cx, reject, reason, RejectMode, promiseObj))
         return false;
 
     // Step 1.b.
     args.rval().setObject(*promiseObj);
     return true;
@@ -346,30 +356,31 @@ ResolvePromiseInternal(JSContext* cx, Ha
     RootedObject resolution(cx, &resolutionVal.toObject());
 
     // Step 6.
     if (resolution == promise) {
         // Step 6.a.
         JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                                   JSMSG_CANNOT_RESOLVE_PROMISE_WITH_ITSELF);
         RootedValue selfResolutionError(cx);
-        MOZ_ALWAYS_TRUE(GetAndClearException(cx, &selfResolutionError));
+        if (!MaybeGetAndClearException(cx, &selfResolutionError))
+            return false;
 
         // Step 6.b.
         return RejectMaybeWrappedPromise(cx, promise, selfResolutionError);
     }
 
     // Step 8.
     RootedValue thenVal(cx);
     bool status = GetProperty(cx, resolution, resolution, cx->names().then, &thenVal);
 
     // Step 9.
     if (!status) {
         RootedValue error(cx);
-        if (!GetAndClearException(cx, &error))
+        if (!MaybeGetAndClearException(cx, &error))
             return false;
 
         return RejectMaybeWrappedPromise(cx, promise, error);
     }
 
     // Step 10 (implicit).
 
     // Step 11.
@@ -894,19 +905,17 @@ PromiseReactionJob(JSContext* cx, unsign
             handlerResult = argument;
         }
     } else {
         // Step 6.
         FixedInvokeArgs<1> args2(cx);
         args2[0].set(argument);
         if (!Call(cx, handlerVal, UndefinedHandleValue, args2, &handlerResult)) {
             resolutionMode = RejectMode;
-            // Not much we can do about uncatchable exceptions, so just bail
-            // for those.
-            if (!cx->isExceptionPending() || !GetAndClearException(cx, &handlerResult))
+            if (!MaybeGetAndClearException(cx, &handlerResult))
                 return false;
         }
     }
 
     // Steps 7-9.
     size_t hookSlot = resolutionMode == RejectMode
                       ? ReactionRecordSlot_Reject
                       : ReactionRecordSlot_Resolve;
@@ -973,17 +982,17 @@ PromiseResolveThenableJob(JSContext* cx,
     args2[1].set(rejectVal);
 
     RootedValue rval(cx);
 
     // In difference to the usual pattern, we return immediately on success.
     if (Call(cx, then, thenable, args2, &rval))
         return true;
 
-    if (!GetAndClearException(cx, &rval))
+    if (!MaybeGetAndClearException(cx, &rval))
         return false;
 
     FixedInvokeArgs<1> rejectArgs(cx);
     rejectArgs[0].set(rval);
 
     return Call(cx, rejectVal, UndefinedHandleValue, rejectArgs, &rval);
 }
 
@@ -1316,19 +1325,17 @@ PromiseObject::create(JSContext* cx, Han
 
         RootedValue calleeOrRval(cx, ObjectValue(*executor));
         success = Call(cx, calleeOrRval, UndefinedHandleValue, args, &calleeOrRval);
     }
 
     // Step 10.
     if (!success) {
         RootedValue exceptionVal(cx);
-        // Not much we can do about uncatchable exceptions, so just bail
-        // for those.
-        if (!cx->isExceptionPending() || !GetAndClearException(cx, &exceptionVal))
+        if (!MaybeGetAndClearException(cx, &exceptionVal))
             return nullptr;
 
         FixedInvokeArgs<1> args(cx);
 
         args[0].set(exceptionVal);
 
         // |rejectVal| is unused after this, so we can safely write to it.
         if (!Call(cx, rejectVal, UndefinedHandleValue, args, &rejectVal))
@@ -2120,23 +2127,19 @@ js::CreatePromiseObjectForAsync(JSContex
     promise->setFixedSlot(PromiseSlot_AwaitGenerator, generatorVal);
     return promise;
 }
 
 // Async Functions proposal 2.2 steps 3.f, 3.g.
 MOZ_MUST_USE bool
 js::AsyncFunctionThrown(JSContext* cx, Handle<PromiseObject*> resultPromise)
 {
-    // Not much we can do about uncatchable exceptions, so just bail.
-    if (!cx->isExceptionPending())
-        return false;
-
     // Step 3.f.
     RootedValue exc(cx);
-    if (!GetAndClearException(cx, &exc))
+    if (!MaybeGetAndClearException(cx, &exc))
         return false;
 
     if (!RejectMaybeWrappedPromise(cx, resultPromise, exc))
         return false;
 
     // Step 3.g.
     return true;
 }
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/promise/handling-oom-during-exception-throwing.js
@@ -0,0 +1,12 @@
+// |jit-test| error: out of memory
+var y = {
+    then: function(m) {
+        throw 0;
+    }
+};
+Promise.resolve(y);
+
+g = newGlobal();
+g.parent = this;
+g.eval("Debugger(parent).onExceptionUnwind = function(fr, e) {return {throw:e}};");
+gcparam("maxBytes", gcparam("gcBytes"));