Source file src/cmd/vendor/golang.org/x/tools/go/analysis/passes/copylock/copylock.go

     1  // Copyright 2013 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 copylock defines an Analyzer that checks for locks
     6  // erroneously passed by value.
     7  package copylock
     8  
     9  import (
    10  	"bytes"
    11  	"fmt"
    12  	"go/ast"
    13  	"go/token"
    14  	"go/types"
    15  
    16  	"golang.org/x/tools/go/analysis"
    17  	"golang.org/x/tools/go/analysis/passes/inspect"
    18  	"golang.org/x/tools/go/analysis/passes/internal/analysisutil"
    19  	"golang.org/x/tools/go/ast/inspector"
    20  	"golang.org/x/tools/internal/typeparams"
    21  	"golang.org/x/tools/internal/versions"
    22  )
    23  
    24  const Doc = `check for locks erroneously passed by value
    25  
    26  Inadvertently copying a value containing a lock, such as sync.Mutex or
    27  sync.WaitGroup, may cause both copies to malfunction. Generally such
    28  values should be referred to through a pointer.`
    29  
    30  var Analyzer = &analysis.Analyzer{
    31  	Name:             "copylocks",
    32  	Doc:              Doc,
    33  	URL:              "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/copylock",
    34  	Requires:         []*analysis.Analyzer{inspect.Analyzer},
    35  	RunDespiteErrors: true,
    36  	Run:              run,
    37  }
    38  
    39  func run(pass *analysis.Pass) (interface{}, error) {
    40  	inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
    41  
    42  	var goversion string // effective file version ("" => unknown)
    43  	nodeFilter := []ast.Node{
    44  		(*ast.AssignStmt)(nil),
    45  		(*ast.CallExpr)(nil),
    46  		(*ast.CompositeLit)(nil),
    47  		(*ast.File)(nil),
    48  		(*ast.FuncDecl)(nil),
    49  		(*ast.FuncLit)(nil),
    50  		(*ast.GenDecl)(nil),
    51  		(*ast.RangeStmt)(nil),
    52  		(*ast.ReturnStmt)(nil),
    53  	}
    54  	inspect.WithStack(nodeFilter, func(node ast.Node, push bool, stack []ast.Node) bool {
    55  		if !push {
    56  			return false
    57  		}
    58  		switch node := node.(type) {
    59  		case *ast.File:
    60  			goversion = versions.FileVersion(pass.TypesInfo, node)
    61  		case *ast.RangeStmt:
    62  			checkCopyLocksRange(pass, node)
    63  		case *ast.FuncDecl:
    64  			checkCopyLocksFunc(pass, node.Name.Name, node.Recv, node.Type)
    65  		case *ast.FuncLit:
    66  			checkCopyLocksFunc(pass, "func", nil, node.Type)
    67  		case *ast.CallExpr:
    68  			checkCopyLocksCallExpr(pass, node)
    69  		case *ast.AssignStmt:
    70  			checkCopyLocksAssign(pass, node, goversion, parent(stack))
    71  		case *ast.GenDecl:
    72  			checkCopyLocksGenDecl(pass, node)
    73  		case *ast.CompositeLit:
    74  			checkCopyLocksCompositeLit(pass, node)
    75  		case *ast.ReturnStmt:
    76  			checkCopyLocksReturnStmt(pass, node)
    77  		}
    78  		return true
    79  	})
    80  	return nil, nil
    81  }
    82  
    83  // checkCopyLocksAssign checks whether an assignment
    84  // copies a lock.
    85  func checkCopyLocksAssign(pass *analysis.Pass, assign *ast.AssignStmt, goversion string, parent ast.Node) {
    86  	lhs := assign.Lhs
    87  	for i, x := range assign.Rhs {
    88  		if path := lockPathRhs(pass, x); path != nil {
    89  			pass.ReportRangef(x, "assignment copies lock value to %v: %v", analysisutil.Format(pass.Fset, assign.Lhs[i]), path)
    90  			lhs = nil // An lhs has been reported. We prefer the assignment warning and do not report twice.
    91  		}
    92  	}
    93  
    94  	// After GoVersion 1.22, loop variables are implicitly copied on each iteration.
    95  	// So a for statement may inadvertently copy a lock when any of the
    96  	// iteration variables contain locks.
    97  	if assign.Tok == token.DEFINE && versions.AtLeast(goversion, versions.Go1_22) {
    98  		if parent, _ := parent.(*ast.ForStmt); parent != nil && parent.Init == assign {
    99  			for _, l := range lhs {
   100  				if id, ok := l.(*ast.Ident); ok && id.Name != "_" {
   101  					if obj := pass.TypesInfo.Defs[id]; obj != nil && obj.Type() != nil {
   102  						if path := lockPath(pass.Pkg, obj.Type(), nil); path != nil {
   103  							pass.ReportRangef(l, "for loop iteration copies lock value to %v: %v", analysisutil.Format(pass.Fset, l), path)
   104  						}
   105  					}
   106  				}
   107  			}
   108  		}
   109  	}
   110  }
   111  
   112  // checkCopyLocksGenDecl checks whether lock is copied
   113  // in variable declaration.
   114  func checkCopyLocksGenDecl(pass *analysis.Pass, gd *ast.GenDecl) {
   115  	if gd.Tok != token.VAR {
   116  		return
   117  	}
   118  	for _, spec := range gd.Specs {
   119  		valueSpec := spec.(*ast.ValueSpec)
   120  		for i, x := range valueSpec.Values {
   121  			if path := lockPathRhs(pass, x); path != nil {
   122  				pass.ReportRangef(x, "variable declaration copies lock value to %v: %v", valueSpec.Names[i].Name, path)
   123  			}
   124  		}
   125  	}
   126  }
   127  
   128  // checkCopyLocksCompositeLit detects lock copy inside a composite literal
   129  func checkCopyLocksCompositeLit(pass *analysis.Pass, cl *ast.CompositeLit) {
   130  	for _, x := range cl.Elts {
   131  		if node, ok := x.(*ast.KeyValueExpr); ok {
   132  			x = node.Value
   133  		}
   134  		if path := lockPathRhs(pass, x); path != nil {
   135  			pass.ReportRangef(x, "literal copies lock value from %v: %v", analysisutil.Format(pass.Fset, x), path)
   136  		}
   137  	}
   138  }
   139  
   140  // checkCopyLocksReturnStmt detects lock copy in return statement
   141  func checkCopyLocksReturnStmt(pass *analysis.Pass, rs *ast.ReturnStmt) {
   142  	for _, x := range rs.Results {
   143  		if path := lockPathRhs(pass, x); path != nil {
   144  			pass.ReportRangef(x, "return copies lock value: %v", path)
   145  		}
   146  	}
   147  }
   148  
   149  // checkCopyLocksCallExpr detects lock copy in the arguments to a function call
   150  func checkCopyLocksCallExpr(pass *analysis.Pass, ce *ast.CallExpr) {
   151  	var id *ast.Ident
   152  	switch fun := ce.Fun.(type) {
   153  	case *ast.Ident:
   154  		id = fun
   155  	case *ast.SelectorExpr:
   156  		id = fun.Sel
   157  	}
   158  	if fun, ok := pass.TypesInfo.Uses[id].(*types.Builtin); ok {
   159  		switch fun.Name() {
   160  		case "new", "len", "cap", "Sizeof", "Offsetof", "Alignof":
   161  			return
   162  		}
   163  	}
   164  	for _, x := range ce.Args {
   165  		if path := lockPathRhs(pass, x); path != nil {
   166  			pass.ReportRangef(x, "call of %s copies lock value: %v", analysisutil.Format(pass.Fset, ce.Fun), path)
   167  		}
   168  	}
   169  }
   170  
   171  // checkCopyLocksFunc checks whether a function might
   172  // inadvertently copy a lock, by checking whether
   173  // its receiver, parameters, or return values
   174  // are locks.
   175  func checkCopyLocksFunc(pass *analysis.Pass, name string, recv *ast.FieldList, typ *ast.FuncType) {
   176  	if recv != nil && len(recv.List) > 0 {
   177  		expr := recv.List[0].Type
   178  		if path := lockPath(pass.Pkg, pass.TypesInfo.Types[expr].Type, nil); path != nil {
   179  			pass.ReportRangef(expr, "%s passes lock by value: %v", name, path)
   180  		}
   181  	}
   182  
   183  	if typ.Params != nil {
   184  		for _, field := range typ.Params.List {
   185  			expr := field.Type
   186  			if path := lockPath(pass.Pkg, pass.TypesInfo.Types[expr].Type, nil); path != nil {
   187  				pass.ReportRangef(expr, "%s passes lock by value: %v", name, path)
   188  			}
   189  		}
   190  	}
   191  
   192  	// Don't check typ.Results. If T has a Lock field it's OK to write
   193  	//     return T{}
   194  	// because that is returning the zero value. Leave result checking
   195  	// to the return statement.
   196  }
   197  
   198  // checkCopyLocksRange checks whether a range statement
   199  // might inadvertently copy a lock by checking whether
   200  // any of the range variables are locks.
   201  func checkCopyLocksRange(pass *analysis.Pass, r *ast.RangeStmt) {
   202  	checkCopyLocksRangeVar(pass, r.Tok, r.Key)
   203  	checkCopyLocksRangeVar(pass, r.Tok, r.Value)
   204  }
   205  
   206  func checkCopyLocksRangeVar(pass *analysis.Pass, rtok token.Token, e ast.Expr) {
   207  	if e == nil {
   208  		return
   209  	}
   210  	id, isId := e.(*ast.Ident)
   211  	if isId && id.Name == "_" {
   212  		return
   213  	}
   214  
   215  	var typ types.Type
   216  	if rtok == token.DEFINE {
   217  		if !isId {
   218  			return
   219  		}
   220  		obj := pass.TypesInfo.Defs[id]
   221  		if obj == nil {
   222  			return
   223  		}
   224  		typ = obj.Type()
   225  	} else {
   226  		typ = pass.TypesInfo.Types[e].Type
   227  	}
   228  
   229  	if typ == nil {
   230  		return
   231  	}
   232  	if path := lockPath(pass.Pkg, typ, nil); path != nil {
   233  		pass.Reportf(e.Pos(), "range var %s copies lock: %v", analysisutil.Format(pass.Fset, e), path)
   234  	}
   235  }
   236  
   237  type typePath []string
   238  
   239  // String pretty-prints a typePath.
   240  func (path typePath) String() string {
   241  	n := len(path)
   242  	var buf bytes.Buffer
   243  	for i := range path {
   244  		if i > 0 {
   245  			fmt.Fprint(&buf, " contains ")
   246  		}
   247  		// The human-readable path is in reverse order, outermost to innermost.
   248  		fmt.Fprint(&buf, path[n-i-1])
   249  	}
   250  	return buf.String()
   251  }
   252  
   253  func lockPathRhs(pass *analysis.Pass, x ast.Expr) typePath {
   254  	x = ast.Unparen(x) // ignore parens on rhs
   255  
   256  	if _, ok := x.(*ast.CompositeLit); ok {
   257  		return nil
   258  	}
   259  	if _, ok := x.(*ast.CallExpr); ok {
   260  		// A call may return a zero value.
   261  		return nil
   262  	}
   263  	if star, ok := x.(*ast.StarExpr); ok {
   264  		if _, ok := ast.Unparen(star.X).(*ast.CallExpr); ok {
   265  			// A call may return a pointer to a zero value.
   266  			return nil
   267  		}
   268  	}
   269  	if tv, ok := pass.TypesInfo.Types[x]; ok && tv.IsValue() {
   270  		return lockPath(pass.Pkg, tv.Type, nil)
   271  	}
   272  	return nil
   273  }
   274  
   275  // lockPath returns a typePath describing the location of a lock value
   276  // contained in typ. If there is no contained lock, it returns nil.
   277  //
   278  // The seen map is used to short-circuit infinite recursion due to type cycles.
   279  func lockPath(tpkg *types.Package, typ types.Type, seen map[types.Type]bool) typePath {
   280  	if typ == nil || seen[typ] {
   281  		return nil
   282  	}
   283  	if seen == nil {
   284  		seen = make(map[types.Type]bool)
   285  	}
   286  	seen[typ] = true
   287  
   288  	if tpar, ok := types.Unalias(typ).(*types.TypeParam); ok {
   289  		terms, err := typeparams.StructuralTerms(tpar)
   290  		if err != nil {
   291  			return nil // invalid type
   292  		}
   293  		for _, term := range terms {
   294  			subpath := lockPath(tpkg, term.Type(), seen)
   295  			if len(subpath) > 0 {
   296  				if term.Tilde() {
   297  					// Prepend a tilde to our lock path entry to clarify the resulting
   298  					// diagnostic message. Consider the following example:
   299  					//
   300  					//  func _[Mutex interface{ ~sync.Mutex; M() }](m Mutex) {}
   301  					//
   302  					// Here the naive error message will be something like "passes lock
   303  					// by value: Mutex contains sync.Mutex". This is misleading because
   304  					// the local type parameter doesn't actually contain sync.Mutex,
   305  					// which lacks the M method.
   306  					//
   307  					// With tilde, it is clearer that the containment is via an
   308  					// approximation element.
   309  					subpath[len(subpath)-1] = "~" + subpath[len(subpath)-1]
   310  				}
   311  				return append(subpath, typ.String())
   312  			}
   313  		}
   314  		return nil
   315  	}
   316  
   317  	for {
   318  		atyp, ok := typ.Underlying().(*types.Array)
   319  		if !ok {
   320  			break
   321  		}
   322  		typ = atyp.Elem()
   323  	}
   324  
   325  	ttyp, ok := typ.Underlying().(*types.Tuple)
   326  	if ok {
   327  		for i := 0; i < ttyp.Len(); i++ {
   328  			subpath := lockPath(tpkg, ttyp.At(i).Type(), seen)
   329  			if subpath != nil {
   330  				return append(subpath, typ.String())
   331  			}
   332  		}
   333  		return nil
   334  	}
   335  
   336  	// We're only interested in the case in which the underlying
   337  	// type is a struct. (Interfaces and pointers are safe to copy.)
   338  	styp, ok := typ.Underlying().(*types.Struct)
   339  	if !ok {
   340  		return nil
   341  	}
   342  
   343  	// We're looking for cases in which a pointer to this type
   344  	// is a sync.Locker, but a value is not. This differentiates
   345  	// embedded interfaces from embedded values.
   346  	if types.Implements(types.NewPointer(typ), lockerType) && !types.Implements(typ, lockerType) {
   347  		return []string{typ.String()}
   348  	}
   349  
   350  	// In go1.10, sync.noCopy did not implement Locker.
   351  	// (The Unlock method was added only in CL 121876.)
   352  	// TODO(adonovan): remove workaround when we drop go1.10.
   353  	if analysisutil.IsNamedType(typ, "sync", "noCopy") {
   354  		return []string{typ.String()}
   355  	}
   356  
   357  	nfields := styp.NumFields()
   358  	for i := 0; i < nfields; i++ {
   359  		ftyp := styp.Field(i).Type()
   360  		subpath := lockPath(tpkg, ftyp, seen)
   361  		if subpath != nil {
   362  			return append(subpath, typ.String())
   363  		}
   364  	}
   365  
   366  	return nil
   367  }
   368  
   369  // parent returns the second from the last node on stack if it exists.
   370  func parent(stack []ast.Node) ast.Node {
   371  	if len(stack) >= 2 {
   372  		return stack[len(stack)-2]
   373  	}
   374  	return nil
   375  }
   376  
   377  var lockerType *types.Interface
   378  
   379  // Construct a sync.Locker interface type.
   380  func init() {
   381  	nullary := types.NewSignature(nil, nil, nil, false) // func()
   382  	methods := []*types.Func{
   383  		types.NewFunc(token.NoPos, nil, "Lock", nullary),
   384  		types.NewFunc(token.NoPos, nil, "Unlock", nullary),
   385  	}
   386  	lockerType = types.NewInterface(methods, nil).Complete()
   387  }
   388  

View as plain text