Bug 1288817, part 1 - Make XPCNativeScriptableShared refcounted. r=billm draft
authorAndrew McCreight <continuation@gmail.com>
Wed, 27 Jul 2016 16:35:19 -0700
changeset 400027 9be5a903b9d616222c9b6791f4b7ea8e401d8bea
parent 399698 233ab21b64b5d5e9f2f16ea2d4cfb4c8b293c5c4
child 400028 12458b93844d15ae43ed2cbc721d27f45d91e707
push id26073
push userbmo:continuation@gmail.com
push dateFri, 12 Aug 2016 15:56:00 +0000
reviewersbillm
bugs1288817
milestone51.0a1
Bug 1288817, part 1 - Make XPCNativeScriptableShared refcounted. r=billm This adds a heap allocation in XPCNativeScriptableSharedMap::GetNewOrUsed() on the fast path. Hopefully that code is not hot enough for it to matter. I could work around this if needed, but it would be ugly. mNativeScriptableSharedMap has a weak pointer to XPCNativeScriptableShared. I moved this removal from XPCJSRuntime::FinalizeCallback() to the dtor. There are two types of scriptable: one is a dummy one created in GetNewOrUsed() to do a lookup, and the other is a fully filled out one. The dummy one is not added to the hashtable, and may have had its name stolen if we created a new scriptable. The latter makes it so that you crash if you try to look it up in the hashtable anyways, so this patch only looks up fully filled out scriptables. This patch also removes MOZ_COUNT_CTOR/DTOR because they are not needed for refcounted classes. MozReview-Commit-ID: GeG0pAYqXpR
js/xpconnect/src/XPCJSRuntime.cpp
js/xpconnect/src/XPCMaps.cpp
js/xpconnect/src/XPCMaps.h
js/xpconnect/src/XPCWrappedNativeJSOps.cpp
js/xpconnect/src/xpcprivate.h
--- a/js/xpconnect/src/XPCJSRuntime.cpp
+++ b/js/xpconnect/src/XPCJSRuntime.cpp
@@ -821,32 +821,16 @@ XPCJSRuntime::FinalizeCallback(JSFreeOp*
             // compartment being collected will be marked.
             //
             // Ideally, if NativeInterfaces from different compartments were
             // kept separate, we could sweep only the ones belonging to
             // compartments being collected. Currently, though, NativeInterfaces
             // are shared between compartments. This ought to be fixed.
             bool doSweep = !isCompartmentGC;
 
-            // We don't want to sweep the JSClasses at shutdown time.
-            // At this point there may be JSObjects using them that have
-            // been removed from the other maps.
-            if (!nsXPConnect::XPConnect()->IsShuttingDown()) {
-                for (auto i = self->mNativeScriptableSharedMap->Iter(); !i.Done(); i.Next()) {
-                    auto entry = static_cast<XPCNativeScriptableSharedMap::Entry*>(i.Get());
-                    XPCNativeScriptableShared* shared = entry->key;
-                    if (shared->IsMarked()) {
-                        shared->Unmark();
-                    } else if (doSweep) {
-                        delete shared;
-                        i.Remove();
-                    }
-                }
-            }
-
             if (doSweep) {
                 for (auto i = self->mClassInfo2NativeSetMap->Iter(); !i.Done(); i.Next()) {
                     auto entry = static_cast<ClassInfo2NativeSetMap::Entry*>(i.Get());
                     if (!entry->value->IsMarked())
                         i.Remove();
                 }
             }
 
--- a/js/xpconnect/src/XPCMaps.cpp
+++ b/js/xpconnect/src/XPCMaps.cpp
@@ -435,38 +435,39 @@ XPCNativeScriptableSharedMap::XPCNativeS
 bool
 XPCNativeScriptableSharedMap::GetNewOrUsed(uint32_t flags,
                                            char* name,
                                            XPCNativeScriptableInfo* si)
 {
     NS_PRECONDITION(name,"bad param");
     NS_PRECONDITION(si,"bad param");
 
-    XPCNativeScriptableShared key(flags, name, /* populate = */ false);
-    auto entry = static_cast<Entry*>(mTable.Add(&key, fallible));
+    RefPtr<XPCNativeScriptableShared> key =
+        new XPCNativeScriptableShared(flags, name, /* populate = */ false);
+    auto entry = static_cast<Entry*>(mTable.Add(key, fallible));
     if (!entry)
         return false;
 
-    XPCNativeScriptableShared* shared = entry->key;
+    RefPtr<XPCNativeScriptableShared> shared = entry->key;
 
     // XXX: this XPCNativeScriptableShared is heap-allocated, which means the
     // js::Class it contains is also heap-allocated. This causes problems for
     // memory reporting. See the comment above the BaseShape case in
     // StatsCellCallback() in js/src/vm/MemoryMetrics.cpp.
     //
     // When the code below is removed (bug 1265271) and there are no longer any
     // heap-allocated js::Class instances, the disabled code in
     // StatsCellCallback() should be reinstated.
     //
     if (!shared) {
-        entry->key = shared =
-            new XPCNativeScriptableShared(flags, key.TransferNameOwnership(),
-                                          /* populate = */ true);
+        shared = new XPCNativeScriptableShared(flags, key->TransferNameOwnership(),
+                                               /* populate = */ true);
+        entry->key = shared;
     }
-    si->SetScriptableShared(shared);
+    si->SetScriptableShared(shared.forget());
     return true;
 }
 
 /***************************************************************************/
 // implement XPCWrappedNativeProtoMap...
 
 // static
 XPCWrappedNativeProtoMap*
--- a/js/xpconnect/src/XPCMaps.h
+++ b/js/xpconnect/src/XPCMaps.h
@@ -492,16 +492,18 @@ private:
 
 /***************************************************************************/
 
 class XPCNativeScriptableSharedMap
 {
 public:
     struct Entry : public PLDHashEntryHdr
     {
+        // This is a weak reference that will be cleared
+        // in the XPCNativeScriptableShared destructor.
         XPCNativeScriptableShared* key;
 
         static PLDHashNumber
         Hash(const void* key);
 
         static bool
         Match(const PLDHashEntryHdr* entry, const void* key);
 
@@ -509,17 +511,17 @@ public:
     };
 
     static XPCNativeScriptableSharedMap* newMap(int length);
 
     bool GetNewOrUsed(uint32_t flags, char* name, XPCNativeScriptableInfo* si);
 
     inline uint32_t Count() { return mTable.EntryCount(); }
 
-    PLDHashTable::Iterator Iter() { return mTable.Iter(); }
+    void Remove(XPCNativeScriptableShared* key) { mTable.Remove(key); }
 
 private:
     XPCNativeScriptableSharedMap();    // no implementation
     explicit XPCNativeScriptableSharedMap(int size);
 private:
     PLDHashTable mTable;
 };
 
--- a/js/xpconnect/src/XPCWrappedNativeJSOps.cpp
+++ b/js/xpconnect/src/XPCWrappedNativeJSOps.cpp
@@ -957,18 +957,16 @@ static const js::ObjectOps XPC_WN_Object
     nullptr,  // funToString
 };
 
 XPCNativeScriptableShared::XPCNativeScriptableShared(uint32_t aFlags,
                                                      char* aName,
                                                      bool aPopulate)
     : mFlags(aFlags)
 {
-    MOZ_COUNT_CTOR(XPCNativeScriptableShared);
-
     // Initialize the js::Class.
 
     memset(&mJSClass, 0, sizeof(mJSClass));
     mJSClass.name = aName;  // take ownership
 
     if (!aPopulate)
         return;
 
@@ -1054,16 +1052,31 @@ XPCNativeScriptableShared::XPCNativeScri
     mJSClass.ext = &XPC_WN_JSClassExtension;
 
     // Initialize the js::ObjectOps.
 
     if (mFlags.WantNewEnumerate())
         mJSClass.oOps = &XPC_WN_ObjectOpsWithEnumerate;
 }
 
