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