nilness.go raw

   1  package nilness
   2  
   3  import (
   4  	"fmt"
   5  	"go/token"
   6  	"go/types"
   7  	"reflect"
   8  
   9  	"honnef.co/go/tools/go/ir"
  10  	"honnef.co/go/tools/go/types/typeutil"
  11  	"honnef.co/go/tools/internal/passes/buildir"
  12  
  13  	"golang.org/x/tools/go/analysis"
  14  )
  15  
  16  // neverReturnsNilFact denotes that a function's return value will never
  17  // be nil (typed or untyped). The analysis errs on the side of false
  18  // negatives.
  19  type neverReturnsNilFact struct {
  20  	Rets []neverNilness
  21  }
  22  
  23  func (*neverReturnsNilFact) AFact() {}
  24  func (fact *neverReturnsNilFact) String() string {
  25  	return fmt.Sprintf("never returns nil: %v", fact.Rets)
  26  }
  27  
  28  type Result struct {
  29  	m map[*types.Func][]neverNilness
  30  }
  31  
  32  var Analysis = &analysis.Analyzer{
  33  	Name:       "nilness",
  34  	Doc:        "Annotates return values that will never be nil (typed or untyped)",
  35  	Run:        run,
  36  	Requires:   []*analysis.Analyzer{buildir.Analyzer},
  37  	FactTypes:  []analysis.Fact{(*neverReturnsNilFact)(nil)},
  38  	ResultType: reflect.TypeOf((*Result)(nil)),
  39  }
  40  
  41  // MayReturnNil reports whether the ret's return value of fn might be
  42  // a typed or untyped nil value. The value of ret is zero-based. When
  43  // globalOnly is true, the only possible nil values are global
  44  // variables.
  45  //
  46  // The analysis has false positives: MayReturnNil can incorrectly
  47  // report true, but never incorrectly reports false.
  48  func (r *Result) MayReturnNil(fn *types.Func, ret int) (yes bool, globalOnly bool) {
  49  	if !typeutil.IsPointerLike(fn.Type().(*types.Signature).Results().At(ret).Type()) {
  50  		return false, false
  51  	}
  52  	if len(r.m[fn]) == 0 {
  53  		return true, false
  54  	}
  55  
  56  	v := r.m[fn][ret]
  57  	return v != neverNil, v == onlyGlobal
  58  }
  59  
  60  func run(pass *analysis.Pass) (interface{}, error) {
  61  	seen := map[*ir.Function]struct{}{}
  62  	out := &Result{
  63  		m: map[*types.Func][]neverNilness{},
  64  	}
  65  	for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs {
  66  		impl(pass, fn, seen)
  67  	}
  68  
  69  	for _, fact := range pass.AllObjectFacts() {
  70  		out.m[fact.Object.(*types.Func)] = fact.Fact.(*neverReturnsNilFact).Rets
  71  	}
  72  
  73  	return out, nil
  74  }
  75  
  76  type neverNilness uint8
  77  
  78  const (
  79  	neverNil   neverNilness = 1
  80  	onlyGlobal neverNilness = 2
  81  	nilly      neverNilness = 3
  82  )
  83  
  84  func (n neverNilness) String() string {
  85  	switch n {
  86  	case neverNil:
  87  		return "never"
  88  	case onlyGlobal:
  89  		return "global"
  90  	case nilly:
  91  		return "nil"
  92  	default:
  93  		return "BUG"
  94  	}
  95  }
  96  
  97  func impl(pass *analysis.Pass, fn *ir.Function, seenFns map[*ir.Function]struct{}) []neverNilness {
  98  	if fn.Object() == nil {
  99  		// TODO(dh): support closures
 100  		return nil
 101  	}
 102  	if fact := new(neverReturnsNilFact); pass.ImportObjectFact(fn.Object(), fact) {
 103  		return fact.Rets
 104  	}
 105  	if fn.Pkg != pass.ResultOf[buildir.Analyzer].(*buildir.IR).Pkg {
 106  		return nil
 107  	}
 108  	if fn.Blocks == nil {
 109  		return nil
 110  	}
 111  	if _, ok := seenFns[fn]; ok {
 112  		// break recursion
 113  		return nil
 114  	}
 115  
 116  	seenFns[fn] = struct{}{}
 117  
 118  	seen := map[ir.Value]struct{}{}
 119  
 120  	var mightReturnNil func(v ir.Value) neverNilness
 121  	mightReturnNil = func(v ir.Value) neverNilness {
 122  		if _, ok := seen[v]; ok {
 123  			// break cycle
 124  			return nilly
 125  		}
 126  		if !typeutil.IsPointerLike(v.Type()) {
 127  			return neverNil
 128  		}
 129  		seen[v] = struct{}{}
 130  		switch v := v.(type) {
 131  		case *ir.MakeInterface:
 132  			return mightReturnNil(v.X)
 133  		case *ir.Convert:
 134  			return mightReturnNil(v.X)
 135  		case *ir.SliceToArrayPointer:
 136  			if typeutil.CoreType(v.Type()).(*types.Pointer).Elem().Underlying().(*types.Array).Len() == 0 {
 137  				return mightReturnNil(v.X)
 138  			} else {
 139  				// converting a slice to an array pointer of length > 0 panics if the slice is nil
 140  				return neverNil
 141  			}
 142  		case *ir.Slice:
 143  			return mightReturnNil(v.X)
 144  		case *ir.Phi:
 145  			ret := neverNil
 146  			for _, e := range v.Edges {
 147  				if n := mightReturnNil(e); n > ret {
 148  					ret = n
 149  				}
 150  			}
 151  			return ret
 152  		case *ir.Extract:
 153  			switch d := v.Tuple.(type) {
 154  			case *ir.Call:
 155  				if callee := d.Call.StaticCallee(); callee != nil {
 156  					ret := impl(pass, callee, seenFns)
 157  					if len(ret) == 0 {
 158  						return nilly
 159  					}
 160  					return ret[v.Index]
 161  				} else {
 162  					return nilly
 163  				}
 164  			case *ir.TypeAssert, *ir.Next, *ir.Select, *ir.MapLookup, *ir.TypeSwitch, *ir.Recv, *ir.Sigma:
 165  				// we don't need to look at the Extract's index
 166  				// because we've already checked its type.
 167  				return nilly
 168  			default:
 169  				panic(fmt.Sprintf("internal error: unhandled type %T", d))
 170  			}
 171  		case *ir.Call:
 172  			if callee := v.Call.StaticCallee(); callee != nil {
 173  				ret := impl(pass, callee, seenFns)
 174  				if len(ret) == 0 {
 175  					return nilly
 176  				}
 177  				return ret[0]
 178  			} else {
 179  				return nilly
 180  			}
 181  		case *ir.BinOp, *ir.UnOp, *ir.Alloc, *ir.FieldAddr, *ir.IndexAddr, *ir.Global, *ir.MakeSlice, *ir.MakeClosure, *ir.Function, *ir.MakeMap, *ir.MakeChan:
 182  			return neverNil
 183  		case *ir.Sigma:
 184  			iff, ok := v.From.Control().(*ir.If)
 185  			if !ok {
 186  				return nilly
 187  			}
 188  			binop, ok := iff.Cond.(*ir.BinOp)
 189  			if !ok {
 190  				return nilly
 191  			}
 192  			isNil := func(v ir.Value) bool {
 193  				k, ok := v.(*ir.Const)
 194  				if !ok {
 195  					return false
 196  				}
 197  				return k.Value == nil
 198  			}
 199  			if binop.X == v.X && isNil(binop.Y) || binop.Y == v.X && isNil(binop.X) {
 200  				op := binop.Op
 201  				if v.From.Succs[0] != v.Block() {
 202  					// we're in the false branch, negate op
 203  					switch op {
 204  					case token.EQL:
 205  						op = token.NEQ
 206  					case token.NEQ:
 207  						op = token.EQL
 208  					default:
 209  						panic(fmt.Sprintf("internal error: unhandled token %v", op))
 210  					}
 211  				}
 212  				switch op {
 213  				case token.EQL:
 214  					return nilly
 215  				case token.NEQ:
 216  					return neverNil
 217  				default:
 218  					panic(fmt.Sprintf("internal error: unhandled token %v", op))
 219  				}
 220  			}
 221  			return nilly
 222  		case *ir.ChangeType:
 223  			return mightReturnNil(v.X)
 224  		case *ir.MultiConvert:
 225  			return mightReturnNil(v.X)
 226  		case *ir.Load:
 227  			if _, ok := v.X.(*ir.Global); ok {
 228  				return onlyGlobal
 229  			}
 230  			return nilly
 231  		case *ir.AggregateConst:
 232  			return neverNil
 233  		case *ir.TypeAssert, *ir.ChangeInterface, *ir.Field, *ir.Const, *ir.GenericConst, *ir.Index, *ir.MapLookup, *ir.Parameter, *ir.Recv, *ir.TypeSwitch:
 234  			return nilly
 235  		default:
 236  			panic(fmt.Sprintf("internal error: unhandled type %T", v))
 237  		}
 238  	}
 239  	ret := fn.Exit.Control().(*ir.Return)
 240  	out := make([]neverNilness, len(ret.Results))
 241  	export := false
 242  	for i, v := range ret.Results {
 243  		// OPT(dh): couldn't we check the result type's pointer-likeness early, and skip
 244  		// processing the return value altogether?
 245  		v := mightReturnNil(v)
 246  		out[i] = v
 247  		if v != nilly && typeutil.IsPointerLike(fn.Signature.Results().At(i).Type()) {
 248  			export = true
 249  		}
 250  	}
 251  	if export {
 252  		pass.ExportObjectFact(fn.Object(), &neverReturnsNilFact{out})
 253  	}
 254  	return out
 255  }
 256