github.com/amarpal/go-tools@v0.0.0-20240422043104-40142f59f616/analysis/facts/nilness/nilness.go (about)

     1  package nilness
     2  
     3  import (
     4  	"fmt"
     5  	"go/token"
     6  	"go/types"
     7  	"reflect"
     8  
     9  	"github.com/amarpal/go-tools/go/ir"
    10  	"github.com/amarpal/go-tools/go/types/typeutil"
    11  	"github.com/amarpal/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.Load:
   225  			if _, ok := v.X.(*ir.Global); ok {
   226  				return onlyGlobal
   227  			}
   228  			return nilly
   229  		case *ir.AggregateConst:
   230  			return neverNil
   231  		case *ir.TypeAssert, *ir.ChangeInterface, *ir.Field, *ir.Const, *ir.GenericConst, *ir.Index, *ir.MapLookup, *ir.Parameter, *ir.Recv, *ir.TypeSwitch:
   232  			return nilly
   233  		default:
   234  			panic(fmt.Sprintf("internal error: unhandled type %T", v))
   235  		}
   236  	}
   237  	ret := fn.Exit.Control().(*ir.Return)
   238  	out := make([]neverNilness, len(ret.Results))
   239  	export := false
   240  	for i, v := range ret.Results {
   241  		v := mightReturnNil(v)
   242  		out[i] = v
   243  		if v != nilly && typeutil.IsPointerLike(fn.Signature.Results().At(i).Type()) {
   244  			export = true
   245  		}
   246  	}
   247  	if export {
   248  		pass.ExportObjectFact(fn.Object(), &neverReturnsNilFact{out})
   249  	}
   250  	return out
   251  }