Open Bug 1569185 Opened 5 years ago Updated 2 years ago

ImplCycleCollectionTraverse for nsRefPtrHashtable doesn't traverse its keys

Categories

(Core :: Cycle Collector, defect)

defect

Tracking

()

People

(Reporter: zbraniecki, Unassigned)

Details

In bug 1517880 we encountered a bug where this table:

  nsRefPtrHashtable<nsRefPtrHashKey<Element>, nsXULPrototypeElement>
      mL10nProtoElements;

doesn't NS_IMPL_CYCLE_COLLECTION_TRAVERSE/NS_IMPL_CYCLE_COLLECTION_UNLINK its keys which lead to leaks.

This is actually an issue for all the XPCOM hashtables, since they could all be using nsRefPtrHashKey...

Component: DOM: Core & HTML → XPCOM

I'm pretty surprised this hasn't come up before. Maybe people have just been hacking around it. A fix for this will have to audit all existing users.

Presumably a proper fix will require some kind of SFINAE thing that attempts to get a traverse for the key, and falls back to not having one. This is implemented as ImplCycleCollectionTraverse in nsRefPtrHashtable. Ideally, this fix would also be applied to all other users of these hash table keys.

Summary: nsRefPtrHashtable<nsRefPtrHashKey<Element>, T> doesn't traverse its keys → ImplCycleCollectionTraverse for nsRefPtrHashtable doesn't traverse its keys

(In reply to Zibi Braniecki [:zbraniecki][:gandalf] from comment #0)

In bug 1517880 we encountered a bug where this table:

  nsRefPtrHashtable<nsRefPtrHashKey<Element>, nsXULPrototypeElement>
      mL10nProtoElements;

doesn't NS_IMPL_CYCLE_COLLECTION_TRAVERSE/NS_IMPL_CYCLE_COLLECTION_UNLINK its keys which lead to leaks.

It looks like the traverse implementation is correct:

https://searchfox.org/mozilla-central/source/xpcom/ds/nsRefPtrHashtable.h#79-86

Maybe the problem is that we don't have the right overloads for iter.UserData(); nsTHashtable's implementation of the same is slightly different:

https://searchfox.org/mozilla-central/source/xpcom/ds/nsTHashtable.h#446-454

Is the right thing to note children, as nsRefPtrHashtable does, or to directly traverse the children, as nsTHashtable does?

Does the unlink implementation need to call unlink on its elements?

https://searchfox.org/mozilla-central/source/xpcom/ds/nsRefPtrHashtable.h#74-77
https://searchfox.org/mozilla-central/source/xpcom/ds/nsTHashtable.h#441-444

Flags: needinfo?(continuation)

The problem with the traverse is that it is traversing the values in the hash table, but not the keys.

It needs an additional line like
CycleCollectionNoteChild(aCallback, iter.Key(), aName, aFlags);
...except of course, only when the hash table has a refcounted reference to the key.

Maybe we could restructure these iterators to deal with EntryTypes for more code reuse?

Unlink is fine. We clear the hashtable, which will break any cycles.

Flags: needinfo?(continuation)

(In reply to Andrew McCreight [:mccr8] from comment #4)

Maybe we could restructure these iterators to deal with EntryTypes for more code reuse?

Well, the iterator, not the entry.

Maybe people have just been hacking around it.

https://searchfox.org/mozilla-central/rev/0bcdfa99339c0512e3730903835fb6f8ed45e3c4/accessible/base/NotificationController.cpp#62 seems to be.

So is https://searchfox.org/mozilla-central/rev/0bcdfa99339c0512e3730903835fb6f8ed45e3c4/dom/base/FragmentOrElement.cpp#1840

https://searchfox.org/mozilla-central/rev/0bcdfa99339c0512e3730903835fb6f8ed45e3c4/dom/animation/EffectCompositor.cpp#57 is a slightly more complicated case...

We'd presumably want to look at the CC story around https://searchfox.org/mozilla-central/search?q=symbol:T_nsRefPtrHashKey&redirect=false

So fwiw, for the specific case of nsRefPtrHashtable, I get a compiling tree when I do this:

// We can't partially-specialize functions, but we can
// partially-specialize classes.
template<typename KeyClass>
struct KeyTraverser {
  static inline void Traverse(nsCycleCollectionTraversalCallback& aCallback,
                              typename KeyClass::KeyType aKey,
                              const char* aName,
                              uint32_t aFlags) {
    // Default impl does nothing
  }
};

template<typename T>
struct KeyTraverser<nsRefPtrHashKey<T>> {
  static inline void Traverse(nsCycleCollectionTraversalCallback& aCallback,
                              T* aKey,
                              const char* aName,
                              uint32_t aFlags) {
    CycleCollectionNoteChild(aCallback, aKey, aName, aFlags);
  }
};

class nsAtom;
template<>
struct KeyTraverser<nsRefPtrHashKey<nsAtom>> {
  static inline void Traverse(nsCycleCollectionTraversalCallback& aCallback,
                              nsAtom* aKey,
                              const char* aName,
                              uint32_t aFlags) {
    // Atoms don't need to be CCed
  }
};

and then

    KeyTraverser<K>::Traverse(aCallback, iter.Key(), aName, aFlags);

in the iteration loop in ImplCycleCollectionTraverse for nsRefPtrHashtable. But again, we'd ideally like to do this for all hashtables, so maybe we can move the ImplCycleCollectionTraverse bit to base hashtable and then have specializations like that for both the key and the value?

Assignee: nobody → continuation
Assignee: continuation → nobody
Severity: normal → S3
Component: XPCOM → Cycle Collector
You need to log in before you can comment on or make changes to this bug.