642 lines
21 KiB
Go
642 lines
21 KiB
Go
// 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)
|
|
}
|
|
}
|