-
Notifications
You must be signed in to change notification settings - Fork 1.8k
RefPtr Basics
Many objects in WebKit are reference counted. The pattern is that classes have member functions named ref and deref that increment and decrement a reference count. When the deref function is called on an object with a reference count of 1, the object is deleted. Many classes in WebKit implement this pattern by deriving from the RefCounted class template.
This practice started in 2005, when we discovered that there were many memory leaks, especially in HTML editing code, caused by misuse of ref and deref calls. We decided to use smart pointers to mitigate the problem. Early experiments showed that smart pointers led to additional manipulation of reference counts that hurt performance. For example, for a function that took a smart pointer as an argument and returned that same smart pointer as a return value, passing the parameter and returning the value would increment and then decrement the reference count two to four times as the object moved from one smart pointer to another.
We solved that problem in 2005 with a set of smart pointer class templates. C++ move semantics, introduced in C++11, made it possible to streamline those class templates without reintroducing reference count churn.
Later, in 2013, we noticed that our use of pointers in general, and smart pointers in particular, was causing a proliferation of null checks and uncertainty about what can be null. We started using references rather than pointers wherever possible in WebKit code.
Maciej Stachowiak created the class template, RefPtr, that implements WebKit’s intrusive reference counting, and we have since adapted it so that it works well with move semantics. Andreas Kling created a related class template, Ref, which works with RefPtr and provides clarity and even greater efficiency when dealing with reference counted objects in contexts where there is no need for a null pointer.
Later, in 2025, we noticed that many serious security bugs derive from incorrect reference count management. Ryosuke Niwa created static analysis that mandates use of a smart pointer in any non-trivial context in which a reference counted object might otherwise be deleted.
When discussing smart pointers such as the RefPtr class template we use the term raw pointer to refer to the C++ language’s built in pointer type. Here’s the canonical setter function, written with raw pointers:
// example, not preferred style
class Document {
...
Title* m_title { nullptr };
}
Document::~Document()
{
if (m_title)
m_title->deref();
}
void Document::setTitle(Title* title)
{
if (title)
title->ref();
if (m_title)
m_title->deref();
m_title = title;
}
RefPtr is a smart pointer class template that calls ref on incoming values and deref on outgoing values. RefPtr works on any object with both a ref and a deref member function. Here’s the setter function example, written with RefPtr:
// example, not preferred style
class Document {
...
RefPtr<Title> m_title;
}
void Document::setTitle(Title* title)
{
m_title = title;
}
Functions that take ownership of reference counted arguments can lead to reference count churn.
// example, not preferred style
RefPtr untitledTitle = titleFactory().createUniqueUntitledTitle();
document.setTitle(untitledTitle);
The title starts with a reference count of 1. The setTitle function stores it in the data member, and the reference count is incremented to 2. Then the local variable untitledTitle goes out of scope and the reference count is decremented back to 1.
The way to define a function that takes ownership of an object is to use an rvalue reference.
// preferred style
class Document {
...
RefPtr<Title> m_title;
}
void Document::setTitle(RefPtr<Title>&& title)
{
m_title = WTF::move(title);
}
…
RefPtr untitledTitle = titleFactory().createUniqueUntitledTitle();
document.setTitle(WTF::move(untitledTitle));
The title makes it all the way into the data member with a reference count of 1; it’s never incremented or decremented.
Note the use of WTF::move instead of std::move. The WTF version adds a couple of compile time checks to catch common errors, and should be used throughout the WebKit project in place of std::move.
Note that RefPtr does not need an explicit template parameter when initialized by assignment. The template parameter is deduced based on the pointer's type.
Ref is like RefPtr, except that it acts like a reference rather than a pointer; it doesn’t have a null value.
Ref works particularly well with return values; it’s often straightforward to be sure that a newly created object will never be null.
// preferred style
Ref<Title> TitleFactory::createUniqueUntitledTitle()
{
return createTitle(makeString("untitled "_s, m_nextAvailableUntitledNumber++));
}
Using Ref helps makes it clear to the caller that this function will never return null.
The RefPtr class mixes with raw pointers much as the smart pointers in the C++ standard library, such as std::unique_ptr, do.
When using a RefPtr or Ref to call a function that takes a raw pointer or reference, use implicit type conversion.
printNode(stderr, refPtr);
printNodeNonNull(stderr, ref);
Many operations can be done on a RefPtr directly, without resorting to an explicit .get() or .ptr() call.
Ref a = createSpecialNode(); // Ref<Node>
RefPtr b = findNode(); // RefPtr<Node>
Node* c = getOrdinaryNode();
// the * operator
*b = value;
// the -> operator
a->clear();
b->clear();
// null check in an if statement
if (b)
log("not empty");
// the ! operator
if (!b)
log("empty");
// the == and != operators, mixing with raw pointers
if (b == c)
log("equal");
if (b != c)
log("not equal");
// some type casts
RefPtr d = static_pointer_cast<DerivedNode>(d);
New objects of classes that make use of the RefCounted class template are created with a reference count of 1. The best programming idiom to use is to put such objects right into a Ref to make it impossible to forget to deref the object when done with it. This means that anyone calling new on such an object should immediately call adoptRef. adoptRef produces a Ref or RefPtr without increasing the refcount. In WebKit we use functions named create instead of direct calls to new for these classes.
// preferred style
Ref<Node> Node::create()
{
return adoptRef(*new Node);
}
Ref node = Node::create();
Because of the way adoptRef is implemented, this is an efficient idiom. The object starts with a reference count of 1 and no code is generated to examine or modify the reference count.
// preferred style
Ref<Node> createSpecialNode()
{
Ref a = Node::create();
a->setCreated(true);
return a;
}
Ref b = createSpecialNode();
The node object is put into a Ref by a call to adoptRef inside Node::create, then passes into a return value and is passed into b, all without touching the reference count.
The RefCounted class implements a runtime check so we get an assertion failure if we create an object and call ref or deref without first calling adoptRef.
A function call can have unintended side-effects that might delete an object that's in use by the caller or callee. A common example is firing an event, which executes arbitrary user-specified JavaScript, which can remove a Node from the DOM.
We declare a RefPtr or Ref in local scope to ensure an object's lifetime across a function call. Inside a function call, we assume that any raw pointer function argument already has a RefPtr or Ref in the caller's scope.
// preferred style
void fireEventOnANode(Document* document)
{
Ref a = document->aNode(); // caller holds a RefPtr for 'document'
a->fireEvent(); // we hold a Ref for 'a'
}
Sometimes a RefPtr data member can create a reference cycle, resulting in a storage leak.
// example, unwanted storage leak
class Document {
...
RefPtr<Title> m_title;
}
class Title {
...
RefPtr<Document> m_document; // reference cycle!
}
WeakPtr is a smart pointer type that, instead of extending the lifetime of a pointer, becomes null automatically when the pointed-to object is deleted. Use WeakPtr to resolve reference cycles.
// example, storage leak fixed
class Document {
...
RefPtr<Title> m_title;
}
class Title {
...
WeakPtr<Document> m_document;
}
We’ve developed these guidelines for use of RefPtr and Ref in WebKit code.
For all submitted patches, these guidelines are also enforced by a clang static analysis run by the SaferCPP EWS bot.
If the code needs to hold ownership or guarantee lifetime, a local variable should be a Ref, or if it can be null, a RefPtr. If the scope of usage is trivial, or an overlooking local Ref or RefPtr ensures ownership, a local variable can be a raw reference or pointer.
If the class needs to hold ownership or guarantee lifetime, the data member should be a Ref or RefPtr. If a Ref or RefPtr would cause a cycle, and therefore a storage leak, the data member should be WeakPtr.
When a Vector or HashMap is a data member, the type stored in it should be Ref or RefPtr (or WeakPtr), as they would be if they were direct data members. When a Vector or HashMap is a local variable, the type stored in it should be Ref or RefPtr (or WeakPtr), as they would be if they were direct local variables.
If a function does not take ownership of an object, the argument should be a raw reference or raw pointer. If a function does take ownership of an object, the argument should be a Ref&& or a RefPtr&&. This includes many setter functions.
If a function’s result is an object, but ownership is not being transferred, the result should be a raw reference or raw pointer. This includes most getter functions. If a function’s result is a new object or ownership is being transferred for any other reason, the result should be a Ref or RefPtr.
New objects should be put into a Ref as soon as possible after creation to allow the smart pointers to do all reference counting automatically. For RefCounted objects, the above should be done with the adoptRef function. Best idiom is to use a private constructor and a public create function that returns a Ref.
- Giving a function argument a type of Ref, RefPtr, Ref&&, or RefPtr&& when it should instead be a raw reference or raw pointer. A function that sometimes takes ownership can work just fine with a raw reference or raw pointer. The rvalue reference form is appropriate when passing ownership is the primary way the function is used and is the case that needs to be optimized. Not all setters need to take an rvalue reference.
- Forgetting to call WTF::move can result in unnecessary reference count churn.
- Calling WTF::move when returning a value can result in unnecessary reference count churn (by disabling the named return value optimization).
One or more of the following topics could be covered by this document.
- How we we mix reference counting with garbage collection to implement the DOM and the JavaScript and Objective-C DOM bindings.
- Comparison of WebKit intrusive reference counting with other schemes such as the external reference counting in std::shared_ptr.
- Guidelines for use of std::unique_ptr and std::make_unique.
- RetainPtr
- CheckedPtr