diff options
Diffstat (limited to 'tools/checklocks')
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: ¶meterGuard{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: ¶meterGuard{ - 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 -} |