mirror of
				https://github.com/mozilla/gecko-dev.git
				synced 2025-11-04 10:18:41 +02:00 
			
		
		
		
	
		
			
				
	
	
		
			190 lines
		
	
	
	
		
			8.5 KiB
		
	
	
	
		
			ReStructuredText
		
	
	
	
	
	
			
		
		
	
	
			190 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.
 |