| # Zircon thread safety annotations |
| |
| Zircon code takes advantage of clang's thread safety analysis feature to |
| document and machine-verify some of our synchronization invariants. These |
| annotations are checked when building for clang (see |
| [getting started](/docs/development/kernel/getting_started.md) for instructions on building with |
| clang). |
| |
| ## How to use |
| |
| [Clang's documentation](https://clang.llvm.org/docs/ThreadSafetyAnalysis.html) |
| |
| In Zircon, we provide our own set of macros wrapping the annotations and have |
| annotated our synchronization primitives. When writing new code involving |
| synchronization or annotating existing code, in most cases you should use the |
| thread annotation macros provided by |
| [<lib/zircon-internal/thread\_annotations.h](/zircon/system/ulib/zircon-internal/include/lib/zircon-internal/thread_annotations.h). |
| These macros all begin with the prefix `"TA_"` for thread analysis. The most |
| commonly used ones are: |
| |
| * `TA_GUARDED(x)` the annotated variable is guarded by the capability (e.g. lock) `x` |
| * `TA_ACQ(x...)` function acquires all of the mutexes in the set `x` and hold them after returning |
| * `TA_REL(x...)` function releases all of the mutexes in the set `x` |
| * `TA_REQ(x...)` function requires that the caller hold all of the mutexes in the set `x` |
| * `TA_EXCL(x...)` function requires that the caller not be holding any of the mutexes in the set `x` |
| |
| For example, a class containing a member variable `'int foo_'` protected by a |
| mutex would be annotated like so: |
| |
| ``` |
| // example.h |
| |
| class Example { |
| public: |
| // Public function has no locking requirements and thus needs no annotation. |
| int IncreaseFoo(int by); |
| |
| private: |
| // This is an internal helper routine that can only be called with |lock_| |
| // held. Calling this without holding |lock_| is a compile-time error. |
| // Annotations like TA_REQ, TA_ACQ, TA_REL, etc are part of the function's |
| // interface and must be on the function declaration, usually in the header, |
| // not the definition. |
| int IncreaseFooLocked(int by) TA_REQ(lock_); |
| |
| // This internal routine requires that both |lock_| and |foo_lock_| be held by the |
| // caller. |
| int IncreaseFooAndBarLocked(int foo_by, int bar_by) TA_REQ(lock_) TA_REQ(bar_lock_); |
| |
| // The TA_GUARDED(lock_) annotation on |foo_| means that |lock_| must be |
| // held to read or write from |foo_|. |
| int foo_ TA_GUARDED(lock_); |
| |
| // |lock_| can be declared after annotations referencing it, |
| // if desired. |
| Mutex lock_; |
| |
| Mutex bar_lock_; |
| }; |
| |
| // example.cpp |
| |
| int Example::IncreaseFoo(int by) { |
| int new_value; |
| { |
| AutoLock lock(&lock_); // fbl::AutoLock is annotated |
| new_value = IncreaseFooLocked(by); |
| } |
| return new_value; |
| } |
| ``` |
| |
| Note that for annotations, which allow sets of mutex objects, one may either |
| apply the annotation multiple times, or provided a comma separated list to the |
| annotation. In other words, the following two declarations are equivalent. |
| |
| ``` |
| int IncreaseFooAndBarLocked(int foo_by, int bar_by) TA_REQ(lock_) TA_REQ(bar_lock_); |
| int IncreaseFooAndBarLocked(int foo_by, int bar_by) TA_REQ(lock_, bar_lock_); |
| ``` |
| |
| Library code exposed through the sysroot must use the more awkwardly named |
| macros provided by |
| [system/public/zircon/compiler.h](/zircon/system/public/zircon/compiler.h) to |
| avoid collisions with consumers of the sysroot. |
| |
| ## Best practices |
| |
| Annotations should complement the comments and identifiers to make the code |
| understandable. Annotations do not replace comments or clear names. Try to |
| follow these best practices when writing code involving locking: |
| |
| * Group member variables protected by a lock with the lock. Where it makes |
| sense, document what is protected by what with a comment in addition to the |
| annotations. For example when several member variables are protected by one lock |
| and several are protected by a different lock, a comment is easier to read than |
| going through each annotation. |
| |
| * Name functions that require a lock be held with a 'Locked()' suffix. If there |
| are multiple locks that could be plausibly held to call the function, consider |
| making the choice clear in the function name. Keep in mind readers of calling |
| code will not be able to see the annotations. |
| |
| ## Limitations |
| |
| The thread safety analysis is a purely static check done at compile time and |
| cannot understand conditionally held locks or locking patterns that span |
| compilation units in ways not expressible via static annotations. In many |
| situations, this analysis is still useful but there are situations that the |
| analysis simply cannot understand. The main escape hatch for disabling analysis |
| is to add the annotation `TA_NO_THREAD_SAFETY_ANALYSIS` to the function definition |
| containing the code the analysis is confused by. Other escape mechanisms are |
| available as well - see the Clang documentation for details. Situations that |
| require disabling the analysis are likely to be complex for humans to understand |
| as well as machines and should be accompanied by a comment indicating the |
| invariants in use. |
| |
| The thread safety analysis can be defeated in a number of ways, for instance |
| when using pointers. For example, when taking the address of a guarded data |
| member Clang loses track of the guard, e.g. for a foo_ protected by a lock_ |
| a call to `memset(&foo_, 0, sizeof(foo_))` without holding lock_ won't be caught |
| as a violation. |