gvisor.dev/gvisor@v0.0.0-20240520182842-f9d4d51c7e0f/tools/checklocks/README.md (about) 1 # CheckLocks Analyzer 2 3 <!--* freshness: { owner: 'gvisor-eng' reviewed: '2022-02-02' } *--> 4 5 Checklocks is an analyzer for lock and atomic constraints. The analyzer relies 6 on explicit annotations to identify fields that should be checked for access. 7 8 ## Installation and Usage 9 10 The analyzer is integrated into the gVisor `nogo` framework. It automatically 11 applies to all code in this repository. 12 13 For external usage and to iterate quickly, it may be used as part of `go vet`. 14 You can install the tool separately via: 15 16 ```sh 17 go install gvisor.dev/gvisor/tools/checklocks/cmd/checklocks@go 18 ``` 19 20 And, if installed to the default path, run it via: 21 22 ```sh 23 go vet -vettool=$HOME/go/bin/checklocks ./... 24 ``` 25 26 ## Annotations 27 28 This analyzer supports annotations for atomic access and lock enforcement, in 29 order to allow for mixed semantics. These are first described separately, then 30 the combination is discussed. 31 32 ### Atomic Access Enforcement 33 34 Individual struct members may be noted as requiring atomic access. These 35 annotations are of the form `+checkatomic`, for example: 36 37 ```go 38 type foo struct { 39 // +checkatomic 40 bar int32 41 } 42 ``` 43 44 This will ensure that all accesses to bar are atomic, with the exception of 45 operations on newly allocated objects (when detectable). 46 47 ## Lock Enforcement 48 49 Individual struct members may be protected by annotations that indicate locking 50 requirements for accessing members. These annotations are of the form 51 `+checklocks`, for example: 52 53 ```go 54 type foo struct { 55 mu sync.Mutex 56 57 // +checklocks:mu 58 bar int 59 60 foo int // No annotation on foo means it's not guarded by mu. 61 62 secondMu sync.RWMutex 63 64 // Multiple annotations indicate that both must be held but the 65 // checker does not assert any lock ordering. 66 // +checklocks:secondMu 67 // +checklocks:mu 68 foobar int 69 } 70 ``` 71 72 These semantics are enforcable on `sync.Mutex`, `sync.RWMutex` and `sync.Locker` 73 fields. Semantics with respect to reading and writing are automatically detected 74 and enforced. If an access is read-only, then the lock need only be held as a 75 read lock, in the case of an `sync.RWMutex`. 76 77 The locks must be resolvable within the scope of the declaration. This means the 78 lock must refer to one of: 79 80 * A struct-local lock (e.g. mu). 81 * A lock resolvable from the local struct (e.g. fieldX.mu). 82 * A global lock (e.g. globalMu). 83 * A lock resolvable from a global struct (e.g. globalX.mu). 84 85 Like atomic access enforcement, checks may be elided on newly allocated objects. 86 87 ### Function Annotations 88 89 The `+checklocks` annotation may apply to functions. For example: 90 91 ```go 92 // +checklocks:f.mu 93 func (f *foo) doThingLocked() { } 94 ``` 95 96 The field provided in the `+checklocks` annotation must be resolvable as one of: 97 98 * A parameter, receiver or return value (e.g. mu). 99 * A lock resolvable from a parameter, receiver or return value (e.g. f.mu). 100 * A global lock (e.g. globalMu). 101 * A lock resolvable from a global struct (e.g. globalX.mu). 102 103 This annotation will ensure that the given lock is held for all calls, and all 104 analysis of this function will assume that this is the case. 105 106 Additional variants of the `+checklocks` annotation are supported for functions: 107 108 * `+checklocksread`: This enforces that at least a read lock is held. Note 109 that this assumption will apply locally, so accesses and function calls will 110 assume that only a read lock is available. 111 * `+checklocksacquire`: This enforces that the given lock is *not* held on 112 entry, but it will be held on exit. This assertion will be checked locally 113 and applied to the caller's lock state. 114 * `+checklocksrelease`: This enforces that the given lock is held on entry, 115 and will be release on exit. This assertion is checked locally and applied 116 to the caller's lock state. 117 * `+checklocksacquireread`: A read variant of `+checklocksacquire`. 118 * `+checklocksreleaseread`: A read variant of `+checklocksrelease`. 119 * `+checklocksalias:a.b.c=x.y`: For parameters with complex relationships, 120 this annotation can be used to specify that the `a.b.c` lock is equivalent 121 to the `x.y` state. That is, any operation on either of these locks applies 122 to both, and any assertions that can be made about either applies to both. 123 124 For examples of these cases see the tests. 125 126 #### Anonymous Functions and Closures 127 128 Anonymous functions and closures cannot be annotated. 129 130 If anonymous functions and closures are bound and invoked within a single scope, 131 the analysis will happen with the available lock state. For example, the 132 following will not report any violations: 133 134 ```go 135 func foo(ts *testStruct) { 136 x := func() { 137 ts.guardedField = 1 138 } 139 ts.mu.Lock() 140 x() // We know the context x is being invoked. 141 ts.mu.Unlock() 142 } 143 ``` 144 145 This pattern often applies to defer usage, which allows defered functions to be 146 fully analyzed with the lock state at time of execution. 147 148 However, if a closure is passed to another function, the anonymous function 149 backing that closure will be analyzed assuming no available lock state. For 150 example, the following will report violations: 151 152 ```go 153 func runFunc(f func()) { 154 f() 155 } 156 157 func foo(ts *testStruct) { 158 x := func() { 159 ts.guardedField = 1 160 } 161 ts.mu.Lock() 162 runFunc(x) // We can't know what will happen with x. 163 ts.mu.Unlock() 164 } 165 ``` 166 167 Since x cannot be annotated, this may require use of the force annotation used 168 below. However, if anonymous functions and closures require annotations, there 169 may be an opportunity to split them into named functions for improved analysis 170 and debuggability, and avoid the need to use force annotations. 171 172 ### Mixed Atomic Access and Lock Enforcement 173 174 Some members may allow read-only atomic access, but be protected against writes 175 by a mutex. Generally, this imposes the following requirements: 176 177 For a read, one of the following must be true: 178 179 1. A lock held be held. 180 1. The access is atomic. 181 182 For a write, both of the following must be true: 183 184 1. The lock must be held. 185 1. The write must be atomic. 186 187 In order to annotate a relevant field, simply apply *both* annotations from 188 above. For example: 189 190 ```go 191 type foo struct { 192 mu sync.Mutex 193 // +checklocks:mu 194 // +checkatomic 195 bar int32 196 } 197 ``` 198 199 This enforces that the preconditions above are upheld. 200 201 ## Ignoring and Forcing 202 203 From time to time, it may be necessary to ignore results produced by the 204 analyzer. These can be disabled on a per-field, per-function or per-line basis. 205 206 For fields, only lock suggestions may be ignored. See below for details. 207 208 For functions, the `+checklocksignore` annotation can be applied. This prevents 209 any local analysis from taking place. Note that the other annotations can still 210 be applied to the function, which will enforce assertions in caller analysis. 211 For example: 212 213 ```go 214 // +checklocks:ts.mu 215 // +checklocksignore 216 func foo(ts *testStruct) { 217 ts.guardedField = 1 218 } 219 ``` 220 221 For individual lines, the `+checklocksforce` annotation can be applied after the 222 statement. This does not simply ignore the line, rather it *forces* the 223 necessary assertion to become true. For example, if a lock must be held, this 224 annotation will mark that lock as held for all subsequent lines. For example: 225 226 ```go 227 func foo(ts *testStruct) { 228 ts.guardedField = 1 // +checklocksforce: don't care about locking. 229 } 230 ``` 231 232 In general, both annotations should be highly discouraged. It should be possible 233 to avoid their use by factoring functions in such a way that annotations can be 234 applied consistently and without the need for ignoring and forcing. 235 236 ## Testing 237 238 Tests can be built using the `+checklocksfail` annotation. When applied after a 239 statement, these will generate a report if the line does *not* fail an 240 assertion. For example: 241 242 ```go 243 func foo(ts *testStruct) { 244 ts.guardedField = 1 // +checklocksfail: violation. 245 } 246 ``` 247 248 These annotations are primarily useful for analyzer development and testing. 249 250 ## Suggestions 251 252 Based on locks held during field access, the analyzer may suggest annotations. 253 These can be ignored with the `+checklocksignore` annotation on fields. 254 255 ```go 256 type foo struct { 257 mu sync.Mutex 258 // +checklocksignore: mu is not required, it just happens to be held always. 259 bar int32 260 } 261 ``` 262 263 The annotation will be generated when the lock is held the vast majority of the 264 time the field is accessed. Note that it is possible for this frequency to be 265 greater than 100%, if the lock is held multiple times. For example: 266 267 ```go 268 func foo(ts1 *testStruct, ts2 *testStruct) { 269 ts1.Lock() 270 ts2.Lock() 271 ts1.guardedField = 1 // 200% locks held. 272 ts1.Unlock() 273 ts2.Unlock() 274 } 275 ``` 276 277 It should be expected that this annotation is also rare. If the field is not 278 protected by the mutex, it suggests that the critical section could be made 279 smaller by restructuring the code or the structure instead of applying the 280 ignore annotation.