forked from mirrors/gecko-dev
		
	
		
			
				
	
	
		
			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.
 | 
