github.com/karrick/go@v0.0.0-20170817181416-d5b0ec858b37/src/cmd/vet/copylock.go (about)

     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  // This file contains the code to check that locks are not passed by value.
     6  
     7  package main
     8  
     9  import (
    10  	"bytes"
    11  	"fmt"
    12  	"go/ast"
    13  	"go/token"
    14  	"go/types"
    15  )
    16  
    17  func init() {
    18  	register("copylocks",
    19  		"check that locks are not passed by value",
    20  		checkCopyLocks,
    21  		funcDecl, rangeStmt, funcLit, callExpr, assignStmt, genDecl, compositeLit, returnStmt)
    22  }
    23  
    24  // checkCopyLocks checks whether node might
    25  // inadvertently copy a lock.
    26  func checkCopyLocks(f *File, node ast.Node) {
    27  	switch node := node.(type) {
    28  	case *ast.RangeStmt:
    29  		checkCopyLocksRange(f, node)
    30  	case *ast.FuncDecl:
    31  		checkCopyLocksFunc(f, node.Name.Name, node.Recv, node.Type)
    32  	case *ast.FuncLit:
    33  		checkCopyLocksFunc(f, "func", nil, node.Type)
    34  	case *ast.CallExpr:
    35  		checkCopyLocksCallExpr(f, node)
    36  	case *ast.AssignStmt:
    37  		checkCopyLocksAssign(f, node)
    38  	case *ast.GenDecl:
    39  		checkCopyLocksGenDecl(f, node)
    40  	case *ast.CompositeLit:
    41  		checkCopyLocksCompositeLit(f, node)
    42  	case *ast.ReturnStmt:
    43  		checkCopyLocksReturnStmt(f, node)
    44  	}
    45  }
    46  
    47  // checkCopyLocksAssign checks whether an assignment
    48  // copies a lock.
    49  func checkCopyLocksAssign(f *File, as *ast.AssignStmt) {
    50  	for i, x := range as.Rhs {
    51  		if path := lockPathRhs(f, x); path != nil {
    52  			f.Badf(x.Pos(), "assignment copies lock value to %v: %v", f.gofmt(as.Lhs[i]), path)
    53  		}
    54  	}
    55  }
    56  
    57  // checkCopyLocksGenDecl checks whether lock is copied
    58  // in variable declaration.
    59  func checkCopyLocksGenDecl(f *File, gd *ast.GenDecl) {
    60  	if gd.Tok != token.VAR {
    61  		return
    62  	}
    63  	for _, spec := range gd.Specs {
    64  		valueSpec := spec.(*ast.ValueSpec)
    65  		for i, x := range valueSpec.Values {
    66  			if path := lockPathRhs(f, x); path != nil {
    67  				f.Badf(x.Pos(), "variable declaration copies lock value to %v: %v", valueSpec.Names[i].Name, path)
    68  			}
    69  		}
    70  	}
    71  }
    72  
    73  // checkCopyLocksCompositeLit detects lock copy inside a composite literal
    74  func checkCopyLocksCompositeLit(f *File, cl *ast.CompositeLit) {
    75  	for _, x := range cl.Elts {
    76  		if node, ok := x.(*ast.KeyValueExpr); ok {
    77  			x = node.Value
    78  		}
    79  		if path := lockPathRhs(f, x); path != nil {
    80  			f.Badf(x.Pos(), "literal copies lock value from %v: %v", f.gofmt(x), path)
    81  		}
    82  	}
    83  }
    84  
    85  // checkCopyLocksReturnStmt detects lock copy in return statement
    86  func checkCopyLocksReturnStmt(f *File, rs *ast.ReturnStmt) {
    87  	for _, x := range rs.Results {
    88  		if path := lockPathRhs(f, x); path != nil {
    89  			f.Badf(x.Pos(), "return copies lock value: %v", path)
    90  		}
    91  	}
    92  }
    93  
    94  // checkCopyLocksCallExpr detects lock copy in the arguments to a function call
    95  func checkCopyLocksCallExpr(f *File, ce *ast.CallExpr) {
    96  	if id, ok := ce.Fun.(*ast.Ident); ok && f.pkg.types[id].IsBuiltin() {
    97  		switch id.Name {
    98  		case "new", "len", "cap":
    99  			return
   100  		}
   101  	}
   102  	for _, x := range ce.Args {
   103  		if path := lockPathRhs(f, x); path != nil {
   104  			f.Badf(x.Pos(), "call of %s copies lock value: %v", f.gofmt(ce.Fun), path)
   105  		}
   106  	}
   107  }
   108  
   109  // checkCopyLocksFunc checks whether a function might
   110  // inadvertently copy a lock, by checking whether
   111  // its receiver, parameters, or return values
   112  // are locks.
   113  func checkCopyLocksFunc(f *File, name string, recv *ast.FieldList, typ *ast.FuncType) {
   114  	if recv != nil && len(recv.List) > 0 {
   115  		expr := recv.List[0].Type
   116  		if path := lockPath(f.pkg.typesPkg, f.pkg.types[expr].Type); path != nil {
   117  			f.Badf(expr.Pos(), "%s passes lock by value: %v", name, path)
   118  		}
   119  	}
   120  
   121  	if typ.Params != nil {
   122  		for _, field := range typ.Params.List {
   123  			expr := field.Type
   124  			if path := lockPath(f.pkg.typesPkg, f.pkg.types[expr].Type); path != nil {
   125  				f.Badf(expr.Pos(), "%s passes lock by value: %v", name, path)
   126  			}
   127  		}
   128  	}
   129  
   130  	// Don't check typ.Results. If T has a Lock field it's OK to write
   131  	//     return T{}
   132  	// because that is returning the zero value. Leave result checking
   133  	// to the return statement.
   134  }
   135  
   136  // checkCopyLocksRange checks whether a range statement
   137  // might inadvertently copy a lock by checking whether
   138  // any of the range variables are locks.
   139  func checkCopyLocksRange(f *File, r *ast.RangeStmt) {
   140  	checkCopyLocksRangeVar(f, r.Tok, r.Key)
   141  	checkCopyLocksRangeVar(f, r.Tok, r.Value)
   142  }
   143  
   144  func checkCopyLocksRangeVar(f *File, rtok token.Token, e ast.Expr) {
   145  	if e == nil {
   146  		return
   147  	}
   148  	id, isId := e.(*ast.Ident)
   149  	if isId && id.Name == "_" {
   150  		return
   151  	}
   152  
   153  	var typ types.Type
   154  	if rtok == token.DEFINE {
   155  		if !isId {
   156  			return
   157  		}
   158  		obj := f.pkg.defs[id]
   159  		if obj == nil {
   160  			return
   161  		}
   162  		typ = obj.Type()
   163  	} else {
   164  		typ = f.pkg.types[e].Type
   165  	}
   166  
   167  	if typ == nil {
   168  		return
   169  	}
   170  	if path := lockPath(f.pkg.typesPkg, typ); path != nil {
   171  		f.Badf(e.Pos(), "range var %s copies lock: %v", f.gofmt(e), path)
   172  	}
   173  }
   174  
   175  type typePath []types.Type
   176  
   177  // String pretty-prints a typePath.
   178  func (path typePath) String() string {
   179  	n := len(path)
   180  	var buf bytes.Buffer
   181  	for i := range path {
   182  		if i > 0 {
   183  			fmt.Fprint(&buf, " contains ")
   184  		}
   185  		// The human-readable path is in reverse order, outermost to innermost.
   186  		fmt.Fprint(&buf, path[n-i-1].String())
   187  	}
   188  	return buf.String()
   189  }
   190  
   191  func lockPathRhs(f *File, x ast.Expr) typePath {
   192  	if _, ok := x.(*ast.CompositeLit); ok {
   193  		return nil
   194  	}
   195  	if _, ok := x.(*ast.CallExpr); ok {
   196  		// A call may return a zero value.
   197  		return nil
   198  	}
   199  	if star, ok := x.(*ast.StarExpr); ok {
   200  		if _, ok := star.X.(*ast.CallExpr); ok {
   201  			// A call may return a pointer to a zero value.
   202  			return nil
   203  		}
   204  	}
   205  	return lockPath(f.pkg.typesPkg, f.pkg.types[x].Type)
   206  }
   207  
   208  // lockPath returns a typePath describing the location of a lock value
   209  // contained in typ. If there is no contained lock, it returns nil.
   210  func lockPath(tpkg *types.Package, typ types.Type) typePath {
   211  	if typ == nil {
   212  		return nil
   213  	}
   214  
   215  	for {
   216  		atyp, ok := typ.Underlying().(*types.Array)
   217  		if !ok {
   218  			break
   219  		}
   220  		typ = atyp.Elem()
   221  	}
   222  
   223  	// We're only interested in the case in which the underlying
   224  	// type is a struct. (Interfaces and pointers are safe to copy.)
   225  	styp, ok := typ.Underlying().(*types.Struct)
   226  	if !ok {
   227  		return nil
   228  	}
   229  
   230  	// We're looking for cases in which a reference to this type
   231  	// can be locked, but a value cannot. This differentiates
   232  	// embedded interfaces from embedded values.
   233  	if plock := types.NewMethodSet(types.NewPointer(typ)).Lookup(tpkg, "Lock"); plock != nil {
   234  		if lock := types.NewMethodSet(typ).Lookup(tpkg, "Lock"); lock == nil {
   235  			return []types.Type{typ}
   236  		}
   237  	}
   238  
   239  	nfields := styp.NumFields()
   240  	for i := 0; i < nfields; i++ {
   241  		ftyp := styp.Field(i).Type()
   242  		subpath := lockPath(tpkg, ftyp)
   243  		if subpath != nil {
   244  			return append(subpath, typ)
   245  		}
   246  	}
   247  
   248  	return nil
   249  }