Bug 1065811 - Track Xray waivers with CPOWs. r=billm
authorBobby Holley <bobbyholley@gmail.com>
Thu, 25 Sep 2014 13:13:29 +0200
changeset 230468 4d94f2bf456a1a941851bd8ee0e50a52dffd61c1
parent 230467 bc71142337a34496ae657fcade46e04c1214b71f
child 230469 1926709eaf906f6667979a2830d748807e466878
push id4187
push userbhearsum@mozilla.com
push dateFri, 28 Nov 2014 15:29:12 +0000
treeherdermozilla-beta@f23cc6a30c11 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbillm
bugs1065811
milestone35.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 1065811 - Track Xray waivers with CPOWs. r=billm
content/base/test/chrome/cpows_parent.xul
js/ipc/JavaScriptChild.cpp
js/ipc/JavaScriptLogging.h
js/ipc/JavaScriptParent.cpp
js/ipc/JavaScriptShared.cpp
js/ipc/JavaScriptShared.h
js/ipc/WrapperAnswer.cpp
js/ipc/WrapperOwner.cpp
--- a/content/base/test/chrome/cpows_parent.xul
+++ b/content/base/test/chrome/cpows_parent.xul
@@ -218,17 +218,17 @@
       let cpowLocation = Cu.getCompartmentLocation(getUnprivilegedObject);
       ok(/Privileged Junk/.test(cpowLocation),
          "parent->child CPOWs should live in the privileged junk scope: " + cpowLocation);
 
       // Make sure that parent->child CPOWs point through a privileged scope in the child
       // (the privileged junk scope, but we don't have a good way to test for that
       // specifically).
       is(unprivilegedObject.expando, undefined, "parent->child references should get Xrays");
-      todo_is(unprivilegedObject.wrappedJSObject.expando, 42, "parent->child references should get waivable Xrays - see bug 1065811");
+      is(unprivilegedObject.wrappedJSObject.expando, 42, "parent->child references should get waivable Xrays");
 
       // Send an object to the child to let it verify invariants in the other direction.
       function passMe() { return 42; };
       passMe.expando = 42;
       let results = testParentObject(passMe);
       ok(results.length > 0, "Need results");
       results.forEach((x) => is(x.result, "PASS", x.message));
     }
