Bug 1354294 - Preserve IsCallable and IsConstructor when nuking wrappers. r=evilpie, a=me
authorShu-yu Guo <shu@rfrn.org>
Wed, 12 Apr 2017 17:55:00 -0400
changeset 400871 ec0e2532c1b00acee8852611a9e4d543ae41c9cb
parent 400870 66a6e0bb7fd45343cad3826af9092a2f77b110ef
child 400872 89b29e846791ee116c4374361bcff50088e009ed
push id7391
push usermtabara@mozilla.com
push dateMon, 12 Jun 2017 13:08:53 +0000
treeherdermozilla-beta@2191d7f87e2e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersevilpie, me
bugs1354294
milestone55.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 1354294 - Preserve IsCallable and IsConstructor when nuking wrappers. r=evilpie, a=me
js/src/builtin/TestingFunctions.cpp
js/src/jit-test/tests/proxy/preserve-iscallable-isconstructor.js
js/src/proxy/DeadObjectProxy.cpp
js/src/proxy/DeadObjectProxy.h
js/src/vm/ProxyObject.cpp
--- a/js/src/builtin/TestingFunctions.cpp
+++ b/js/src/builtin/TestingFunctions.cpp
@@ -4300,16 +4300,27 @@ GetErrorNotes(JSContext* cx, unsigned ar
     RootedObject notesArray(cx, CreateErrorNotesArray(cx, report));
     if (!notesArray)
         return false;
 
     args.rval().setObject(*notesArray);
     return true;
 }
 
+static bool
+IsConstructor(JSContext* cx, unsigned argc, Value* vp)
+{
+    CallArgs args = CallArgsFromVp(argc, vp);
+    if (args.length() < 1)
+        args.rval().setBoolean(false);
+    else
+        args.rval().setBoolean(IsConstructor(args[0]));
+    return true;
+}
+
 static const JSFunctionSpecWithHelp TestingFunctions[] = {
     JS_FN_HELP("gc", ::GC, 0, 0,
 "gc([obj] | 'zone' [, 'shrinking'])",
 "  Run the garbage collector. When obj is given, GC only its zone.\n"
 "  If 'zone' is given, GC any zones that were scheduled for\n"
 "  GC via schedulegc.\n"
 "  If 'shrinking' is passed as the optional second argument, perform a\n"
 "  shrinking GC rather than a normal GC."),
@@ -4862,16 +4873,20 @@ gc::ZealModeHelpText),
 "  Call the __AFL_LOOP() runtime function (see AFL docs)\n"),
 #endif
 
     JS_FN_HELP("timeSinceCreation", TimeSinceCreation, 0, 0,
 "TimeSinceCreation()",
 "  Returns the time in milliseconds since process creation.\n"
 "  This uses a clock compatible with the profiler.\n"),
 
