github.com/aclements/go-misc@v0.0.0-20240129233631-2f6ede80790c/rtcheck/lockclass.go (about)

     1  // Copyright 2016 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package main
     6  
     7  import (
     8  	"fmt"
     9  	"go/types"
    10  	"strings"
    11  
    12  	"golang.org/x/tools/go/ssa"
    13  )
    14  
    15  // A LockClass represents a set of locks. LockClasses form equivalence
    16  // classes: a given lock instance belongs to exactly one lock class.
    17  // Often, a LockClass is identified by the struct type and field that
    18  // embeds the lock object. It may also simply be a package-level
    19  // variable.
    20  type LockClass struct {
    21  	label    string
    22  	isUnique bool
    23  	id       int
    24  	lca      *LockClassAnalysis
    25  }
    26  
    27  func (lc *LockClass) Analysis() *LockClassAnalysis {
    28  	return lc.lca
    29  }
    30  
    31  func (lc *LockClass) String() string {
    32  	if !lc.isUnique {
    33  		return lc.label + "*"
    34  	}
    35  	return lc.label
    36  }
    37  
    38  // IsUnique returns true if lc is inhabited by a single lock instance.
    39  func (lc *LockClass) IsUnique() bool {
    40  	return lc.isUnique
    41  }
    42  
    43  // Id returns a small integer ID for this lock class that is unique
    44  // within the LockClassAnalysis that returned this *LockClass.
    45  func (lc *LockClass) Id() int {
    46  	return lc.id
    47  }
    48  
    49  type lockClassKey struct {
    50  	parent interface{}
    51  	field  int
    52  	global *ssa.Global
    53  	typ    *types.Named
    54  }
    55  
    56  type LockClassAnalysis struct {
    57  	classes map[lockClassKey]*LockClass
    58  	list    []*LockClass
    59  }
    60  
    61  // Get returns the LockClass of the given ssa.Value, which must be a
    62  // pointer to a global or a field address expression. If Get cannot
    63  // resolve the lock class of v, it returns an error indicating why.
    64  //
    65  // The returned pointer uniquely identifies the lock class. That is,
    66  // a.Get(x) == a.Get(y) if and only if x and y are the same lock
    67  // class.
    68  func (a *LockClassAnalysis) Get(v ssa.Value) (*LockClass, error) {
    69  	// Strip away FieldAddrs until we get to something that's a
    70  	// global or a *struct value.
    71  	label := make([]string, 0, 10)
    72  	var key lockClassKey
    73  	var isUnique bool
    74  loop:
    75  	for {
    76  		switch v2 := v.(type) {
    77  		case *ssa.FieldAddr:
    78  			// TODO: How does this handle nested structs?
    79  			label = append(label, v2.X.Type().Underlying().(*types.Pointer).Elem().Underlying().(*types.Struct).Field(v2.Field).Name())
    80  			key = lockClassKey{parent: key, field: v2.Field}
    81  			v = v2.X
    82  
    83  		case *ssa.Global:
    84  			// TODO: Check formatting
    85  			label = append(label, v2.String())
    86  			key = lockClassKey{parent: key, global: v2}
    87  			isUnique = true
    88  			break loop
    89  
    90  		default:
    91  			if len(label) == 0 {
    92  				// This wasn't a field of something,
    93  				// so we have no idea what its lock
    94  				// class is. This happens in the
    95  				// runtime for example in
    96  				// parkunlock_c, which cases an
    97  				// unsafe.Pointer argument to a
    98  				// *mutex.
    99  				return nil, fmt.Errorf("lock is not a field or global")
   100  			}
   101  			// This must be a *struct. Get the struct's
   102  			// name.
   103  			styp, ok := v.Type().Underlying().(*types.Pointer).Elem().(*types.Named)
   104  			if !ok {
   105  				return nil, fmt.Errorf("lock is a field of an unnamed struct")
   106  			}
   107  			sname := styp.Obj().Name()
   108  			label = append(label, styp.Obj().Pkg().Name()+"."+sname)
   109  			key = lockClassKey{parent: key, typ: styp}
   110  			isUnique = false
   111  			break loop
   112  		}
   113  	}
   114  
   115  	if a.classes == nil {
   116  		a.classes = make(map[lockClassKey]*LockClass)
   117  	}
   118  	if lc, ok := a.classes[key]; ok {
   119  		return lc, nil
   120  	}
   121  
   122  	for i := 0; i < len(label)/2; i++ {
   123  		label[i], label[len(label)-i-1] = label[len(label)-i-1], label[i]
   124  	}
   125  	lc := &LockClass{
   126  		label:    strings.Join(label, "."),
   127  		isUnique: isUnique,
   128  		id:       len(a.list),
   129  		lca:      a,
   130  	}
   131  	a.classes[key] = lc
   132  	a.list = append(a.list, lc)
   133  	return lc, nil
   134  }
   135  
   136  // NewLockClass returns a new lock class that is distinct from every
   137  // other lock class. This can be used to model lock-like abstractions
   138  // that are not actually Go objects.
   139  func (a *LockClassAnalysis) NewLockClass(label string, isUnique bool) *LockClass {
   140  	lc := &LockClass{
   141  		label:    label,
   142  		isUnique: isUnique,
   143  		id:       len(a.list),
   144  		lca:      a,
   145  	}
   146  	a.list = append(a.list, lc)
   147  	return lc
   148  }
   149  
   150  // Lookup returns the *LockClass whose Id() is id.
   151  func (a *LockClassAnalysis) Lookup(id int) *LockClass {
   152  	return a.list[id]
   153  }