Home | History | Annotate | Download | only in vet
      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)
     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 	}
     35 }
     36 
     37 // checkCopyLocksFunc checks whether a function might
     38 // inadvertently copy a lock, by checking whether
     39 // its receiver, parameters, or return values
     40 // are locks.
     41 func checkCopyLocksFunc(f *File, name string, recv *ast.FieldList, typ *ast.FuncType) {
     42 	if recv != nil && len(recv.List) > 0 {
     43 		expr := recv.List[0].Type
     44 		if path := lockPath(f.pkg.typesPkg, f.pkg.types[expr].Type); path != nil {
     45 			f.Badf(expr.Pos(), "%s passes Lock by value: %v", name, path)
     46 		}
     47 	}
     48 
     49 	if typ.Params != nil {
     50 		for _, field := range typ.Params.List {
     51 			expr := field.Type
     52 			if path := lockPath(f.pkg.typesPkg, f.pkg.types[expr].Type); path != nil {
     53 				f.Badf(expr.Pos(), "%s passes Lock by value: %v", name, path)
     54 			}
     55 		}
     56 	}
     57 
     58 	if typ.Results != nil {
     59 		for _, field := range typ.Results.List {
     60 			expr := field.Type
     61 			if path := lockPath(f.pkg.typesPkg, f.pkg.types[expr].Type); path != nil {
     62 				f.Badf(expr.Pos(), "%s returns Lock by value: %v", name, path)
     63 			}
     64 		}
     65 	}
     66 }
     67 
     68 // checkCopyLocksRange checks whether a range statement
     69 // might inadvertently copy a lock by checking whether
     70 // any of the range variables are locks.
     71 func checkCopyLocksRange(f *File, r *ast.RangeStmt) {
     72 	checkCopyLocksRangeVar(f, r.Tok, r.Key)
     73 	checkCopyLocksRangeVar(f, r.Tok, r.Value)
     74 }
     75 
     76 func checkCopyLocksRangeVar(f *File, rtok token.Token, e ast.Expr) {
     77 	if e == nil {
     78 		return
     79 	}
     80 	id, isId := e.(*ast.Ident)
     81 	if isId && id.Name == "_" {
     82 		return
     83 	}
     84 
     85 	var typ types.Type
     86 	if rtok == token.DEFINE {
     87 		if !isId {
     88 			return
     89 		}
     90 		obj := f.pkg.defs[id]
     91 		if obj == nil {
     92 			return
     93 		}
     94 		typ = obj.Type()
     95 	} else {
     96 		typ = f.pkg.types[e].Type
     97 	}
     98 
     99 	if typ == nil {
    100 		return
    101 	}
    102 	if path := lockPath(f.pkg.typesPkg, typ); path != nil {
    103 		f.Badf(e.Pos(), "range var %s copies Lock: %v", f.gofmt(e), path)
    104 	}
    105 }
    106 
    107 type typePath []types.Type
    108 
    109 // String pretty-prints a typePath.
    110 func (path typePath) String() string {
    111 	n := len(path)
    112 	var buf bytes.Buffer
    113 	for i := range path {
    114 		if i > 0 {
    115 			fmt.Fprint(&buf, " contains ")
    116 		}
    117 		// The human-readable path is in reverse order, outermost to innermost.
    118 		fmt.Fprint(&buf, path[n-i-1].String())
    119 	}
    120 	return buf.String()
    121 }
    122 
    123 // lockPath returns a typePath describing the location of a lock value
    124 // contained in typ. If there is no contained lock, it returns nil.
    125 func lockPath(tpkg *types.Package, typ types.Type) typePath {
    126 	if typ == nil {
    127 		return nil
    128 	}
    129 
    130 	// We're only interested in the case in which the underlying
    131 	// type is a struct. (Interfaces and pointers are safe to copy.)
    132 	styp, ok := typ.Underlying().(*types.Struct)
    133 	if !ok {
    134 		return nil
    135 	}
    136 
    137 	// We're looking for cases in which a reference to this type
    138 	// can be locked, but a value cannot. This differentiates
    139 	// embedded interfaces from embedded values.
    140 	if plock := types.NewMethodSet(types.NewPointer(typ)).Lookup(tpkg, "Lock"); plock != nil {
    141 		if lock := types.NewMethodSet(typ).Lookup(tpkg, "Lock"); lock == nil {
    142 			return []types.Type{typ}
    143 		}
    144 	}
    145 
    146 	nfields := styp.NumFields()
    147 	for i := 0; i < nfields; i++ {
    148 		ftyp := styp.Field(i).Type()
    149 		subpath := lockPath(tpkg, ftyp)
    150 		if subpath != nil {
    151 			return append(subpath, typ)
    152 		}
    153 	}
    154 
    155 	return nil
    156 }
    157