+XPCNativeScriptableShared::~XPCNativeScriptableShared()
+{
+    // mJSClass.cOps will be null if |this| was created with
+    // populate=false. Otherwise, it was created with populate=true
+    // and there is a weak reference in a global map that must be
+    // removed.
+
+    if (mJSClass.cOps) {
+        XPCJSRuntime::Get()->GetNativeScriptableSharedMap()->Remove(this);
+        free((void*)mJSClass.cOps);
+    }
+
+    free((void*)mJSClass.name);
+}
+
 /***************************************************************************/
 /***************************************************************************/
 
 // Compatibility hack.
 //
 // XPConnect used to do all sorts of funny tricks to find the "correct"
 // |this| object for a given method (often to the detriment of proper
 // call/apply). When these tricks were removed, a fair amount of chrome
--- a/js/xpconnect/src/xpcprivate.h
+++ b/js/xpconnect/src/xpcprivate.h
@@ -1494,39 +1494,37 @@ public:
 // this inside the XPCNativeScriptableInfo (usually owned by instances of
 // XPCWrappedNativeProto. This had two problems... It was wasteful, and it
 // was a big problem when wrappers are reparented to different scopes (and
 // thus different protos (the DOM does this).
 
 class XPCNativeScriptableShared final
 {
 public:
+    NS_INLINE_DECL_REFCOUNTING(XPCNativeScriptableShared)
+
     const XPCNativeScriptableFlags& GetFlags() const { return mFlags; }
 
     const JSClass* GetJSClass() { return Jsvalify(&mJSClass); }
 
     XPCNativeScriptableShared(uint32_t aFlags, char* aName, bool aPopulate);
 
-    ~XPCNativeScriptableShared() {
-        free((void*)mJSClass.name);
-        free((void*)mJSClass.cOps);
-        MOZ_COUNT_DTOR(XPCNativeScriptableShared);
-    }
-
     char* TransferNameOwnership() {
         char* name = (char*)mJSClass.name;
         mJSClass.name = nullptr;
         return name;
     }
 
     void Mark()   { mFlags.Mark(); }
     void Unmark() { mFlags.Unmark(); }
     bool IsMarked() const { return mFlags.IsMarked(); }
 
 private:
+    ~XPCNativeScriptableShared();
+
     XPCNativeScriptableFlags mFlags;
 
     // This is an unusual js::Class instance: its name and cOps members are
     // heap-allocated, unlike all other instances for which they are statically
     // allocated. So we must free them in the destructor.
     js::Class mJSClass;
 };
 
