Bug 1173525 - Add JS::Construct overload for specifying new.target. (r=Waldo)
authorEric Faust <efaustbmo@mozilla.com>
Wed, 17 Jun 2015 14:37:48 -0700
changeset 280247 cfaa6fb7d84edb86c3a940fc645a94b3fcb283f5
parent 280246 cf1683220e012bdac62af2ca69649575eae78c72
child 280248 f11b7896950f6a708fd295ce21314652cb6e07e2
push id4932
push userjlund@mozilla.com
push dateMon, 10 Aug 2015 18:23:06 +0000
treeherdermozilla-beta@6dd5a4f5f745 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersWaldo
bugs1173525
milestone41.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 1173525 - Add JS::Construct overload for specifying new.target. (r=Waldo)
js/src/jsapi-tests/moz.build
js/src/jsapi-tests/testDifferentNewTargetInvokeConstructor.cpp
js/src/jsapi.cpp
js/src/jsapi.h
--- a/js/src/jsapi-tests/moz.build
+++ b/js/src/jsapi-tests/moz.build
@@ -19,16 +19,17 @@ UNIFIED_SOURCES += [
     'testClassGetter.cpp',
     'testCloneScript.cpp',
     'testContexts.cpp',
     'testDebugger.cpp',
     'testDeepFreeze.cpp',
     'testDefineGetterSetterNonEnumerable.cpp',
     'testDefineProperty.cpp',
     'testDefinePropertyIgnoredAttributes.cpp',
+    'testDifferentNewTargetInvokeConstructor.cpp',
     'testEnclosingFunction.cpp',
     'testErrorCopying.cpp',
     'testException.cpp',
     'testExternalStrings.cpp',
     'testFindSCCs.cpp',
     'testForOfIterator.cpp',
     'testForwardSetProperty.cpp',
     'testFreshGlobalEvalRedefinition.cpp',
new file mode 100644
--- /dev/null
+++ b/js/src/jsapi-tests/testDifferentNewTargetInvokeConstructor.cpp
@@ -0,0 +1,37 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "jsapi.h"
+
+#include "jsapi-tests/tests.h"
+
+BEGIN_TEST(testDifferentNewTargetInvokeConstructor)
+{
+    JS::RootedValue func(cx);
+    JS::RootedValue otherFunc(cx);
+
+    EVAL("(function() { /* This is a different new.target function */ })", &otherFunc);
+
+    EVAL("(function(expected) { if (expected !== new.target) throw new Error('whoops'); })",
+         &func);
+
+    JS::AutoValueArray<1> args(cx);
+    args[0].set(otherFunc);
+
+    JS::RootedValue rval(cx);
+
+    JS::RootedObject newTarget(cx, &otherFunc.toObject());
+
+    CHECK(JS::Construct(cx, func, newTarget, args, &rval));
+
+    // It should fail, though, if newTarget is not a constructor
+    JS::RootedValue plain(cx);
+    EVAL("({})", &plain);
+    args[0].set(plain);
+    newTarget = &plain.toObject();
+    CHECK(!JS::Construct(cx, func, newTarget, args, &rval));
+
+    return true;
+}
+END_TEST(testDifferentNewTargetInvokeConstructor)
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -4439,16 +4439,47 @@ JS::Construct(JSContext* cx, HandleValue
     AssertHeapIsIdle(cx);
     CHECK_REQUEST(cx);
     assertSameCompartment(cx, fval, args);
     AutoLastFrameCheck lfc(cx);
 
     return InvokeConstructor(cx, fval, args.length(), args.begin(), false, rval);
 }
 
+JS_PUBLIC_API(bool)
+JS::Construct(JSContext* cx, HandleValue fval, HandleObject newTarget, const JS::HandleValueArray& args,
+              MutableHandleValue rval)
+{
+    AssertHeapIsIdle(cx);
+    CHECK_REQUEST(cx);
+    assertSameCompartment(cx, fval, newTarget, args);
+    AutoLastFrameCheck lfc(cx);
+
+    // Reflect.construct ensures that the supplied new.target value is a
+    // constructor. Frankly, this makes good sense, so we reproduce the check.
+    if (!newTarget->isConstructor()) {
+        RootedValue val(cx, ObjectValue(*newTarget));
+        ReportValueError(cx, JSMSG_NOT_CONSTRUCTOR, JSDVG_IGNORE_STACK, val, nullptr);
+        return false;
+    }
+
+    // This is a littlesilly, but we need to convert from what's useful for our
+    // consumers to what we can actually handle internally.
+    AutoValueVector argv(cx);
+    unsigned argc = args.length();
+    if (!argv.reserve(argc + 1))
+        return false;
+    for (unsigned i = 0; i < argc; i++) {
+        argv.infallibleAppend(args[i]);
+    }
+    argv.infallibleAppend(ObjectValue(*newTarget));
+
+    return InvokeConstructor(cx, fval, argc, argv.begin(), true, rval);
+}
+
 static JSObject*
 JS_NewHelper(JSContext* cx, HandleObject ctor, const JS::HandleValueArray& inputArgs)
 {
     AssertHeapIsIdle(cx);
     CHECK_REQUEST(cx);
     assertSameCompartment(cx, ctor, inputArgs);
 
     // This is not a simple variation of JS_CallFunctionValue because JSOP_NEW
--- a/js/src/jsapi.h
+++ b/js/src/jsapi.h
@@ -3951,16 +3951,22 @@ Call(JSContext* cx, JS::HandleValue this
     JS::RootedValue fun(cx, JS::ObjectValue(*funObj));
     return Call(cx, thisv, fun, args, rval);
 }
 
 extern JS_PUBLIC_API(bool)
 Construct(JSContext* cx, JS::HandleValue fun,
           const JS::HandleValueArray& args,
           MutableHandleValue rval);
+
+extern JS_PUBLIC_API(bool)
+Construct(JSContext* cx, JS::HandleValue fun,
+          HandleObject newTarget, const JS::HandleValueArray &args,
+          MutableHandleValue rval);
+
 } /* namespace JS */
 
 extern JS_PUBLIC_API(bool)
 JS_CheckForInterrupt(JSContext* cx);
 
 /*
  * These functions allow setting an interrupt callback that will be called
  * from the JS thread some time after any thread triggered the callback using