Bug 1354294 - Preserve IsCallable and IsConstructor when nuking wrappers. r=evilpie, a=lizzard
authorShu-yu Guo <shu@rfrn.org>
Thu, 13 Apr 2017 10:07:26 -0700
changeset 395857 21484939216b657bf6f40de9d8d54a49b42df220
parent 395856 812a8c51cc980b352c9d5d62f2cca25c8b9f18f9
child 395858 189ed7b2306fcf0a7683df6959b78fdb94bb34e7
push id1468
push userasasaki@mozilla.com
push dateMon, 05 Jun 2017 19:31:07 +0000
treeherdermozilla-release@0641fc6ee9d1 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersevilpie, lizzard
bugs1354294
milestone54.0a2
Bug 1354294 - Preserve IsCallable and IsConstructor when nuking wrappers. r=evilpie, a=lizzard
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
toolkit/components/extensions/test/mochitest/test_ext_contentscript_create_iframe.html
--- a/js/src/builtin/TestingFunctions.cpp
+++ b/js/src/builtin/TestingFunctions.cpp
@@ -4258,16 +4258,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."),
@@ -4811,16 +4822,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)
 {
@@ -143,16 +144,32 @@ DeadObjectProxy::fun_toString(JSContext*
 
 bool
 DeadObjectProxy::regexp_toShared(JSContext* cx, HandleObject proxy, RegExpGuard* g) 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
@@ -46,16 +46,19 @@ class DeadObjectProxy : public BaseProxy
     virtual bool hasInstance(JSContext* cx, HandleObject proxy, MutableHandleValue v,
                              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, RegExpGuard* g) 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
--- a/toolkit/components/extensions/test/mochitest/test_ext_contentscript_create_iframe.html
+++ b/toolkit/components/extensions/test/mochitest/test_ext_contentscript_create_iframe.html
@@ -143,22 +143,22 @@ add_task(function* test_contentscript_cr
     manifest = ww.browser.runtime.getManifest();
   } catch (e) {
     manifestException = e;
   }
 
   ok(!manifest, "manifest should be undefined");
 
   is(String(manifestException), "TypeError: ww.browser.runtime is undefined",
-     "expected \"TypeError: ... is undefined\" exception received");
+     "expected exception received");
 
   let getManifestException = ww.testGetManifestException();
 
-  is(getManifestException, "TypeError: window.GET_MANIFEST is not a function",
-     "expected \"TypeError: ... is not a function\" exception received");
+  is(getManifestException, "TypeError: can't access dead object",
+     "expected exception received");
 
   win.close();
 
   info("done");
 });
 </script>
 
 </body>