Bug 950407 - Fix ES6 Proxy forwarding, and apropriately throw on indirect ArrayBuffer __proto__ sets. (r=bholley,Waldo sr=mrbkap)
authorEric Faust <efaustbmo@gmail.com>
Thu, 16 Jan 2014 15:09:50 -0800
changeset 163862 19cb3daa91a754fadda67fa0843ea0cb23a7991f
parent 163861 5a7ba959a5d17c96101908c48d61b1e2e74bd437
child 163863 b97134e8179824adff83e7be1abf5d07a739b244
push id38573
push userefaustbmo@gmail.com
push dateThu, 16 Jan 2014 23:12:16 +0000
treeherdermozilla-inbound@b97134e81798 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbholley, Waldo, mrbkap
bugs950407
milestone29.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 950407 - Fix ES6 Proxy forwarding, and apropriately throw on indirect ArrayBuffer __proto__ sets. (r=bholley,Waldo sr=mrbkap)
js/src/jsobjinlines.h
js/src/jsproxy.cpp
js/src/tests/ecma_6/Proxy/proxy-__proto__.js
js/src/tests/ecma_6/Proxy/regress-bug950407.js
js/src/tests/ecma_6/Proxy/shell.js
js/src/vm/GlobalObject.cpp
--- a/js/src/jsobjinlines.h
+++ b/js/src/jsobjinlines.h
@@ -424,16 +424,36 @@ JSObject::getProto(JSContext *cx, js::Ha
 JSObject::setProto(JSContext *cx, JS::HandleObject obj, JS::HandleObject proto, bool *succeeded)
 {
     /* Proxies live in their own little world. */
     if (obj->getTaggedProto().isLazy()) {
         JS_ASSERT(obj->is<js::ProxyObject>());
         return js::Proxy::setPrototypeOf(cx, obj, proto, succeeded);
     }
 
+    /*
+     * Disallow mutating the [[Prototype]] on ArrayBuffer objects, which
+     * due to their complicated delegate-object shenanigans can't easily
+     * have a mutable [[Prototype]].
+     */
+    if (obj->is<js::ArrayBufferObject>()) {
+        JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_INCOMPATIBLE_PROTO,
+                             "Object", "__proto__ setter", "ArrayBuffer");
+        return false;
+    }
+
+    /*
+     * Explicityly disallow mutating the [[Prototype]] of Location objects
+     * for flash-related security reasons.
+     */
+    if (!strcmp(obj->getClass()->name, "Location")) {
+        JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_SETPROTOTYPEOF_FAIL);
+        return false;
+    }
+
     /* ES6 9.1.2 step 5 forbids changing [[Prototype]] if not [[Extensible]]. */
     bool extensible;
     if (!JSObject::isExtensible(cx, obj, &extensible))
         return false;
     if (!extensible) {
         *succeeded = false;
         return true;
     }
--- a/js/src/jsproxy.cpp
+++ b/js/src/jsproxy.cpp
@@ -3245,25 +3245,22 @@ proxy(JSContext *cx, unsigned argc, jsva
         return false;
     }
     RootedObject target(cx, NonNullObject(cx, args[0]));
     if (!target)
         return false;
     RootedObject handler(cx, NonNullObject(cx, args[1]));
     if (!handler)
         return false;
