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 }