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 108156 7228effb2e5b55055f7d8597a86d42857a270beb
parent 108155 fd398d69d052954dc376c64f1e17dbbc05579037
child 108157 ff22c54142371c4e34f06d975df7f4494072f448
push id23539
push userryanvm@gmail.com
push dateWed, 26 Sep 2012 22:55:55 +0000
treeherderautoland@ec079fd92224 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbholley
bugs787856
milestone18.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 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;
             }