Bug 1065811 - Clean up ObjectId handling with static type checking. r=billm
authorBobby Holley <bobbyholley@gmail.com>
Thu, 25 Sep 2014 13:13:29 +0200
changeset 230467 bc71142337a34496ae657fcade46e04c1214b71f
parent 230466 8fb1ab37e3e08e8565c722f73853f599e3b033e2
child 230468 4d94f2bf456a1a941851bd8ee0e50a52dffd61c1
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 - Clean up ObjectId handling with static type checking. r=billm While adding the CPOW flag for xray waivers, I discovered a bunch of inconsistency and sloppiness with respect to our handling of object ids, and a general lack of clarity about when the id included flags or not. Given the fact that I'm removing static callability for CPOWs, we _could_ just get rid of the flags, and store the xray waiver state on the answer-side only. But I eventually decided that these kinds of flags (which are accessible to both the Answer _and_ the Owner) had enough potential utility that they were worth cleaning up. It's worth noting that that utility comes with the large caveat that the flags can't be trusted for security-sensitive decisions (at least in the parent->child case), since they could be forged by a compromised child.
js/ipc/JavaScriptBase.h
js/ipc/JavaScriptLogging.h
js/ipc/JavaScriptShared.cpp
js/ipc/JavaScriptShared.h
js/ipc/JavaScriptTypes.ipdlh
js/ipc/WrapperOwner.cpp
--- a/js/ipc/JavaScriptBase.h
+++ b/js/ipc/JavaScriptBase.h
@@ -30,191 +30,191 @@ class JavaScriptBase : public WrapperOwn
     virtual ~JavaScriptBase() {}
 
     virtual void ActorDestroy(WrapperOwner::ActorDestroyReason why) {
         WrapperOwner::ActorDestroy(why);
     }
 
     /*** IPC handlers ***/
 
