...

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	)
    21	
    22	const Doc = `check for locks erroneously passed by value
    23	
    24	Inadvertently copying a value containing a lock, such as sync.Mutex or
    25	sync.WaitGroup, may cause both copies to malfunction. Generally such
    26	values should be referred to through a pointer.`
    27	
    28	var Analyzer = &analysis.Analyzer{
    29		Name:             "copylocks",
    30		Doc:              Doc,
    31		Requires:         []*analysis.Analyzer{inspect.Analyzer},
    32		RunDespiteErrors: true,
    33		Run:              run,
    34	}
    35	
    36	func run(pass *analysis.Pass) (interface{}, error) {
    37		inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
    38	
    39		nodeFilter := []ast.Node{
    40			(*ast.AssignStmt)(nil),
    41			(*ast.CallExpr)(nil),
    42			(*ast.CompositeLit)(nil),
    43			(*ast.FuncDecl)(nil),
    44			(*ast.FuncLit)(nil),
    45			(*ast.GenDecl)(nil),
    46			(*ast.RangeStmt)(nil),
    47			(*ast.ReturnStmt)(nil),
    48		}
    49		inspect.Preorder(nodeFilter, func(node ast.Node) {
    50			switch node := node.(type) {
    51			case *ast.RangeStmt:
    52				checkCopyLocksRange(pass, node)
    53			case *ast.FuncDecl:
    54				checkCopyLocksFunc(pass, node.Name.Name, node.Recv, node.Type)
    55			case *ast.FuncLit:
    56				checkCopyLocksFunc(pass, "func", nil, node.Type)
    57			case *ast.CallExpr:
    58				checkCopyLocksCallExpr(pass, node)
    59			case *ast.AssignStmt:
    60				checkCopyLocksAssign(pass, node)
    61			case *ast.GenDecl:
    62				checkCopyLocksGenDecl(pass, node)
    63			case *ast.CompositeLit:
    64				checkCopyLocksCompositeLit(pass, node)
    65			case *ast.ReturnStmt:
    66				checkCopyLocksReturnStmt(pass, node)
    67			}
    68		})
    69		return nil, nil
    70	}
    71	
    72	// checkCopyLocksAssign checks whether an assignment
    73	// copies a lock.
    74	func checkCopyLocksAssign(pass *analysis.Pass, as *ast.AssignStmt) {
    75		for i, x := range as.Rhs {
    76			if path := lockPathRhs(pass, x); path != nil {
    77				pass.Reportf(x.Pos(), "assignment copies lock value to %v: %v", analysisutil.Format(pass.Fset, as.Lhs[i]), path)
    78			}
    79		}
    80	}
    81	
    82	// checkCopyLocksGenDecl checks whether lock is copied
    83	// in variable declaration.
    84	func checkCopyLocksGenDecl(pass *analysis.Pass, gd *ast.GenDecl) {
    85		if gd.Tok != token.VAR {
    86			return
    87		}
    88		for _, spec := range gd.Specs {
    89			valueSpec := spec.(*ast.ValueSpec)
    90			for i, x := range valueSpec.Values {
    91				if path := lockPathRhs(pass, x); path != nil {
    92					pass.Reportf(x.Pos(), "variable declaration copies lock value to %v: %v", valueSpec.Names[i].Name, path)
    93				}
    94			}
    95		}
    96	}
    97	
    98	// checkCopyLocksCompositeLit detects lock copy inside a composite literal
    99	func checkCopyLocksCompositeLit(pass *analysis.Pass, cl *ast.CompositeLit) {
   100		for _, x := range cl.Elts {
   101			if node, ok := x.(*ast.KeyValueExpr); ok {
   102				x = node.Value
   103			}
   104			if path := lockPathRhs(pass, x); path != nil {
   105				pass.Reportf(x.Pos(), "literal copies lock value from %v: %v", analysisutil.Format(pass.Fset, x), path)
   106			}
   107		}
   108	}
   109	
   110	// checkCopyLocksReturnStmt detects lock copy in return statement
   111	func checkCopyLocksReturnStmt(pass *analysis.Pass, rs *ast.ReturnStmt) {
   112		for _, x := range rs.Results {
   113			if path := lockPathRhs(pass, x); path != nil {
   114				pass.Reportf(x.Pos(), "return copies lock value: %v", path)
   115			}
   116		}
   117	}
   118	
   119	// checkCopyLocksCallExpr detects lock copy in the arguments to a function call
   120	func checkCopyLocksCallExpr(pass *analysis.Pass, ce *ast.CallExpr) {
   121		var id *ast.Ident
   122		switch fun := ce.Fun.(type) {
   123		case *ast.Ident:
   124			id = fun
   125		case *ast.SelectorExpr:
   126			id = fun.Sel
   127		}
   128		if fun, ok := pass.TypesInfo.Uses[id].(*types.Builtin); ok {
   129			switch fun.Name() {
   130			case "new", "len", "cap", "Sizeof":
   131				return
   132			}
   133		}
   134		for _, x := range ce.Args {
   135			if path := lockPathRhs(pass, x); path != nil {
   136				pass.Reportf(x.Pos(), "call of %s copies lock value: %v", analysisutil.Format(pass.Fset, ce.Fun), path)
   137			}
   138		}
   139	}
   140	
   141	// checkCopyLocksFunc checks whether a function might
   142	// inadvertently copy a lock, by checking whether
   143	// its receiver, parameters, or return values
   144	// are locks.
   145	func checkCopyLocksFunc(pass *analysis.Pass, name string, recv *ast.FieldList, typ *ast.FuncType) {
   146		if recv != nil && len(recv.List) > 0 {
   147			expr := recv.List[0].Type
   148			if path := lockPath(pass.Pkg, pass.TypesInfo.Types[expr].Type); path != nil {
   149				pass.Reportf(expr.Pos(), "%s passes lock by value: %v", name, path)
   150			}
   151		}
   152	
   153		if typ.Params != nil {
   154			for _, field := range typ.Params.List {
   155				expr := field.Type
   156				if path := lockPath(pass.Pkg, pass.TypesInfo.Types[expr].Type); path != nil {
   157					pass.Reportf(expr.Pos(), "%s passes lock by value: %v", name, path)
   158				}
   159			}
   160		}
   161	
   162		// Don't check typ.Results. If T has a Lock field it's OK to write
   163		//     return T{}
   164		// because that is returning the zero value. Leave result checking
   165		// to the return statement.
   166	}
   167	
   168	// checkCopyLocksRange checks whether a range statement
   169	// might inadvertently copy a lock by checking whether
   170	// any of the range variables are locks.
   171	func checkCopyLocksRange(pass *analysis.Pass, r *ast.RangeStmt) {
   172		checkCopyLocksRangeVar(pass, r.Tok, r.Key)
   173		checkCopyLocksRangeVar(pass, r.Tok, r.Value)
   174	}
   175	
   176	func checkCopyLocksRangeVar(pass *analysis.Pass, rtok token.Token, e ast.Expr) {
   177		if e == nil {
   178			return
   179		}
   180		id, isId := e.(*ast.Ident)
   181		if isId && id.Name == "_" {
   182			return
   183		}
   184	
   185		var typ types.Type
   186		if rtok == token.DEFINE {
   187			if !isId {
   188				return
   189			}
   190			obj := pass.TypesInfo.Defs[id]
   191			if obj == nil {
   192				return
   193			}
   194			typ = obj.Type()
   195		} else {
   196			typ = pass.TypesInfo.Types[e].Type
   197		}
   198	
   199		if typ == nil {
   200			return
   201		}
   202		if path := lockPath(pass.Pkg, typ); path != nil {
   203			pass.Reportf(e.Pos(), "range var %s copies lock: %v", analysisutil.Format(pass.Fset, e), path)
   204		}
   205	}
   206	
   207	type typePath []types.Type
   208	
   209	// String pretty-prints a typePath.
   210	func (path typePath) String() string {
   211		n := len(path)
   212		var buf bytes.Buffer
   213		for i := range path {
   214			if i > 0 {
   215				fmt.Fprint(&buf, " contains ")
   216			}
   217			// The human-readable path is in reverse order, outermost to innermost.
   218			fmt.Fprint(&buf, path[n-i-1].String())
   219		}
   220		return buf.String()
   221	}
   222	
   223	func lockPathRhs(pass *analysis.Pass, x ast.Expr) typePath {
   224		if _, ok := x.(*ast.CompositeLit); ok {
   225			return nil
   226		}
   227		if _, ok := x.(*ast.CallExpr); ok {
   228			// A call may return a zero value.
   229			return nil
   230		}
   231		if star, ok := x.(*ast.StarExpr); ok {
   232			if _, ok := star.X.(*ast.CallExpr); ok {
   233				// A call may return a pointer to a zero value.
   234				return nil
   235			}
   236		}
   237		return lockPath(pass.Pkg, pass.TypesInfo.Types[x].Type)
   238	}
   239	
   240	// lockPath returns a typePath describing the location of a lock value
   241	// contained in typ. If there is no contained lock, it returns nil.
   242	func lockPath(tpkg *types.Package, typ types.Type) typePath {
   243		if typ == nil {
   244			return nil
   245		}
   246	
   247		for {
   248			atyp, ok := typ.Underlying().(*types.Array)
   249			if !ok {
   250				break
   251			}
   252			typ = atyp.Elem()
   253		}
   254	
   255		// We're only interested in the case in which the underlying
   256		// type is a struct. (Interfaces and pointers are safe to copy.)
   257		styp, ok := typ.Underlying().(*types.Struct)
   258		if !ok {
   259			return nil
   260		}
   261	
   262		// We're looking for cases in which a pointer to this type
   263		// is a sync.Locker, but a value is not. This differentiates
   264		// embedded interfaces from embedded values.
   265		if types.Implements(types.NewPointer(typ), lockerType) && !types.Implements(typ, lockerType) {
   266			return []types.Type{typ}
   267		}
   268	
   269		// In go1.10, sync.noCopy did not implement Locker.
   270		// (The Unlock method was added only in CL 121876.)
   271		// TODO(adonovan): remove workaround when we drop go1.10.
   272		if named, ok := typ.(*types.Named); ok &&
   273			named.Obj().Name() == "noCopy" &&
   274			named.Obj().Pkg().Path() == "sync" {
   275			return []types.Type{typ}
   276		}
   277	
   278		nfields := styp.NumFields()
   279		for i := 0; i < nfields; i++ {
   280			ftyp := styp.Field(i).Type()
   281			subpath := lockPath(tpkg, ftyp)
   282			if subpath != nil {
   283				return append(subpath, typ)
   284			}
   285		}
   286	
   287		return nil
   288	}
   289	
   290	var lockerType *types.Interface
   291	
   292	// Construct a sync.Locker interface type.
   293	func init() {
   294		nullary := types.NewSignature(nil, nil, nil, false) // func()
   295		methods := []*types.Func{
   296			types.NewFunc(token.NoPos, nil, "Lock", nullary),
   297			types.NewFunc(token.NoPos, nil, "Unlock", nullary),
   298		}
   299		lockerType = types.NewInterface(methods, nil).Complete()
   300	}
   301	

View as plain text