-    RootedObject proto(cx);
-    if (!JSObject::getProto(cx, target, &proto))
-        return false;
     RootedValue priv(cx, ObjectValue(*target));
     ProxyOptions options;
     options.setCallable(target->isCallable());
     ProxyObject *proxy =
         ProxyObject::New(cx, &ScriptedDirectProxyHandler::singleton,
-                         priv, TaggedProto(proto), cx->global(),
+                         priv, TaggedProto(TaggedProto::LazyProto), cx->global(),
                          options);
     if (!proxy)
         return false;
     proxy->setExtra(0, ObjectOrNullValue(handler));
     vp->setObject(*proxy);
     return true;
 }
 
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_6/Proxy/proxy-__proto__.js
@@ -0,0 +1,59 @@
+/*
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/licenses/publicdomain/
+ */
+
+var gTestfile = 'proxy-__proto__.js';
+var BUGNUMBER = 950407;
+var summary = "Behavior of __proto__ on ES6 proxies";
+
+print(BUGNUMBER + ": " + summary);
+
+/**************
+ * BEGIN TEST *
+ **************/
+
+var protoDesc = Object.getOwnPropertyDescriptor(Object.prototype, "__proto__");
+var protoGetter = protoDesc.get;
+var protoSetter = protoDesc.set;
+
+function testProxy(target, initialProto)
+{
+  print("Now testing behavior for new Proxy(" + ("" + target) + ", {})");
+
+  var pobj = new Proxy(target, {});
+
+  // Check [[Prototype]] before attempted mutation
+  assertEq(Object.getPrototypeOf(pobj), initialProto);
+  assertEq(protoGetter.call(pobj), initialProto);
+
+  // Attempt [[Prototype]] mutation
+  protoSetter.call(pobj, null);
+
+  // Check [[Prototype]] after attempted mutation
+  assertEq(Object.getPrototypeOf(pobj), null);
+  assertEq(protoGetter.call(pobj), null);
+  assertEq(Object.getPrototypeOf(target), null);
+}
+
+// Proxy object with non-null [[Prototype]]
+var nonNullProto = { toString: function() { return "non-null prototype"; } };
+var target = Object.create(nonNullProto);
+testProxy(target, nonNullProto);
+
+// Proxy object with null [[Prototype]]
+target = Object.create(null);
+target.toString = function() { return "null prototype" };
+testProxy(target, null);
+
+// Proxy function with [[Call]]
+var callForCallOnly = function () { };
+callForCallOnly.toString = function() { return "callable target"; };
+testProxy(callForCallOnly, Function.prototype);
+
+/******************************************************************************/
+
+if (typeof reportCompare === "function")
+  reportCompare(true, true);
+
+print("Tests complete");
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_6/Proxy/regress-bug950407.js
@@ -0,0 +1,11 @@
+var ab = ArrayBuffer(5);
+var p = new Proxy(ab, {});
+var ps = Object.getOwnPropertyDescriptor(Object.prototype, "__proto__").set;
+var threw = 0;
+try {
+    ps.call(p, {});
+} catch(ex) {
+    threw = 1;
+}
+
+reportCompare(1, threw, "Setting __proto__ on a proxy to an ArrayBuffer must throw.");
new file mode 100644
--- a/js/src/vm/GlobalObject.cpp
+++ b/js/src/vm/GlobalObject.cpp
@@ -118,41 +118,24 @@ ProtoSetterImpl(JSContext *cx, CallArgs 
         return true;
     }
 
     if (!cx->runningWithTrustedPrincipals())
         ++sSetProtoCalled;
 
     Rooted<JSObject*> obj(cx, &args.thisv().toObject());
 
-    /*
-     * Disallow mutating the [[Prototype]] on ArrayBuffer objects, which
-     * due to their complicated delegate-object shenanigans can't easily
-     * have a mutable [[Prototype]].
-     */
-    if (obj->is<ArrayBufferObject>()) {
-        JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_INCOMPATIBLE_PROTO,
-                             "Object", "__proto__ setter", "ArrayBuffer");
-        return false;
-    }
-
     /* Do nothing if __proto__ isn't being set to an object or null. */
     if (args.length() == 0 || !args[0].isObjectOrNull()) {
         args.rval().setUndefined();
         return true;
     }
 
     Rooted<JSObject*> newProto(cx, args[0].toObjectOrNull());
 
-    unsigned dummy;
-    RootedId nid(cx, NameToId(cx->names().proto));
-    RootedValue v(cx);
-    if (!CheckAccess(cx, obj, nid, JSAccessMode(JSACC_PROTO | JSACC_WRITE), &v, &dummy))
-        return false;
-
     bool success;
     if (!JSObject::setProto(cx, obj, newProto, &success))
         return false;
 
     if (!success) {
         JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_SETPROTOTYPEOF_FAIL);
         return false;
     }