--- a/js/ipc/JavaScriptChild.cpp
+++ b/js/ipc/JavaScriptChild.cpp
@@ -48,17 +48,18 @@ JavaScriptChild::init()
     JS_AddWeakPointerCallback(rt_, UpdateChildWeakPointersAfterGC, this);
     return true;
 }
 
 void
 JavaScriptChild::updateWeakPointers()
 {
     objects_.sweep();
-    objectIds_.sweep();
+    unwaivedObjectIds_.sweep();
+    waivedObjectIds_.sweep();
 }
 
 JSObject *
 JavaScriptChild::scopeForTargetObjects()
 {
     // CPOWs from the parent need to point into the child's privileged junk
     // scope so that they can benefit from XrayWrappers in the child.
     return xpc::PrivilegedJunkScope();
--- a/js/ipc/JavaScriptLogging.h
+++ b/js/ipc/JavaScriptLogging.h
@@ -91,17 +91,17 @@ class Logging
         out = NS_ConvertUTF16toUTF8(str);
     }
 
     void formatObject(bool incoming, bool local, ObjectId id, nsCString &out) {
         const char *side, *objDesc;
 
         if (local == incoming) {
             JS::RootedObject obj(cx);
-            obj = shared->findObjectById(id);
+            obj = shared->objects_.find(id);
             if (obj) {
                 JSAutoCompartment ac(cx, obj);
                 objDesc = js_ObjectClassName(cx, obj);
             } else {
                 objDesc = "<dead object>";
             }
 
             side = shared->isParent() ? "parent" : "child";
--- a/js/ipc/JavaScriptParent.cpp
+++ b/js/ipc/JavaScriptParent.cpp
@@ -48,17 +48,18 @@ JavaScriptParent::init()
     return true;
 }
 
 void
 JavaScriptParent::trace(JSTracer *trc)
 {
     if (active()) {
         objects_.trace(trc);
-        objectIds_.trace(trc);
+        unwaivedObjectIds_.trace(trc);
+        waivedObjectIds_.trace(trc);
     }
 }
 
 JSObject *
 JavaScriptParent::scopeForTargetObjects()
 {
     // CPWOWs from the child need to point into the parent's unprivileged junk
     // scope so that a compromised child cannot compromise the parent. In
--- a/js/ipc/JavaScriptShared.cpp
+++ b/js/ipc/JavaScriptShared.cpp
@@ -5,16 +5,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 "JavaScriptShared.h"
 #include "mozilla/dom/BindingUtils.h"
 #include "mozilla/dom/TabChild.h"
 #include "jsfriendapi.h"
 #include "xpcprivate.h"
+#include "WrapperFactory.h"
 #include "mozilla/Preferences.h"
 
 using namespace js;
 using namespace JS;
 using namespace mozilla;
 using namespace mozilla::jsipc;
 
 IdToObjectMap::IdToObjectMap()
@@ -181,17 +182,19 @@ JavaScriptShared::JavaScriptShared(JSRun
 
 bool
 JavaScriptShared::init()
 {
     if (!objects_.init())
         return false;
     if (!cpows_.init())
         return false;
-    if (!objectIds_.init())
+    if (!unwaivedObjectIds_.init())
+        return false;
+    if (!waivedObjectIds_.init())
         return false;
 
     return true;
 }
 
 void
 JavaScriptShared::decref()
 {
@@ -378,29 +381,34 @@ JavaScriptShared::ConvertID(const JSIID 
     to->m3[5] = from.m3_5();
     to->m3[6] = from.m3_6();
     to->m3[7] = from.m3_7();
 }
 
 JSObject *
 JavaScriptShared::findObjectById(JSContext *cx, const ObjectId &objId)
 {
-    RootedObject obj(cx, findObjectById(objId));
+    RootedObject obj(cx, objects_.find(objId));
     if (!obj) {
         JS_ReportError(cx, "operation not possible on dead CPOW");
         return nullptr;
     }
 
     // Each process has a dedicated compartment for CPOW targets. All CPOWs
     // from the other process point to objects in this scope. From there, they
     // can access objects in other compartments using cross-compartment
     // wrappers.
     JSAutoCompartment ac(cx, scopeForTargetObjects());
-    if (!JS_WrapObject(cx, &obj))
-        return nullptr;
+    if (objId.hasXrayWaiver()) {
+        if (!xpc::WrapperFactory::WaiveXrayAndWrap(cx, &obj))
+            return nullptr;
+    } else {
+        if (!JS_WrapObject(cx, &obj))
+            return nullptr;
+    }
     return obj;
 }
 
 static const uint64_t DefaultPropertyOp = 1;
 static const uint64_t UnknownPropertyOp = 2;
 
 bool
 JavaScriptShared::fromDescriptor(JSContext *cx, Handle<JSPropertyDescriptor> desc,
--- a/js/ipc/JavaScriptShared.h
+++ b/js/ipc/JavaScriptShared.h
@@ -19,48 +19,48 @@ namespace jsipc {
 class ObjectId {
   public:
     // Use 47 bits at most, to be safe, since jsval privates are encoded as
     // doubles. See bug 1065811 comment 12 for an explanation.
     static const size_t SERIAL_NUMBER_BITS = 47;
     static const size_t FLAG_BITS = 1;
     static const uint64_t SERIAL_NUMBER_MAX = (uint64_t(1) << SERIAL_NUMBER_BITS) - 1;
 
-    explicit ObjectId(uint64_t serialNumber, bool isCallable)
-      : serialNumber_(serialNumber), isCallable_(isCallable)
+    explicit ObjectId(uint64_t serialNumber, bool hasXrayWaiver)
+      : serialNumber_(serialNumber), hasXrayWaiver_(hasXrayWaiver)
     {
         if (MOZ_UNLIKELY(serialNumber == 0 || serialNumber > SERIAL_NUMBER_MAX))
             MOZ_CRASH("Bad CPOW Id");
     }
 
     bool operator==(const ObjectId &other) const {
         bool equal = serialNumber() == other.serialNumber();
-        MOZ_ASSERT_IF(equal, isCallable() == other.isCallable());
+        MOZ_ASSERT_IF(equal, hasXrayWaiver() == other.hasXrayWaiver());
         return equal;
     }
 
     bool isNull() { return !serialNumber_; }
 
     uint64_t serialNumber() const { return serialNumber_; }
-    bool isCallable() const { return isCallable_; }
+    bool hasXrayWaiver() const { return hasXrayWaiver_; }
     uint64_t serialize() const {
         MOZ_ASSERT(serialNumber(), "Don't send a null ObjectId over IPC");
-        return uint64_t((serialNumber() << FLAG_BITS) | ((isCallable() ? 1 : 0) << 0));
+        return uint64_t((serialNumber() << FLAG_BITS) | ((hasXrayWaiver() ? 1 : 0) << 0));
     }
 
     static ObjectId nullId() { return ObjectId(); }
     static ObjectId deserialize(uint64_t data) {
         return ObjectId(data >> FLAG_BITS, data & 1);
     }
 
   private:
-    ObjectId() : serialNumber_(0), isCallable_(false) {}
+    ObjectId() : serialNumber_(0), hasXrayWaiver_(false) {}
 
     uint64_t serialNumber_ : SERIAL_NUMBER_BITS;
-    bool isCallable_ : 1;
+    bool hasXrayWaiver_ : 1;
 };
 
 class JavaScriptShared;
 
 class CpowIdHolder : public CpowHolder
 {
   public:
     CpowIdHolder(JavaScriptShared *js, const InfallibleTArray<CpowEntry> &cpows)
@@ -169,19 +169,16 @@ class JavaScriptShared
     virtual JSObject *fromObjectVariant(JSContext *cx, ObjectVariant objVar) = 0;
 
     static void ConvertID(const nsID &from, JSIID *to);
     static void ConvertID(const JSIID &from, nsID *to);
 
     JSObject *findCPOWById(const ObjectId &objId) {
         return cpows_.find(objId);
     }
-    JSObject *findObjectById(const ObjectId &objId) {
-        return objects_.find(objId);
-    }
     JSObject *findObjectById(JSContext *cx, const ObjectId &objId);
 
     static bool LoggingEnabled() { return sLoggingEnabled; }
     static bool StackLoggingEnabled() { return sStackLoggingEnabled; }
 
     friend class Logging;
 
     virtual bool isParent() = 0;
@@ -191,17 +188,37 @@ class JavaScriptShared
   protected:
     JSRuntime *rt_;
     uintptr_t refcount_;
 
     IdToObjectMap objects_;
     IdToObjectMap cpows_;
 
     uint64_t nextSerialNumber_;
-    ObjectToIdMap objectIds_;
+
+    // CPOW references can be weak, and any object we store in a map may be
+    // GCed (at which point the CPOW will report itself "dead" to the owner).
+    // This means that we don't want to store any js::Wrappers in the CPOW map,
+    // because CPOW will die if the wrapper is GCed, even if the underlying
+    // object is still alive.
+    //
+    // This presents a tricky situation for Xray waivers, since they're normally
+    // represented as a special same-compartment wrapper. We have to strip them
+    // off before putting them in the id-to-object and object-to-id maps, so we
+    // need a way of distinguishing them at lookup-time.
+    //
+    // For the id-to-object map, we encode waiver-or-not information into the id
+    // itself, which lets us do the right thing when accessing the object.
+    //
+    // For the object-to-id map, we just keep two maps, one for each type.
+    ObjectToIdMap unwaivedObjectIds_;
+    ObjectToIdMap waivedObjectIds_;
+    ObjectToIdMap &objectIdMap(bool waiver) {
+        return waiver ? waivedObjectIds_ : unwaivedObjectIds_;
+    }
 
     static bool sLoggingInitialized;
     static bool sLoggingEnabled;
     static bool sStackLoggingEnabled;
 };
 
 } // namespace jsipc
 } // namespace mozilla
--- a/js/ipc/WrapperAnswer.cpp
+++ b/js/ipc/WrapperAnswer.cpp
@@ -674,15 +674,15 @@ WrapperAnswer::AnswerIsConstructor(const
     *result = JS::IsConstructor(obj);
     return true;
 }
 
 
 bool
 WrapperAnswer::RecvDropObject(const ObjectId &objId)
 {
-    JSObject *obj = findObjectById(objId);
+    JSObject *obj = objects_.find(objId);
     if (obj) {
-        objectIds_.remove(obj);
+        objectIdMap(objId.hasXrayWaiver()).remove(obj);
         objects_.remove(objId);
     }
     return true;
 }
--- a/js/ipc/WrapperOwner.cpp
+++ b/js/ipc/WrapperOwner.cpp
@@ -6,16 +6,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "WrapperOwner.h"
 #include "JavaScriptLogging.h"
 #include "mozilla/unused.h"
 #include "mozilla/dom/BindingUtils.h"
 #include "jsfriendapi.h"
 #include "xpcprivate.h"
+#include "WrapperFactory.h"
 
 using namespace js;
 using namespace JS;
 using namespace mozilla;
 using namespace mozilla::jsipc;
 
 WrapperOwner::WrapperOwner(JSRuntime *rt)
   : JavaScriptShared(rt),
@@ -865,37 +866,40 @@ WrapperOwner::toObjectVariant(JSContext 
 {
     RootedObject obj(cx, objArg);
     JS_ASSERT(obj);
 
     // We always save objects unwrapped in the CPOW table. If we stored
     // wrappers, then the wrapper might be GCed while the target remained alive.
     // Whenever operating on an object that comes from the table, we wrap it
     // in findObjectById.
-    obj = js::UncheckedUnwrap(obj, false);
+    unsigned wrapperFlags = 0;
+    obj = js::UncheckedUnwrap(obj, false, &wrapperFlags);
     if (obj && IsCPOW(obj) && OwnerOf(obj) == this) {
         *objVarp = LocalObject(idOf(obj).serialize());
         return true;
     }
+    bool waiveXray = wrapperFlags & xpc::WrapperFactory::WAIVE_XRAY_WRAPPER_FLAG;
 
-    ObjectId id = objectIds_.find(obj);
+    ObjectId id = objectIdMap(waiveXray).find(obj);
     if (!id.isNull()) {
+        MOZ_ASSERT(id.hasXrayWaiver() == waiveXray);
         *objVarp = RemoteObject(id.serialize());
         return true;
     }
 
     // Need to call PreserveWrapper on |obj| in case it's a reflector.
     // FIXME: What if it's an XPCWrappedNative?
     if (mozilla::dom::IsDOMObject(obj))
         mozilla::dom::TryPreserveWrapper(obj);
 
-    id = ObjectId(nextSerialNumber_++, JS::IsCallable(obj));
+    id = ObjectId(nextSerialNumber_++, waiveXray);
     if (!objects_.add(id, obj))
         return false;
-    if (!objectIds_.add(cx, obj, id))
+    if (!objectIdMap(waiveXray).add(cx, obj, id))
         return false;
 
     *objVarp = RemoteObject(id.serialize());
     return true;
 }
 
 JSObject *
 WrapperOwner::fromObjectVariant(JSContext *cx, ObjectVariant objVar)