347 lines
9.5 KiB
Go
347 lines
9.5 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/token"
|
|
"go/types"
|
|
"strings"
|
|
"sync/atomic"
|
|
|
|
"golang.org/x/tools/go/ssa"
|
|
)
|
|
|
|
// 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
|
|
// valueAsString below, which maps to specific structure fields, etc.
|
|
//
|
|
// The value indicates whether this is an exclusive lock.
|
|
lockedMutexes map[string]bool
|
|
|
|
// 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 valueAsString. 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]bool),
|
|
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]bool)
|
|
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.
|
|
func (l *lockState) isHeld(rv resolvedValue, exclusiveRequired bool) (string, bool) {
|
|
if !rv.valid {
|
|
return rv.valueAsString(l), false
|
|
}
|
|
s := rv.valueAsString(l)
|
|
isExclusive, ok := l.lockedMutexes[s]
|
|
if !ok {
|
|
return s, false
|
|
}
|
|
// Accept a weaker lock if exclusiveRequired is false.
|
|
if exclusiveRequired && !isExclusive {
|
|
return s, false
|
|
}
|
|
return s, true
|
|
}
|
|
|
|
// lockField locks the given field.
|
|
//
|
|
// If false is returned, the field was already locked.
|
|
func (l *lockState) lockField(rv resolvedValue, exclusive bool) (string, bool) {
|
|
if !rv.valid {
|
|
return rv.valueAsString(l), false
|
|
}
|
|
s := rv.valueAsString(l)
|
|
if _, ok := l.lockedMutexes[s]; ok {
|
|
return s, false
|
|
}
|
|
l.modify()
|
|
l.lockedMutexes[s] = exclusive
|
|
return s, true
|
|
}
|
|
|
|
// unlockField unlocks the given field.
|
|
//
|
|
// If false is returned, the field was not locked.
|
|
func (l *lockState) unlockField(rv resolvedValue, exclusive bool) (string, bool) {
|
|
if !rv.valid {
|
|
return rv.valueAsString(l), false
|
|
}
|
|
s := rv.valueAsString(l)
|
|
wasExclusive, ok := l.lockedMutexes[s]
|
|
if !ok {
|
|
return s, false
|
|
}
|
|
if wasExclusive != 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.
|
|
func (l *lockState) downgradeField(rv resolvedValue) (string, bool) {
|
|
if !rv.valid {
|
|
return rv.valueAsString(l), false
|
|
}
|
|
s := rv.valueAsString(l)
|
|
wasExclusive, ok := l.lockedMutexes[s]
|
|
if !ok {
|
|
return s, false
|
|
}
|
|
if !wasExclusive {
|
|
return s, false
|
|
}
|
|
l.modify()
|
|
l.lockedMutexes[s] = false // 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, isExclusive := range l.lockedMutexes {
|
|
otherExclusive, otherOk := other.lockedMutexes[k]
|
|
if !otherOk {
|
|
return false
|
|
}
|
|
// Accept weaker locks as a subset.
|
|
if isExclusive && !otherExclusive {
|
|
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
|
|
}
|
|
|
|
// valueAsString returns a string for a given value.
|
|
//
|
|
// 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) valueAsString(v ssa.Value) string {
|
|
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.valueAsString(v)
|
|
}
|
|
return fmt.Sprintf("{param:%s}", x.Name())
|
|
case *ssa.Global:
|
|
return fmt.Sprintf("{global:%s}", x.Name())
|
|
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.valueAsString(stored)
|
|
}
|
|
}
|
|
return fmt.Sprintf("{freevar:%s}", x.Name())
|
|
case *ssa.Convert:
|
|
// Just disregard conversion.
|
|
return l.valueAsString(x.X)
|
|
case *ssa.ChangeType:
|
|
// Ditto, disregard.
|
|
return l.valueAsString(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.valueAsString(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.valueAsString(v)
|
|
}
|
|
}
|
|
// x.X.Type is pointer. We must construct this type
|
|
// dynamically, since the ssa.Value could be synthetic.
|
|
return fmt.Sprintf("*(%s)", l.valueAsString(x.X))
|
|
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)
|
|
return fmt.Sprintf("%s.%s", l.valueAsString(x.X), fieldObj.Name())
|
|
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)
|
|
return fmt.Sprintf("&(%s.%s)", l.valueAsString(x.X), fieldObj.Name())
|
|
case *ssa.Index:
|
|
return fmt.Sprintf("%s[%s]", l.valueAsString(x.X), l.valueAsString(x.Index))
|
|
case *ssa.IndexAddr:
|
|
return fmt.Sprintf("&(%s[%s])", l.valueAsString(x.X), l.valueAsString(x.Index))
|
|
case *ssa.Lookup:
|
|
return fmt.Sprintf("%s[%s]", l.valueAsString(x.X), l.valueAsString(x.Index))
|
|
case *ssa.Extract:
|
|
return fmt.Sprintf("%s[%d]", l.valueAsString(x.Tuple), x.Index)
|
|
}
|
|
|
|
// 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)
|
|
}
|
|
|
|
// 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, exclusive := range l.lockedMutexes {
|
|
// Include the exclusive status of each lock.
|
|
keys = append(keys, fmt.Sprintf("%s %s", k, exclusiveStr(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
|
|
}
|