summaryrefslogtreecommitdiffhomepage
path: root/tools/checklocks
diff options
context:
space:
mode:
Diffstat (limited to 'tools/checklocks')
-rw-r--r--tools/checklocks/BUILD21
-rw-r--r--tools/checklocks/README.md153
-rw-r--r--tools/checklocks/analysis.go820
-rw-r--r--tools/checklocks/annotations.go133
-rw-r--r--tools/checklocks/checklocks.go196
-rw-r--r--tools/checklocks/facts.go836
-rw-r--r--tools/checklocks/state.go377
-rw-r--r--tools/checklocks/test/BUILD30
-rw-r--r--tools/checklocks/test/aliases.go26
-rw-r--r--tools/checklocks/test/alignment.go51
-rw-r--r--tools/checklocks/test/anon.go35
-rw-r--r--tools/checklocks/test/atomics.go91
-rw-r--r--tools/checklocks/test/basics.go145
-rw-r--r--tools/checklocks/test/branches.go72
-rw-r--r--tools/checklocks/test/closures.go118
-rw-r--r--tools/checklocks/test/defer.go38
-rw-r--r--tools/checklocks/test/globals.go85
-rw-r--r--tools/checklocks/test/incompat.go45
-rw-r--r--tools/checklocks/test/inferred.go35
-rw-r--r--tools/checklocks/test/locker.go33
-rw-r--r--tools/checklocks/test/methods.go117
-rw-r--r--tools/checklocks/test/parameters.go48
-rw-r--r--tools/checklocks/test/return.go61
-rw-r--r--tools/checklocks/test/rwmutex.go52
-rw-r--r--tools/checklocks/test/test.go64
25 files changed, 0 insertions, 3682 deletions
diff --git a/tools/checklocks/BUILD b/tools/checklocks/BUILD
deleted file mode 100644
index d23b7cde6..000000000
--- a/tools/checklocks/BUILD
+++ /dev/null
@@ -1,21 +0,0 @@
-load("//tools:defs.bzl", "go_library")
-
-package(licenses = ["notice"])
-
-go_library(
- name = "checklocks",
- srcs = [
- "analysis.go",
- "annotations.go",
- "checklocks.go",
- "facts.go",
- "state.go",
- ],
- nogo = False,
- visibility = ["//tools/nogo:__subpackages__"],
- deps = [
- "@org_golang_x_tools//go/analysis:go_default_library",
- "@org_golang_x_tools//go/analysis/passes/buildssa:go_default_library",
- "@org_golang_x_tools//go/ssa:go_default_library",
- ],
-)
diff --git a/tools/checklocks/README.md b/tools/checklocks/README.md
deleted file mode 100644
index 7444acfa0..000000000
--- a/tools/checklocks/README.md
+++ /dev/null
@@ -1,153 +0,0 @@
-# CheckLocks Analyzer
-
-<!--* freshness: { owner: 'gvisor-eng' reviewed: '2021-10-20' } *-->
-
-Checklocks is an analyzer for lock and atomic constraints. The analyzer relies
-on explicit annotations to identify fields that should be checked for access.
-
-## Atomic annotations
-
-Individual struct members may be noted as requiring atomic access. These
-annotations are of the form:
-
-```go
-type foo struct {
- // +checkatomic
- bar int32
-}
-```
-
-This will ensure that all accesses to bar are atomic, with the exception of
-operations on newly allocated objects.
-
-## Lock annotations
-
-Individual struct members may be protected by annotations that indicate locking
-requirements for accessing members. These annotations are of the form:
-
-```go
-type foo struct {
- mu sync.Mutex
- // +checklocks:mu
- bar int
-
- foo int // No annotation on foo means it's not guarded by mu.
-
- secondMu sync.Mutex
-
- // Multiple annotations indicate that both must be held but the
- // checker does not assert any lock ordering.
- // +checklocks:secondMu
- // +checklocks:mu
- foobar int
-}
-```
-
-The checklocks annotation may also apply to functions. For example:
-
-```go
-// +checklocks:f.mu
-func (f *foo) doThingLocked() { }
-```
-
-This will check that the "f.mu" is locked for any calls, where possible.
-
-In case of functions which initialize structs that may have annotations one can
-use the following annotation on the function to disable reporting by the lock
-checker. The lock checker will still track any mutexes acquired or released but
-won't report any failures for this function for unguarded field access.
-
-```go
-// +checklocks:ignore
-func newXXX() *X {
-...
-}
-```
-
-***The checker treats both 'sync.Mutex' and 'sync.RWMutex' identically, i.e, as
-a sync.Mutex. The checker does not distinguish between read locks vs. exclusive
-locks and treats all locks as exclusive locks***.
-
-For cases the checker is able to correctly handle today please see test/test.go.
-
-The checklocks check also flags any invalid annotations where the mutex
-annotation refers either to something that is not a 'sync.Mutex' or
-'sync.RWMutex' or where the field does not exist at all. This will prevent the
-annotations from becoming stale over time as fields are renamed, etc.
-
-## Lock suggestions
-
-Based on locks held during field access, the analyzer will suggest annotations.
-These can be ignored with the standard `+checklocksignore` annotation.
-
-The annotation will be generated when the lock is held the vast majority of the
-time the field is accessed. Note that it is possible for this frequency to be
-greater than 100%, if the lock is held multiple times. For example:
-
-```go
-func foo(ts1 *testStruct, ts2 *testStruct) {
- ts1.Lock()
- ts2.Lock()
- ts1.gaurdedField = 1 // 200% locks held.
- ts1.Unlock()
- ts2.Unlock()
-}
-```
-
-## Currently not supported
-
-1. Anonymous functions are not correctly evaluated. The analyzer does not
- currently support specifying annotations on anonymous functions as a result
- evaluation of a function that accesses protected fields will fail.
-
-```go
-type A struct {
- mu sync.Mutex
-
- // +checklocks:mu
- x int
-}
-
-func abc() {
- var a A
- f := func() { a.x = 1 } <=== This line will be flagged by analyzer
- a.mu.Lock()
- f()
- a.mu.Unlock()
-}
-```
-
-### Explicitly Not Supported
-
-1. The checker will not support guards on anything other than the cases
- described above. For example, global mutexes cannot be referred to by
- checklocks. Only struct members can be used.
-
-2. The checker will not support checking for lock ordering violations.
-
-## Mixed mode
-
-Some members may allow read-only atomic access, but be protected against writes
-by a mutex. Generally, this imposes the following requirements:
-
-For a read, one of the following must be true:
-
-1. A lock held be held.
-1. The access is atomic.
-
-For a write, both of the following must be true:
-
-1. The lock must be held.
-1. The write must be atomic.
-
-In order to annotate a relevant field, simply apply *both* annotations from
-above. For example:
-
-```go
-type foo struct {
- mu sync.Mutex
- // +checklocks:mu
- // +checkatomic
- bar int32
-}
-```
diff --git a/tools/checklocks/analysis.go b/tools/checklocks/analysis.go
deleted file mode 100644
index c3216cc0d..000000000
--- a/tools/checklocks/analysis.go
+++ /dev/null
@@ -1,820 +0,0 @@
-// Copyright 2020 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package checklocks
-
-import (
- "go/token"
- "go/types"
- "strings"
-
- "golang.org/x/tools/go/ssa"
-)
-
-func gcd(a, b atomicAlignment) atomicAlignment {
- for b != 0 {
- a, b = b, a%b
- }
- return a
-}
-
-// typeAlignment returns the type alignment for the given type.
-func (pc *passContext) typeAlignment(pkg *types.Package, obj types.Object) atomicAlignment {
- requiredOffset := atomicAlignment(1)
- if pc.pass.ImportObjectFact(obj, &requiredOffset) {
- return requiredOffset
- }
-
- switch x := obj.Type().Underlying().(type) {
- case *types.Struct:
- fields := make([]*types.Var, x.NumFields())
- for i := 0; i < x.NumFields(); i++ {
- fields[i] = x.Field(i)
- }
- offsets := pc.pass.TypesSizes.Offsetsof(fields)
- for i := 0; i < x.NumFields(); i++ {
- // Check the offset, and then assuming that this offset
- // aligns with the offset for the broader type.
- fieldRequired := pc.typeAlignment(pkg, fields[i])
- if offsets[i]%int64(fieldRequired) != 0 {
- // The offset of this field is not compatible.
- pc.maybeFail(fields[i].Pos(), "have alignment %d, need %d", offsets[i], fieldRequired)
- }
- // Ensure the requiredOffset is the LCM of the offset.
- requiredOffset *= fieldRequired / gcd(requiredOffset, fieldRequired)
- }
- case *types.Array:
- // Export direct alignment requirements.
- if named, ok := x.Elem().(*types.Named); ok {
- requiredOffset = pc.typeAlignment(pkg, named.Obj())
- }
- default:
- // Use the compiler's underlying alignment.
- requiredOffset = atomicAlignment(pc.pass.TypesSizes.Alignof(obj.Type().Underlying()))
- }
-
- if pkg == obj.Pkg() {
- // Cache as an object fact, to subsequent calls. Note that we
- // can only export object facts for the package that we are
- // currently analyzing. There may be no exported facts for
- // array types or alias types, for example.
- pc.pass.ExportObjectFact(obj, &requiredOffset)
- }
-
- return requiredOffset
-}
-
-// checkTypeAlignment checks the alignment of the given type.
-//
-// This calls typeAlignment, which resolves all types recursively. This method
-// should be called for all types individual to ensure full coverage.
-func (pc *passContext) checkTypeAlignment(pkg *types.Package, typ *types.Named) {
- _ = pc.typeAlignment(pkg, typ.Obj())
-}
-
-// checkAtomicCall checks for an atomic access.
-//
-// inst is the instruction analyzed, obj is used only for maybeFail.
-//
-// If mustBeAtomic is true, then we assert that the instruction *is* an atomic
-// fucnction call. If it is false, then we assert that it is *not* an atomic
-// dispatch.
-//
-// If readOnly is true, then only atomic read access are allowed. Note that
-// readOnly is only meaningful if mustBeAtomic is set.
-func (pc *passContext) checkAtomicCall(inst ssa.Instruction, obj types.Object, mustBeAtomic, readOnly bool) {
- switch x := inst.(type) {
- case *ssa.Call:
- if x.Common().IsInvoke() {
- if mustBeAtomic {
- // This is an illegal interface dispatch.
- pc.maybeFail(inst.Pos(), "dynamic dispatch with atomic-only field")
- }
- return
- }
- fn, ok := x.Common().Value.(*ssa.Function)
- if !ok {
- if mustBeAtomic {
- // This is an illegal call to a non-static function.
- pc.maybeFail(inst.Pos(), "dispatch to non-static function with atomic-only field")
- }
- return
- }
- pkg := fn.Package()
- if pkg == nil {
- if mustBeAtomic {
- // This is a call to some shared wrapper function.
- pc.maybeFail(inst.Pos(), "dispatch to shared function or wrapper")
- }
- return
- }
- var lff lockFunctionFacts // Check for exemption.
- if obj := fn.Object(); obj != nil && pc.pass.ImportObjectFact(obj, &lff) && lff.Ignore {
- return
- }
- if name := pkg.Pkg.Name(); name != "atomic" && name != "atomicbitops" {
- if mustBeAtomic {
- // This is an illegal call to a non-atomic package function.
- pc.maybeFail(inst.Pos(), "dispatch to non-atomic function with atomic-only field")
- }
- return
- }
- if !mustBeAtomic {
- // We are *not* expecting an atomic dispatch.
- if _, ok := pc.forced[pc.positionKey(inst.Pos())]; !ok {
- pc.maybeFail(inst.Pos(), "unexpected call to atomic function")
- }
- }
- if !strings.HasPrefix(fn.Name(), "Load") && readOnly {
- // We are not allowing any reads in this context.
- if _, ok := pc.forced[pc.positionKey(inst.Pos())]; !ok {
- pc.maybeFail(inst.Pos(), "unexpected call to atomic write function, is a lock missing?")
- }
- return
- }
- default:
- if mustBeAtomic {
- // This is something else entirely.
- if _, ok := pc.forced[pc.positionKey(inst.Pos())]; !ok {
- pc.maybeFail(inst.Pos(), "illegal use of atomic-only field by %T instruction", inst)
- }
- return
- }
- }
-}
-
-func resolveStruct(typ types.Type) (*types.Struct, bool) {
- structType, ok := typ.Underlying().(*types.Struct)
- if ok {
- return structType, true
- }
- ptrType, ok := typ.Underlying().(*types.Pointer)
- if ok {
- return resolveStruct(ptrType.Elem())
- }
- return nil, false
-}
-
-func findField(typ types.Type, field int) (types.Object, bool) {
- structType, ok := resolveStruct(typ)
- if !ok || field >= structType.NumFields() {
- return nil, false
- }
- return structType.Field(field), true
-}
-
-// almostInst is a generalization over ssa.Field, ssa.FieldAddr, ssa.Global.
-type almostInst interface {
- Pos() token.Pos
- Referrers() *[]ssa.Instruction
-}
-
-// checkGuards checks the guards held.
-//
-// This also enforces atomicity constraints for fields that must be accessed
-// atomically. The parameter isWrite indicates whether this field is used
-// downstream for a write operation.
-//
-// Note that this function is not called if lff.Ignore is true, since it cannot
-// discover any local anonymous functions or closures.
-func (pc *passContext) checkGuards(inst almostInst, from ssa.Value, accessObj types.Object, ls *lockState, isWrite bool) {
- var (
- lgf lockGuardFacts
- guardsFound int
- guardsHeld = make(map[string]struct{}) // Keyed by resolved string.
- )
-
- // Load the facts for the object accessed.
- pc.pass.ImportObjectFact(accessObj, &lgf)
-
- // Check guards held.
- for guardName, fgr := range lgf.GuardedBy {
- guardsFound++
- r := fgr.resolveField(pc, ls, from)
- if !r.valid() {
- // See above; this cannot be forced.
- pc.maybeFail(inst.Pos(), "field %s cannot be resolved", guardName)
- continue
- }
- s, ok := ls.isHeld(r, isWrite)
- if ok {
- guardsHeld[s] = struct{}{}
- continue
- }
- if _, ok := pc.forced[pc.positionKey(inst.Pos())]; ok {
- // Mark this as locked, since it has been forced. All
- // forces are treated as an exclusive lock.
- s, _ := ls.lockField(r, true /* exclusive */)
- guardsHeld[s] = struct{}{}
- continue
- }
- // Note that we may allow this if the disposition is atomic,
- // and we are allowing atomic reads only. This will fall into
- // the atomic disposition check below, which asserts that the
- // access is atomic. Further, len(guardsHeld) < guardsFound
- // will be true for this case, so we require it to be
- // read-only.
- if lgf.AtomicDisposition != atomicRequired {
- // There is no force key, no atomic access and no lock held.
- pc.maybeFail(inst.Pos(), "invalid field access, %s (%s) must be locked when accessing %s (locks: %s)", guardName, s, accessObj.Name(), ls.String())
- }
- }
-
- // Check the atomic access for this field.
- switch lgf.AtomicDisposition {
- case atomicRequired:
- // Check that this is used safely as an input.
- readOnly := len(guardsHeld) < guardsFound
- if refs := inst.Referrers(); refs != nil {
- for _, otherInst := range *refs {
- pc.checkAtomicCall(otherInst, accessObj, true, readOnly)
- }
- }
- // Check that this is not otherwise written non-atomically,
- // even if we do hold all the locks.
- if isWrite {
- pc.maybeFail(inst.Pos(), "non-atomic write of field %s, writes must still be atomic with locks held (locks: %s)", accessObj.Name(), ls.String())
- }
- case atomicDisallow:
- // Check that this is *not* used atomically.
- if refs := inst.Referrers(); refs != nil {
- for _, otherInst := range *refs {
- pc.checkAtomicCall(otherInst, accessObj, false, false)
- }
- }
- }
-
- // Check inferred locks.
- if accessObj.Pkg() == pc.pass.Pkg {
- oo := pc.observationsFor(accessObj)
- oo.total++
- for s, info := range ls.lockedMutexes {
- // Is this an object for which we have facts? If there
- // is no ability to name this object, then we don't
- // bother with any inferrence. We also ignore any self
- // references (e.g. accessing a mutex while you are
- // holding that exact mutex).
- if info.object == nil || accessObj == info.object {
- continue
- }
- // Has this already been held?
- if _, ok := guardsHeld[s]; ok {
- oo.counts[info.object]++
- continue
- }
- // Is this a global? Record directly.
- if _, ok := from.(*ssa.Global); ok {
- oo.counts[info.object]++
- continue
- }
- // Is the object a sibling to the accessObj? We need to
- // check all fields and see if they match. We accept
- // only siblings and globals for this recommendation.
- structType, ok := resolveStruct(from.Type())
- if !ok {
- continue
- }
- for i := 0; i < structType.NumFields(); i++ {
- if fieldObj := structType.Field(i); fieldObj == info.object {
- // Add to the maybe list.
- oo.counts[info.object]++
- }
- }
- }
- }
-}
-
-// checkFieldAccess checks the validity of a field access.
-func (pc *passContext) checkFieldAccess(inst almostInst, structObj ssa.Value, field int, ls *lockState, isWrite bool) {
- fieldObj, _ := findField(structObj.Type(), field)
- pc.checkGuards(inst, structObj, fieldObj, ls, isWrite)
-}
-
-// checkGlobalAccess checks the validity of a global access.
-func (pc *passContext) checkGlobalAccess(g *ssa.Global, ls *lockState, isWrite bool) {
- pc.checkGuards(g, g, g.Object(), ls, isWrite)
-}
-
-func (pc *passContext) checkCall(call callCommon, lff *lockFunctionFacts, ls *lockState) {
- // See: https://godoc.org/golang.org/x/tools/go/ssa#CallCommon
- //
- // "invoke" mode: Method is non-nil, and Value is the underlying value.
- if fn := call.Common().Method; fn != nil {
- var nlff lockFunctionFacts
- pc.pass.ImportObjectFact(fn, &nlff)
- nlff.Ignore = nlff.Ignore || lff.Ignore // Inherit ignore.
- pc.checkFunctionCall(call, fn, &nlff, ls)
- return
- }
-
- // "call" mode: when Method is nil (!IsInvoke), a CallCommon represents an ordinary
- // function call of the value in Value, which may be a *Builtin, a *Function or any
- // other value of kind 'func'.
- //
- // Value may be one of:
- // (a) a *Function, indicating a statically dispatched call
- // to a package-level function, an anonymous function, or
- // a method of a named type.
- //
- // (b) a *MakeClosure, indicating an immediately applied
- // function literal with free variables.
- //
- // (c) a *Builtin, indicating a statically dispatched call
- // to a built-in function.
- //
- // (d) any other value, indicating a dynamically dispatched
- // function call.
- switch fn := call.Common().Value.(type) {
- case *ssa.Function:
- nlff := lockFunctionFacts{
- Ignore: lff.Ignore, // Inherit ignore.
- }
- if obj := fn.Object(); obj != nil {
- pc.pass.ImportObjectFact(obj, &nlff)
- nlff.Ignore = nlff.Ignore || lff.Ignore // See above.
- pc.checkFunctionCall(call, obj.(*types.Func), &nlff, ls)
- } else {
- // Anonymous functions have no facts, and cannot be
- // annotated. We don't check for violations using the
- // function facts, since they cannot exist. Instead, we
- // do a fresh analysis using the current lock state.
- fnls := ls.fork()
- for i, arg := range call.Common().Args {
- fnls.store(fn.Params[i], arg)
- }
- pc.checkFunction(call, fn, &nlff, fnls, true /* force */)
- }
- case *ssa.MakeClosure:
- // Note that creating and then invoking closures locally is
- // allowed, but analysis of passing closures is done when
- // checking individual instructions.
- pc.checkClosure(call, fn, lff, ls)
- default:
- return
- }
-}
-
-// postFunctionCallUpdate updates all conditions.
-func (pc *passContext) postFunctionCallUpdate(call callCommon, lff *lockFunctionFacts, ls *lockState, aliases bool) {
- // Release all locks not still held.
- for fieldName, fg := range lff.HeldOnEntry {
- if _, ok := lff.HeldOnExit[fieldName]; ok {
- continue
- }
- if fg.IsAlias && !aliases {
- continue
- }
- r := fg.Resolver.resolveCall(pc, ls, call.Common().Args, call.Value())
- if !r.valid() {
- // See above: this cannot be forced.
- pc.maybeFail(call.Pos(), "field %s cannot be resolved", fieldName)
- continue
- }
- if s, ok := ls.unlockField(r, fg.Exclusive); !ok && !lff.Ignore {
- if _, ok := pc.forced[pc.positionKey(call.Pos())]; !ok && !lff.Ignore {
- pc.maybeFail(call.Pos(), "attempt to release %s (%s), but not held (locks: %s)", fieldName, s, ls.String())
- }
- }
- }
-
- // Update all held locks if acquired.
- for fieldName, fg := range lff.HeldOnExit {
- if _, ok := lff.HeldOnEntry[fieldName]; ok {
- continue
- }
- if fg.IsAlias && !aliases {
- continue
- }
- // Acquire the lock per the annotation.
- r := fg.Resolver.resolveCall(pc, ls, call.Common().Args, call.Value())
- if s, ok := ls.lockField(r, fg.Exclusive); !ok && !lff.Ignore {
- if _, ok := pc.forced[pc.positionKey(call.Pos())]; !ok && !lff.Ignore {
- pc.maybeFail(call.Pos(), "attempt to acquire %s (%s), but already held (locks: %s)", fieldName, s, ls.String())
- }
- }
- }
-}
-
-// exclusiveStr returns a string describing exclusive requirements.
-func exclusiveStr(exclusive bool) string {
- if exclusive {
- return "exclusively"
- }
- return "non-exclusively"
-}
-
-// checkFunctionCall checks preconditions for function calls, and tracks the
-// lock state by recording relevant calls to sync functions. Note that calls to
-// atomic functions are tracked by checkFieldAccess by looking directly at the
-// referrers (because ordering doesn't matter there, so we need not scan in
-// instruction order).
-func (pc *passContext) checkFunctionCall(call callCommon, fn *types.Func, lff *lockFunctionFacts, ls *lockState) {
- // Extract the "receiver" properly.
- var args []ssa.Value
- if call.Common().Method != nil {
- // This is an interface dispatch for sync.Locker.
- args = append([]ssa.Value{call.Common().Value}, call.Common().Args...)
- } else {
- // This matches the signature for the relevant
- // sync.Lock/sync.Unlock functions below.
- args = call.Common().Args
- }
-
- // Check all guards required are held. Note that this explicitly does
- // not include aliases, hence false being passed below.
- for fieldName, fg := range lff.HeldOnEntry {
- if fg.IsAlias {
- continue
- }
- r := fg.Resolver.resolveCall(pc, ls, args, call.Value())
- if s, ok := ls.isHeld(r, fg.Exclusive); !ok {
- if _, ok := pc.forced[pc.positionKey(call.Pos())]; !ok && !lff.Ignore {
- pc.maybeFail(call.Pos(), "must hold %s %s (%s) to call %s, but not held (locks: %s)", fieldName, exclusiveStr(fg.Exclusive), s, fn.Name(), ls.String())
- } else {
- // Force the lock to be acquired.
- ls.lockField(r, fg.Exclusive)
- }
- }
- }
-
- // Update all lock state accordingly.
- pc.postFunctionCallUpdate(call, lff, ls, false /* aliases */)
-
- // Check if it's a method dispatch for something in the sync package.
- // See: https://godoc.org/golang.org/x/tools/go/ssa#Function
- if fn.Pkg() != nil && fn.Pkg().Name() == "sync" && len(args) > 0 {
- rv := makeResolvedValue(args[0], nil)
- isExclusive := false
- switch fn.Name() {
- case "Lock":
- isExclusive = true
- fallthrough
- case "RLock":
- if s, ok := ls.lockField(rv, isExclusive); !ok && !lff.Ignore {
- if _, ok := pc.forced[pc.positionKey(call.Pos())]; !ok {
- // Double locking a mutex that is already locked.
- pc.maybeFail(call.Pos(), "%s already locked (locks: %s)", s, ls.String())
- }
- }
- case "Unlock":
- isExclusive = true
- fallthrough
- case "RUnlock":
- if s, ok := ls.unlockField(rv, isExclusive); !ok && !lff.Ignore {
- if _, ok := pc.forced[pc.positionKey(call.Pos())]; !ok {
- // Unlocking something that is already unlocked.
- pc.maybeFail(call.Pos(), "%s already unlocked or locked differently (locks: %s)", s, ls.String())
- }
- }
- case "DowngradeLock":
- if s, ok := ls.downgradeField(rv); !ok {
- if _, ok := pc.forced[pc.positionKey(call.Pos())]; !ok && !lff.Ignore {
- // Downgrading something that may not be downgraded.
- pc.maybeFail(call.Pos(), "%s already unlocked or not exclusive (locks: %s)", s, ls.String())
- }
- }
- }
- }
-}
-
-// checkClosure forks the lock state, and creates a binding for the FreeVars of
-// the closure. This allows the analysis to resolve the closure.
-func (pc *passContext) checkClosure(call callCommon, fn *ssa.MakeClosure, lff *lockFunctionFacts, ls *lockState) {
- clls := ls.fork()
- clfn := fn.Fn.(*ssa.Function)
- for i, fv := range clfn.FreeVars {
- clls.store(fv, fn.Bindings[i])
- }
-
- // Note that this is *not* a call to check function call, which checks
- // against the function preconditions. Instead, this does a fresh
- // analysis of the function from source code with a different state.
- nlff := lockFunctionFacts{
- Ignore: lff.Ignore, // Inherit ignore.
- }
- pc.checkFunction(call, clfn, &nlff, clls, true /* force */)
-}
-
-// freshAlloc indicates that v has been allocated within the local scope. There
-// is no lock checking done on objects that are freshly allocated.
-func freshAlloc(v ssa.Value) bool {
- switch x := v.(type) {
- case *ssa.Alloc:
- return true
- case *ssa.FieldAddr:
- return freshAlloc(x.X)
- case *ssa.Field:
- return freshAlloc(x.X)
- case *ssa.IndexAddr:
- return freshAlloc(x.X)
- case *ssa.Index:
- return freshAlloc(x.X)
- case *ssa.Convert:
- return freshAlloc(x.X)
- case *ssa.ChangeType:
- return freshAlloc(x.X)
- default:
- return false
- }
-}
-
-// isWrite indicates that this value is used as the addr field in a store.
-//
-// Note that this may still be used for a write. The return here is optimistic
-// but sufficient for basic analysis.
-func isWrite(v ssa.Value) bool {
- refs := v.Referrers()
- if refs == nil {
- return false
- }
- for _, ref := range *refs {
- if s, ok := ref.(*ssa.Store); ok && s.Addr == v {
- return true
- }
- }
- return false
-}
-
-// callCommon is an ssa.Value that also implements Common.
-type callCommon interface {
- Pos() token.Pos
- Common() *ssa.CallCommon
- Value() *ssa.Call
-}
-
-// checkInstruction checks the legality the single instruction based on the
-// current lockState.
-func (pc *passContext) checkInstruction(inst ssa.Instruction, lff *lockFunctionFacts, ls *lockState) (*ssa.Return, *lockState) {
- // Record any observed globals, and check for violations. The global
- // value is not itself an instruction, but we check all referrers to
- // see where they are consumed.
- var stackLocal [16]*ssa.Value
- ops := inst.Operands(stackLocal[:])
- for _, v := range ops {
- if v == nil {
- continue
- }
- g, ok := (*v).(*ssa.Global)
- if !ok {
- continue
- }
- _, isWrite := inst.(*ssa.Store)
- pc.checkGlobalAccess(g, ls, isWrite)
- }
-
- // Process the instruction.
- switch x := inst.(type) {
- case *ssa.Store:
- // Record that this value is holding this other value. This is
- // because at the beginning of each ssa execution, there is a
- // series of assignments of parameter values to alloc objects.
- // This allows us to trace these back to the original
- // parameters as aliases above.
- //
- // Note that this may overwrite an existing value in the lock
- // state, but this is intentional.
- ls.store(x.Addr, x.Val)
- case *ssa.Field:
- if !freshAlloc(x.X) && !lff.Ignore {
- pc.checkFieldAccess(x, x.X, x.Field, ls, false)
- }
- case *ssa.FieldAddr:
- if !freshAlloc(x.X) && !lff.Ignore {
- pc.checkFieldAccess(x, x.X, x.Field, ls, isWrite(x))
- }
- case *ssa.Call:
- pc.checkCall(x, lff, ls)
- case *ssa.Defer:
- ls.pushDefer(x)
- case *ssa.RunDefers:
- for d := ls.popDefer(); d != nil; d = ls.popDefer() {
- pc.checkCall(d, lff, ls)
- }
- case *ssa.MakeClosure:
- if refs := x.Referrers(); refs != nil {
- var (
- calls int
- nonCalls int
- )
- for _, ref := range *refs {
- switch ref.(type) {
- case *ssa.Call, *ssa.Defer:
- // Analysis will be done on the call
- // itself subsequently, including the
- // lock state at the time of the call.
- calls++
- default:
- // We need to analyze separately. Per
- // below, this means that we'll analyze
- // at closure construction time no zero
- // assumptions about when it will be
- // called.
- nonCalls++
- }
- }
- if calls > 0 && nonCalls == 0 {
- return nil, nil
- }
- }
- // Analyze the closure without bindings. This means that we
- // assume no lock facts or have any existing lock state. Only
- // trivial closures are acceptable in this case.
- clfn := x.Fn.(*ssa.Function)
- nlff := lockFunctionFacts{
- Ignore: lff.Ignore, // Inherit ignore.
- }
- pc.checkFunction(nil, clfn, &nlff, nil, false /* force */)
- case *ssa.Return:
- return x, ls // Valid return state.
- }
- return nil, nil
-}
-
-// checkBasicBlock traverses the control flow graph starting at a set of given
-// block and checks each instruction for allowed operations.
-func (pc *passContext) checkBasicBlock(fn *ssa.Function, block *ssa.BasicBlock, lff *lockFunctionFacts, parent *lockState, seen map[*ssa.BasicBlock]*lockState, rg map[*ssa.BasicBlock]struct{}) *lockState {
- // Check for cached results from entering this block from a *different*
- // execution path. Note that this is not the same path, which is
- // checked with the recursion guard below.
- if oldLS, ok := seen[block]; ok && oldLS.isCompatible(parent) {
- return nil
- }
-
- // Prevent recursion. If the lock state is constantly changing and we
- // are a recursive path, then there will never be a return block.
- if rg == nil {
- rg = make(map[*ssa.BasicBlock]struct{})
- }
- if _, ok := rg[block]; ok {
- return nil
- }
- rg[block] = struct{}{}
- defer func() { delete(rg, block) }()
-
- // If the lock state is not compatible, then we need to do the
- // recursive analysis to ensure that it is still sane. For example, the
- // following is guaranteed to generate incompatible locking states:
- //
- // if foo {
- // mu.Lock()
- // }
- // other stuff ...
- // if foo {
- // mu.Unlock()
- // }
-
- var (
- rv *ssa.Return
- rls *lockState
- )
-
- // Analyze this block.
- seen[block] = parent
- ls := parent.fork()
- for _, inst := range block.Instrs {
- rv, rls = pc.checkInstruction(inst, lff, ls)
- if rls != nil {
- failed := false
- // Validate held locks.
- for fieldName, fg := range lff.HeldOnExit {
- r := fg.Resolver.resolveStatic(pc, ls, fn, rv)
- if !r.valid() {
- // This cannot be forced, since we have no reference.
- pc.maybeFail(rv.Pos(), "lock %s cannot be resolved", fieldName)
- continue
- }
- if s, ok := rls.isHeld(r, fg.Exclusive); !ok {
- if _, ok := pc.forced[pc.positionKey(rv.Pos())]; !ok && !lff.Ignore {
- pc.maybeFail(rv.Pos(), "lock %s (%s) not held %s (locks: %s)", fieldName, s, exclusiveStr(fg.Exclusive), rls.String())
- failed = true
- } else {
- // Force the lock to be acquired.
- rls.lockField(r, fg.Exclusive)
- }
- }
- }
- // Check for other locks, but only if the above didn't trip.
- if !failed && rls.count() != len(lff.HeldOnExit) && !lff.Ignore {
- pc.maybeFail(rv.Pos(), "return with unexpected locks held (locks: %s)", rls.String())
- }
- }
- }
-
- // Analyze all successors.
- for _, succ := range block.Succs {
- // Collect possible return values, and make sure that the lock
- // state aligns with any return value that we may have found
- // above. Note that checkBasicBlock will recursively analyze
- // the lock state to ensure that Releases and Acquires are
- // respected.
- if pls := pc.checkBasicBlock(fn, succ, lff, ls, seen, rg); pls != nil {
- if rls != nil && !rls.isCompatible(pls) {
- if _, ok := pc.forced[pc.positionKey(fn.Pos())]; !ok && !lff.Ignore {
- pc.maybeFail(fn.Pos(), "incompatible return states (first: %s, second: %s)", rls.String(), pls.String())
- }
- }
- rls = pls
- }
- }
- return rls
-}
-
-// checkFunction checks a function invocation, typically starting with nil lockState.
-func (pc *passContext) checkFunction(call callCommon, fn *ssa.Function, lff *lockFunctionFacts, parent *lockState, force bool) {
- defer func() {
- // Mark this function as checked. This is used by the top-level
- // loop to ensure that all anonymous functions are scanned, if
- // they are not explicitly invoked here. Note that this can
- // happen if the anonymous functions are e.g. passed only as
- // parameters or used to initialize some structure.
- pc.functions[fn] = struct{}{}
- }()
- if _, ok := pc.functions[fn]; !force && ok {
- // This function has already been analyzed at least once.
- // That's all we permit for each function, although this may
- // cause some anonymous functions to be analyzed in only one
- // context.
- return
- }
-
- // If no return value is provided, then synthesize one. This is used
- // below only to check against the locks preconditions, which may
- // include return values.
- if call == nil {
- call = &ssa.Call{Call: ssa.CallCommon{Value: fn}}
- }
-
- // Initialize ls with any preconditions that require locks to be held
- // for the method to be invoked. Note that in the overwhleming majority
- // of cases, parent will be nil. However, in the case of closures and
- // anonymous functions, we may start with a non-nil lock state.
- //
- // Note that this will include all aliases, which are also released
- // appropriately below.
- ls := parent.fork()
- for fieldName, fg := range lff.HeldOnEntry {
- // The first is the method object itself so we skip that when looking
- // for receiver/function parameters.
- r := fg.Resolver.resolveStatic(pc, ls, fn, call.Value())
- if !r.valid() {
- // See above: this cannot be forced.
- pc.maybeFail(fn.Pos(), "lock %s cannot be resolved", fieldName)
- continue
- }
- if s, ok := ls.lockField(r, fg.Exclusive); !ok && !lff.Ignore {
- // This can only happen if the same value is declared
- // multiple times, and should be caught by the earlier
- // fact scanning. Keep it here as a sanity check.
- pc.maybeFail(fn.Pos(), "lock %s (%s) acquired multiple times or differently (locks: %s)", fieldName, s, ls.String())
- }
- }
-
- // Scan the blocks.
- seen := make(map[*ssa.BasicBlock]*lockState)
- if len(fn.Blocks) > 0 {
- pc.checkBasicBlock(fn, fn.Blocks[0], lff, ls, seen, nil)
- }
-
- // Scan the recover block.
- if fn.Recover != nil {
- pc.checkBasicBlock(fn, fn.Recover, lff, ls, seen, nil)
- }
-
- // Update all lock state accordingly. This will be called only if we
- // are doing inline analysis for e.g. an anonymous function.
- if call != nil && parent != nil {
- pc.postFunctionCallUpdate(call, lff, parent, true /* aliases */)
- }
-}
-
-// checkInferred checks for any inferred lock annotations.
-func (pc *passContext) checkInferred() {
- for obj, oo := range pc.observations {
- var lgf lockGuardFacts
- pc.pass.ImportObjectFact(obj, &lgf)
- for other, count := range oo.counts {
- // Is this already a guard?
- if _, ok := lgf.GuardedBy[other.Name()]; ok {
- continue
- }
- // Check to see if this field is used with a given lock
- // held above the threshold. If yes, provide a helpful
- // hint that this may something you wish to annotate.
- const threshold = 0.9
- if usage := float64(count) / float64(oo.total); usage >= threshold {
- pc.maybeFail(obj.Pos(), "may require checklocks annotation for %s, used with lock held %2.0f%% of the time", other.Name(), usage*100)
- }
- }
- }
-}
diff --git a/tools/checklocks/annotations.go b/tools/checklocks/annotations.go
deleted file mode 100644
index 950168ee1..000000000
--- a/tools/checklocks/annotations.go
+++ /dev/null
@@ -1,133 +0,0 @@
-// Copyright 2020 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package checklocks
-
-import (
- "fmt"
-
- "go/token"
- "strconv"
- "strings"
-)
-
-const (
- checkLocksAnnotation = "// +checklocks:"
- checkLocksAnnotationRead = "// +checklocksread:"
- checkLocksAcquires = "// +checklocksacquire:"
- checkLocksAcquiresRead = "// +checklocksacquireread:"
- checkLocksReleases = "// +checklocksrelease:"
- checkLocksReleasesRead = "// +checklocksreleaseread:"
- checkLocksIgnore = "// +checklocksignore"
- checkLocksForce = "// +checklocksforce"
- checkLocksFail = "// +checklocksfail"
- checkLocksAlias = "// +checklocksalias:"
- checkAtomicAnnotation = "// +checkatomic"
-)
-
-// failData indicates an expected failure.
-type failData struct {
- pos token.Pos
- count int
- seen int
-}
-
-// positionKey is a simple position string.
-type positionKey string
-
-// positionKey converts from a token.Pos to a key we can use to track failures
-// as the position of the failure annotation is not the same as the position of
-// the actual failure (different column/offsets). Hence we ignore these fields
-// and only use the file/line numbers to track failures.
-func (pc *passContext) positionKey(pos token.Pos) positionKey {
- position := pc.pass.Fset.Position(pos)
- return positionKey(fmt.Sprintf("%s:%d", position.Filename, position.Line))
-}
-
-// addFailures adds an expected failure.
-func (pc *passContext) addFailures(pos token.Pos, s string) {
- count := 1
- if len(s) > 0 && s[0] == ':' {
- parsedCount, err := strconv.Atoi(s[1:])
- if err != nil {
- pc.pass.Reportf(pos, "unable to parse failure annotation %q: %v", s[1:], err)
- return
- }
- count = parsedCount
- }
- pc.failures[pc.positionKey(pos)] = &failData{
- pos: pos,
- count: count,
- }
-}
-
-// addExemption adds an exemption.
-func (pc *passContext) addExemption(pos token.Pos) {
- pc.exemptions[pc.positionKey(pos)] = struct{}{}
-}
-
-// addForce adds a force annotation.
-func (pc *passContext) addForce(pos token.Pos) {
- pc.forced[pc.positionKey(pos)] = struct{}{}
-}
-
-// maybeFail checks a potential failure against a specific failure map.
-func (pc *passContext) maybeFail(pos token.Pos, fmtStr string, args ...interface{}) {
- if fd, ok := pc.failures[pc.positionKey(pos)]; ok {
- fd.seen++
- return
- }
- if _, ok := pc.exemptions[pc.positionKey(pos)]; ok {
- return // Ignored, not counted.
- }
- pc.pass.Reportf(pos, fmtStr, args...)
-}
-
-// checkFailure checks for the expected failure counts.
-func (pc *passContext) checkFailures() {
- for _, fd := range pc.failures {
- if fd.count != fd.seen {
- // We are missing expect failures, report as much as possible.
- pc.pass.Reportf(fd.pos, "got %d failures, want %d failures", fd.seen, fd.count)
- }
- }
-}
-
-// extractAnnotations extracts annotations from text.
-func (pc *passContext) extractAnnotations(s string, fns map[string]func(p string)) {
- for prefix, fn := range fns {
- if strings.HasPrefix(s, prefix) {
- fn(s[len(prefix):])
- }
- }
-}
-
-// extractLineFailures extracts all line-based exceptions.
-//
-// Note that this applies only to individual line exemptions, and does not
-// consider function-wide exemptions, or specific field exemptions, which are
-// extracted separately as part of the saved facts for those objects.
-func (pc *passContext) extractLineFailures() {
- for _, f := range pc.pass.Files {
- for _, cg := range f.Comments {
- for _, c := range cg.List {
- pc.extractAnnotations(c.Text, map[string]func(string){
- checkLocksFail: func(p string) { pc.addFailures(c.Pos(), p) },
- checkLocksIgnore: func(string) { pc.addExemption(c.Pos()) },
- checkLocksForce: func(string) { pc.addForce(c.Pos()) },
- })
- }
- }
- }
-}
diff --git a/tools/checklocks/checklocks.go b/tools/checklocks/checklocks.go
deleted file mode 100644
index 939af4239..000000000
--- a/tools/checklocks/checklocks.go
+++ /dev/null
@@ -1,196 +0,0 @@
-// Copyright 2020 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-// Package checklocks performs lock analysis to identify and flag unprotected
-// access to annotated fields.
-//
-// For detailed usage refer to README.md in the same directory.
-package checklocks
-
-import (
- "go/ast"
- "go/token"
- "go/types"
-
- "golang.org/x/tools/go/analysis"
- "golang.org/x/tools/go/analysis/passes/buildssa"
- "golang.org/x/tools/go/ssa"
-)
-
-// Analyzer is the main entrypoint.
-var Analyzer = &analysis.Analyzer{
- Name: "checklocks",
- Doc: "checks lock preconditions on functions and fields",
- Run: run,
- Requires: []*analysis.Analyzer{buildssa.Analyzer},
- FactTypes: []analysis.Fact{
- (*atomicAlignment)(nil),
- (*lockGuardFacts)(nil),
- (*lockFunctionFacts)(nil),
- },
-}
-
-// objectObservations tracks lock correlations.
-type objectObservations struct {
- counts map[types.Object]int
- total int
-}
-
-// passContext is a pass with additional expected failures.
-type passContext struct {
- pass *analysis.Pass
- failures map[positionKey]*failData
- exemptions map[positionKey]struct{}
- forced map[positionKey]struct{}
- functions map[*ssa.Function]struct{}
- observations map[types.Object]*objectObservations
-}
-
-// observationsFor retrieves observations for the given object.
-func (pc *passContext) observationsFor(obj types.Object) *objectObservations {
- if pc.observations == nil {
- pc.observations = make(map[types.Object]*objectObservations)
- }
- oo, ok := pc.observations[obj]
- if !ok {
- oo = &objectObservations{
- counts: make(map[types.Object]int),
- }
- pc.observations[obj] = oo
- }
- return oo
-}
-
-// forAllGlobals applies the given function to all globals.
-func (pc *passContext) forAllGlobals(fn func(ts *ast.ValueSpec)) {
- for _, f := range pc.pass.Files {
- for _, decl := range f.Decls {
- d, ok := decl.(*ast.GenDecl)
- if !ok || d.Tok != token.VAR {
- continue
- }
- for _, gs := range d.Specs {
- fn(gs.(*ast.ValueSpec))
- }
- }
- }
-}
-
-// forAllTypes applies the given function over all types.
-func (pc *passContext) forAllTypes(fn func(ts *ast.TypeSpec)) {
- for _, f := range pc.pass.Files {
- for _, decl := range f.Decls {
- d, ok := decl.(*ast.GenDecl)
- if !ok || d.Tok != token.TYPE {
- continue
- }
- for _, gs := range d.Specs {
- fn(gs.(*ast.TypeSpec))
- }
- }
- }
-}
-
-// forAllFunctions applies the given function over all functions.
-func (pc *passContext) forAllFunctions(fn func(fn *ast.FuncDecl)) {
- for _, f := range pc.pass.Files {
- for _, decl := range f.Decls {
- d, ok := decl.(*ast.FuncDecl)
- if !ok {
- continue
- }
- fn(d)
- }
- }
-}
-
-// run is the main entrypoint.
-func run(pass *analysis.Pass) (interface{}, error) {
- pc := &passContext{
- pass: pass,
- failures: make(map[positionKey]*failData),
- exemptions: make(map[positionKey]struct{}),
- forced: make(map[positionKey]struct{}),
- functions: make(map[*ssa.Function]struct{}),
- }
-
- // Find all line failure annotations.
- pc.extractLineFailures()
-
- // Find all struct declarations and export relevant facts.
- pc.forAllGlobals(func(vs *ast.ValueSpec) {
- if ss, ok := vs.Type.(*ast.StructType); ok {
- structType := pc.pass.TypesInfo.TypeOf(vs.Type).Underlying().(*types.Struct)
- pc.structLockGuardFacts(structType, ss)
- }
- pc.globalLockGuardFacts(vs)
- })
- pc.forAllTypes(func(ts *ast.TypeSpec) {
- if ss, ok := ts.Type.(*ast.StructType); ok {
- structType := pc.pass.TypesInfo.TypeOf(ts.Name).Underlying().(*types.Struct)
- pc.structLockGuardFacts(structType, ss)
- }
- })
-
- // Check all alignments.
- pc.forAllTypes(func(ts *ast.TypeSpec) {
- typ, ok := pass.TypesInfo.TypeOf(ts.Name).(*types.Named)
- if !ok {
- return
- }
- pc.checkTypeAlignment(pass.Pkg, typ)
- })
-
- // Find all function declarations and export relevant facts.
- pc.forAllFunctions(func(fn *ast.FuncDecl) {
- pc.functionFacts(fn)
- })
-
- // Scan all code looking for invalid accesses.
- state := pass.ResultOf[buildssa.Analyzer].(*buildssa.SSA)
- for _, fn := range state.SrcFuncs {
- // Import function facts generated above.
- //
- // Note that anonymous(closures) functions do not have an
- // object but do show up in the SSA. They can only be invoked
- // by named functions in the package, and they are analyzing
- // inline on every call. Thus we skip the analysis here. They
- // will be hit on calls, or picked up in the pass below.
- if obj := fn.Object(); obj == nil {
- continue
- }
- var lff lockFunctionFacts
- pc.pass.ImportObjectFact(fn.Object(), &lff)
-
- // Check the basic blocks in the function.
- pc.checkFunction(nil, fn, &lff, nil, false /* force */)
- }
- for _, fn := range state.SrcFuncs {
- // Ensure all anonymous functions are hit. They are not
- // permitted to have any lock preconditions.
- if obj := fn.Object(); obj != nil {
- continue
- }
- var nolff lockFunctionFacts
- pc.checkFunction(nil, fn, &nolff, nil, false /* force */)
- }
-
- // Check for inferred checklocks annotations.
- pc.checkInferred()
-
- // Check for expected failures.
- pc.checkFailures()
-
- return nil, nil
-}
diff --git a/tools/checklocks/facts.go b/tools/checklocks/facts.go
deleted file mode 100644
index f6dfeaec9..000000000
--- a/tools/checklocks/facts.go
+++ /dev/null
@@ -1,836 +0,0 @@
-// Copyright 2020 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package checklocks
-
-import (
- "encoding/gob"
- "fmt"
- "go/ast"
- "go/token"
- "go/types"
- "regexp"
- "strings"
-
- "golang.org/x/tools/go/analysis/passes/buildssa"
- "golang.org/x/tools/go/ssa"
-)
-
-// atomicAlignment is saved per type.
-//
-// This represents the alignment required for the type, which may
-// be implied and imposed by other types within the aggregate type.
-type atomicAlignment int
-
-// AFact implements analysis.Fact.AFact.
-func (*atomicAlignment) AFact() {}
-
-// atomicDisposition is saved per field.
-//
-// This represents how the field must be accessed. It must either
-// be non-atomic (default), atomic or ignored.
-type atomicDisposition int
-
-const (
- atomicDisallow atomicDisposition = iota
- atomicIgnore
- atomicRequired
-)
-
-// fieldEntry is a single field type.
-type fieldEntry interface {
- // synthesize produces a string that is compatible with valueAndObject,
- // along with the same object that should be produced in that case.
- //
- // Note that it is called synthesize because this is produced only the
- // type information, and not with any ssa.Value objects.
- synthesize(s string, typ types.Type) (string, types.Object)
-}
-
-// fieldStruct is a non-pointer struct element.
-type fieldStruct int
-
-// synthesize implements fieldEntry.synthesize.
-func (f fieldStruct) synthesize(s string, typ types.Type) (string, types.Object) {
- field, ok := findField(typ, int(f))
- if !ok {
- // Should not happen as long as fieldList construction is correct.
- panic(fmt.Sprintf("unable to resolve field %d in %s", int(f), typ.String()))
- }
- return fmt.Sprintf("&(%s.%s)", s, field.Name()), field
-}
-
-// fieldStructPtr is a pointer struct element.
-type fieldStructPtr int
-
-// synthesize implements fieldEntry.synthesize.
-func (f fieldStructPtr) synthesize(s string, typ types.Type) (string, types.Object) {
- field, ok := findField(typ, int(f))
- if !ok {
- // See above, this should not happen.
- panic(fmt.Sprintf("unable to resolve ptr field %d in %s", int(f), typ.String()))
- }
- return fmt.Sprintf("*(&(%s.%s))", s, field.Name()), field
-}
-
-// fieldList is a simple list of fields, used in two types below.
-type fieldList []fieldEntry
-
-// resolvedValue is an ssa.Value with additional fields.
-//
-// This can be resolved to a string as part of a lock state.
-type resolvedValue struct {
- value ssa.Value
- fieldList fieldList
-}
-
-// makeResolvedValue makes a new resolvedValue.
-func makeResolvedValue(v ssa.Value, fl fieldList) resolvedValue {
- return resolvedValue{
- value: v,
- fieldList: fl,
- }
-}
-
-// valid indicates whether this is a valid resolvedValue.
-func (rv *resolvedValue) valid() bool {
- return rv.value != nil
-}
-
-// valueAndObject returns a string and object.
-//
-// This uses the lockState valueAndObject in order to produce a string and
-// object for the base ssa.Value, then synthesizes a string representation
-// based on the fieldList.
-func (rv *resolvedValue) valueAndObject(ls *lockState) (string, types.Object) {
- // N.B. obj.Type() and typ should be equal, but a check is omitted
- // since, 1) we automatically chase through pointers during field
- // resolution, and 2) obj may be nil if there is no source object.
- s, obj := ls.valueAndObject(rv.value)
- typ := rv.value.Type()
- for _, entry := range rv.fieldList {
- s, obj = entry.synthesize(s, typ)
- typ = obj.Type()
- }
- return s, obj
-}
-
-// fieldGuardResolver details a guard for a field.
-type fieldGuardResolver interface {
- // resolveField is used to resolve a guard during a field access. The
- // parent structure is available, as well as the current lock state.
- resolveField(pc *passContext, ls *lockState, parent ssa.Value) resolvedValue
-}
-
-// functionGuardResolver details a guard for a function.
-type functionGuardResolver interface {
- // resolveStatic is used to resolve a guard during static analysis,
- // e.g. based on static annotations applied to a method. The function's
- // ssa object is available, as well as the return value.
- resolveStatic(pc *passContext, ls *lockState, fn *ssa.Function, rv interface{}) resolvedValue
-
- // resolveCall is used to resolve a guard during a call. The ssa
- // return value is available from the instruction context where the
- // call occurs, but the target's ssa representation is not available.
- resolveCall(pc *passContext, ls *lockState, args []ssa.Value, rv ssa.Value) resolvedValue
-}
-
-// lockGuardFacts contains guard information.
-type lockGuardFacts struct {
- // GuardedBy is the set of locks that are guarding this field. The key
- // is the original annotation value, and the field list is the object
- // traversal path.
- GuardedBy map[string]fieldGuardResolver
-
- // AtomicDisposition is the disposition for this field. Note that this
- // can affect the interpretation of the GuardedBy field above, see the
- // relevant comment.
- AtomicDisposition atomicDisposition
-}
-
-// AFact implements analysis.Fact.AFact.
-func (*lockGuardFacts) AFact() {}
-
-// globalGuard is a global value.
-type globalGuard struct {
- // Object indicates the object from which resolution should occur.
- Object types.Object
-
- // FieldList is the traversal path from object.
- FieldList fieldList
-}
-
-// ssaPackager returns the ssa package.
-type ssaPackager interface {
- Package() *ssa.Package
-}
-
-// resolveCommon implements resolution for all cases.
-func (g *globalGuard) resolveCommon(pc *passContext, ls *lockState) resolvedValue {
- state := pc.pass.ResultOf[buildssa.Analyzer].(*buildssa.SSA)
- v := state.Pkg.Members[g.Object.Name()].(ssa.Value)
- return makeResolvedValue(v, g.FieldList)
-}
-
-// resolveStatic implements functionGuardResolver.resolveStatic.
-func (g *globalGuard) resolveStatic(pc *passContext, ls *lockState, _ *ssa.Function, v interface{}) resolvedValue {
- return g.resolveCommon(pc, ls)
-}
-
-// resolveCall implements functionGuardResolver.resolveCall.
-func (g *globalGuard) resolveCall(pc *passContext, ls *lockState, _ []ssa.Value, v ssa.Value) resolvedValue {
- return g.resolveCommon(pc, ls)
-}
-
-// resolveField implements fieldGuardResolver.resolveField.
-func (g *globalGuard) resolveField(pc *passContext, ls *lockState, parent ssa.Value) resolvedValue {
- return g.resolveCommon(pc, ls)
-}
-
-// fieldGuard is a field-based guard.
-type fieldGuard struct {
- // FieldList is the traversal path from the parent.
- FieldList fieldList
-}
-
-// resolveField implements fieldGuardResolver.resolveField.
-func (f *fieldGuard) resolveField(_ *passContext, _ *lockState, parent ssa.Value) resolvedValue {
- return makeResolvedValue(parent, f.FieldList)
-}
-
-// parameterGuard is a parameter-based guard.
-type parameterGuard struct {
- // Index is the parameter index of the object that contains the
- // guarding mutex.
- Index int
-
- // fieldList is the traversal path from the parameter.
- FieldList fieldList
-}
-
-// resolveStatic implements functionGuardResolver.resolveStatic.
-func (p *parameterGuard) resolveStatic(_ *passContext, _ *lockState, fn *ssa.Function, _ interface{}) resolvedValue {
- return makeResolvedValue(fn.Params[p.Index], p.FieldList)
-}
-
-// resolveCall implements functionGuardResolver.resolveCall.
-func (p *parameterGuard) resolveCall(_ *passContext, _ *lockState, args []ssa.Value, _ ssa.Value) resolvedValue {
- return makeResolvedValue(args[p.Index], p.FieldList)
-}
-
-// returnGuard is a return-based guard.
-type returnGuard struct {
- // Index is the index of the return value.
- Index int
-
- // NeedsExtract is used in the case of a return value, and indicates
- // that the field must be extracted from a tuple.
- NeedsExtract bool
-
- // FieldList is the traversal path from the return value.
- FieldList fieldList
-}
-
-// resolveCommon implements resolution for both cases.
-func (r *returnGuard) resolveCommon(rv interface{}) resolvedValue {
- if rv == nil {
- // For defers and other objects, this may be nil. This is
- // handled in state.go in the actual lock checking logic. This
- // means that there is no resolvedValue available.
- return resolvedValue{}
- }
- // If this is a *ssa.Return object, i.e. we are analyzing the function
- // and not the call site, then we can just pull the result directly.
- if ret, ok := rv.(*ssa.Return); ok {
- return makeResolvedValue(ret.Results[r.Index], r.FieldList)
- }
- if r.NeedsExtract {
- // Resolve on the extracted field, this is necessary if the
- // type here is not an explicit return. Note that rv must be an
- // ssa.Value, since it is not an *ssa.Return.
- v := rv.(ssa.Value)
- if refs := v.Referrers(); refs != nil {
- for _, inst := range *refs {
- if x, ok := inst.(*ssa.Extract); ok && x.Tuple == v && x.Index == r.Index {
- return makeResolvedValue(x, r.FieldList)
- }
- }
- }
- // Nothing resolved.
- return resolvedValue{}
- }
- if r.Index != 0 {
- // This should not happen, NeedsExtract should always be set.
- panic("NeedsExtract is false, but return value index is non-zero")
- }
- // Resolve on the single return.
- return makeResolvedValue(rv.(ssa.Value), r.FieldList)
-}
-
-// resolveStatic implements functionGuardResolver.resolveStatic.
-func (r *returnGuard) resolveStatic(_ *passContext, _ *lockState, _ *ssa.Function, rv interface{}) resolvedValue {
- return r.resolveCommon(rv)
-}
-
-// resolveCall implements functionGuardResolver.resolveCall.
-func (r *returnGuard) resolveCall(_ *passContext, _ *lockState, _ []ssa.Value, rv ssa.Value) resolvedValue {
- return r.resolveCommon(rv)
-}
-
-// functionGuardInfo is information about a method guard.
-type functionGuardInfo struct {
- // Resolver is the resolver for this guard.
- Resolver functionGuardResolver
-
- // IsAlias indicates that this guard is an alias.
- IsAlias bool
-
- // Exclusive indicates an exclusive lock is required.
- Exclusive bool
-}
-
-// lockFunctionFacts apply on every method.
-type lockFunctionFacts struct {
- // HeldOnEntry tracks the names and number of parameter (including receiver)
- // lockFuncfields that guard calls to this function.
- //
- // The key is the name specified in the checklocks annotation. e.g given
- // the following code:
- //
- // ```
- // type A struct {
- // mu sync.Mutex
- // a int
- // }
- //
- // // +checklocks:a.mu
- // func xyz(a *A) {..}
- // ```
- //
- // '`+checklocks:a.mu' will result in an entry in this map as shown below.
- // HeldOnEntry: {"a.mu" => {Resolver: &parameterGuard{Index: 0}}
- HeldOnEntry map[string]functionGuardInfo
-
- // HeldOnExit tracks the locks that are expected to be held on exit.
- HeldOnExit map[string]functionGuardInfo
-
- // Ignore means this function has local analysis ignores.
- //
- // This is not used outside the local package.
- Ignore bool
-}
-
-// AFact implements analysis.Fact.AFact.
-func (*lockFunctionFacts) AFact() {}
-
-// checkGuard validates the guardName.
-func (lff *lockFunctionFacts) checkGuard(pc *passContext, d *ast.FuncDecl, guardName string, exclusive bool, allowReturn bool) (functionGuardInfo, bool) {
- if _, ok := lff.HeldOnEntry[guardName]; ok {
- pc.maybeFail(d.Pos(), "annotation %s specified more than once, already required", guardName)
- return functionGuardInfo{}, false
- }
- if _, ok := lff.HeldOnExit[guardName]; ok {
- pc.maybeFail(d.Pos(), "annotation %s specified more than once, already acquired", guardName)
- return functionGuardInfo{}, false
- }
- fg, ok := pc.findFunctionGuard(d, guardName, exclusive, allowReturn)
- return fg, ok
-}
-
-// addGuardedBy adds a field to both HeldOnEntry and HeldOnExit.
-func (lff *lockFunctionFacts) addGuardedBy(pc *passContext, d *ast.FuncDecl, guardName string, exclusive bool) {
- if fg, ok := lff.checkGuard(pc, d, guardName, exclusive, false /* allowReturn */); ok {
- if lff.HeldOnEntry == nil {
- lff.HeldOnEntry = make(map[string]functionGuardInfo)
- }
- if lff.HeldOnExit == nil {
- lff.HeldOnExit = make(map[string]functionGuardInfo)
- }
- lff.HeldOnEntry[guardName] = fg
- lff.HeldOnExit[guardName] = fg
- }
-}
-
-// addAcquires adds a field to HeldOnExit.
-func (lff *lockFunctionFacts) addAcquires(pc *passContext, d *ast.FuncDecl, guardName string, exclusive bool) {
- if fg, ok := lff.checkGuard(pc, d, guardName, exclusive, true /* allowReturn */); ok {
- if lff.HeldOnExit == nil {
- lff.HeldOnExit = make(map[string]functionGuardInfo)
- }
- lff.HeldOnExit[guardName] = fg
- }
-}
-
-// addReleases adds a field to HeldOnEntry.
-func (lff *lockFunctionFacts) addReleases(pc *passContext, d *ast.FuncDecl, guardName string, exclusive bool) {
- if fg, ok := lff.checkGuard(pc, d, guardName, exclusive, false /* allowReturn */); ok {
- if lff.HeldOnEntry == nil {
- lff.HeldOnEntry = make(map[string]functionGuardInfo)
- }
- lff.HeldOnEntry[guardName] = fg
- }
-}
-
-// addAlias adds an alias.
-func (lff *lockFunctionFacts) addAlias(pc *passContext, d *ast.FuncDecl, guardName string) {
- // Parse the alias.
- parts := strings.Split(guardName, "=")
- if len(parts) != 2 {
- pc.maybeFail(d.Pos(), "invalid annotation %s for alias", guardName)
- return
- }
-
- // Parse the actual guard.
- fg, ok := lff.checkGuard(pc, d, parts[0], true /* exclusive */, true /* allowReturn */)
- if !ok {
- return
- }
- fg.IsAlias = true
-
- // Find the existing specification.
- _, entryOk := lff.HeldOnEntry[parts[1]]
- if entryOk {
- lff.HeldOnEntry[guardName] = fg
- }
- _, exitOk := lff.HeldOnExit[parts[1]]
- if exitOk {
- lff.HeldOnExit[guardName] = fg
- }
- if !entryOk && !exitOk {
- pc.maybeFail(d.Pos(), "alias annotation %s does not refer to an existing guard", guardName)
- }
-}
-
-// fieldEntryFor returns the fieldList value for the given object.
-func (pc *passContext) fieldEntryFor(fieldObj types.Object, index int) fieldEntry {
-
- // Return the resolution path.
- if _, ok := fieldObj.Type().Underlying().(*types.Pointer); ok {
- return fieldStructPtr(index)
- }
- if _, ok := fieldObj.Type().Underlying().(*types.Interface); ok {
- return fieldStructPtr(index)
- }
- return fieldStruct(index)
-}
-
-// findField resolves a field in a single struct.
-func (pc *passContext) findField(structType *types.Struct, fieldName string) (fl fieldList, fieldObj types.Object, ok bool) {
- // Scan to match the next field.
- for i := 0; i < structType.NumFields(); i++ {
- fieldObj := structType.Field(i)
- if fieldObj.Name() != fieldName {
- continue
- }
- fl = append(fl, pc.fieldEntryFor(fieldObj, i))
- return fl, fieldObj, true
- }
-
- // Is this an embed?
- for i := 0; i < structType.NumFields(); i++ {
- fieldObj := structType.Field(i)
- if !fieldObj.Embedded() {
- continue
- }
-
- // Is this an embedded struct?
- structType, ok := resolveStruct(fieldObj.Type())
- if !ok {
- continue
- }
-
- // Need to check that there is a resolution path. If there is
- // no resolution path that's not a failure: we just continue
- // scanning the next embed to find a match.
- flEmbed := pc.fieldEntryFor(fieldObj, i)
- flNext, fieldObjNext, ok := pc.findField(structType, fieldName)
- if !ok {
- continue
- }
-
- // Found an embedded chain.
- fl = append(fl, flEmbed)
- fl = append(fl, flNext...)
- return fl, fieldObjNext, true
- }
-
- return nil, nil, false
-}
-
-var (
- mutexRE = regexp.MustCompile("((.*/)|^)sync.(CrossGoroutineMutex|Mutex)")
- rwMutexRE = regexp.MustCompile("((.*/)|^)sync.(CrossGoroutineRWMutex|RWMutex)")
- lockerRE = regexp.MustCompile("((.*/)|^)sync.Locker")
-)
-
-// validateMutex validates the mutex type.
-//
-// This function returns true iff the object is a valid mutex with an error
-// reported at the given position if necessary.
-func (pc *passContext) validateMutex(pos token.Pos, obj types.Object, exclusive bool) bool {
- // Check that it is indeed a mutex.
- s := obj.Type().String()
- switch {
- case mutexRE.MatchString(s), lockerRE.MatchString(s):
- // Safe for exclusive cases.
- if !exclusive {
- pc.maybeFail(pos, "field %s must be a RWMutex", obj.Name())
- return false
- }
- return true
- case rwMutexRE.MatchString(s):
- // Safe for all cases.
- return true
- default:
- // Not a mutex at all?
- pc.maybeFail(pos, "field %s is not a Mutex or an RWMutex", obj.Name())
- return false
- }
-}
-
-// findFieldList resolves a set of fields given a string, such a 'a.b.c'.
-//
-// Note that parts must be non-zero in length. If it may be zero, then
-// maybeFindFieldList should be used instead with an appropriate object.
-func (pc *passContext) findFieldList(pos token.Pos, structType *types.Struct, parts []string, exclusive bool) (fl fieldList, ok bool) {
- var obj types.Object
-
- // This loop requires at least one iteration in order to ensure that
- // obj above is non-nil, and the type can be validated.
- for i, fieldName := range parts {
- flOne, fieldObj, ok := pc.findField(structType, fieldName)
- if !ok {
- return nil, false
- }
- fl = append(fl, flOne...)
- obj = fieldObj
- if i < len(parts)-1 {
- structType, ok = resolveStruct(obj.Type())
- if !ok {
- // N.B. This is associated with the original position.
- pc.maybeFail(pos, "field %s expected to be struct", fieldName)
- return nil, false
- }
- }
- }
-
- // Validate the final field. This reports the field to the caller
- // anyways, since the error will be reported only once.
- _ = pc.validateMutex(pos, obj, exclusive)
- return fl, true
-}
-
-// maybeFindFieldList resolves the given object.
-//
-// Parts may be the empty list, unlike findFieldList.
-func (pc *passContext) maybeFindFieldList(pos token.Pos, obj types.Object, parts []string, exclusive bool) (fl fieldList, ok bool) {
- if len(parts) > 0 {
- structType, ok := resolveStruct(obj.Type())
- if !ok {
- // This does not have any fields; the access is not allowed.
- pc.maybeFail(pos, "attempted field access on non-struct")
- return nil, false
- }
- return pc.findFieldList(pos, structType, parts, exclusive)
- }
-
- // See above.
- _ = pc.validateMutex(pos, obj, exclusive)
- return nil, true
-}
-
-// findFieldGuardResolver finds a symbol resolver.
-type findFieldGuardResolver func(pos token.Pos, guardName string) (fieldGuardResolver, bool)
-
-// findFunctionGuardResolver finds a symbol resolver.
-type findFunctionGuardResolver func(pos token.Pos, guardName string) (functionGuardResolver, bool)
-
-// fillLockGuardFacts fills the facts with guard information.
-func (pc *passContext) fillLockGuardFacts(obj types.Object, cg *ast.CommentGroup, find findFieldGuardResolver, lgf *lockGuardFacts) {
- if cg == nil {
- return
- }
- for _, l := range cg.List {
- pc.extractAnnotations(l.Text, map[string]func(string){
- checkAtomicAnnotation: func(string) {
- switch lgf.AtomicDisposition {
- case atomicRequired:
- pc.maybeFail(obj.Pos(), "annotation is redundant, already atomic required")
- case atomicIgnore:
- pc.maybeFail(obj.Pos(), "annotation is contradictory, already atomic ignored")
- }
- lgf.AtomicDisposition = atomicRequired
- },
- checkLocksIgnore: func(string) {
- switch lgf.AtomicDisposition {
- case atomicIgnore:
- pc.maybeFail(obj.Pos(), "annotation is redundant, already atomic ignored")
- case atomicRequired:
- pc.maybeFail(obj.Pos(), "annotation is contradictory, already atomic required")
- }
- lgf.AtomicDisposition = atomicIgnore
- },
- checkLocksAnnotation: func(guardName string) {
- // Check for a duplicate annotation.
- if _, ok := lgf.GuardedBy[guardName]; ok {
- pc.maybeFail(obj.Pos(), "annotation %s specified more than once", guardName)
- return
- }
- // Add the item.
- if lgf.GuardedBy == nil {
- lgf.GuardedBy = make(map[string]fieldGuardResolver)
- }
- fr, ok := find(obj.Pos(), guardName)
- if !ok {
- pc.maybeFail(obj.Pos(), "annotation %s cannot be resolved", guardName)
- return
- }
- lgf.GuardedBy[guardName] = fr
- },
- // N.B. We support only the vanilla annotation on
- // individual fields. If the field is a read lock, then
- // we will allow read access by default.
- checkLocksAnnotationRead: func(guardName string) {
- pc.maybeFail(obj.Pos(), "annotation %s not legal on fields", guardName)
- },
- })
- }
- // Save only if there is something meaningful.
- if len(lgf.GuardedBy) > 0 || lgf.AtomicDisposition != atomicDisallow {
- pc.pass.ExportObjectFact(obj, lgf)
- }
-}
-
-// findGlobalGuard attempts to resolve a name globally.
-func (pc *passContext) findGlobalGuard(pos token.Pos, guardName string) (*globalGuard, bool) {
- // Attempt to resolve the object.
- parts := strings.Split(guardName, ".")
- globalObj := pc.pass.Pkg.Scope().Lookup(parts[0])
- if globalObj == nil {
- // No global object.
- return nil, false
- }
- fl, ok := pc.maybeFindFieldList(pos, globalObj, parts[1:], true /* exclusive */)
- if !ok {
- // Invalid fields.
- return nil, false
- }
- return &globalGuard{
- Object: globalObj,
- FieldList: fl,
- }, true
-}
-
-// findGlobalFieldGuard is compatible with findFieldGuardResolver.
-func (pc *passContext) findGlobalFieldGuard(pos token.Pos, guardName string) (fieldGuardResolver, bool) {
- g, ok := pc.findGlobalGuard(pos, guardName)
- return g, ok
-}
-
-// findGlobalFunctionGuard is compatible with findFunctionGuardResolver.
-func (pc *passContext) findGlobalFunctionGuard(pos token.Pos, guardName string) (functionGuardResolver, bool) {
- g, ok := pc.findGlobalGuard(pos, guardName)
- return g, ok
-}
-
-// structLockGuardFacts finds all relevant guard information for structures.
-func (pc *passContext) structLockGuardFacts(structType *types.Struct, ss *ast.StructType) {
- var fieldObj *types.Var
- findLocal := func(pos token.Pos, guardName string) (fieldGuardResolver, bool) {
- // Try to resolve from the local structure first.
- fl, ok := pc.findFieldList(pos, structType, strings.Split(guardName, "."), true /* exclusive */)
- if ok {
- // Found a valid resolution.
- return &fieldGuard{
- FieldList: fl,
- }, true
- }
- // Attempt a global resolution.
- return pc.findGlobalFieldGuard(pos, guardName)
- }
- for i, field := range ss.Fields.List {
- var lgf lockGuardFacts
- fieldObj = structType.Field(i) // N.B. Captured above.
- pc.fillLockGuardFacts(fieldObj, field.Doc, findLocal, &lgf)
-
- // See above, for anonymous structure fields.
- if ss, ok := field.Type.(*ast.StructType); ok {
- if st, ok := fieldObj.Type().(*types.Struct); ok {
- pc.structLockGuardFacts(st, ss)
- }
- }
- }
-}
-
-// globalLockGuardFacts finds all relevant guard information for globals.
-//
-// Note that the Type is checked in checklocks.go at the top-level.
-func (pc *passContext) globalLockGuardFacts(vs *ast.ValueSpec) {
- var lgf lockGuardFacts
- globalObj := pc.pass.TypesInfo.ObjectOf(vs.Names[0])
- pc.fillLockGuardFacts(globalObj, vs.Doc, pc.findGlobalFieldGuard, &lgf)
-}
-
-// countFields gives an accurate field count, according for unnamed arguments
-// and return values and the compact identifier format.
-func countFields(fl []*ast.Field) (count int) {
- for _, field := range fl {
- if len(field.Names) == 0 {
- count++
- continue
- }
- count += len(field.Names)
- }
- return
-}
-
-// matchFieldList attempts to match the given field.
-//
-// This function may or may not report an error. This is indicated in the
-// reported return value. If reported is true, then the specification is
-// ambiguous or not valid, and should be propagated.
-func (pc *passContext) matchFieldList(pos token.Pos, fields []*ast.Field, guardName string, exclusive bool) (number int, fl fieldList, reported, ok bool) {
- parts := strings.Split(guardName, ".")
- firstName := parts[0]
- index := 0
- for _, field := range fields {
- // See countFields, above.
- if len(field.Names) == 0 {
- index++
- continue
- }
- for _, name := range field.Names {
- if name.Name != firstName {
- index++
- continue
- }
- obj := pc.pass.TypesInfo.ObjectOf(name)
- fl, ok := pc.maybeFindFieldList(pos, obj, parts[1:], exclusive)
- if !ok {
- // Some intermediate name does not match. The
- // resolveField function will not report.
- pc.maybeFail(pos, "name %s does not resolve to a field", guardName)
- return 0, nil, true, false
- }
- // Successfully found a field.
- return index, fl, false, true
- }
- }
-
- // Nothing matching.
- return 0, nil, false, false
-}
-
-// findFunctionGuard identifies the parameter number and field number for a
-// particular string of the 'a.b'.
-//
-// This function will report any errors directly.
-func (pc *passContext) findFunctionGuard(d *ast.FuncDecl, guardName string, exclusive bool, allowReturn bool) (functionGuardInfo, bool) {
- // Match against receiver & parameters.
- var parameterList []*ast.Field
- if d.Recv != nil {
- parameterList = append(parameterList, d.Recv.List...)
- }
- if d.Type.Params != nil {
- parameterList = append(parameterList, d.Type.Params.List...)
- }
- if index, fl, reported, ok := pc.matchFieldList(d.Pos(), parameterList, guardName, exclusive); reported || ok {
- if !ok {
- return functionGuardInfo{}, false
- }
- return functionGuardInfo{
- Resolver: &parameterGuard{
- Index: index,
- FieldList: fl,
- },
- Exclusive: exclusive,
- }, true
- }
-
- // Match against return values, if allowed.
- if allowReturn {
- var returnList []*ast.Field
- if d.Type.Results != nil {
- returnList = append(returnList, d.Type.Results.List...)
- }
- if index, fl, reported, ok := pc.matchFieldList(d.Pos(), returnList, guardName, exclusive); reported || ok {
- if !ok {
- return functionGuardInfo{}, false
- }
- return functionGuardInfo{
- Resolver: &returnGuard{
- Index: index,
- FieldList: fl,
- NeedsExtract: countFields(returnList) > 1,
- },
- Exclusive: exclusive,
- }, true
- }
- }
-
- // Match against globals.
- if g, ok := pc.findGlobalFunctionGuard(d.Pos(), guardName); ok {
- return functionGuardInfo{
- Resolver: g,
- Exclusive: exclusive,
- }, true
- }
-
- // No match found.
- pc.maybeFail(d.Pos(), "annotation %s does not have a match any parameter, return value or global", guardName)
- return functionGuardInfo{}, false
-}
-
-// functionFacts exports relevant function findings.
-func (pc *passContext) functionFacts(d *ast.FuncDecl) {
- // Extract guard information.
- if d.Doc == nil || d.Doc.List == nil {
- return
- }
- var lff lockFunctionFacts
- for _, l := range d.Doc.List {
- pc.extractAnnotations(l.Text, map[string]func(string){
- checkLocksIgnore: func(string) {
- // Note that this applies to all atomic
- // analysis as well. There is no provided way
- // to selectively ignore only lock analysis or
- // atomic analysis, as we expect this use to be
- // extremely rare.
- lff.Ignore = true
- },
- checkLocksAnnotation: func(guardName string) { lff.addGuardedBy(pc, d, guardName, true /* exclusive */) },
- checkLocksAnnotationRead: func(guardName string) { lff.addGuardedBy(pc, d, guardName, false /* exclusive */) },
- checkLocksAcquires: func(guardName string) { lff.addAcquires(pc, d, guardName, true /* exclusive */) },
- checkLocksAcquiresRead: func(guardName string) { lff.addAcquires(pc, d, guardName, false /* exclusive */) },
- checkLocksReleases: func(guardName string) { lff.addReleases(pc, d, guardName, true /* exclusive */) },
- checkLocksReleasesRead: func(guardName string) { lff.addReleases(pc, d, guardName, false /* exclusive */) },
- checkLocksAlias: func(guardName string) { lff.addAlias(pc, d, guardName) },
- })
- }
-
- // Export the function facts if there is anything to save.
- if lff.Ignore || len(lff.HeldOnEntry) > 0 || len(lff.HeldOnExit) > 0 {
- funcObj := pc.pass.TypesInfo.Defs[d.Name].(*types.Func)
- pc.pass.ExportObjectFact(funcObj, &lff)
- }
-}
-
-func init() {
- gob.Register((*returnGuard)(nil))
- gob.Register((*globalGuard)(nil))
- gob.Register((*parameterGuard)(nil))
- gob.Register((*fieldGuard)(nil))
- gob.Register((*fieldStructPtr)(nil))
- gob.Register((*fieldStruct)(nil))
-}
diff --git a/tools/checklocks/state.go b/tools/checklocks/state.go
deleted file mode 100644
index 2de373b27..000000000
--- a/tools/checklocks/state.go
+++ /dev/null
@@ -1,377 +0,0 @@
-// Copyright 2020 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package checklocks
-
-import (
- "fmt"
- "go/token"
- "go/types"
- "strings"
- "sync/atomic"
-
- "golang.org/x/tools/go/ssa"
-)
-
-// lockInfo describes a held lock.
-type lockInfo struct {
- exclusive bool
- object types.Object
-}
-
-// lockState tracks the locking state and aliases.
-type lockState struct {
- // lockedMutexes is used to track which mutexes in a given struct are
- // currently locked. Note that most of the heavy lifting is done by
- // valueAndObject below, which maps to specific structure fields, etc.
- //
- // The value indicates whether this is an exclusive lock.
- lockedMutexes map[string]lockInfo
-
- // stored stores values that have been stored in memory, bound to
- // FreeVars or passed as Parameterse.
- stored map[ssa.Value]ssa.Value
-
- // used is a temporary map, used only for valueAndObject. It prevents
- // multiple use of the same memory location.
- used map[ssa.Value]struct{}
-
- // defers are the stack of defers that have been pushed.
- defers []*ssa.Defer
-
- // refs indicates the number of references on this structure. If it's
- // greater than one, we will do copy-on-write.
- refs *int32
-}
-
-// newLockState makes a new lockState.
-func newLockState() *lockState {
- refs := int32(1) // Not shared.
- return &lockState{
- lockedMutexes: make(map[string]lockInfo),
- used: make(map[ssa.Value]struct{}),
- stored: make(map[ssa.Value]ssa.Value),
- defers: make([]*ssa.Defer, 0),
- refs: &refs,
- }
-}
-
-// fork forks the locking state. When a lockState is forked, any modifications
-// will cause maps to be copied.
-func (l *lockState) fork() *lockState {
- if l == nil {
- return newLockState()
- }
- atomic.AddInt32(l.refs, 1)
- return &lockState{
- lockedMutexes: l.lockedMutexes,
- used: make(map[ssa.Value]struct{}),
- stored: l.stored,
- defers: l.defers,
- refs: l.refs,
- }
-}
-
-// modify indicates that this state will be modified.
-func (l *lockState) modify() {
- if atomic.LoadInt32(l.refs) > 1 {
- // Copy the lockedMutexes.
- lm := make(map[string]lockInfo)
- for k, v := range l.lockedMutexes {
- lm[k] = v
- }
- l.lockedMutexes = lm
-
- // Copy the stored values.
- s := make(map[ssa.Value]ssa.Value)
- for k, v := range l.stored {
- s[k] = v
- }
- l.stored = s
-
- // Reset the used values.
- l.used = make(map[ssa.Value]struct{})
-
- // Copy the defers.
- ds := make([]*ssa.Defer, len(l.defers))
- copy(ds, l.defers)
- l.defers = ds
-
- // Drop our reference.
- atomic.AddInt32(l.refs, -1)
- newRefs := int32(1) // Not shared.
- l.refs = &newRefs
- }
-}
-
-// isHeld indicates whether the field is held is not.
-//
-// Precondition: rv must be valid.
-func (l *lockState) isHeld(rv resolvedValue, exclusiveRequired bool) (string, bool) {
- if !rv.valid() {
- panic("invalid resolvedValue passed to isHeld")
- }
- s, _ := rv.valueAndObject(l)
- info, ok := l.lockedMutexes[s]
- if !ok {
- return s, false
- }
- // Accept a weaker lock if exclusiveRequired is false.
- if exclusiveRequired && !info.exclusive {
- return s, false
- }
- return s, true
-}
-
-// lockField locks the given field.
-//
-// If false is returned, the field was already locked.
-//
-// Precondition: rv must be valid.
-func (l *lockState) lockField(rv resolvedValue, exclusive bool) (string, bool) {
- if !rv.valid() {
- panic("invalid resolvedValue passed to isHeld")
- }
- s, obj := rv.valueAndObject(l)
- if _, ok := l.lockedMutexes[s]; ok {
- return s, false
- }
- l.modify()
- l.lockedMutexes[s] = lockInfo{
- exclusive: exclusive,
- object: obj,
- }
- return s, true
-}
-
-// unlockField unlocks the given field.
-//
-// If false is returned, the field was not locked.
-//
-// Precondition: rv must be valid.
-func (l *lockState) unlockField(rv resolvedValue, exclusive bool) (string, bool) {
- if !rv.valid() {
- panic("invalid resolvedValue passed to isHeld")
- }
- s, _ := rv.valueAndObject(l)
- info, ok := l.lockedMutexes[s]
- if !ok {
- return s, false
- }
- if info.exclusive != exclusive {
- return s, false
- }
- l.modify()
- delete(l.lockedMutexes, s)
- return s, true
-}
-
-// downgradeField downgrades the given field.
-//
-// If false was returned, the field was not downgraded.
-//
-// Precondition: rv must be valid.
-func (l *lockState) downgradeField(rv resolvedValue) (string, bool) {
- if !rv.valid() {
- panic("invalid resolvedValue passed to isHeld")
- }
- s, _ := rv.valueAndObject(l)
- info, ok := l.lockedMutexes[s]
- if !ok {
- return s, false
- }
- if !info.exclusive {
- return s, false
- }
- l.modify()
- info.exclusive = false
- l.lockedMutexes[s] = info // Downgraded.
- return s, true
-}
-
-// store records an alias.
-func (l *lockState) store(addr ssa.Value, v ssa.Value) {
- l.modify()
- l.stored[addr] = v
-}
-
-// isSubset indicates other holds all the locks held by l.
-func (l *lockState) isSubset(other *lockState) bool {
- for k, info := range l.lockedMutexes {
- otherInfo, otherOk := other.lockedMutexes[k]
- if !otherOk {
- return false
- }
- // Accept weaker locks as a subset.
- if info.exclusive && !otherInfo.exclusive {
- return false
- }
- }
- return true
-}
-
-// count indicates the number of locks held.
-func (l *lockState) count() int {
- return len(l.lockedMutexes)
-}
-
-// isCompatible returns true if the states are compatible.
-func (l *lockState) isCompatible(other *lockState) bool {
- return l.isSubset(other) && other.isSubset(l)
-}
-
-// elemType is a type that implements the Elem function.
-type elemType interface {
- Elem() types.Type
-}
-
-// valueAndObject returns a string for a given value, along with a source level
-// object (if available and relevant).
-//
-// This decomposes the value into the simplest possible representation in terms
-// of parameters, free variables and globals. During resolution, stored values
-// may be transferred, as well as bound free variables.
-//
-// Nil may not be passed here.
-func (l *lockState) valueAndObject(v ssa.Value) (string, types.Object) {
- switch x := v.(type) {
- case *ssa.Parameter:
- // Was this provided as a paramter for a local anonymous
- // function invocation?
- v, ok := l.stored[x]
- if ok {
- return l.valueAndObject(v)
- }
- return fmt.Sprintf("{param:%s}", x.Name()), x.Object()
- case *ssa.Global:
- return fmt.Sprintf("{global:%s}", x.Name()), x.Object()
- case *ssa.FreeVar:
- // Attempt to resolve this, in case we are being invoked in a
- // scope where all the variables are bound.
- v, ok := l.stored[x]
- if ok {
- // The FreeVar is typically bound to a location, so we
- // check what's been stored there. Note that the second
- // may map to the same FreeVar, which we can check.
- stored, ok := l.stored[v]
- if ok {
- return l.valueAndObject(stored)
- }
- }
- // FreeVar does not have a corresponding source-level object
- // that we can return here.
- return fmt.Sprintf("{freevar:%s}", x.Name()), nil
- case *ssa.Convert:
- // Just disregard conversion.
- return l.valueAndObject(x.X)
- case *ssa.ChangeType:
- // Ditto, disregard.
- return l.valueAndObject(x.X)
- case *ssa.UnOp:
- if x.Op != token.MUL {
- break
- }
- // Is this loading a free variable? If yes, then this can be
- // resolved in the original isAlias function.
- if fv, ok := x.X.(*ssa.FreeVar); ok {
- return l.valueAndObject(fv)
- }
- // Should be try to resolve via a memory address? This needs to
- // be done since a memory location can hold its own value.
- if _, ok := l.used[x.X]; !ok {
- // Check if we know what the accessed location holds.
- // This is used to disambiguate memory locations.
- v, ok := l.stored[x.X]
- if ok {
- l.used[x.X] = struct{}{}
- defer func() { delete(l.used, x.X) }()
- return l.valueAndObject(v)
- }
- }
- // x.X.Type is pointer. We must construct this type
- // dynamically, since the ssa.Value could be synthetic.
- s, obj := l.valueAndObject(x.X)
- return fmt.Sprintf("*(%s)", s), obj
- case *ssa.Field:
- structType, ok := resolveStruct(x.X.Type())
- if !ok {
- // This should not happen.
- panic(fmt.Sprintf("structType not available for struct: %#v", x.X))
- }
- fieldObj := structType.Field(x.Field)
- s, _ := l.valueAndObject(x.X)
- return fmt.Sprintf("%s.%s", s, fieldObj.Name()), fieldObj
- case *ssa.FieldAddr:
- structType, ok := resolveStruct(x.X.Type())
- if !ok {
- // This should not happen.
- panic(fmt.Sprintf("structType not available for struct: %#v", x.X))
- }
- fieldObj := structType.Field(x.Field)
- s, _ := l.valueAndObject(x.X)
- return fmt.Sprintf("&(%s.%s)", s, fieldObj.Name()), fieldObj
- case *ssa.Index:
- s, _ := l.valueAndObject(x.X)
- i, _ := l.valueAndObject(x.Index)
- return fmt.Sprintf("%s[%s]", s, i), nil
- case *ssa.IndexAddr:
- s, _ := l.valueAndObject(x.X)
- i, _ := l.valueAndObject(x.Index)
- return fmt.Sprintf("&(%s[%s])", s, i), nil
- case *ssa.Lookup:
- s, _ := l.valueAndObject(x.X)
- i, _ := l.valueAndObject(x.Index)
- return fmt.Sprintf("%s[%s]", s, i), nil
- case *ssa.Extract:
- s, _ := l.valueAndObject(x.Tuple)
- return fmt.Sprintf("%s[%d]", s, x.Index), nil
- }
-
- // In the case of any other type (e.g. this may be an alloc, a return
- // value, etc.), just return the literal pointer value to the Value.
- // This will be unique within the ssa graph, and so if two values are
- // equal, they are from the same type.
- return fmt.Sprintf("{%T:%p}", v, v), nil
-}
-
-// String returns the full lock state.
-func (l *lockState) String() string {
- if l.count() == 0 {
- return "no locks held"
- }
- keys := make([]string, 0, len(l.lockedMutexes))
- for k, info := range l.lockedMutexes {
- // Include the exclusive status of each lock.
- keys = append(keys, fmt.Sprintf("%s %s", k, exclusiveStr(info.exclusive)))
- }
- return strings.Join(keys, ",")
-}
-
-// pushDefer pushes a defer onto the stack.
-func (l *lockState) pushDefer(d *ssa.Defer) {
- l.modify()
- l.defers = append(l.defers, d)
-}
-
-// popDefer pops a defer from the stack.
-func (l *lockState) popDefer() *ssa.Defer {
- // Does not technically modify the underlying slice.
- count := len(l.defers)
- if count == 0 {
- return nil
- }
- d := l.defers[count-1]
- l.defers = l.defers[:count-1]
- return d
-}
diff --git a/tools/checklocks/test/BUILD b/tools/checklocks/test/BUILD
deleted file mode 100644
index 21a68fbdf..000000000
--- a/tools/checklocks/test/BUILD
+++ /dev/null
@@ -1,30 +0,0 @@
-load("//tools:defs.bzl", "go_library")
-
-package(licenses = ["notice"])
-
-go_library(
- name = "test",
- srcs = [
- "aliases.go",
- "alignment.go",
- "anon.go",
- "atomics.go",
- "basics.go",
- "branches.go",
- "closures.go",
- "defer.go",
- "globals.go",
- "incompat.go",
- "inferred.go",
- "locker.go",
- "methods.go",
- "parameters.go",
- "return.go",
- "rwmutex.go",
- "test.go",
- ],
- # This ensures that there are no dependencies, since we want to explicitly
- # control expected failures for analysis.
- marshal = False,
- stateify = False,
-)
diff --git a/tools/checklocks/test/aliases.go b/tools/checklocks/test/aliases.go
deleted file mode 100644
index e28027fe5..000000000
--- a/tools/checklocks/test/aliases.go
+++ /dev/null
@@ -1,26 +0,0 @@
-// Copyright 2021 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package test
-
-// +checklocks:tc.mu
-// +checklocksalias:tc2.mu=tc.mu
-func testAliasValid(tc *oneGuardStruct, tc2 *oneGuardStruct) {
- tc2.guardedField = 1
-}
-
-// +checklocks:tc.mu
-func testAliasInvalid(tc *oneGuardStruct, tc2 *oneGuardStruct) {
- tc2.guardedField = 1 // +checklocksfail
-}
diff --git a/tools/checklocks/test/alignment.go b/tools/checklocks/test/alignment.go
deleted file mode 100644
index cd857ff73..000000000
--- a/tools/checklocks/test/alignment.go
+++ /dev/null
@@ -1,51 +0,0 @@
-// Copyright 2020 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package test
-
-type alignedStruct32 struct {
- v int32
-}
-
-type alignedStruct64 struct {
- v int64
-}
-
-type alignedStructGood struct {
- v0 alignedStruct32
- v1 alignedStruct32
- v2 alignedStruct64
-}
-
-type alignedStructGoodArray0 struct {
- v0 [3]alignedStruct32
- v1 [3]alignedStruct32
- v2 alignedStruct64
-}
-
-type alignedStructGoodArray1 [16]alignedStructGood
-
-type alignedStructBad struct {
- v0 alignedStruct32
- v1 alignedStruct64
- v2 alignedStruct32
-}
-
-type alignedStructBadArray0 struct {
- v0 [3]alignedStruct32
- v1 [2]alignedStruct64
- v2 [1]alignedStruct32
-}
-
-type alignedStructBadArray1 [16]alignedStructBad
diff --git a/tools/checklocks/test/anon.go b/tools/checklocks/test/anon.go
deleted file mode 100644
index a1f6bddda..000000000
--- a/tools/checklocks/test/anon.go
+++ /dev/null
@@ -1,35 +0,0 @@
-// Copyright 2021 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package test
-
-import "sync"
-
-type anonStruct struct {
- anon struct {
- mu sync.RWMutex
- // +checklocks:mu
- x int
- }
-}
-
-func testAnonAccessValid(tc *anonStruct) {
- tc.anon.mu.Lock()
- tc.anon.x = 1
- tc.anon.mu.Unlock()
-}
-
-func testAnonAccessInvalid(tc *anonStruct) {
- tc.anon.x = 1 // +checklocksfail
-}
diff --git a/tools/checklocks/test/atomics.go b/tools/checklocks/test/atomics.go
deleted file mode 100644
index 8e060d8a2..000000000
--- a/tools/checklocks/test/atomics.go
+++ /dev/null
@@ -1,91 +0,0 @@
-// Copyright 2020 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package test
-
-import (
- "sync"
- "sync/atomic"
-)
-
-type atomicStruct struct {
- accessedNormally int32
-
- // +checkatomic
- accessedAtomically int32
-
- // +checklocksignore
- ignored int32
-}
-
-func testNormalAccess(tc *atomicStruct, v chan int32, p chan *int32) {
- v <- tc.accessedNormally
- p <- &tc.accessedNormally
-}
-
-func testAtomicAccess(tc *atomicStruct, v chan int32) {
- v <- atomic.LoadInt32(&tc.accessedAtomically)
-}
-
-func testAtomicAccessInvalid(tc *atomicStruct, v chan int32) {
- v <- atomic.LoadInt32(&tc.accessedNormally) // +checklocksfail
-}
-
-func testNormalAccessInvalid(tc *atomicStruct, v chan int32, p chan *int32) {
- v <- tc.accessedAtomically // +checklocksfail
- p <- &tc.accessedAtomically // +checklocksfail
-}
-
-func testIgnored(tc *atomicStruct, v chan int32, p chan *int32) {
- v <- atomic.LoadInt32(&tc.ignored)
- v <- tc.ignored
- p <- &tc.ignored
-}
-
-type atomicMixedStruct struct {
- mu sync.Mutex
-
- // +checkatomic
- // +checklocks:mu
- accessedMixed int32
-}
-
-func testAtomicMixedValidRead(tc *atomicMixedStruct, v chan int32) {
- v <- atomic.LoadInt32(&tc.accessedMixed)
-}
-
-func testAtomicMixedInvalidRead(tc *atomicMixedStruct, v chan int32, p chan *int32) {
- v <- tc.accessedMixed // +checklocksfail
- p <- &tc.accessedMixed // +checklocksfail
-}
-
-func testAtomicMixedValidLockedWrite(tc *atomicMixedStruct, v chan int32, p chan *int32) {
- tc.mu.Lock()
- atomic.StoreInt32(&tc.accessedMixed, 1)
- tc.mu.Unlock()
-}
-
-func testAtomicMixedInvalidLockedWrite(tc *atomicMixedStruct, v chan int32, p chan *int32) {
- tc.mu.Lock()
- tc.accessedMixed = 1 // +checklocksfail:2
- tc.mu.Unlock()
-}
-
-func testAtomicMixedInvalidAtomicWrite(tc *atomicMixedStruct, v chan int32, p chan *int32) {
- atomic.StoreInt32(&tc.accessedMixed, 1) // +checklocksfail
-}
-
-func testAtomicMixedInvalidWrite(tc *atomicMixedStruct, v chan int32, p chan *int32) {
- tc.accessedMixed = 1 // +checklocksfail:2
-}
diff --git a/tools/checklocks/test/basics.go b/tools/checklocks/test/basics.go
deleted file mode 100644
index e941fba5b..000000000
--- a/tools/checklocks/test/basics.go
+++ /dev/null
@@ -1,145 +0,0 @@
-// Copyright 2020 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package test
-
-import (
- "sync"
-)
-
-func testLockedAccessValid(tc *oneGuardStruct) {
- tc.mu.Lock()
- tc.guardedField = 1
- tc.mu.Unlock()
-}
-
-func testLockedAccessIgnore(tc *oneGuardStruct) {
- tc.mu.Lock()
- tc.unguardedField = 1
- tc.mu.Unlock()
-}
-
-func testUnlockedAccessInvalidWrite(tc *oneGuardStruct) {
- tc.guardedField = 2 // +checklocksfail
-}
-
-func testUnlockedAccessInvalidRead(tc *oneGuardStruct) {
- x := tc.guardedField // +checklocksfail
- _ = x
-}
-
-func testUnlockedAccessValid(tc *oneGuardStruct) {
- tc.unguardedField = 2
-}
-
-func testCallValidAccess(tc *oneGuardStruct) {
- callValidAccess(tc)
-}
-
-func callValidAccess(tc *oneGuardStruct) {
- tc.mu.Lock()
- tc.guardedField = 1
- tc.mu.Unlock()
-}
-
-func testCallValueMixup(tc *oneGuardStruct) {
- callValueMixup(tc, tc)
-}
-
-func callValueMixup(tc1, tc2 *oneGuardStruct) {
- tc1.mu.Lock()
- tc2.guardedField = 2 // +checklocksfail
- tc1.mu.Unlock()
-}
-
-func testCallPreconditionsInvalid(tc *oneGuardStruct) {
- callPreconditions(tc) // +checklocksfail
-}
-
-func testCallPreconditionsValid(tc *oneGuardStruct) {
- tc.mu.Lock()
- callPreconditions(tc)
- tc.mu.Unlock()
-}
-
-// +checklocks:tc.mu
-func callPreconditions(tc *oneGuardStruct) {
- tc.guardedField = 1
-}
-
-type nestedFieldsStruct struct {
- mu sync.Mutex
-
- // +checklocks:mu
- nestedStruct struct {
- nested1 int
- nested2 int
- }
-}
-
-func testNestedGuardValid(tc *nestedFieldsStruct) {
- tc.mu.Lock()
- tc.nestedStruct.nested1 = 1
- tc.nestedStruct.nested2 = 2
- tc.mu.Unlock()
-}
-
-func testNestedGuardInvalid(tc *nestedFieldsStruct) {
- tc.nestedStruct.nested1 = 1 // +checklocksfail
-}
-
-type rwGuardStruct struct {
- rwMu sync.RWMutex
-
- // +checklocks:rwMu
- guardedField int
-}
-
-func testRWValidRead(tc *rwGuardStruct) {
- tc.rwMu.Lock()
- _ = tc.guardedField
- tc.rwMu.Unlock()
-}
-
-func testRWValidWrite(tc *rwGuardStruct) {
- tc.rwMu.Lock()
- tc.guardedField = 2
- tc.rwMu.Unlock()
-}
-
-func testRWInvalidWrite(tc *rwGuardStruct) {
- tc.guardedField = 3 // +checklocksfail
-}
-
-func testRWInvalidRead(tc *rwGuardStruct) {
- x := tc.guardedField + 3 // +checklocksfail
- _ = x
-}
-
-func testTwoLocksDoubleGuardStructValid(tc *twoLocksDoubleGuardStruct) {
- tc.mu.Lock()
- tc.secondMu.Lock()
- tc.doubleGuardedField = 1
- tc.secondMu.Unlock()
-}
-
-func testTwoLocksDoubleGuardStructOnlyOne(tc *twoLocksDoubleGuardStruct) {
- tc.mu.Lock()
- tc.doubleGuardedField = 2 // +checklocksfail
- tc.mu.Unlock()
-}
-
-func testTwoLocksDoubleGuardStructInvalid(tc *twoLocksDoubleGuardStruct) {
- tc.doubleGuardedField = 3 // +checklocksfail:2
-}
diff --git a/tools/checklocks/test/branches.go b/tools/checklocks/test/branches.go
deleted file mode 100644
index 247885a49..000000000
--- a/tools/checklocks/test/branches.go
+++ /dev/null
@@ -1,72 +0,0 @@
-// Copyright 2020 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package test
-
-import (
- "math/rand"
-)
-
-func testInconsistentReturn(tc *oneGuardStruct) { // +checklocksfail
- if x := rand.Intn(10); x%2 == 1 {
- tc.mu.Lock()
- }
-}
-
-func testConsistentBranching(tc *oneGuardStruct) {
- x := rand.Intn(10)
- if x%2 == 1 {
- tc.mu.Lock()
- } else {
- tc.mu.Lock()
- }
- tc.guardedField = 1
- if x%2 == 1 {
- tc.mu.Unlock()
- } else {
- tc.mu.Unlock()
- }
-}
-
-func testInconsistentBranching(tc *oneGuardStruct) { // +checklocksfail:2
- // We traverse the control flow graph in all consistent ways. We cannot
- // determine however, that the first if block and second if block will
- // evaluate to the same condition. Therefore, there are two consistent
- // paths through this code, and two inconsistent paths. Either way, the
- // guardedField should be also marked as an invalid access.
- x := rand.Intn(10)
- if x%2 == 1 {
- tc.mu.Lock()
- }
- tc.guardedField = 1 // +checklocksfail
- if x%2 == 1 {
- tc.mu.Unlock() // +checklocksforce
- }
-}
-
-func testUnboundedLocks(tc []*oneGuardStruct) {
- for _, l := range tc {
- l.mu.Lock()
- }
- // This test should have the above *not fail*, though the exact
- // lock state cannot be tracked through the below. Therefore, we
- // expect the next loop to actually fail, and we force the unlock
- // loop to succeed in exactly the same way.
- for _, l := range tc {
- l.guardedField = 1 // +checklocksfail
- }
- for _, l := range tc {
- l.mu.Unlock() // +checklocksforce
- }
-}
diff --git a/tools/checklocks/test/closures.go b/tools/checklocks/test/closures.go
deleted file mode 100644
index 316d12ce1..000000000
--- a/tools/checklocks/test/closures.go
+++ /dev/null
@@ -1,118 +0,0 @@
-// Copyright 2020 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package test
-
-func testClosureInvalid(tc *oneGuardStruct) {
- // This is expected to fail.
- callClosure(func() {
- tc.guardedField = 1 // +checklocksfail
- })
-}
-
-func testClosureUnsupported(tc *oneGuardStruct) {
- // Locked outside the closure, so may or may not be valid. This cannot
- // be handled and we should explicitly fail. This can't be handled
- // because of the call through callClosure, below, which means the
- // closure will actually be passed as a value somewhere.
- tc.mu.Lock()
- callClosure(func() {
- tc.guardedField = 1 // +checklocksfail
- })
- tc.mu.Unlock()
-}
-
-func testClosureValid(tc *oneGuardStruct) {
- // All locking happens within the closure. This should not present a
- // problem for analysis.
- callClosure(func() {
- tc.mu.Lock()
- tc.guardedField = 1
- tc.mu.Unlock()
- })
-}
-
-func testClosureInline(tc *oneGuardStruct) {
- // If the closure is being dispatching inline only, then we should be
- // able to analyze this call and give it a thumbs up.
- tc.mu.Lock()
- func() {
- tc.guardedField = 1
- }()
- tc.mu.Unlock()
-}
-
-// +checklocksignore
-func testClosureIgnore(tc *oneGuardStruct) {
- // Inherit the checklocksignore.
- x := func() {
- tc.guardedField = 1
- }
- x()
-}
-
-func testAnonymousInvalid(tc *oneGuardStruct) {
- // Invalid, as per testClosureInvalid above.
- callAnonymous(func(tc *oneGuardStruct) {
- tc.guardedField = 1 // +checklocksfail
- }, tc)
-}
-
-func testAnonymousUnsupported(tc *oneGuardStruct) {
- // Not supportable, as per testClosureUnsupported above.
- tc.mu.Lock()
- callAnonymous(func(tc *oneGuardStruct) {
- tc.guardedField = 1 // +checklocksfail
- }, tc)
- tc.mu.Unlock()
-}
-
-func testAnonymousValid(tc *oneGuardStruct) {
- // Valid, as per testClosureValid above.
- callAnonymous(func(tc *oneGuardStruct) {
- tc.mu.Lock()
- tc.guardedField = 1
- tc.mu.Unlock()
- }, tc)
-}
-
-func testAnonymousInline(tc *oneGuardStruct) {
- // Unlike the closure case, we are able to dynamically infer the set of
- // preconditions for the function dispatch and assert that this is
- // a valid call.
- tc.mu.Lock()
- func(tc *oneGuardStruct) {
- tc.guardedField = 1
- }(tc)
- tc.mu.Unlock()
-}
-
-// +checklocksignore
-func testAnonymousIgnore(tc *oneGuardStruct) {
- // Inherit the checklocksignore.
- x := func(tc *oneGuardStruct) {
- tc.guardedField = 1
- }
- x(tc)
-}
-
-//go:noinline
-func callClosure(fn func()) {
- fn()
-}
-
-//go:noinline
-func callAnonymous(fn func(*oneGuardStruct), tc *oneGuardStruct) {
- fn(tc)
-}
diff --git a/tools/checklocks/test/defer.go b/tools/checklocks/test/defer.go
deleted file mode 100644
index 6e574e5eb..000000000
--- a/tools/checklocks/test/defer.go
+++ /dev/null
@@ -1,38 +0,0 @@
-// Copyright 2020 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package test
-
-func testDeferValidUnlock(tc *oneGuardStruct) {
- tc.mu.Lock()
- tc.guardedField = 1
- defer tc.mu.Unlock()
-}
-
-func testDeferValidAccess(tc *oneGuardStruct) {
- tc.mu.Lock()
- defer func() {
- tc.guardedField = 1
- tc.mu.Unlock()
- }()
-}
-
-func testDeferInvalidAccess(tc *oneGuardStruct) {
- tc.mu.Lock()
- defer func() {
- // N.B. Executed after tc.mu.Unlock().
- tc.guardedField = 1 // +checklocksfail
- }()
- tc.mu.Unlock()
-}
diff --git a/tools/checklocks/test/globals.go b/tools/checklocks/test/globals.go
deleted file mode 100644
index 656b0c9a3..000000000
--- a/tools/checklocks/test/globals.go
+++ /dev/null
@@ -1,85 +0,0 @@
-// Copyright 2020 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package test
-
-import (
- "sync"
-)
-
-var (
- globalMu sync.Mutex
- globalRWMu sync.RWMutex
-)
-
-var globalStruct struct {
- mu sync.Mutex
- // +checklocks:mu
- guardedField int
-}
-
-var otherStruct struct {
- // +checklocks:globalMu
- guardedField1 int
- // +checklocks:globalRWMu
- guardedField2 int
- // +checklocks:globalStruct.mu
- guardedField3 int
-}
-
-func testGlobalValid() {
- globalMu.Lock()
- otherStruct.guardedField1 = 1
- globalMu.Unlock()
-
- globalRWMu.Lock()
- otherStruct.guardedField2 = 1
- globalRWMu.Unlock()
-
- globalRWMu.RLock()
- _ = otherStruct.guardedField2
- globalRWMu.RUnlock()
-
- globalStruct.mu.Lock()
- globalStruct.guardedField = 1
- otherStruct.guardedField3 = 1
- globalStruct.mu.Unlock()
-}
-
-// +checklocks:globalStruct.mu
-func testGlobalValidPreconditions0() {
- globalStruct.guardedField = 1
-}
-
-// +checklocks:globalMu
-func testGlobalValidPreconditions1() {
- otherStruct.guardedField1 = 1
-}
-
-// +checklocks:globalRWMu
-func testGlobalValidPreconditions2() {
- otherStruct.guardedField2 = 1
-}
-
-// +checklocks:globalStruct.mu
-func testGlobalValidPreconditions3() {
- otherStruct.guardedField3 = 1
-}
-
-func testGlobalInvalid() {
- globalStruct.guardedField = 1 // +checklocksfail
- otherStruct.guardedField1 = 1 // +checklocksfail
- otherStruct.guardedField2 = 1 // +checklocksfail
- otherStruct.guardedField3 = 1 // +checklocksfail
-}
diff --git a/tools/checklocks/test/incompat.go b/tools/checklocks/test/incompat.go
deleted file mode 100644
index f55fa532d..000000000
--- a/tools/checklocks/test/incompat.go
+++ /dev/null
@@ -1,45 +0,0 @@
-// Copyright 2020 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package test
-
-import (
- "sync"
-)
-
-// badFieldsStruct verifies that refering invalid fields fails.
-type badFieldsStruct struct {
- // +checklocks:mu
- x int // +checklocksfail
-}
-
-// redundantStruct verifies that redundant annotations fail.
-type redundantStruct struct {
- mu sync.Mutex
-
- // +checklocks:mu
- // +checklocks:mu
- x int // +checklocksfail
-}
-
-// conflictsStruct verifies that conflicting annotations fail.
-type conflictsStruct struct {
- // +checkatomicignore
- // +checkatomic
- x int // +checklocksfail
-
- // +checkatomic
- // +checkatomicignore
- y int // +checklocksfail
-}
diff --git a/tools/checklocks/test/inferred.go b/tools/checklocks/test/inferred.go
deleted file mode 100644
index 5495bdb2a..000000000
--- a/tools/checklocks/test/inferred.go
+++ /dev/null
@@ -1,35 +0,0 @@
-// Copyright 2020 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package test
-
-import (
- "sync"
-)
-
-type inferredStruct struct {
- mu sync.Mutex
- guardedField int // +checklocksfail
- unguardedField int
-}
-
-func testInferredPositive(tc *inferredStruct) {
- tc.mu.Lock()
- tc.guardedField = 1
- tc.mu.Unlock()
-}
-
-func testInferredNegative(tc *inferredStruct) {
- tc.unguardedField = 1
-}
diff --git a/tools/checklocks/test/locker.go b/tools/checklocks/test/locker.go
deleted file mode 100644
index b0e7d1143..000000000
--- a/tools/checklocks/test/locker.go
+++ /dev/null
@@ -1,33 +0,0 @@
-// Copyright 2021 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package test
-
-import "sync"
-
-type lockerStruct struct {
- mu sync.Locker
- // +checklocks:mu
- guardedField int
-}
-
-func testLockerValid(tc *lockerStruct) {
- tc.mu.Lock()
- tc.guardedField = 1
- tc.mu.Unlock()
-}
-
-func testLockerInvalid(tc *lockerStruct) {
- tc.guardedField = 1 // +checklocksfail
-}
diff --git a/tools/checklocks/test/methods.go b/tools/checklocks/test/methods.go
deleted file mode 100644
index b67657b61..000000000
--- a/tools/checklocks/test/methods.go
+++ /dev/null
@@ -1,117 +0,0 @@
-// Copyright 2020 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package test
-
-import (
- "sync"
-)
-
-type testMethods struct {
- mu sync.Mutex
-
- // +checklocks:mu
- guardedField int
-}
-
-func (t *testMethods) methodValid() {
- t.mu.Lock()
- t.guardedField = 1
- t.mu.Unlock()
-}
-
-func (t *testMethods) methodInvalid() {
- t.guardedField = 2 // +checklocksfail
-}
-
-// +checklocks:t.mu
-func (t *testMethods) MethodLocked(a, b, c int) {
- t.guardedField = 3
-}
-
-// +checklocksignore
-func (t *testMethods) methodIgnore() {
- t.guardedField = 2
-}
-
-func testMethodCallsValid(tc *testMethods) {
- tc.methodValid()
-}
-
-func testMethodCallsValidPreconditions(tc *testMethods) {
- tc.mu.Lock()
- tc.MethodLocked(1, 2, 3)
- tc.mu.Unlock()
-}
-
-func testMethodCallsInvalid(tc *testMethods) {
- tc.MethodLocked(4, 5, 6) // +checklocksfail
-}
-
-func testMultipleParameters(tc1, tc2, tc3 *testMethods) {
- tc1.mu.Lock()
- tc1.guardedField = 1
- tc2.guardedField = 2 // +checklocksfail
- tc3.guardedField = 3 // +checklocksfail
- tc1.mu.Unlock()
-}
-
-type testMethodsWithParameters struct {
- mu sync.Mutex
-
- // +checklocks:mu
- guardedField int
-}
-
-type ptrToTestMethodsWithParameters *testMethodsWithParameters
-
-// +checklocks:t.mu
-// +checklocks:a.mu
-func (t *testMethodsWithParameters) methodLockedWithParameters(a *testMethodsWithParameters, b *testMethodsWithParameters) {
- t.guardedField = a.guardedField
- b.guardedField = a.guardedField // +checklocksfail
-}
-
-// +checklocks:t.mu
-// +checklocks:a.mu
-// +checklocks:b.mu
-func (t *testMethodsWithParameters) methodLockedWithPtrType(a *testMethodsWithParameters, b ptrToTestMethodsWithParameters) {
- t.guardedField = a.guardedField
- b.guardedField = a.guardedField
-}
-
-// +checklocks:a.mu
-func standaloneFunctionWithGuard(a *testMethodsWithParameters) {
- a.guardedField = 1
- a.mu.Unlock()
- a.guardedField = 1 // +checklocksfail
-}
-
-type testMethodsWithEmbedded struct {
- mu sync.Mutex
-
- // +checklocks:mu
- guardedField int
- p *testMethodsWithParameters // +checklocksignore: Inferred as protected by mu.
-}
-
-// +checklocks:t.mu
-func (t *testMethodsWithEmbedded) DoLocked(a, b *testMethodsWithParameters) {
- t.guardedField = 1
- a.mu.Lock()
- b.mu.Lock()
- t.p.methodLockedWithParameters(a, b) // +checklocksfail
- a.mu.Unlock()
- b.mu.Unlock()
-}
diff --git a/tools/checklocks/test/parameters.go b/tools/checklocks/test/parameters.go
deleted file mode 100644
index 5b9e664b6..000000000
--- a/tools/checklocks/test/parameters.go
+++ /dev/null
@@ -1,48 +0,0 @@
-// Copyright 2020 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package test
-
-func testParameterPassingbyAddrValid(tc *oneGuardStruct) {
- tc.mu.Lock()
- nestedWithGuardByAddr(&tc.guardedField, &tc.unguardedField)
- tc.mu.Unlock()
-}
-
-func testParameterPassingByAddrInalid(tc *oneGuardStruct) {
- nestedWithGuardByAddr(&tc.guardedField, &tc.unguardedField) // +checklocksfail
-}
-
-func testParameterPassingByValueValid(tc *oneGuardStruct) {
- tc.mu.Lock()
- nestedWithGuardByValue(tc.guardedField, tc.unguardedField)
- tc.mu.Unlock()
-}
-
-func testParameterPassingByValueInalid(tc *oneGuardStruct) {
- nestedWithGuardByValue(tc.guardedField, tc.unguardedField) // +checklocksfail
-}
-
-func nestedWithGuardByAddr(guardedField, unguardedField *int) {
- *guardedField = 4
- *unguardedField = 5
-}
-
-func nestedWithGuardByValue(guardedField, unguardedField int) {
- // read the fields to keep SA4009 static analyzer happy.
- _ = guardedField
- _ = unguardedField
- guardedField = 4
- unguardedField = 5
-}
diff --git a/tools/checklocks/test/return.go b/tools/checklocks/test/return.go
deleted file mode 100644
index 47c7b6773..000000000
--- a/tools/checklocks/test/return.go
+++ /dev/null
@@ -1,61 +0,0 @@
-// Copyright 2020 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package test
-
-// +checklocks:tc.mu
-func testReturnInvalidGuard() (tc *oneGuardStruct) { // +checklocksfail
- return new(oneGuardStruct)
-}
-
-// +checklocksrelease:tc.mu
-func testReturnInvalidRelease() (tc *oneGuardStruct) { // +checklocksfail
- return new(oneGuardStruct)
-}
-
-// +checklocksacquire:tc.mu
-func testReturnInvalidAcquire() (tc *oneGuardStruct) {
- return new(oneGuardStruct) // +checklocksfail
-}
-
-// +checklocksacquire:tc.mu
-func testReturnValidAcquire() (tc *oneGuardStruct) {
- tc = new(oneGuardStruct)
- tc.mu.Lock()
- return tc
-}
-
-func testReturnAcquireCall() {
- tc := testReturnValidAcquire()
- tc.guardedField = 1
- tc.mu.Unlock()
-}
-
-// +checklocksacquire:tc.val.mu
-// +checklocksacquire:tc.ptr.mu
-func testReturnValidNestedAcquire() (tc *nestedGuardStruct) {
- tc = new(nestedGuardStruct)
- tc.ptr = new(oneGuardStruct)
- tc.val.mu.Lock()
- tc.ptr.mu.Lock()
- return tc
-}
-
-func testReturnNestedAcquireCall() {
- tc := testReturnValidNestedAcquire()
- tc.val.guardedField = 1
- tc.ptr.guardedField = 1
- tc.val.mu.Unlock()
- tc.ptr.mu.Unlock()
-}
diff --git a/tools/checklocks/test/rwmutex.go b/tools/checklocks/test/rwmutex.go
deleted file mode 100644
index d27ed10e3..000000000
--- a/tools/checklocks/test/rwmutex.go
+++ /dev/null
@@ -1,52 +0,0 @@
-// Copyright 2021 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package test
-
-import (
- "sync"
-)
-
-// oneReadGuardStruct has one read-guarded field.
-type oneReadGuardStruct struct {
- mu sync.RWMutex
- // +checklocks:mu
- guardedField int
-}
-
-func testRWAccessValidRead(tc *oneReadGuardStruct) {
- tc.mu.Lock()
- _ = tc.guardedField
- tc.mu.Unlock()
- tc.mu.RLock()
- _ = tc.guardedField
- tc.mu.RUnlock()
-}
-
-func testRWAccessValidWrite(tc *oneReadGuardStruct) {
- tc.mu.Lock()
- tc.guardedField = 1
- tc.mu.Unlock()
-}
-
-func testRWAccessInvalidWrite(tc *oneReadGuardStruct) {
- tc.guardedField = 2 // +checklocksfail
- tc.mu.RLock()
- tc.guardedField = 2 // +checklocksfail
- tc.mu.RUnlock()
-}
-
-func testRWAccessInvalidRead(tc *oneReadGuardStruct) {
- _ = tc.guardedField // +checklocksfail
-}
diff --git a/tools/checklocks/test/test.go b/tools/checklocks/test/test.go
deleted file mode 100644
index d1a9992fb..000000000
--- a/tools/checklocks/test/test.go
+++ /dev/null
@@ -1,64 +0,0 @@
-// Copyright 2020 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-// Package test is a test package.
-//
-// Tests are all compilation tests in separate files.
-package test
-
-import (
- "sync"
-)
-
-// oneGuardStruct has one guarded field.
-type oneGuardStruct struct {
- mu sync.Mutex
- // +checklocks:mu
- guardedField int
- unguardedField int
-}
-
-// twoGuardStruct has two guarded fields.
-type twoGuardStruct struct {
- mu sync.Mutex
- // +checklocks:mu
- guardedField1 int
- // +checklocks:mu
- guardedField2 int
-}
-
-// twoLocksStruct has two locks and two fields.
-type twoLocksStruct struct {
- mu sync.Mutex
- secondMu sync.Mutex
- // +checklocks:mu
- guardedField1 int
- // +checklocks:secondMu
- guardedField2 int
-}
-
-// twoLocksDoubleGuardStruct has two locks and a single field with two guards.
-type twoLocksDoubleGuardStruct struct {
- mu sync.Mutex
- secondMu sync.Mutex // +checklocksignore: mu is inferred as requisite.
- // +checklocks:mu
- // +checklocks:secondMu
- doubleGuardedField int
-}
-
-// nestedGuardStruct nests oneGuardStruct fields.
-type nestedGuardStruct struct {
- val oneGuardStruct
- ptr *oneGuardStruct
-}