+    JS_FN_HELP("isConstructor", IsConstructor, 1, 0,
+"isConstructor(value)",
+"  Returns whether the value is considered IsConstructor.\n"),
+
     JS_FS_HELP_END
 };
 
 static const JSFunctionSpecWithHelp FuzzingUnsafeTestingFunctions[] = {
 #ifdef DEBUG
     JS_FN_HELP("parseRegExp", ParseRegExp, 3, 0,
 "parseRegExp(pattern[, flags[, match_only])",
 "  Parses a RegExp pattern and returns a tree, potentially throwing."),
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/proxy/preserve-iscallable-isconstructor.js
@@ -0,0 +1,17 @@
+load(libdir + "asserts.js");
+
+var global = newGlobal()
+var fun = global.eval("(function() {})")
+var p = new Proxy(fun, {})
+
+// Nuking should preserve IsCallable and IsConstructor.
+assertEq(isConstructor(p), true);
+assertEq(typeof p, "function");
+nukeCCW(fun);
+assertEq(isConstructor(p), true);
+assertEq(typeof p, "function");
+
+// But actually calling and constructing should throw, because it's been
+// nuked.
+assertThrowsInstanceOf(() => { p(); }, TypeError);
+assertThrowsInstanceOf(() => { new p(); }, TypeError);
--- a/js/src/proxy/DeadObjectProxy.cpp
+++ b/js/src/proxy/DeadObjectProxy.cpp
@@ -4,16 +4,17 @@
  * 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 "proxy/DeadObjectProxy.h"
 
 #include "jsapi.h"
 #include "jsfun.h" // XXXefaust Bug 1064662
 
+#include "proxy/ScriptedProxyHandler.h"
 #include "vm/ProxyObject.h"
 
 using namespace js;
 using namespace js::gc;
 
 static void
 ReportDead(JSContext *cx)
 {
@@ -144,16 +145,32 @@ DeadObjectProxy::fun_toString(JSContext*
 bool
 DeadObjectProxy::regexp_toShared(JSContext* cx, HandleObject proxy,
                                  MutableHandle<RegExpShared*> shared) const
 {
     ReportDead(cx);
     return false;
 }
 
+bool
+DeadObjectProxy::isCallable(JSObject* obj) const
+{
+    static const uint32_t slot = ScriptedProxyHandler::IS_CALLCONSTRUCT_EXTRA;
+    uint32_t callConstruct = obj->as<ProxyObject>().extra(slot).toPrivateUint32();
+    return !!(callConstruct & ScriptedProxyHandler::IS_CALLABLE);
+}
+
+bool
+DeadObjectProxy::isConstructor(JSObject* obj) const
+{
+    static const uint32_t slot = ScriptedProxyHandler::IS_CALLCONSTRUCT_EXTRA;
+    uint32_t callConstruct = obj->as<ProxyObject>().extra(slot).toPrivateUint32();
+    return !!(callConstruct & ScriptedProxyHandler::IS_CONSTRUCTOR);
+}
+
 const char DeadObjectProxy::family = 0;
 const DeadObjectProxy DeadObjectProxy::singleton;
 
 bool
 js::IsDeadProxyObject(JSObject* obj)
 {
     return IsDerivedProxyObject(obj, &DeadObjectProxy::singleton);
 }
--- a/js/src/proxy/DeadObjectProxy.h
+++ b/js/src/proxy/DeadObjectProxy.h
@@ -47,16 +47,19 @@ class DeadObjectProxy : public BaseProxy
                              bool* bp) const override;
     virtual bool getBuiltinClass(JSContext* cx, HandleObject proxy, ESClass* cls) const override;
     virtual bool isArray(JSContext* cx, HandleObject proxy, JS::IsArrayAnswer* answer) const override;
     virtual const char* className(JSContext* cx, HandleObject proxy) const override;
     virtual JSString* fun_toString(JSContext* cx, HandleObject proxy, unsigned indent) const override;
     virtual bool regexp_toShared(JSContext* cx, HandleObject proxy,
                                  MutableHandle<RegExpShared*> shared) const override;
 
+    virtual bool isCallable(JSObject* obj) const override;
+    virtual bool isConstructor(JSObject* obj) const override;
+
     static const char family;
     static const DeadObjectProxy singleton;
 };
 
 bool
 IsDeadProxyObject(JSObject* obj);
 
 } /* namespace js */
--- a/js/src/vm/ProxyObject.cpp
+++ b/js/src/vm/ProxyObject.cpp
@@ -4,16 +4,17 @@
  * 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 "vm/ProxyObject.h"
 
 #include "jscompartment.h"
 
 #include "proxy/DeadObjectProxy.h"
+#include "proxy/ScriptedProxyHandler.h"
 
 #include "jsobjinlines.h"
 
 using namespace js;
 
 /* static */ ProxyObject*
 ProxyObject::New(JSContext* cx, const BaseProxyHandler* handler, HandleValue priv, TaggedProto proto_,
                  const ProxyOptions& options)
@@ -109,16 +110,25 @@ ProxyObject::setSameCompartmentPrivate(c
 {
     MOZ_ASSERT(IsObjectValueInCompartment(priv, compartment()));
     *slotOfPrivate() = priv;
 }
 
 void
 ProxyObject::nuke()
 {
+    // When nuking scripted proxies, isCallable and isConstructor values for
+    // the proxy needs to be preserved. Do this before clearing the target.
+    uint32_t callable = handler()->isCallable(this)
+                        ? ScriptedProxyHandler::IS_CALLABLE : 0;
+    uint32_t constructor = handler()->isConstructor(this)
+                           ? ScriptedProxyHandler::IS_CONSTRUCTOR : 0;
+    setExtra(ScriptedProxyHandler::IS_CALLCONSTRUCT_EXTRA,
+             PrivateUint32Value(callable | constructor));
+
     // Clear the target reference.
     setSameCompartmentPrivate(NullValue());
 
     // Update the handler to make this a DeadObjectProxy.
     setHandler(&DeadObjectProxy::singleton);
 
     // The proxy's extra slots are not cleared and will continue to be
     // traced. This avoids the possibility of triggering write barriers while