Bug 787856 - Use lazy protos for cross-compartment wrappers (r=bholley)
authorBill McCloskey <wmccloskey@mozilla.com>
Mon, 03 Sep 2012 16:42:22 -0700
changeset 108265 7228effb2e5b55055f7d8597a86d42857a270beb
parent 108264 fd398d69d052954dc376c64f1e17dbbc05579037
child 108266 ff22c54142371c4e34f06d975df7f4494072f448
push id82
push usershu@rfrn.org
push dateFri, 05 Oct 2012 13:20:22 +0000
reviewersbholley
bugs787856
milestone18.0a1
Bug 787856 - Use lazy protos for cross-compartment wrappers (r=bholley)
js/src/jsapi.cpp
js/src/jscompartment.cpp
js/src/jswrapper.cpp
js/src/jswrapper.h
js/xpconnect/wrappers/WrapperFactory.cpp
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -1571,17 +1571,17 @@ JS_TransplantObject(JSContext *cx, JSObj
         // There might already be a wrapper for the original object in
         // the new compartment. If there is, we use its identity and swap
         // in the contents of |target|.
         newIdentity = &p->value.toObject();
 
         // When we remove origv from the wrapper map, its wrapper, newIdentity,
         // must immediately cease to be a cross-compartment wrapper. Neuter it.
         map.remove(p);
-        NukeCrossCompartmentWrapper(newIdentity);
+        NukeCrossCompartmentWrapper(cx, newIdentity);
 
         if (!newIdentity->swap(cx, target))
             return NULL;
     } else {
         // Otherwise, we use |target| for the new identity object.
         newIdentity = target;
     }
 
@@ -1644,17 +1644,17 @@ js_TransplantObjectWithWrapper(JSContext
     if (WrapperMap::Ptr p = map.lookup(origv)) {
         // There is. Make the existing cross-compartment wrapper a same-
         // compartment wrapper.
         newWrapper = &p->value.toObject();
 
         // When we remove origv from the wrapper map, its wrapper, newWrapper,
         // must immediately cease to be a cross-compartment wrapper. Neuter it.
         map.remove(p);
-        NukeCrossCompartmentWrapper(newWrapper);
+        NukeCrossCompartmentWrapper(cx, newWrapper);
 
         if (!newWrapper->swap(cx, targetwrapper))
             return NULL;
     } else {
         // Otherwise, use the passed-in wrapper as the same-compartment wrapper.
         newWrapper = targetwrapper;
     }
 
--- a/js/src/jscompartment.cpp
+++ b/js/src/jscompartment.cpp
@@ -284,29 +284,17 @@ JSCompartment::wrap(JSContext *cx, Value
         if (!wrapped)
             return false;
         vp->setString(wrapped);
         return crossCompartmentWrappers.put(orig, *vp);
     }
 
     RootedObject obj(cx, &vp->toObject());
 
-    /*
-     * Recurse to wrap the prototype. Long prototype chains will run out of
-     * stack, causing an error in CHECK_RECURSE.
-     *
-     * Wrapping the proto before creating the new wrapper and adding it to the
-     * cache helps avoid leaving a bad entry in the cache on OOM. But note that
-     * if we wrapped both proto and parent, we would get infinite recursion
-     * here (since Object.prototype->parent->proto leads to Object.prototype
-     * itself).
-     */
-    RootedObject proto(cx, obj->getTaggedProto().raw());
-    if (!wrap(cx, proto.address()))
-        return false;
+    JSObject *proto = Proxy::LazyProto;
 
     /*
      * We hand in the original wrapped object into the wrap hook to allow
      * the wrap hook to reason over what wrappers are currently applied
      * to the object.
      */
     RootedObject wrapper(cx, cx->runtime->wrapObjectCallback(cx, obj, proto, global, flags));
     if (!wrapper)
--- a/js/src/jswrapper.cpp
+++ b/js/src/jswrapper.cpp
@@ -835,16 +835,43 @@ bool
 CrossCompartmentWrapper::iteratorNext(JSContext *cx, JSObject *wrapper, Value *vp)
 {
     PIERCE(cx, wrapper, GET,
            NOTHING,
            IndirectProxyHandler::iteratorNext(cx, wrapper, vp),
            cx->compartment->wrap(cx, vp));
 }
 
+bool
+CrossCompartmentWrapper::getPrototypeOf(JSContext *cx, JSObject *proxy, JSObject **protop)
+{
+    assertSameCompartment(cx, proxy);
+
+    if (!proxy->getTaggedProto().isLazy()) {
+        *protop = proxy->getTaggedProto().toObjectOrNull();
+        return true;
+    }
+
+    RootedObject proto(cx);
+    {
+        RootedObject wrapped(cx, wrappedObject(proxy));
+        AutoCompartment call(cx, wrapped);
+        if (!JSObject::getProto(cx, wrapped, &proto))
+            return false;
+        if (proto)
+            proto->setDelegate(cx);
+    }
+
+    if (!proxy->compartment()->wrap(cx, proto.address()))
+        return false;
+
+    *protop = proto;
+    return true;
+}
+
 CrossCompartmentWrapper CrossCompartmentWrapper::singleton(0u);
 
 /* Security wrappers. */
 
 template <class Base>
 SecurityWrapper<Base>::SecurityWrapper(unsigned flags)
   : Base(flags)
 {}
@@ -1012,30 +1039,37 @@ DeadObjectProxy::iteratorNext(JSContext 
 bool
 DeadObjectProxy::getElementIfPresent(JSContext *cx, JSObject *obj, JSObject *receiver,
                                      uint32_t index, Value *vp, bool *present)
 {
     JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_DEAD_OBJECT);
     return false;
 }
 
+bool
+DeadObjectProxy::getPrototypeOf(JSContext *cx, JSObject *proxy, JSObject **protop)
+{
+    *protop = NULL;
+    return true;
+}
+
 DeadObjectProxy DeadObjectProxy::singleton;
 int DeadObjectProxy::sDeadObjectFamily;
 
 } // namespace js
 
 JSObject *
 js::NewDeadProxyObject(JSContext *cx, JSObject *parent)
 {
     return NewProxyObject(cx, &DeadObjectProxy::singleton, NullValue(),
                           NULL, parent, NULL, NULL);
 }
 
 void
