Bug 1342070 - Part 2: Only create result Promises in Promise#catch if it's used or the creation is otherwise observable. r=anba
authorTill Schneidereit <till@tillschneidereit.net>
Wed, 02 Aug 2017 09:43:45 +0200
changeset 414100 f054209125461575f57ce0f1e6b962653c523572
parent 414099 c2d33789539668305c1e8ac2d65a05a590c8a548
child 414101 e66e4b3ee60f04411e0df5d446794d4166c3ba80
push id33858
push userncsoregi@mozilla.com
push dateTue, 17 Apr 2018 21:55:44 +0000
treeherdermozilla-central@d6eb5597d744 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersanba
bugs1342070
milestone61.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 1342070 - Part 2: Only create result Promises in Promise#catch if it's used or the creation is otherwise observable. r=anba Includes porting catch to C++ to make this feasible.
js/src/builtin/Promise.cpp
js/src/builtin/Promise.h
--- a/js/src/builtin/Promise.cpp
+++ b/js/src/builtin/Promise.cpp
@@ -2339,18 +2339,18 @@ js::PromiseResolve(JSContext* cx, Handle
 {
     RootedValue C(cx, ObjectValue(*constructor));
     return CommonStaticResolveRejectImpl(cx, C, value, ResolveMode);
 }
 
 /**
  * ES2016, 25.4.4.4, Promise.reject.
  */
-bool
-js::Promise_reject(JSContext* cx, unsigned argc, Value* vp)
+static bool
+Promise_reject(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     RootedValue thisVal(cx, args.thisv());
     RootedValue argVal(cx, args.get(0));
     JSObject* result = CommonStaticResolveRejectImpl(cx, thisVal, argVal, RejectMode);
     if (!result)
         return false;
     args.rval().setObject(*result);
@@ -2368,18 +2368,18 @@ PromiseObject::unforgeableReject(JSConte
         return nullptr;
     RootedValue cVal(cx, ObjectValue(*promiseCtor));
     return CommonStaticResolveRejectImpl(cx, cVal, value, RejectMode);
 }
 
 /**
  * ES2016, 25.4.4.5, Promise.resolve.
  */
-bool
-js::Promise_static_resolve(JSContext* cx, unsigned argc, Value* vp)
+static bool
+Promise_static_resolve(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     RootedValue thisVal(cx, args.thisv());
     RootedValue argVal(cx, args.get(0));
     JSObject* result = CommonStaticResolveRejectImpl(cx, thisVal, argVal, ResolveMode);
     if (!result)
         return false;
     args.rval().setObject(*result);
@@ -3023,26 +3023,61 @@ js::AsyncGeneratorEnqueue(JSContext* cx,
             return false;
     }
 
     // Step 9.
     result.setObject(*resultPromise);
     return true;
 }
 
-bool Promise_then_impl(JSContext* cx, unsigned argc, Value* vp, bool rvalUsed)
+static bool Promise_then(JSContext* cx, unsigned argc, Value* vp);
+static bool Promise_then_impl(JSContext* cx, HandleValue promiseVal, HandleValue onFulfilled,
+                              HandleValue onRejected, MutableHandleValue rval, bool rvalUsed);
+
+static bool
+Promise_catch_impl(JSContext* cx, unsigned argc, Value* vp, bool rvalUsed)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
 
     // Step 1.
-    RootedValue promiseVal(cx, args.thisv());
-
-    RootedValue onFulfilled(cx, args.get(0));
-    RootedValue onRejected(cx, args.get(1));
-
+    RootedValue thenVal(cx);
+    if (!GetProperty(cx, args.thisv(), cx->names().then, &thenVal))
+        return false;
+
+    if (IsNativeFunction(thenVal, &Promise_then)) {
+        return Promise_then_impl(cx, args.thisv(), UndefinedHandleValue, args.get(0),
+                                 args.rval(), rvalUsed);
+    }
+
+    FixedInvokeArgs<2> iargs(cx);
+    iargs[0].setUndefined();
+    iargs[1].set(args.get(0));
+
+    return Call(cx, thenVal, args.thisv(), iargs, args.rval());
+}
+
+// ES2016, 25.4.5.3.
+static bool
+Promise_catch_noRetVal(JSContext* cx, unsigned argc, Value* vp)
+{
+    return Promise_catch_impl(cx, argc, vp, false);
+}
+
+// ES2016, 25.4.5.3.
+static bool
+Promise_catch(JSContext* cx, unsigned argc, Value* vp)
+{
+    return Promise_catch_impl(cx, argc, vp, true);
+}
+
+static bool
+Promise_then_impl(JSContext* cx, HandleValue promiseVal, HandleValue onFulfilled,
+                  HandleValue onRejected, MutableHandleValue rval, bool rvalUsed)
+{
+    // Step 1 (implicit).
     // Step 2.
     if (!promiseVal.isObject()) {
         JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_NOT_NONNULL_OBJECT,
                                   "Receiver of Promise.prototype.then call");
         return false;
     }
     RootedObject promiseObj(cx, &promiseVal.toObject());
     Rooted<PromiseObject*> promise(cx);
@@ -3071,34 +3106,36 @@ bool Promise_then_impl(JSContext* cx, un
     RootedObject resultPromise(cx);
     if (!OriginalPromiseThen(cx, promise, onFulfilled, onRejected, &resultPromise,
                              createDependent))
     {
         return false;
     }
 
     if (rvalUsed)
-        args.rval().setObject(*resultPromise);
+        rval.setObject(*resultPromise);
     else
-        args.rval().setUndefined();
+        rval.setUndefined();
     return true;
 }
 
 // ES2016, 25.4.5.3.
 bool
 Promise_then_noRetVal(JSContext* cx, unsigned argc, Value* vp)
 {
-    return Promise_then_impl(cx, argc, vp, false);
+    CallArgs args = CallArgsFromVp(argc, vp);
+    return Promise_then_impl(cx, args.thisv(), args.get(0), args.get(1), args.rval(), false);
 }
 
 // ES2016, 25.4.5.3.
-bool
-js::Promise_then(JSContext* cx, unsigned argc, Value* vp)
+static bool
+Promise_then(JSContext* cx, unsigned argc, Value* vp)
 {
-    return Promise_then_impl(cx, argc, vp, true);
+    CallArgs args = CallArgsFromVp(argc, vp);
+    return Promise_then_impl(cx, args.thisv(), args.get(0), args.get(1), args.rval(), true);
 }
 
 // ES2016, 25.4.5.3.1.
 static MOZ_MUST_USE bool
 PerformPromiseThen(JSContext* cx, Handle<PromiseObject*> promise, HandleValue onFulfilled_,
                    HandleValue onRejected_, HandleObject resultPromise,
                    HandleObject resolve, HandleObject reject)
 {
@@ -3766,19 +3803,28 @@ const JSJitInfo promise_then_info = {
   { (JSJitGetterOp)Promise_then_noRetVal },
   { 0 }, /* unused */
   { 0 }, /* unused */
   JSJitInfo::IgnoresReturnValueNative,
   JSJitInfo::AliasEverything,
   JSVAL_TYPE_UNDEFINED,
 };
 
+const JSJitInfo promise_catch_info = {
+  { (JSJitGetterOp)Promise_catch_noRetVal },
+  { 0 }, /* unused */
+  { 0 }, /* unused */
+  JSJitInfo::IgnoresReturnValueNative,
+  JSJitInfo::AliasEverything,
+  JSVAL_TYPE_UNDEFINED,
+};
+
 static const JSFunctionSpec promise_methods[] = {
-    JS_SELF_HOSTED_FN("catch", "Promise_catch", 1, 0),
     JS_FNINFO("then", Promise_then, &promise_then_info, 2, 0),
+    JS_FNINFO("catch", Promise_catch, &promise_catch_info, 1, 0),
     JS_SELF_HOSTED_FN("finally", "Promise_finally", 1, 0),
     JS_FS_END
 };
 
 static const JSPropertySpec promise_properties[] = {
     JS_STRING_SYM_PS(toStringTag, "Promise", JSPROP_READONLY),
     JS_PS_END
 };
--- a/js/src/builtin/Promise.h
+++ b/js/src/builtin/Promise.h
@@ -254,18 +254,11 @@ class OffThreadPromiseRuntimeState
     // called to periodically drain the dispatch queue before shutdown.
     void internalDrain(JSContext* cx);
     bool internalHasPending();
 
     // shutdown() must be called by the JSRuntime while the JSRuntime is valid.
     void shutdown(JSContext* cx);
 };
 
-bool
-Promise_static_resolve(JSContext* cx, unsigned argc, Value* vp);
-bool
-Promise_reject(JSContext* cx, unsigned argc, Value* vp);
-bool
-Promise_then(JSContext* cx, unsigned argc, Value* vp);
-
 } // namespace js
 
 #endif /* builtin_Promise_h */