summaryrefslogtreecommitdiffhomepage
path: root/tools/checklocks/facts.go
diff options
context:
space:
mode:
Diffstat (limited to 'tools/checklocks/facts.go')
-rw-r--r--tools/checklocks/facts.go641
1 files changed, 0 insertions, 641 deletions
diff --git a/tools/checklocks/facts.go b/tools/checklocks/facts.go
deleted file mode 100644
index fd681adc3..000000000
--- a/tools/checklocks/facts.go
+++ /dev/null
@@ -1,641 +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/ast"
- "go/token"
- "go/types"
- "regexp"
- "strings"
-
- "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
-)
-
-// fieldList is a simple list of fields, used in two types below.
-//
-// Note that the integers in this list refer to one of two things:
-// - A positive integer refers to a field index in a struct.
-// - A negative integer refers to a field index in a struct, where
-// that field is a pointer and must be subsequently resolved.
-type fieldList []int
-
-// 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
- valid bool
- fieldList []int
-}
-
-// findExtract finds a relevant extract. This must exist within the referrers
-// to the call object. If this doesn't then the object which is locked is never
-// consumed, and we should consider this a bug.
-func findExtract(v ssa.Value, index int) (ssa.Value, bool) {
- if refs := v.Referrers(); refs != nil {
- for _, inst := range *refs {
- if x, ok := inst.(*ssa.Extract); ok && x.Tuple == v && x.Index == index {
- return inst.(ssa.Value), true
- }
- }
- }
- return nil, false
-}
-
-// resolve resolves the given field list.
-func (fl fieldList) resolve(v ssa.Value) (rv resolvedValue) {
- return resolvedValue{
- value: v,
- fieldList: fl,
- valid: true,
- }
-}
-
-// valueAsString returns a string representing this value.
-//
-// This must align with how the string is generated in valueAsString.
-func (rv resolvedValue) valueAsString(ls *lockState) string {
- typ := rv.value.Type()
- s := ls.valueAsString(rv.value)
- for i, fieldNumber := range rv.fieldList {
- switch {
- case fieldNumber > 0:
- field, ok := findField(typ, fieldNumber-1)
- if !ok {
- // This can't be resolved, return for debugging.
- return fmt.Sprintf("{%s+%v}", s, rv.fieldList[i:])
- }
- s = fmt.Sprintf("&(%s.%s)", s, field.Name())
- typ = field.Type()
- case fieldNumber < 1:
- field, ok := findField(typ, (-fieldNumber)-1)
- if !ok {
- // See above.
- return fmt.Sprintf("{%s+%v}", s, rv.fieldList[i:])
- }
- s = fmt.Sprintf("*(&(%s.%s))", s, field.Name())
- typ = field.Type()
- }
- }
- return s
-}
-
-// lockFieldFacts apply on every struct field.
-type lockFieldFacts struct {
- // IsMutex is true if the field is of type sync.Mutex.
- IsMutex bool
-
- // IsRWMutex is true if the field is of type sync.RWMutex.
- IsRWMutex bool
-
- // IsPointer indicates if the field is a pointer.
- IsPointer bool
-
- // FieldNumber is the number of this field in the struct.
- FieldNumber int
-}
-
-// AFact implements analysis.Fact.AFact.
-func (*lockFieldFacts) AFact() {}
-
-// 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]fieldList
-
- // 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() {}
-
-// functionGuard is used by lockFunctionFacts, below.
-type functionGuard struct {
- // ParameterNumber is the index of the object that contains the
- // guarding mutex. From this parameter, a walk is performed
- // subsequently using the resolve method.
- //
- // Note that is ParameterNumber is beyond the size of parameters, then
- // it may return to a return value. This applies only for the Acquires
- // relation below.
- ParameterNumber 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 to the object.
- FieldList fieldList
-
- // Exclusive indicates an exclusive lock is required.
- Exclusive bool
-}
-
-// resolveReturn resolves a return value.
-//
-// Precondition: rv is either an ssa.Value, or an *ssa.Return.
-func (fg *functionGuard) resolveReturn(rv interface{}, args int) 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.
- return resolvedValue{
- value: nil,
- valid: false,
- }
- }
- index := fg.ParameterNumber - args
- // 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 r, ok := rv.(*ssa.Return); ok {
- return fg.FieldList.resolve(r.Results[index])
- }
- if fg.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, ok := findExtract(rv.(ssa.Value), index)
- if !ok {
- return resolvedValue{
- value: v,
- valid: false,
- }
- }
- return fg.FieldList.resolve(v)
- }
- if 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 fg.FieldList.resolve(rv.(ssa.Value))
-}
-
-// resolveStatic returns an ssa.Value representing the given field.
-//
-// Precondition: per resolveReturn.
-func (fg *functionGuard) resolveStatic(fn *ssa.Function, rv interface{}) resolvedValue {
- if fg.ParameterNumber >= len(fn.Params) {
- return fg.resolveReturn(rv, len(fn.Params))
- }
- return fg.FieldList.resolve(fn.Params[fg.ParameterNumber])
-}
-
-// resolveCall returns an ssa.Value representing the given field.
-func (fg *functionGuard) resolveCall(args []ssa.Value, rv ssa.Value) resolvedValue {
- if fg.ParameterNumber >= len(args) {
- return fg.resolveReturn(rv, len(args))
- }
- return fg.FieldList.resolve(args[fg.ParameterNumber])
-}
-
-// 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" => {ParameterNumber: 0, FieldNumbers: {0}}
- //
- // Unlikely lockFieldFacts, there is no atomic interpretation.
- HeldOnEntry map[string]functionGuard
-
- // HeldOnExit tracks the locks that are expected to be held on exit.
- HeldOnExit map[string]functionGuard
-
- // 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) (functionGuard, bool) {
- if _, ok := lff.HeldOnEntry[guardName]; ok {
- pc.maybeFail(d.Pos(), "annotation %s specified more than once, already required", guardName)
- return functionGuard{}, false
- }
- if _, ok := lff.HeldOnExit[guardName]; ok {
- pc.maybeFail(d.Pos(), "annotation %s specified more than once, already acquired", guardName)
- return functionGuard{}, 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]functionGuard)
- }
- if lff.HeldOnExit == nil {
- lff.HeldOnExit = make(map[string]functionGuard)
- }
- 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]functionGuard)
- }
- 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]functionGuard)
- }
- lff.HeldOnEntry[guardName] = fg
- }
-}
-
-// fieldListFor returns the fieldList for the given object.
-func (pc *passContext) fieldListFor(pos token.Pos, fieldObj types.Object, index int, fieldName string, checkMutex bool, exclusive bool) (int, bool) {
- var lff lockFieldFacts
- if !pc.pass.ImportObjectFact(fieldObj, &lff) {
- // This should not happen: we export facts for all fields.
- panic(fmt.Sprintf("no lockFieldFacts available for field %s", fieldName))
- }
- // Check that it is indeed a mutex.
- if checkMutex && !lff.IsMutex && !lff.IsRWMutex {
- pc.maybeFail(pos, "field %s is not a Mutex or an RWMutex", fieldName)
- return 0, false
- }
- if checkMutex && !exclusive && !lff.IsRWMutex {
- pc.maybeFail(pos, "field %s must be a RWMutex, but it is not", fieldName)
- return 0, false
- }
- // Return the resolution path.
- if lff.IsPointer {
- return -(index + 1), true
- }
- return (index + 1), true
-}
-
-// resolveOneField resolves a field in a single struct.
-func (pc *passContext) resolveOneField(pos token.Pos, structType *types.Struct, fieldName string, checkMutex bool, exclusive bool) (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
- }
- flOne, ok := pc.fieldListFor(pos, fieldObj, i, fieldName, checkMutex, exclusive)
- if !ok {
- return nil, nil, false
- }
- fl = append(fl, flOne)
- 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, okEmbed := pc.fieldListFor(pos, fieldObj, i, fieldName, false, exclusive)
- flCont, fieldObjCont, okCont := pc.resolveOneField(pos, structType, fieldName, checkMutex, exclusive)
- if okEmbed && okCont {
- fl = append(fl, flEmbed)
- fl = append(fl, flCont...)
- return fl, fieldObjCont, true
- }
- }
- pc.maybeFail(pos, "field %s does not exist", fieldName)
- return nil, nil, false
-}
-
-// resolveField resolves a set of fields given a string, such a 'a.b.c'.
-//
-// Note that this checks that the final element is a mutex of some kind, and
-// will fail appropriately.
-func (pc *passContext) resolveField(pos token.Pos, structType *types.Struct, parts []string, exclusive bool) (fl fieldList, ok bool) {
- for partNumber, fieldName := range parts {
- flOne, fieldObj, ok := pc.resolveOneField(pos, structType, fieldName, partNumber >= len(parts)-1 /* checkMutex */, exclusive)
- if !ok {
- // Error already reported.
- return nil, false
- }
- fl = append(fl, flOne...)
- if partNumber < len(parts)-1 {
- // Traverse to the next type.
- structType, ok = resolveStruct(fieldObj.Type())
- if !ok {
- pc.maybeFail(pos, "invalid intermediate field %s", fieldName)
- return fl, false
- }
- }
- }
- return fl, true
-}
-
-var (
- mutexRE = regexp.MustCompile("((.*/)|^)sync.(CrossGoroutineMutex|Mutex)")
- rwMutexRE = regexp.MustCompile("((.*/)|^)sync.(CrossGoroutineRWMutex|RWMutex)")
-)
-
-// exportLockFieldFacts finds all struct fields that are mutexes, and ensures
-// that they are annotated properly.
-//
-// This information is consumed subsequently by exportLockGuardFacts, and this
-// function must be called first on all structures.
-func (pc *passContext) exportLockFieldFacts(structType *types.Struct, ss *ast.StructType) {
- for i, field := range ss.Fields.List {
- lff := &lockFieldFacts{
- FieldNumber: i,
- }
- // We use HasSuffix below because fieldType can be fully
- // qualified with the package name eg for the gvisor sync
- // package mutex fields have the type:
- // "<package path>/sync/sync.Mutex"
- fieldObj := structType.Field(i)
- s := fieldObj.Type().String()
- switch {
- case mutexRE.MatchString(s):
- lff.IsMutex = true
- case rwMutexRE.MatchString(s):
- lff.IsRWMutex = true
- }
- // Save whether this is a pointer.
- _, lff.IsPointer = fieldObj.Type().Underlying().(*types.Pointer)
- // We must always export the lockFieldFacts, since traversal
- // can take place along any object in the struct.
- pc.pass.ExportObjectFact(fieldObj, lff)
- // If this is an anonymous type, then we won't discover it via
- // the AST global declarations. We can recurse from here.
- if ss, ok := field.Type.(*ast.StructType); ok {
- if st, ok := fieldObj.Type().(*types.Struct); ok {
- pc.exportLockFieldFacts(st, ss)
- }
- }
- }
-}
-
-// exportLockGuardFacts finds all relevant guard information for structures.
-//
-// This function requires exportLockFieldFacts be called first on all
-// structures.
-func (pc *passContext) exportLockGuardFacts(structType *types.Struct, ss *ast.StructType) {
- for i, field := range ss.Fields.List {
- fieldObj := structType.Field(i)
- if field.Doc != nil {
- var (
- lff lockFieldFacts
- lgf lockGuardFacts
- )
- pc.pass.ImportObjectFact(structType.Field(i), &lff)
- for _, l := range field.Doc.List {
- pc.extractAnnotations(l.Text, map[string]func(string){
- checkAtomicAnnotation: func(string) {
- switch lgf.AtomicDisposition {
- case atomicRequired:
- pc.maybeFail(fieldObj.Pos(), "annotation is redundant, already atomic required")
- case atomicIgnore:
- pc.maybeFail(fieldObj.Pos(), "annotation is contradictory, already atomic ignored")
- }
- lgf.AtomicDisposition = atomicRequired
- },
- checkLocksIgnore: func(string) {
- switch lgf.AtomicDisposition {
- case atomicIgnore:
- pc.maybeFail(fieldObj.Pos(), "annotation is redundant, already atomic ignored")
- case atomicRequired:
- pc.maybeFail(fieldObj.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(fieldObj.Pos(), "annotation %s specified more than once", guardName)
- return
- }
- fl, ok := pc.resolveField(fieldObj.Pos(), structType, strings.Split(guardName, "."), true /* exclusive */)
- if ok {
- // If we successfully resolved the field, then save it.
- if lgf.GuardedBy == nil {
- lgf.GuardedBy = make(map[string]fieldList)
- }
- lgf.GuardedBy[guardName] = fl
- }
- },
- // 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(fieldObj.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(structType.Field(i), &lgf)
- }
- }
- // See above, for anonymous structure fields.
- if ss, ok := field.Type.(*ast.StructType); ok {
- if st, ok := fieldObj.Type().(*types.Struct); ok {
- pc.exportLockGuardFacts(st, ss)
- }
- }
- }
-}
-
-// 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.
-func (pc *passContext) matchFieldList(pos token.Pos, fl []*ast.Field, guardName string, exclusive bool) (functionGuard, bool) {
- parts := strings.Split(guardName, ".")
- parameterName := parts[0]
- parameterNumber := 0
- for _, field := range fl {
- // See countFields, above.
- if len(field.Names) == 0 {
- parameterNumber++
- continue
- }
- for _, name := range field.Names {
- if name.Name != parameterName {
- parameterNumber++
- continue
- }
- ptrType, ok := pc.pass.TypesInfo.TypeOf(field.Type).Underlying().(*types.Pointer)
- if !ok {
- // Since mutexes cannot be copied we only care
- // about parameters that are pointer types when
- // checking for guards.
- pc.maybeFail(pos, "parameter name %s does not refer to a pointer type", parameterName)
- return functionGuard{}, false
- }
- structType, ok := ptrType.Elem().Underlying().(*types.Struct)
- if !ok {
- // Fields can only be in named structures.
- pc.maybeFail(pos, "parameter name %s does not refer to a pointer to a struct", parameterName)
- return functionGuard{}, false
- }
- fg := functionGuard{
- ParameterNumber: parameterNumber,
- Exclusive: exclusive,
- }
- fl, ok := pc.resolveField(pos, structType, parts[1:], exclusive)
- fg.FieldList = fl
- return fg, ok // If ok is false, already failed.
- }
- }
- return functionGuard{}, 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) (functionGuard, bool) {
- var (
- parameterList []*ast.Field
- returnList []*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 fg, ok := pc.matchFieldList(d.Pos(), parameterList, guardName, exclusive); ok {
- return fg, ok
- }
- if allowReturn {
- if d.Type.Results != nil {
- returnList = append(returnList, d.Type.Results.List...)
- }
- if fg, ok := pc.matchFieldList(d.Pos(), returnList, guardName, exclusive); ok {
- // Fix this up to apply to the return value, as noted
- // in fg.ParameterNumber. For the ssa analysis, we must
- // record whether this has multiple results, since
- // *ssa.Call indicates: "The Call instruction yields
- // the function result if there is exactly one.
- // Otherwise it returns a tuple, the components of
- // which are accessed via Extract."
- fg.ParameterNumber += countFields(parameterList)
- fg.NeedsExtract = countFields(returnList) > 1
- return fg, ok
- }
- }
- // We never saw a matching parameter.
- pc.maybeFail(d.Pos(), "annotation %s does not have a matching parameter", guardName)
- return functionGuard{}, false
-}
-
-// exportFunctionFacts exports relevant function findings.
-func (pc *passContext) exportFunctionFacts(d *ast.FuncDecl) {
- 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 */) },
- })
- }
-
- // 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)
- }
-}