Bug 1105045 - Outerize the receiver passed to proxy hooks. r=efaust.
authorJason Orendorff <jorendorff@mozilla.com>
Fri, 07 Aug 2015 10:23:05 -0500
changeset 257764 dc21224de25bd42bb8ad4728c71988dfdb26ea26
parent 257763 b268c7a67b6256c66efa40fdd7b3a39480183738
child 257765 8f410f4e8f5cc1960eb0812ff7c469dc96a08f9c
push id29226
push userryanvm@gmail.com
push dateFri, 14 Aug 2015 13:01:14 +0000
treeherdermozilla-central@1b2402247429 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersefaust
bugs1105045
milestone43.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 1105045 - Outerize the receiver passed to proxy hooks. r=efaust.
js/src/jsobj.h
js/src/proxy/Proxy.cpp
js/src/tests/ecma_6/Proxy/global-receiver.js
--- a/js/src/jsobj.h
+++ b/js/src/jsobj.h
@@ -999,17 +999,18 @@ GetObjectClassName(JSContext* cx, Handle
  *
  * As a result, we have calls to these three "substitute-this-object-for-that-
  * object" functions sprinkled at apparently arbitrary (but actually *very*
  * carefully and nervously selected) places throughout the engine and indeed
  * the universe.
  */
 
 /*
- * If obj a WindowProxy, return its current inner Window. Otherwise return obj.
+ * If obj is a WindowProxy, return its current inner Window. Otherwise return
+ * obj. This function can't fail and never returns nullptr.
  *
  * GetInnerObject is called when we need a scope chain; you never want a
  * WindowProxy on a scope chain.
  *
  * It's also called in a few places where an object comes in from script, and
  * the user probably intends to operate on the Window, not the
  * WindowProxy. Object.prototype.watch and various Debugger features do
  * this. (Users can't simply pass the Window, because the Window isn't exposed
@@ -1022,16 +1023,17 @@ GetInnerObject(JSObject* obj)
         JS::AutoSuppressGCAnalysis nogc;
         return op(obj);
     }
     return obj;
 }
 
 /*
  * If obj is a Window object, return the WindowProxy. Otherwise return obj.
+ * This function can't fail; it never sets an exception or returns nullptr.
  *
  * This must be called before passing an object to script, if the object might
  * be a Window. (But usually those cases involve scope objects, and for those,
  * it is better to call GetThisObject instead.)
  */
 inline JSObject*
 GetOuterObject(JSContext* cx, HandleObject obj)
 {
--- a/js/src/proxy/Proxy.cpp
+++ b/js/src/proxy/Proxy.cpp
@@ -255,27 +255,41 @@ Proxy::hasOwn(JSContext* cx, HandleObjec
     const BaseProxyHandler* handler = proxy->as<ProxyObject>().handler();
     *bp = false; // default result if we refuse to perform this action
     AutoEnterPolicy policy(cx, handler, proxy, id, BaseProxyHandler::GET, true);
     if (!policy.allowed())
         return policy.returnValue();
     return handler->hasOwn(cx, proxy, id, bp);
 }
 
+static Value
+OuterizeValue(JSContext* cx, HandleValue v)
+{
+    if (v.isObject()) {
+        RootedObject obj(cx, &v.toObject());
+        return ObjectValue(*GetOuterObject(cx, obj));
+    }
+    return v;
+}
+
 bool
-Proxy::get(JSContext* cx, HandleObject proxy, HandleObject receiver, HandleId id,
+Proxy::get(JSContext* cx, HandleObject proxy, HandleObject receiver_, HandleId id,
            MutableHandleValue vp)
 {
     JS_CHECK_RECURSION(cx, return false);
     const BaseProxyHandler* handler = proxy->as<ProxyObject>().handler();
     vp.setUndefined(); // default result if we refuse to perform this action
     AutoEnterPolicy policy(cx, handler, proxy, id, BaseProxyHandler::GET, true);
     if (!policy.allowed())
         return policy.returnValue();
 
+    // Outerize the receiver. Proxy handlers shouldn't have to know about
+    // the Window/WindowProxy distinction.
+    RootedObject receiver(cx, GetOuterObject(cx, receiver_));
+
     if (handler->hasPrototype()) {
         bool own;
         if (!handler->hasOwn(cx, proxy, id, &own))
             return false;
         if (!own) {
             RootedObject proto(cx);
             if (!GetPrototype(cx, proxy, &proto))
                 return false;
@@ -303,28 +317,32 @@ Proxy::callProp(JSContext* cx, HandleObj
             return false;
     }
 #endif
 
     return true;
 }
 
 bool
-Proxy::set(JSContext* cx, HandleObject proxy, HandleId id, HandleValue v, HandleValue receiver,
+Proxy::set(JSContext* cx, HandleObject proxy, HandleId id, HandleValue v, HandleValue receiver_,
            ObjectOpResult& result)
 {
     JS_CHECK_RECURSION(cx, return false);
     const BaseProxyHandler* handler = proxy->as<ProxyObject>().handler();
     AutoEnterPolicy policy(cx, handler, proxy, id, BaseProxyHandler::SET, true);
     if (!policy.allowed()) {
         if (!policy.returnValue())
             return false;
         return result.succeed();
     }
 
+    // Outerize the receiver. Proxy handlers shouldn't have to know about
+    // the Window/WindowProxy distinction.
+    RootedValue receiver(cx, OuterizeValue(cx, receiver_));
+
     // Special case. See the comment on BaseProxyHandler::mHasPrototype.
     if (handler->hasPrototype())
         return handler->BaseProxyHandler::set(cx, proxy, id, v, receiver, result);
 
     return handler->set(cx, proxy, id, v, receiver, result);
 }
 
 bool
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_6/Proxy/global-receiver.js
@@ -0,0 +1,29 @@
+// The global object can be the receiver passed to the get and set traps of a Proxy.
+
+var global = this;
+var proto = Object.getPrototypeOf(global);
+var gets = 0, sets = 0;
+Object.setPrototypeOf(global, new Proxy(proto, {
+    has(t, id) {
+        return id === "bareword" || Reflect.has(t, id);
+    },
+    get(t, id, r) {
+        gets++;
+        assertEq(r, global);
+        return Reflect.get(t, id, r);
+    },
+    set(t, id, v, r) {
+        sets++;
+        assertEq(r, global);
+        return Reflect.set(t, id, v, r);
+    }
+}));
+
+assertEq(bareword, undefined);
+assertEq(gets, 1);
+
+bareword = 12;
+assertEq(sets, 1);
+assertEq(global.bareword, 12);
+
+reportCompare(0, 0);