-    bool AnswerPreventExtensions(const ObjectId &objId, ReturnStatus *rs) {
-        return Answer::AnswerPreventExtensions(objId, rs);
+    bool AnswerPreventExtensions(const uint64_t &objId, ReturnStatus *rs) {
+        return Answer::AnswerPreventExtensions(ObjectId::deserialize(objId), rs);
     }
-    bool AnswerGetPropertyDescriptor(const ObjectId &objId, const nsString &id,
+    bool AnswerGetPropertyDescriptor(const uint64_t &objId, const nsString &id,
                                      ReturnStatus *rs,
                                      PPropertyDescriptor *out) {
-        return Answer::AnswerGetPropertyDescriptor(objId, id, rs, out);
+        return Answer::AnswerGetPropertyDescriptor(ObjectId::deserialize(objId), id, rs, out);
     }
-    bool AnswerGetOwnPropertyDescriptor(const ObjectId &objId,
+    bool AnswerGetOwnPropertyDescriptor(const uint64_t &objId,
                                         const nsString &id,
                                         ReturnStatus *rs,
                                         PPropertyDescriptor *out) {
-        return Answer::AnswerGetOwnPropertyDescriptor(objId, id, rs, out);
+        return Answer::AnswerGetOwnPropertyDescriptor(ObjectId::deserialize(objId), id, rs, out);
     }
-    bool AnswerDefineProperty(const ObjectId &objId, const nsString &id,
+    bool AnswerDefineProperty(const uint64_t &objId, const nsString &id,
                               const PPropertyDescriptor &flags,
                               ReturnStatus *rs) {
-        return Answer::AnswerDefineProperty(objId, id, flags, rs);
+        return Answer::AnswerDefineProperty(ObjectId::deserialize(objId), id, flags, rs);
     }
-    bool AnswerDelete(const ObjectId &objId, const nsString &id,
+    bool AnswerDelete(const uint64_t &objId, const nsString &id,
                       ReturnStatus *rs, bool *success) {
-        return Answer::AnswerDelete(objId, id, rs, success);
+        return Answer::AnswerDelete(ObjectId::deserialize(objId), id, rs, success);
     }
 
-    bool AnswerHas(const ObjectId &objId, const nsString &id,
+    bool AnswerHas(const uint64_t &objId, const nsString &id,
                    ReturnStatus *rs, bool *bp) {
-        return Answer::AnswerHas(objId, id, rs, bp);
+        return Answer::AnswerHas(ObjectId::deserialize(objId), id, rs, bp);
     }
-    bool AnswerHasOwn(const ObjectId &objId, const nsString &id,
+    bool AnswerHasOwn(const uint64_t &objId, const nsString &id,
                       ReturnStatus *rs, bool *bp) {
-        return Answer::AnswerHasOwn(objId, id, rs, bp);
+        return Answer::AnswerHasOwn(ObjectId::deserialize(objId), id, rs, bp);
     }
-    bool AnswerGet(const ObjectId &objId, const ObjectVariant &receiverVar,
+    bool AnswerGet(const uint64_t &objId, const ObjectVariant &receiverVar,
                    const nsString &id,
                    ReturnStatus *rs, JSVariant *result) {
-        return Answer::AnswerGet(objId, receiverVar, id, rs, result);
+        return Answer::AnswerGet(ObjectId::deserialize(objId), receiverVar, id, rs, result);
     }
-    bool AnswerSet(const ObjectId &objId, const ObjectVariant &receiverVar,
+    bool AnswerSet(const uint64_t &objId, const ObjectVariant &receiverVar,
                    const nsString &id, const bool &strict,
                    const JSVariant &value, ReturnStatus *rs, JSVariant *result) {
-        return Answer::AnswerSet(objId, receiverVar, id, strict, value, rs, result);
+        return Answer::AnswerSet(ObjectId::deserialize(objId), receiverVar, id, strict, value, rs, result);
     }
 
-    bool AnswerIsExtensible(const ObjectId &objId, ReturnStatus *rs,
+    bool AnswerIsExtensible(const uint64_t &objId, ReturnStatus *rs,
                             bool *result) {
-        return Answer::AnswerIsExtensible(objId, rs, result);
+        return Answer::AnswerIsExtensible(ObjectId::deserialize(objId), rs, result);
     }
-    bool AnswerCallOrConstruct(const ObjectId &objId, const nsTArray<JSParam> &argv,
+    bool AnswerCallOrConstruct(const uint64_t &objId, const nsTArray<JSParam> &argv,
                                const bool &construct, ReturnStatus *rs, JSVariant *result,
                                nsTArray<JSParam> *outparams) {
-        return Answer::AnswerCallOrConstruct(objId, argv, construct, rs, result, outparams);
+        return Answer::AnswerCallOrConstruct(ObjectId::deserialize(objId), argv, construct, rs, result, outparams);
     }
-    bool AnswerHasInstance(const ObjectId &objId, const JSVariant &v, ReturnStatus *rs, bool *bp) {
-        return Answer::AnswerHasInstance(objId, v, rs, bp);
+    bool AnswerHasInstance(const uint64_t &objId, const JSVariant &v, ReturnStatus *rs, bool *bp) {
+        return Answer::AnswerHasInstance(ObjectId::deserialize(objId), v, rs, bp);
     }
-    bool AnswerObjectClassIs(const ObjectId &objId, const uint32_t &classValue,
+    bool AnswerObjectClassIs(const uint64_t &objId, const uint32_t &classValue,
                              bool *result) {
-        return Answer::AnswerObjectClassIs(objId, classValue, result);
+        return Answer::AnswerObjectClassIs(ObjectId::deserialize(objId), classValue, result);
     }
-    bool AnswerClassName(const ObjectId &objId, nsString *result) {
-        return Answer::AnswerClassName(objId, result);
+    bool AnswerClassName(const uint64_t &objId, nsString *result) {
+        return Answer::AnswerClassName(ObjectId::deserialize(objId), result);
     }
 
-    bool AnswerGetPropertyNames(const ObjectId &objId, const uint32_t &flags,
+    bool AnswerGetPropertyNames(const uint64_t &objId, const uint32_t &flags,
                                 ReturnStatus *rs, nsTArray<nsString> *names) {
-        return Answer::AnswerGetPropertyNames(objId, flags, rs, names);
+        return Answer::AnswerGetPropertyNames(ObjectId::deserialize(objId), flags, rs, names);
     }
-    bool AnswerInstanceOf(const ObjectId &objId, const JSIID &iid,
+    bool AnswerInstanceOf(const uint64_t &objId, const JSIID &iid,
                           ReturnStatus *rs, bool *instanceof) {
-        return Answer::AnswerInstanceOf(objId, iid, rs, instanceof);
+        return Answer::AnswerInstanceOf(ObjectId::deserialize(objId), iid, rs, instanceof);
     }
-    bool AnswerDOMInstanceOf(const ObjectId &objId, const int &prototypeID, const int &depth,
+    bool AnswerDOMInstanceOf(const uint64_t &objId, const int &prototypeID, const int &depth,
                              ReturnStatus *rs, bool *instanceof) {
-        return Answer::AnswerDOMInstanceOf(objId, prototypeID, depth, rs, instanceof);
+        return Answer::AnswerDOMInstanceOf(ObjectId::deserialize(objId), prototypeID, depth, rs, instanceof);
     }
 
-    bool AnswerIsCallable(const ObjectId &objId, bool *result) {
-        return Answer::AnswerIsCallable(objId, result);
+    bool AnswerIsCallable(const uint64_t &objId, bool *result) {
+        return Answer::AnswerIsCallable(ObjectId::deserialize(objId), result);
     }
 
-    bool AnswerIsConstructor(const ObjectId &objId, bool *result) {
-        return Answer::AnswerIsConstructor(objId, result);
+    bool AnswerIsConstructor(const uint64_t &objId, bool *result) {
+        return Answer::AnswerIsConstructor(ObjectId::deserialize(objId), result);
     }
 
-    bool RecvDropObject(const ObjectId &objId) {
-        return Answer::RecvDropObject(objId);
+    bool RecvDropObject(const uint64_t &objId) {
+        return Answer::RecvDropObject(ObjectId::deserialize(objId));
     }
 
     /*** Dummy call handlers ***/
 
     bool SendDropObject(const ObjectId &objId) {
-        return Base::SendDropObject(objId);
+        return Base::SendDropObject(objId.serialize());
     }
     bool CallPreventExtensions(const ObjectId &objId, ReturnStatus *rs) {
-        return Base::CallPreventExtensions(objId, rs);
+        return Base::CallPreventExtensions(objId.serialize(), rs);
     }
     bool CallGetPropertyDescriptor(const ObjectId &objId, const nsString &id,
                                      ReturnStatus *rs,
                                      PPropertyDescriptor *out) {
-        return Base::CallGetPropertyDescriptor(objId, id, rs, out);
+        return Base::CallGetPropertyDescriptor(objId.serialize(), id, rs, out);
     }
     bool CallGetOwnPropertyDescriptor(const ObjectId &objId,
                                       const nsString &id,
                                       ReturnStatus *rs,
                                       PPropertyDescriptor *out) {
-        return Base::CallGetOwnPropertyDescriptor(objId, id, rs, out);
+        return Base::CallGetOwnPropertyDescriptor(objId.serialize(), id, rs, out);
     }
     bool CallDefineProperty(const ObjectId &objId, const nsString &id,
                             const PPropertyDescriptor &flags,
                               ReturnStatus *rs) {
-        return Base::CallDefineProperty(objId, id, flags, rs);
+        return Base::CallDefineProperty(objId.serialize(), id, flags, rs);
     }
     bool CallDelete(const ObjectId &objId, const nsString &id,
                     ReturnStatus *rs, bool *success) {
-        return Base::CallDelete(objId, id, rs, success);
+        return Base::CallDelete(objId.serialize(), id, rs, success);
     }
 
     bool CallHas(const ObjectId &objId, const nsString &id,
                    ReturnStatus *rs, bool *bp) {
-        return Base::CallHas(objId, id, rs, bp);
+        return Base::CallHas(objId.serialize(), id, rs, bp);
     }
     bool CallHasOwn(const ObjectId &objId, const nsString &id,
                     ReturnStatus *rs, bool *bp) {
-        return Base::CallHasOwn(objId, id, rs, bp);
+        return Base::CallHasOwn(objId.serialize(), id, rs, bp);
     }
     bool CallGet(const ObjectId &objId, const ObjectVariant &receiverVar,
                  const nsString &id,
                  ReturnStatus *rs, JSVariant *result) {
-        return Base::CallGet(objId, receiverVar, id, rs, result);
+        return Base::CallGet(objId.serialize(), receiverVar, id, rs, result);
     }
     bool CallSet(const ObjectId &objId, const ObjectVariant &receiverVar,
                  const nsString &id, const bool &strict,
                  const JSVariant &value, ReturnStatus *rs, JSVariant *result) {
-        return Base::CallSet(objId, receiverVar, id, strict, value, rs, result);
+        return Base::CallSet(objId.serialize(), receiverVar, id, strict, value, rs, result);
     }
 
     bool CallIsExtensible(const ObjectId &objId, ReturnStatus *rs,
                           bool *result) {
-        return Base::CallIsExtensible(objId, rs, result);
+        return Base::CallIsExtensible(objId.serialize(), rs, result);
     }
     bool CallCallOrConstruct(const ObjectId &objId, const nsTArray<JSParam> &argv,
                              const bool &construct, ReturnStatus *rs, JSVariant *result,
                              nsTArray<JSParam> *outparams) {
-        return Base::CallCallOrConstruct(objId, argv, construct, rs, result, outparams);
+        return Base::CallCallOrConstruct(objId.serialize(), argv, construct, rs, result, outparams);
     }
     bool CallHasInstance(const ObjectId &objId, const JSVariant &v, ReturnStatus *rs, bool *bp) {
-        return Base::CallHasInstance(objId, v, rs, bp);
+        return Base::CallHasInstance(objId.serialize(), v, rs, bp);
     }
     bool CallObjectClassIs(const ObjectId &objId, const uint32_t &classValue,
                            bool *result) {
-        return Base::CallObjectClassIs(objId, classValue, result);
+        return Base::CallObjectClassIs(objId.serialize(), classValue, result);
     }
     bool CallClassName(const ObjectId &objId, nsString *result) {
-        return Base::CallClassName(objId, result);
+        return Base::CallClassName(objId.serialize(), result);
     }
 
     bool CallGetPropertyNames(const ObjectId &objId, const uint32_t &flags,
                               ReturnStatus *rs, nsTArray<nsString> *names) {
-        return Base::CallGetPropertyNames(objId, flags, rs, names);
+        return Base::CallGetPropertyNames(objId.serialize(), flags, rs, names);
     }
     bool CallInstanceOf(const ObjectId &objId, const JSIID &iid,
                         ReturnStatus *rs, bool *instanceof) {
-        return Base::CallInstanceOf(objId, iid, rs, instanceof);
+        return Base::CallInstanceOf(objId.serialize(), iid, rs, instanceof);
     }
     bool CallDOMInstanceOf(const ObjectId &objId, const int &prototypeID, const int &depth,
                            ReturnStatus *rs, bool *instanceof) {
-        return Base::CallDOMInstanceOf(objId, prototypeID, depth, rs, instanceof);
+        return Base::CallDOMInstanceOf(objId.serialize(), prototypeID, depth, rs, instanceof);
     }
 
     bool CallIsCallable(const ObjectId &objId, bool *result) {
-        return Base::CallIsCallable(objId, result);
+        return Base::CallIsCallable(objId.serialize(), result);
     }
 
     bool CallIsConstructor(const ObjectId &objId, bool *result) {
-        return Base::CallIsConstructor(objId, result);
+        return Base::CallIsConstructor(objId.serialize(), result);
     }
 
     /* The following code is needed to suppress a bogus MSVC warning (C4250). */
 
     virtual bool toObjectVariant(JSContext *cx, JSObject *obj, ObjectVariant *objVarp) {
         return WrapperOwner::toObjectVariant(cx, obj, objVarp);
     }
     virtual JSObject *fromObjectVariant(JSContext *cx, ObjectVariant objVar) {
--- a/js/ipc/JavaScriptLogging.h
+++ b/js/ipc/JavaScriptLogging.h
@@ -155,19 +155,19 @@ class Logging
               nsAutoCString tmp;
               format(value.get_nsString(), tmp);
               out = nsPrintfCString("\"%s\"", tmp.get());
               break;
           }
           case JSVariant::TObjectVariant: {
               const ObjectVariant &ovar = value.get_ObjectVariant();
               if (ovar.type() == ObjectVariant::TLocalObject)
-                  formatObject(incoming, true, ovar.get_LocalObject().id(), out);
+                  formatObject(incoming, true, ObjectId::deserialize(ovar.get_LocalObject().serializedId()), out);
               else
-                  formatObject(incoming, false, ovar.get_RemoteObject().id(), out);
+                  formatObject(incoming, false, ObjectId::deserialize(ovar.get_RemoteObject().serializedId()), out);
               break;
           }
           case JSVariant::Tdouble: {
               out = nsPrintfCString("%.0f", value.get_double());
               break;
           }
           case JSVariant::Tbool: {
               out = value.get_bool() ? "true" : "false";
--- a/js/ipc/JavaScriptShared.cpp
+++ b/js/ipc/JavaScriptShared.cpp
@@ -118,17 +118,17 @@ ObjectToIdMap::sweep()
     }
 }
 
 ObjectId
 ObjectToIdMap::find(JSObject *obj)
 {
     Table::Ptr p = table_->lookup(obj);
     if (!p)
-        return 0;
+        return ObjectId::nullId();
     return p->value();
 }
 
 bool
 ObjectToIdMap::add(JSContext *cx, JSObject *obj, ObjectId id)
 {
     if (!table_->put(obj, id))
         return false;
@@ -157,17 +157,17 @@ ObjectToIdMap::remove(JSObject *obj)
 
 bool JavaScriptShared::sLoggingInitialized;
 bool JavaScriptShared::sLoggingEnabled;
 bool JavaScriptShared::sStackLoggingEnabled;
 
 JavaScriptShared::JavaScriptShared(JSRuntime *rt)
   : rt_(rt),
     refcount_(1),
-    lastId_(0)
+    nextSerialNumber_(1)
 {
     if (!sLoggingInitialized) {
         sLoggingInitialized = true;
 
         if (PR_GetEnv("MOZ_CPOW_LOG")) {
             sLoggingEnabled = true;
             sStackLoggingEnabled = true;
         } else {
@@ -376,17 +376,17 @@ JavaScriptShared::ConvertID(const JSIID 
     to->m3[3] = from.m3_3();
     to->m3[4] = from.m3_4();
     to->m3[5] = from.m3_5();
     to->m3[6] = from.m3_6();
     to->m3[7] = from.m3_7();
 }
 
 JSObject *
-JavaScriptShared::findObjectById(JSContext *cx, uint32_t objId)
+JavaScriptShared::findObjectById(JSContext *cx, const ObjectId &objId)
 {
     RootedObject obj(cx, findObjectById(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
--- a/js/ipc/JavaScriptShared.h
+++ b/js/ipc/JavaScriptShared.h
@@ -11,17 +11,57 @@
 #include "mozilla/dom/DOMTypes.h"
 #include "mozilla/jsipc/PJavaScript.h"
 #include "nsJSUtils.h"
 #include "nsFrameMessageManager.h"
 
 namespace mozilla {
 namespace jsipc {
 
-typedef uint64_t ObjectId;
+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)
+    {
+        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());
+        return equal;
+    }
+
+    bool isNull() { return !serialNumber_; }
+
+    uint64_t serialNumber() const { return serialNumber_; }
+    bool isCallable() const { return isCallable_; }
+    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));
+    }
+
+    static ObjectId nullId() { return ObjectId(); }
+    static ObjectId deserialize(uint64_t data) {
+        return ObjectId(data >> FLAG_BITS, data & 1);
+    }
+
+  private:
+    ObjectId() : serialNumber_(0), isCallable_(false) {}
+
+    uint64_t serialNumber_ : SERIAL_NUMBER_BITS;
+    bool isCallable_ : 1;
+};
 
 class JavaScriptShared;
 
 class CpowIdHolder : public CpowHolder
 {
   public:
     CpowIdHolder(JavaScriptShared *js, const InfallibleTArray<CpowEntry> &cpows)
       : js_(js),
@@ -31,22 +71,37 @@ class CpowIdHolder : public CpowHolder
 
     bool ToObject(JSContext *cx, JS::MutableHandleObject objp);
 
   private:
     JavaScriptShared *js_;
     const InfallibleTArray<CpowEntry> &cpows_;
 };
 
+// DefaultHasher<T> requires that T coerce to an integral type. We could make
+// ObjectId do that, but doing so would weaken our type invariants, so we just
+// reimplement it manually.
+struct ObjectIdHasher
+{
+    typedef ObjectId Lookup;
+    static js::HashNumber hash(const Lookup &l) {
+        return l.serialize();
+    }
+    static bool match(const ObjectId &k, const ObjectId &l) {
+        return k == l;
+    }
+    static void rekey(ObjectId &k, const ObjectId& newKey) {
+        k = newKey;
+    }
+};
+
 // Map ids -> JSObjects
 class IdToObjectMap
 {
-    typedef js::DefaultHasher<ObjectId> TableKeyHasher;
-
-    typedef js::HashMap<ObjectId, JS::Heap<JSObject *>, TableKeyHasher, js::SystemAllocPolicy> Table;
+    typedef js::HashMap<ObjectId, JS::Heap<JSObject *>, ObjectIdHasher, js::SystemAllocPolicy> Table;
 
   public:
     IdToObjectMap();
 
     bool init();
     void trace(JSTracer *trc);
     void sweep();
 
@@ -90,19 +145,16 @@ class JavaScriptShared
     explicit JavaScriptShared(JSRuntime *rt);
     virtual ~JavaScriptShared() {}
 
     bool init();
 
     void decref();
     void incref();
 
-    static const uint32_t OBJECT_EXTRA_BITS  = 1;
-    static const uint32_t OBJECT_IS_CALLABLE = (1 << 0);
-
     bool Unwrap(JSContext *cx, const InfallibleTArray<CpowEntry> &aCpows, JS::MutableHandleObject objp);
     bool Wrap(JSContext *cx, JS::HandleObject aObj, InfallibleTArray<CpowEntry> *outCpows);
 
   protected:
     bool toVariant(JSContext *cx, JS::HandleValue from, JSVariant *to);
     bool fromVariant(JSContext *cx, const JSVariant &from, JS::MutableHandleValue to);
 
     bool fromDescriptor(JSContext *cx, JS::Handle<JSPropertyDescriptor> desc,
@@ -114,23 +166,23 @@ class JavaScriptShared
     bool convertGeckoStringToId(JSContext *cx, const nsString &from, JS::MutableHandleId id);
 
     virtual bool toObjectVariant(JSContext *cx, JSObject *obj, ObjectVariant *objVarp) = 0;
     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(uint32_t objId) {
+    JSObject *findCPOWById(const ObjectId &objId) {
         return cpows_.find(objId);
     }
-    JSObject *findObjectById(uint32_t objId) {
+    JSObject *findObjectById(const ObjectId &objId) {
         return objects_.find(objId);
     }
-    JSObject *findObjectById(JSContext *cx, uint32_t 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;
 
@@ -138,23 +190,20 @@ class JavaScriptShared
 
   protected:
     JSRuntime *rt_;
     uintptr_t refcount_;
 
     IdToObjectMap objects_;
     IdToObjectMap cpows_;
 
-    ObjectId lastId_;
+    uint64_t nextSerialNumber_;
     ObjectToIdMap objectIds_;
 
     static bool sLoggingInitialized;
     static bool sLoggingEnabled;
     static bool sStackLoggingEnabled;
 };
 
-// Use 47 at most, to be safe, since jsval privates are encoded as doubles.
-static const uint64_t MAX_CPOW_IDS = (uint64_t(1) << 47) - 1;
-
 } // namespace jsipc
 } // namespace mozilla
 
 #endif
--- a/js/ipc/JavaScriptTypes.ipdlh
+++ b/js/ipc/JavaScriptTypes.ipdlh
@@ -24,22 +24,22 @@ struct JSIID
     uint8_t m3_4;
     uint8_t m3_5;
     uint8_t m3_6;
     uint8_t m3_7;
 };
 
 struct LocalObject
 {
-    uint64_t id;
+    uint64_t serializedId;
 };
 
 struct RemoteObject
 {
-    uint64_t id;
+    uint64_t serializedId;
 };
 
 union ObjectVariant
 {
     LocalObject;
     RemoteObject;
 };
 
--- a/js/ipc/WrapperOwner.cpp
+++ b/js/ipc/WrapperOwner.cpp
@@ -33,18 +33,18 @@ OwnerOf(JSObject *obj)
 ObjectId
 WrapperOwner::idOfUnchecked(JSObject *obj)
 {
     MOZ_ASSERT(IsCPOW(obj));
 
     Value v = GetProxyExtra(obj, 1);
     MOZ_ASSERT(v.isDouble());
 
-    ObjectId objId = BitwiseCast<uint64_t>(v.toDouble());
-    MOZ_ASSERT(objId);
+    ObjectId objId = ObjectId::deserialize(BitwiseCast<uint64_t>(v.toDouble()));
+    MOZ_ASSERT(!objId.isNull());
 
     return objId;
 }
 
 ObjectId
 WrapperOwner::idOf(JSObject *obj)
 {
     ObjectId objId = idOfUnchecked(obj);
@@ -867,71 +867,57 @@ WrapperOwner::toObjectVariant(JSContext 
     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);
     if (obj && IsCPOW(obj) && OwnerOf(obj) == this) {
-        *objVarp = LocalObject(idOf(obj));
+        *objVarp = LocalObject(idOf(obj).serialize());
         return true;
     }
 
     ObjectId id = objectIds_.find(obj);
-    if (id) {
-        *objVarp = RemoteObject(id);
+    if (!id.isNull()) {
+        *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 = ++lastId_;
-    if (id > MAX_CPOW_IDS) {
-        JS_ReportError(cx, "CPOW id limit reached");
-        return false;
-    }
-
-    id <<= OBJECT_EXTRA_BITS;
-    if (JS::IsCallable(obj))
-        id |= OBJECT_IS_CALLABLE;
-
+    id = ObjectId(nextSerialNumber_++, JS::IsCallable(obj));
     if (!objects_.add(id, obj))
         return false;
     if (!objectIds_.add(cx, obj, id))
         return false;
 
-    *objVarp = RemoteObject(id);
+    *objVarp = RemoteObject(id.serialize());
     return true;
 }
 
 JSObject *
 WrapperOwner::fromObjectVariant(JSContext *cx, ObjectVariant objVar)
 {
     if (objVar.type() == ObjectVariant::TRemoteObject) {
         return fromRemoteObjectVariant(cx, objVar.get_RemoteObject());
     } else {
         return fromLocalObjectVariant(cx, objVar.get_LocalObject());
     }
 }
 
 JSObject *
 WrapperOwner::fromRemoteObjectVariant(JSContext *cx, RemoteObject objVar)
 {
-    ObjectId objId = objVar.id();
+    ObjectId objId = ObjectId::deserialize(objVar.serializedId());
     RootedObject obj(cx, findCPOWById(objId));
     if (!obj) {
-        // If we didn't find an existing CPOW, we need to create one.
-        if (objId > MAX_CPOW_IDS) {
-            JS_ReportError(cx, "unusable CPOW id");
-            return nullptr;
-        }
 
         // All CPOWs live in the privileged junk scope.
         RootedObject junkScope(cx, xpc::PrivilegedJunkScope());
         JSAutoCompartment ac(cx, junkScope);
         RootedValue v(cx, UndefinedValue());
         obj = NewProxyObject(cx,
                              &CPOWProxyHandler::singleton,
                              v,
@@ -942,27 +928,27 @@ WrapperOwner::fromRemoteObjectVariant(JS
 
         if (!cpows_.add(objId, obj))
             return nullptr;
 
         // Incref once we know the decref will be called.
         incref();
 
         SetProxyExtra(obj, 0, PrivateValue(this));
-        SetProxyExtra(obj, 1, DoubleValue(BitwiseCast<double>(objId)));
+        SetProxyExtra(obj, 1, DoubleValue(BitwiseCast<double>(objId.serialize())));
     }
 
     if (!JS_WrapObject(cx, &obj))
         return nullptr;
     return obj;
 }
 
 JSObject *
 WrapperOwner::fromLocalObjectVariant(JSContext *cx, LocalObject objVar)
 {
-    ObjectId id = objVar.id();
+    ObjectId id = ObjectId::deserialize(objVar.serializedId());
     Rooted<JSObject*> obj(cx, findObjectById(cx, id));
     if (!obj)
         return nullptr;
     if (!JS_WrapObject(cx, &obj))
         return nullptr;
     return obj;
 }