Source code

Revision control

Copy as Markdown

Other Tools

=======================================
How to make a C++ class cycle collected
=======================================
Should my class be cycle collected?
===================================
First, you need to decide if your class should be cycle
collected. There are three main criteria:
* It can be part of a cycle of strong references, including
refcounted objects and JS. Usually, this happens when it can hold
alive and be held alive by cycle collected objects or JS.
* It must be refcounted.
* It must be single threaded. The cycle collector can only work with
objects that are used on a single thread. The main thread and DOM
worker and worklet threads each have their own cycle collectors.
If your class meets the first criteria but not the second, then
whatever class uniquely owns it should be cycle collected, assuming
that is refcounted, and this class should be traversed and unlinked as
part of that.
The cycle collector supports both nsISupports and non-nsISupports
(known as "native" in CC nomenclature) refcounting. However, we do not
support native cycle collection in the presence of inheritance, if two
classes related by inheritance need different CC
implementations. (This is because we use QueryInterface to find the
right CC implementation for an object.)
Once you've decided to make a class cycle collected, there are a few
things you need to add to your implementation:
* Cycle collected refcounting. Special refcounting is needed so that
the CC can tell when an object is created, used, or destroyed, so
that it can determine if an object is potentially part of a garbage
cycle.
* Traversal. Once the CC has decided an object **might** be garbage,
it needs to know what other cycle collected objects it holds strong
references to. This is done with a "traverse" method.
* Unlinking. Once the CC has decided that an object **is** garbage, it
needs to break the cycles by clearing out all strong references to
other cycle collected objects. This is done with an "unlink"
method. This usually looks very similar to the traverse method.
The traverse and unlink methods, along with other methods needed for
cycle collection, are defined on a special inner class object, called a
"participant", for performance reasons. The existence of the
participant is mostly hidden behind macros so you shouldn't need
to worry about it.
Next, we'll go over what the declaration and definition of these parts
look like. (Spoiler: there are lots of `ALL_CAPS` macros.) This will
mostly cover the most common variants. If you need something slightly
different, you should look at the location of the declaration of the
macros we mention here and see if the variants already exist.
Reference counting
==================
nsISupports
-----------
If your class inherits from nsISupports, you'll need to add
`NS_DECL_CYCLE_COLLECTING_ISUPPORTS` to the class declaration. This
will declare the QueryInterface (QI), AddRef and Release methods you
need to implement nsISupports, as well as the actual refcount field.
In the `.cpp` file for your class you'll have to define the
QueryInterface, AddRef and Release methods. The ins and outs of
defining the QI method is out-of-scope for this document, but you'll
need to use the special cycle collection variants of the macros, like
`NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION`. (This is because we use
the nsISupports system to define a special interface used to
dynamically find the correct CC participant for the object.)
Finally, you'll have to actually define the AddRef and Release methods
for your class. If your class is called `MyClass`, then you'd do this
with the declarations `NS_IMPL_CYCLE_COLLECTING_ADDREF(MyClass)` and
`NS_IMPL_CYCLE_COLLECTING_RELEASE(MyClass)`.
non-nsISupports
---------------
If your class does **not** inherit from nsISupports, you'll need to
add `NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING` to the class
declaration. This will give inline definitions for the AddRef and
Release methods, as well as the actual refcount field.
Cycle collector participant
===========================
Next we need to declare and define the cycle collector
participant. This is mostly boilerplate hidden behind macros, but you
will need to specify which fields are to be traversed and unlinked
because they are strong references to cycle collected objects.
Declaration
-----------
First, we need to add a declaration for the participant. As before,
let's say your class is `MyClass`.
The basic way to declare this for an nsISupports class is
`NS_DECL_CYCLE_COLLECTION_CLASS(MyClass)`.
If your class inherits from multiple classes that inherit from
nsISupports classes, say `Parent1` and `Parent2`, then you'll need to
use `NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(MyClass, Parent1)` to
tell the CC to cast to nsISupports via `Parent1`. You probably want to
pick the first class it inherits from. (The cycle collector needs to be
able to cast `MyClass*` to `nsISupports*`.)
Another situation you might encounter is that your nsISupports class
inherits from another cycle collected class `CycleCollectedParent`. In
that case, your participant declaration will look like
`NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(MyClass,
CycleCollectedParent)`. (This is needed so that the CC can cast from
nsISupports down to `MyClass`.) Note that we do not support inheritance
for non-nsISupports classes.
If your class is non-nsISupports, then you'll need to use the `NATIVE`
family of macros, like
`NS_DECL_CYCLE_COLLECTION_NATIVE_CLASS(MyClass)`.
In addition to these modifiers, these different variations have
further `SCRIPT_HOLDER` variations which are needed if your class
holds alive JavaScript objects. This is because the tracing of JS
objects held alive by this class must be declared separately from the
tracing of C++ objects held alive by this class so that the garbage
collector can also use the tracing. For example,
`NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_AMBIGUOUS(MyClass,
Parent1)`.
There are also `WRAPPERCACHE` variants of the macros which you need to
use if your class is wrapper cached. These are effectively a
specialized form of `SCRIPT_HOLDER`, as a cached wrapper is a
JS object held alive by the C++ object. For example,
`NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS_AMBIGUOUS(MyClass,
Parent1)`.
There is yet another variant of these macros, `SKIPPABLE`. This
document won't go into detail here about how this works, but the basic
idea is that a class can tell the CC when it is definitely alive,
which lets the CC skip it. This is a very important optimization for
things like DOM elements in active documents, but a new class you are
making cycle collected is likely not common enough to worry about.
Implementation
--------------
Finally, you must write the actual implementation of the CC
participant, in the .cpp file for your class. This will define the
traverse and unlink methods, and some other random helper
functions. In the simplest case, this can be done with a single macro
like this: `NS_IMPL_CYCLE_COLLECTION(MyClass, mField1, mField2,
mField3)`, where `mField1` and the rest are the names of the fields of
your class that are strong references to cycle collected
objects. There is some template magic that says how many common types
like RefPtr, nsCOMPtr, and even some arrays, should be traversed and
unlinked. There’s also a variant `NS_IMPL_CYCLE_COLLECTION_INHERITED`,
which you should use when there’s a parent class that is also cycle
collected, to ensure that fields of the parent class are traversed and
unlinked. The name of that parent class is passed in as the second
argument. If either of these work, then you are done. Your class is
now cycle collected. Note that this does not work for fields that are
JS objects.
However, if that doesn’t work, you’ll have to get into the details a
bit more. A good place to start is by copying the definition of
`NS_IMPL_CYCLE_COLLECTION`.
For a script holder method, you also need to define a trace method in
addition to the traverse and unlink, using
`NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN` and other similar
macros. You'll need to include all of the JS fields that your class
holds alive. The trace method will be used by the GC as well as the
CC, so if you miss something you can end up with use-after-free
crashes. You'll also need to call `mozilla::HoldJSObjects(this);` in
the ctor for your class, and `mozilla::DropJSObjects(this);` in the
dtor. This will register (and unregister) each instance of your object
with the JS runtime, to ensure that it gets traced properly. This
does not apply if you have a wrapper cached class that does not have
any additional JS fields, as nsWrapperCache deals with all of that
for you.