-js::NukeCrossCompartmentWrapper(JSObject *wrapper)
+js::NukeCrossCompartmentWrapper(JSContext *cx, JSObject *wrapper)
 {
     JS_ASSERT(IsCrossCompartmentWrapper(wrapper));
 
     SetProxyPrivate(wrapper, NullValue());
     SetProxyHandler(wrapper, &DeadObjectProxy::singleton);
 
     if (IsFunctionProxy(wrapper)) {
         wrapper->setReservedSlot(JSSLOT_PROXY_CALL, NullValue());
@@ -1084,17 +1118,17 @@ js::NukeCrossCompartmentWrappers(JSConte
 
             if (nukeReferencesToWindow == DontNukeWindowReferences &&
                 wrapped->getClass()->ext.innerObject)
                 continue;
 
             if (targetFilter.match(wrapped->compartment())) {
                 // We found a wrapper to nuke.
                 e.removeFront();
-                NukeCrossCompartmentWrapper(wobj);
+                NukeCrossCompartmentWrapper(cx, wobj);
             }
         }
     }
 
     return JS_TRUE;
 }
 
 // Given a cross-compartment wrapper |wobj|, update it to point to
@@ -1118,17 +1152,17 @@ js::RemapWrapper(JSContext *cx, JSObject
 
     // The old value should still be in the cross-compartment wrapper map, and
     // the lookup should return wobj.
     JS_ASSERT(&pmap.lookup(origv)->value.toObject() == wobj);
     pmap.remove(origv);
 
     // When we remove origv from the wrapper map, its wrapper, wobj, must
     // immediately cease to be a cross-compartment wrapper. Neuter it.
-    NukeCrossCompartmentWrapper(wobj);
+    NukeCrossCompartmentWrapper(cx, wobj);
 
     // First, we wrap it in the new compartment. This will return
     // a new wrapper.
     JSObject *tobj = newTarget;
     AutoCompartment ac(cx, wobj);
     if (!wcompartment->wrap(cx, &tobj))
         return false;
 
--- a/js/src/jswrapper.h
+++ b/js/src/jswrapper.h
@@ -254,16 +254,17 @@ class JS_FRIEND_API(CrossCompartmentWrap
     virtual bool nativeCall(JSContext *cx, IsAcceptableThis test, NativeImpl impl,
                             CallArgs args) MOZ_OVERRIDE;
     virtual bool hasInstance(JSContext *cx, HandleObject wrapper, MutableHandleValue v, bool *bp) MOZ_OVERRIDE;
     virtual JSString *obj_toString(JSContext *cx, JSObject *wrapper) MOZ_OVERRIDE;
     virtual JSString *fun_toString(JSContext *cx, JSObject *wrapper, unsigned indent) MOZ_OVERRIDE;
     virtual bool regexp_toShared(JSContext *cx, JSObject *proxy, RegExpGuard *g) MOZ_OVERRIDE;
     virtual bool defaultValue(JSContext *cx, JSObject *wrapper, JSType hint, Value *vp) MOZ_OVERRIDE;
     virtual bool iteratorNext(JSContext *cx, JSObject *wrapper, Value *vp);
+    virtual bool getPrototypeOf(JSContext *cx, JSObject *proxy, JSObject **protop);
 
     static CrossCompartmentWrapper singleton;
     static CrossCompartmentWrapper singletonWithPrototype;
 };
 
 /*
  * Base class for security wrappers. A security wrapper is potentially hiding
  * all or part of some wrapped object thus SecurityWrapper defaults to denying
@@ -315,17 +316,17 @@ class JS_FRIEND_API(DeadObjectProxy) : p
     virtual bool objectClassIs(JSObject *obj, ESClassValue classValue, JSContext *cx);
     virtual JSString *obj_toString(JSContext *cx, JSObject *proxy);
     virtual JSString *fun_toString(JSContext *cx, JSObject *proxy, unsigned indent);
     virtual bool regexp_toShared(JSContext *cx, JSObject *proxy, RegExpGuard *g);
     virtual bool defaultValue(JSContext *cx, JSObject *obj, JSType hint, Value *vp);
     virtual bool iteratorNext(JSContext *cx, JSObject *proxy, Value *vp);
     virtual bool getElementIfPresent(JSContext *cx, JSObject *obj, JSObject *receiver,
                                      uint32_t index, Value *vp, bool *present);
-
+    virtual bool getPrototypeOf(JSContext *cx, JSObject *proxy, JSObject **protop);
 
     static DeadObjectProxy singleton;
 };
 
 extern JSObject *
 TransparentObjectWrapper(JSContext *cx, JSObject *obj, JSObject *wrappedProto, JSObject *parent,
                          unsigned flags);
 
@@ -360,17 +361,17 @@ UnwrapOneChecked(JSContext *cx, HandleOb
 
 JS_FRIEND_API(bool)
 IsCrossCompartmentWrapper(RawObject obj);
 
 JSObject *
 NewDeadProxyObject(JSContext *cx, JSObject *parent);
 
 void
-NukeCrossCompartmentWrapper(JSObject *wrapper);
+NukeCrossCompartmentWrapper(JSContext *cx, JSObject *wrapper);
 
 bool
 RemapWrapper(JSContext *cx, JSObject *wobj, JSObject *newTarget);
 
 JS_FRIEND_API(bool)
 RemapAllWrappersForObject(JSContext *cx, JSObject *oldTarget,
                           JSObject *newTarget);
 
--- a/js/xpconnect/wrappers/WrapperFactory.cpp
+++ b/js/xpconnect/wrappers/WrapperFactory.cpp
@@ -399,21 +399,27 @@ WrapperFactory::Rewrap(JSContext *cx, JS
             // If the prototype chain of chrome object |obj| looks like this:
             //
             // obj => foo => bar => chromeWin.StandardClass.prototype
             //
             // The prototype chain of COW(obj) looks lke this:
             //
             // COW(obj) => COW(foo) => COW(bar) => contentWin.StandardClass.prototype
             JSProtoKey key = JSProto_Null;
-            JSObject *unwrappedProto = NULL;
-            if (wrappedProto && IsCrossCompartmentWrapper(wrappedProto) &&
-                (unwrappedProto = Wrapper::wrappedObject(wrappedProto))) {
-                JSAutoCompartment ac(cx, unwrappedProto);
-                key = JS_IdentifyClassPrototype(cx, unwrappedProto);
+            {
+                JSAutoCompartment ac(cx, obj);
+                JSObject *unwrappedProto;
+                if (!js::GetObjectProto(cx, obj, &unwrappedProto))
+                    return NULL;
+                if (unwrappedProto && IsCrossCompartmentWrapper(unwrappedProto))
+                    unwrappedProto = Wrapper::wrappedObject(unwrappedProto);
+                if (unwrappedProto) {
+                    JSAutoCompartment ac2(cx, unwrappedProto);
+                    key = JS_IdentifyClassPrototype(cx, unwrappedProto);
+                }
             }
             if (key != JSProto_Null) {
                 JSObject *homeProto;
                 if (!JS_GetClassPrototype(cx, key, &homeProto))
                     return NULL;
                 MOZ_ASSERT(homeProto);
                 proxyProto = homeProto;
             }