1
2
3
4
5 package nilness
6
7 import (
8 _ "embed"
9 "fmt"
10 "go/token"
11 "go/types"
12
13 "golang.org/x/tools/go/analysis"
14 "golang.org/x/tools/go/analysis/passes/buildssa"
15 "golang.org/x/tools/go/analysis/passes/internal/analysisutil"
16 "golang.org/x/tools/go/ssa"
17 "golang.org/x/tools/internal/typeparams"
18 )
19
20
21 var doc string
22
23 var Analyzer = &analysis.Analyzer{
24 Name: "nilness",
25 Doc: analysisutil.MustExtractDoc(doc, "nilness"),
26 URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/nilness",
27 Run: run,
28 Requires: []*analysis.Analyzer{buildssa.Analyzer},
29 }
30
31 func run(pass *analysis.Pass) (interface{}, error) {
32 ssainput := pass.ResultOf[buildssa.Analyzer].(*buildssa.SSA)
33 for _, fn := range ssainput.SrcFuncs {
34 runFunc(pass, fn)
35 }
36 return nil, nil
37 }
38
39 func runFunc(pass *analysis.Pass, fn *ssa.Function) {
40 reportf := func(category string, pos token.Pos, format string, args ...interface{}) {
41
42
43 if pos.IsValid() {
44 pass.Report(analysis.Diagnostic{
45 Pos: pos,
46 Category: category,
47 Message: fmt.Sprintf(format, args...),
48 })
49 }
50 }
51
52
53 notNil := func(stack []fact, instr ssa.Instruction, v ssa.Value, descr string) {
54 if nilnessOf(stack, v) == isnil {
55 reportf("nilderef", instr.Pos(), descr)
56 }
57 }
58
59
60
61
62
63
64
65 seen := make([]bool, len(fn.Blocks))
66 var visit func(b *ssa.BasicBlock, stack []fact)
67 visit = func(b *ssa.BasicBlock, stack []fact) {
68 if seen[b.Index] {
69 return
70 }
71 seen[b.Index] = true
72
73
74 for _, instr := range b.Instrs {
75 switch instr := instr.(type) {
76 case ssa.CallInstruction:
77
78 cc := instr.Common()
79 if !(cc.IsInvoke() && typeparams.IsTypeParam(cc.Value.Type())) {
80 notNil(stack, instr, cc.Value, "nil dereference in "+cc.Description())
81 }
82 case *ssa.FieldAddr:
83 notNil(stack, instr, instr.X, "nil dereference in field selection")
84 case *ssa.IndexAddr:
85 switch typeparams.CoreType(instr.X.Type()).(type) {
86 case *types.Pointer:
87 notNil(stack, instr, instr.X, "nil dereference in array index operation")
88 case *types.Slice:
89
90
91 if isRangeIndex(instr) {
92 notNil(stack, instr, instr.X, "range of nil slice")
93 } else {
94 notNil(stack, instr, instr.X, "index of nil slice")
95 }
96 }
97 case *ssa.MapUpdate:
98 notNil(stack, instr, instr.Map, "nil dereference in map update")
99 case *ssa.Range:
100
101 notNil(stack, instr, instr.X, "range over nil map")
102 case *ssa.Slice:
103
104 if is[*types.Pointer](instr.X.Type().Underlying()) {
105 notNil(stack, instr, instr.X, "nil dereference in slice operation")
106 }
107 case *ssa.Store:
108 notNil(stack, instr, instr.Addr, "nil dereference in store")
109 case *ssa.TypeAssert:
110 if !instr.CommaOk {
111 notNil(stack, instr, instr.X, "nil dereference in type assertion")
112 }
113 case *ssa.UnOp:
114 switch instr.Op {
115 case token.MUL:
116 notNil(stack, instr, instr.X, "nil dereference in load")
117 case token.ARROW:
118
119 notNil(stack, instr, instr.X, "receive from nil channel")
120 }
121 case *ssa.Send:
122
123 notNil(stack, instr, instr.Chan, "send to nil channel")
124 }
125 }
126
127
128 for _, instr := range b.Instrs {
129 switch instr := instr.(type) {
130 case *ssa.Panic:
131 if nilnessOf(stack, instr.X) == isnil {
132 reportf("nilpanic", instr.Pos(), "panic with nil value")
133 }
134 case *ssa.SliceToArrayPointer:
135 nn := nilnessOf(stack, instr.X)
136 if nn == isnil && slice2ArrayPtrLen(instr) > 0 {
137 reportf("conversionpanic", instr.Pos(), "nil slice being cast to an array of len > 0 will always panic")
138 }
139 }
140 }
141
142
143
144
145 if binop, tsucc, fsucc := eq(b); binop != nil {
146 xnil := nilnessOf(stack, binop.X)
147 ynil := nilnessOf(stack, binop.Y)
148
149 if ynil != unknown && xnil != unknown && (xnil == isnil || ynil == isnil) {
150
151
152
153 var adj string
154 if (xnil == ynil) == (binop.Op == token.EQL) {
155 adj = "tautological"
156 } else {
157 adj = "impossible"
158 }
159 reportf("cond", binop.Pos(), "%s condition: %s %s %s", adj, xnil, binop.Op, ynil)
160
161
162
163
164
165
166 var skip *ssa.BasicBlock
167 if xnil == ynil {
168 skip = fsucc
169 } else {
170 skip = tsucc
171 }
172 for _, d := range b.Dominees() {
173 if d == skip && len(d.Preds) == 1 {
174 continue
175 }
176 visit(d, stack)
177 }
178 return
179 }
180
181
182 if xnil == isnil || ynil == isnil {
183 var newFacts facts
184 if xnil == isnil {
185
186
187 newFacts = expandFacts(fact{binop.Y, isnil})
188 } else {
189
190
191 newFacts = expandFacts(fact{binop.X, isnil})
192 }
193
194 for _, d := range b.Dominees() {
195
196
197
198
199 s := stack
200 if len(d.Preds) == 1 {
201 if d == tsucc {
202 s = append(s, newFacts...)
203 } else if d == fsucc {
204 s = append(s, newFacts.negate()...)
205 }
206 }
207 visit(d, s)
208 }
209 return
210 }
211 }
212
213
214
215
216
217
218
219 if If, ok := b.Instrs[len(b.Instrs)-1].(*ssa.If); ok {
220
221 cond, fsucc := If.Cond, b.Succs[1]
222 if unop, ok := cond.(*ssa.UnOp); ok && unop.Op == token.NOT {
223 cond, fsucc = unop.X, b.Succs[0]
224 }
225
226
227
228
229
230
231 if extract1, ok := cond.(*ssa.Extract); ok && extract1.Index == 1 {
232 if assert, ok := extract1.Tuple.(*ssa.TypeAssert); ok &&
233 isNillable(assert.AssertedType) {
234 for _, pinstr := range *assert.Referrers() {
235 if extract0, ok := pinstr.(*ssa.Extract); ok &&
236 extract0.Index == 0 &&
237 extract0.Tuple == extract1.Tuple {
238 for _, d := range b.Dominees() {
239 if len(d.Preds) == 1 && d == fsucc {
240 visit(d, append(stack, fact{extract0, isnil}))
241 }
242 }
243 }
244 }
245 }
246 }
247 }
248
249 for _, d := range b.Dominees() {
250 visit(d, stack)
251 }
252 }
253
254
255 if fn.Blocks != nil {
256 visit(fn.Blocks[0], make([]fact, 0, 20))
257 }
258 }
259
260
261
262 type fact struct {
263 value ssa.Value
264 nilness nilness
265 }
266
267 func (f fact) negate() fact { return fact{f.value, -f.nilness} }
268
269 type nilness int
270
271 const (
272 isnonnil = -1
273 unknown nilness = 0
274 isnil = 1
275 )
276
277 var nilnessStrings = []string{"non-nil", "unknown", "nil"}
278
279 func (n nilness) String() string { return nilnessStrings[n+1] }
280
281
282
283 func nilnessOf(stack []fact, v ssa.Value) nilness {
284 switch v := v.(type) {
285
286
287
288
289
290
291
292
293
294
295 case *ssa.ChangeInterface:
296 if underlying := nilnessOf(stack, v.X); underlying != unknown {
297 return underlying
298 }
299 case *ssa.Slice:
300 if underlying := nilnessOf(stack, v.X); underlying != unknown {
301 return underlying
302 }
303 case *ssa.SliceToArrayPointer:
304 nn := nilnessOf(stack, v.X)
305 if slice2ArrayPtrLen(v) > 0 {
306 if nn == isnil {
307
308
309
310 return unknown
311 }
312
313
314
315
316
317 return isnonnil
318 }
319
320 if nn != unknown {
321 return nn
322 }
323 }
324
325
326 switch v := v.(type) {
327 case *ssa.Alloc,
328 *ssa.FieldAddr,
329 *ssa.FreeVar,
330 *ssa.Function,
331 *ssa.Global,
332 *ssa.IndexAddr,
333 *ssa.MakeChan,
334 *ssa.MakeClosure,
335 *ssa.MakeInterface,
336 *ssa.MakeMap,
337 *ssa.MakeSlice:
338 return isnonnil
339 case *ssa.Const:
340 if v.IsNil() {
341 return isnil
342 } else {
343 return unknown
344 }
345 }
346
347
348 for _, f := range stack {
349 if f.value == v {
350 return f.nilness
351 }
352 }
353 return unknown
354 }
355
356 func slice2ArrayPtrLen(v *ssa.SliceToArrayPointer) int64 {
357 return v.Type().(*types.Pointer).Elem().Underlying().(*types.Array).Len()
358 }
359
360
361
362 func eq(b *ssa.BasicBlock) (op *ssa.BinOp, tsucc, fsucc *ssa.BasicBlock) {
363 if If, ok := b.Instrs[len(b.Instrs)-1].(*ssa.If); ok {
364 if binop, ok := If.Cond.(*ssa.BinOp); ok {
365 switch binop.Op {
366 case token.EQL:
367 return binop, b.Succs[0], b.Succs[1]
368 case token.NEQ:
369 return binop, b.Succs[1], b.Succs[0]
370 }
371 }
372 }
373 return nil, nil, nil
374 }
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394 func expandFacts(f fact) []fact {
395 ff := []fact{f}
396
397 Loop:
398 for {
399 switch v := f.value.(type) {
400 case *ssa.ChangeInterface:
401 f = fact{v.X, f.nilness}
402 ff = append(ff, f)
403 default:
404 break Loop
405 }
406 }
407
408 return ff
409 }
410
411 type facts []fact
412
413 func (ff facts) negate() facts {
414 nn := make([]fact, len(ff))
415 for i, f := range ff {
416 nn[i] = f.negate()
417 }
418 return nn
419 }
420
421 func is[T any](x any) bool {
422 _, ok := x.(T)
423 return ok
424 }
425
426 func isNillable(t types.Type) bool {
427 switch t := typeparams.CoreType(t).(type) {
428 case *types.Pointer,
429 *types.Map,
430 *types.Signature,
431 *types.Chan,
432 *types.Interface,
433 *types.Slice:
434 return true
435 case *types.Basic:
436 return t == types.Typ[types.UnsafePointer]
437 }
438 return false
439 }
440
441
442
443
444
445
446 func isRangeIndex(instr *ssa.IndexAddr) bool {
447
448
449
450
451
452
453
454
455
456
457
458
459
460 if incr, ok := instr.Index.(*ssa.BinOp); ok && incr.Op == token.ADD {
461 if b := incr.Block(); b.Comment == "rangeindex.loop" {
462 if If, ok := b.Instrs[len(b.Instrs)-1].(*ssa.If); ok {
463 if cond := If.Cond.(*ssa.BinOp); cond.X == incr && cond.Op == token.LSS {
464 if call, ok := cond.Y.(*ssa.Call); ok {
465 common := call.Common()
466 if blt, ok := common.Value.(*ssa.Builtin); ok && blt.Name() == "len" {
467 return common.Args[0] == instr.X
468 }
469 }
470 }
471 }
472 }
473 }
474 return false
475 }
476
View as plain text