mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-23 21:01:08 +00:00
caf3f02b3f
Differential Revision: https://phabricator.services.mozilla.com/D166234
191 lines
8.5 KiB
ReStructuredText
191 lines
8.5 KiB
ReStructuredText
=======================================
|
||
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.
|