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.