blob: a68de2016a641545ec68a0166029bf85918080b2 [file] [log] [blame] [view]
# Dangling Pointer Guide
A dangling pointer has been found in your patch? This doc will help you fix it.
> This document can also be commented upon:
https://docs.google.com/document/d/19yyWdCQB5KwYtRE5cwadNf30jNSSWwMbim1KJjc-eZQ/edit#
See also the general instructions about the dangling pointer detector:
[docs/dangling_ptr.md](./dangling_ptr.md)
**Table of content**
- [`Case 1` I dont own the affected component](#i-don_t-own-the-affected-component)
- [`Case 2` The dangling pointer does not own the deleted object.](#the-dangling-pointer-does-not-own-the-deleted-object)
- [Incorrect destruction order](#incorrect-destruction-order)
- [Observer callback](#observer-callback)
- [Challenging lifespan](#challenging-lifespan)
- [Fallback solution](#fallback-solution)
- [`Case 3` The pointer manages ownership over the object](#the-pointer-manages-ownership-over-the-object)
- [Smart pointers](#smart-pointers)
- [Object vended from C API](#object-vended-from-c-api)
- [Object conditionally owned](#object-conditionally-owned)
- [Fallback solution](#fallback-solution-1)
## `Case 1` I don’t own the affected component
If you do not directly own the affected component, you **don't need** to solve
the issue yourself… though doing so is a great way to learn about and improve
the codebase.
Please annotate the pointer with `DanglingUntriaged`, and indicate the test case
that can be used to reproduce.
```cpp
// Dangling when executing TestSuite.TestCase on Windows:
raw_ptr<T, DanglingUntriaged> ptr_;
```
Opening and filling a P2 bug is a nice thing to do, but it is not required.
**Rationale:**
Engineers might uncover new dangling pointers, by testing new code paths.
Knowing about dangling pointers is a purely positive increment. In some cases,
the affected component belongs to a different team. We don’t want to disrupt
engineers achieving their primary goal, if they only “discover” a dangling
pointer. Annotating the pointer makes the issue visible directly in the code,
improving our knowledge of Chrome.
## `Case 2` The dangling pointer does not own the deleted object
### Incorrect destruction order
This represents ~25% of the dangling pointers.
In the majority of cases, this happens when dependent objects are declared in
the wrong order in a class, causing the dependency to be released first, thus
creating a dangling pointer in the other.
It is important to reorder them correctly to prevent pre-existing and future UAF
in destructors.
See [Fix member declaration order](https://docs.google.com/document/d/11YYsyPF9rQv_QFf982Khie3YuNPXV0NdhzJPojpZfco/edit?resourcekey=0-h1dr1uDzZGU7YWHth5TRAQ#bookmark=id.jgjtzldk9pvc) and [Fix reset ordering](https://docs.google.com/document/d/11YYsyPF9rQv_QFf982Khie3YuNPXV0NdhzJPojpZfco/edit?resourcekey=0-h1dr1uDzZGU7YWHth5TRAQ#bookmark=id.xdam727ioy4q) examples.
### Observer callback
This represents ~4% of the dangling pointers.
It is important to clear the pointer when the object is about to be deleted.
Chrome uses the observer pattern heavily. In some cases, the observer does not
clear its pointer toward the observed class when notified of its destruction.
### Challenging lifespan
It can be challenging to deal with an object's lifespan. Sometimes, the lifetime
of two objects are completely different.
Removing the pointer may be a good thing to do. Sometimes, it can be replaced
by:
- Passing the pointer as a function argument instead of getting access to it
from a long-lived field.
- A token / ID. For instance
[blink::LocalFrameToken](https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/public/common/tokens/tokens.h;drc=898134d0d40dbbcd308e7d51655518ac7c6392b5;l=34),
[content::GlobalRenderFrameHostId](https://source.chromium.org/chromium/chromium/src/+/main:content/public/browser/global_routing_id.h;drc=898134d0d40dbbcd308e7d51655518ac7c6392b5;l=64)
- A [WeakPtr](https://docs.google.com/document/d/11YYsyPF9rQv_QFf982Khie3YuNPXV0NdhzJPojpZfco/edit?resourcekey=0-h1dr1uDzZGU7YWHth5TRAQ#bookmark=id.geuhahom0twd)
- [Calling a function](https://docs.google.com/document/d/11YYsyPF9rQv_QFf982Khie3YuNPXV0NdhzJPojpZfco/edit?resourcekey=0-h1dr1uDzZGU7YWHth5TRAQ#heading=h.wh99ri7bbq23)
### Fallback solution
As a last resort, when the situation is perfectly understood, and you believe it
is better to let the pointer dangle, the raw_ptr can be annotated with
`DisableDanglingPtrDetection`. A comment explaining why this is safe must be
added.
```cpp
// DisableDanglingPtrDetection option for raw_ptr annotates
// "intentional-and-safe" dangling pointers. It is meant to be used at the
// margin, only if there is no better way to re-architecture the code.
//
// Usage:
// raw_ptr<T, DisableDanglingPtrDetection> dangling_ptr;
//
// When using it, please provide a justification about what guarantees that it
// will never be dereferenced after becoming dangling.
```
**In emergency situations**: `DanglingUntriaged` can be used similarly, in case
your patch needs to land as soon as possible.
## `Case 3` The pointer manages ownership over the object
raw_ptr, just like raw pointers T*, are not meant to keep an object alive. It is
preferable not to manage memory manually using them and new/delete. Calling
delete on a raw_ptr will cause the raw_ptr to become immediately dangling.
### Smart pointers
First, consider replacing the raw_ptr with a smart pointer:
- std::unique_ptr (See
[example](https://docs.google.com/document/d/11YYsyPF9rQv_QFf982Khie3YuNPXV0NdhzJPojpZfco/edit?resourcekey=0-h1dr1uDzZGU7YWHth5TRAQ#heading=h.6itq8twigqt3))
- scoped_refptr
### Object vended from C API
In some cases, the object is vended by a C API. It means new/delete are not
used, but some equivalent API. In this case, it still makes sense to use a
unique_ptr, but with a custom deleter:
[example](https://chromium-review.googlesource.com/c/chromium/src/+/3650764).
For most common objects, a wrapper already exists:
[base::ScopedFILE](https://source.chromium.org/chromium/chromium/src/+/main:base/files/scoped_file.h;drc=898134d0d40dbbcd308e7d51655518ac7c6392b5;l=105),
[base::ScopedTempDir](https://source.chromium.org/chromium/chromium/src/+/main:base/files/scoped_temp_dir.h;l=25?q=ScopedTempDir&sq=&ss=chromium%2Fchromium%2Fsrc),
..
### Object conditionally owned
In some cases, the raw_ptr conditionally owns the memory, depending on some
logic or some `is_owned` boolean. This can still use a unique_ptr
([example](https://chromium-review.googlesource.com/c/chromium/src/+/3829302))
```cpp
std::unique_ptr<T> t_owned_;
raw_ptr<T> t_; // Reference `t_owned_` or an external object.
```
### Fallback solution
If no solution with a smart pointer is found:
You can use `raw_ptr::ClearAndDelete()` or `raw_ptr::ClearAndDeleteArray()` to
clear the pointer and free the object in a single operation.
|Before|After |
|--|--|
| `delete ptr_` | `ptr_.ClearAndDelete();`|
| `delete array_` | `ptr_.ClearAndDeleteArray();`|
When delete is not used, but the deletion happens through some C API or some
external manager, the `raw_ptr::ExtractAsDangling()` can be used to clear the
pointer, and return the pointer to be passed to the API. The return value must
not be saved, thus outliving the line where it was called.
This method should be used wisely, and only if there is no other way to rework
the dangling raw_ptr.
|Before|After |
|--|--|
|`ExternalAPIDelete(ptr_);`|`ExternalAPIDelete(ptr_.ExtractAsDangling());`|