@@ -1536,50 +1534,56 @@ private:
 
 class XPCNativeScriptableInfo final
 {
 public:
     static XPCNativeScriptableInfo*
     Construct(const XPCNativeScriptableCreateInfo* sci);
 
     nsIXPCScriptable*
-    GetCallback() const {return mCallback;}
+    GetCallback() const { return mCallback; }
 
     const XPCNativeScriptableFlags&
-    GetFlags() const      {return mShared->GetFlags();}
+    GetFlags() const { return mShared->GetFlags(); }
 
     const JSClass*
-    GetJSClass()          {return mShared->GetJSClass();}
+    GetJSClass() { return mShared->GetJSClass(); }
 
     void
-    SetScriptableShared(XPCNativeScriptableShared* shared) {mShared = shared;}
-
-    void Mark() {
+    SetScriptableShared(already_AddRefed<XPCNativeScriptableShared>&& shared) { mShared = shared; }
+
+    void Mark()
+    {
         if (mShared)
             mShared->Mark();
     }
 
     void TraceJS(JSTracer* trc) {}
     void AutoTrace(JSTracer* trc) {}
 
 protected:
     explicit XPCNativeScriptableInfo(nsIXPCScriptable* scriptable)
-        : mCallback(scriptable), mShared(nullptr)
-                               {MOZ_COUNT_CTOR(XPCNativeScriptableInfo);}
+        : mCallback(scriptable)
+    {
+        MOZ_COUNT_CTOR(XPCNativeScriptableInfo);
+    }
 public:
-    ~XPCNativeScriptableInfo() {MOZ_COUNT_DTOR(XPCNativeScriptableInfo);}
+    ~XPCNativeScriptableInfo()
+    {
+        MOZ_COUNT_DTOR(XPCNativeScriptableInfo);
+    }
 private:
 
     // disable copy ctor and assignment
     XPCNativeScriptableInfo(const XPCNativeScriptableInfo& r) = delete;
     XPCNativeScriptableInfo& operator= (const XPCNativeScriptableInfo& r) = delete;
 
 private:
-    nsCOMPtr<nsIXPCScriptable>  mCallback;
-    XPCNativeScriptableShared*  mShared;
+    nsCOMPtr<nsIXPCScriptable> mCallback;
+    RefPtr<XPCNativeScriptableShared> mShared;
 };
 
 /***************************************************************************/
 // XPCNativeScriptableCreateInfo is used in creating new wrapper and protos.
 // it abstracts out the scriptable interface pointer and the flags. After
 // creation these are factored differently using XPCNativeScriptableInfo.
 
 class MOZ_STACK_CLASS XPCNativeScriptableCreateInfo final