Skip to content

Commit 0f0698e

Browse files
adonovangopherbot
authored andcommitted
go/analysis/passes/nilness: improve "for range []T(nil)" error
Previously, the error message referred to a "nil dereference in index operation", which is true, but the index operation is unreachable because the range loop over a nil slice is degenerate. Rephrase it to "index of nil slice", which doesn't promise that the operation will panic, or "range over nil slice" if we can infer that the IndexAddr operation came from range-over-slice lowering. Also, add cases for range over a nil map, *array, and chan, with tests. Fixes golang/go#65674 Change-Id: I1bf89bd1118b191a493bc2f230cb1388ff7a9b3b Reviewed-on: https://go-review.googlesource.com/c/tools/+/563476 Reviewed-by: Lasse Folger <[email protected]> Reviewed-by: Tim King <[email protected]> Auto-Submit: Alan Donovan <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]>
1 parent f1914cc commit 0f0698e

File tree

2 files changed

+130
-12
lines changed

2 files changed

+130
-12
lines changed

go/analysis/passes/nilness/nilness.go

Lines changed: 68 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ func runFunc(pass *analysis.Pass, fn *ssa.Function) {
5252
// notNil reports an error if v is provably nil.
5353
notNil := func(stack []fact, instr ssa.Instruction, v ssa.Value, descr string) {
5454
if nilnessOf(stack, v) == isnil {
55-
reportf("nilderef", instr.Pos(), "nil dereference in "+descr)
55+
reportf("nilderef", instr.Pos(), descr)
5656
}
5757
}
5858

@@ -77,29 +77,50 @@ func runFunc(pass *analysis.Pass, fn *ssa.Function) {
7777
// A nil receiver may be okay for type params.
7878
cc := instr.Common()
7979
if !(cc.IsInvoke() && typeparams.IsTypeParam(cc.Value.Type())) {
80-
notNil(stack, instr, cc.Value, cc.Description())
80+
notNil(stack, instr, cc.Value, "nil dereference in "+cc.Description())
8181
}
8282
case *ssa.FieldAddr:
83-
notNil(stack, instr, instr.X, "field selection")
83+
notNil(stack, instr, instr.X, "nil dereference in field selection")
8484
case *ssa.IndexAddr:
85-
notNil(stack, instr, instr.X, "index operation")
85+
switch typeparams.CoreType(instr.X.Type()).(type) {
86+
case *types.Pointer: // *array
87+
notNil(stack, instr, instr.X, "nil dereference in array index operation")
88+
case *types.Slice:
89+
// This is not necessarily a runtime error, because
90+
// it is usually dominated by a bounds check.
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+
}
8697
case *ssa.MapUpdate:
87-
notNil(stack, instr, instr.Map, "map update")
98+
notNil(stack, instr, instr.Map, "nil dereference in map update")
99+
case *ssa.Range:
100+
// (Not a runtime error, but a likely mistake.)
101+
notNil(stack, instr, instr.X, "range over nil map")
88102
case *ssa.Slice:
89103
// A nilcheck occurs in ptr[:] iff ptr is a pointer to an array.
90-
if _, ok := instr.X.Type().Underlying().(*types.Pointer); ok {
91-
notNil(stack, instr, instr.X, "slice operation")
104+
if is[*types.Pointer](instr.X.Type().Underlying()) {
105+
notNil(stack, instr, instr.X, "nil dereference in slice operation")
92106
}
93107
case *ssa.Store:
94-
notNil(stack, instr, instr.Addr, "store")
108+
notNil(stack, instr, instr.Addr, "nil dereference in store")
95109
case *ssa.TypeAssert:
96110
if !instr.CommaOk {
97-
notNil(stack, instr, instr.X, "type assertion")
111+
notNil(stack, instr, instr.X, "nil dereference in type assertion")
98112
}
99113
case *ssa.UnOp:
100-
if instr.Op == token.MUL { // *X
101-
notNil(stack, instr, instr.X, "load")
114+
switch instr.Op {
115+
case token.MUL: // *X
116+
notNil(stack, instr, instr.X, "nil dereference in load")
117+
case token.ARROW: // <-ch
118+
// (Not a runtime error, but a likely mistake.)
119+
notNil(stack, instr, instr.X, "receive from nil channel")
102120
}
121+
case *ssa.Send:
122+
// (Not a runtime error, but a likely mistake.)
123+
notNil(stack, instr, instr.Chan, "send to nil channel")
103124
}
104125
}
105126

@@ -416,3 +437,39 @@ func isNillable(t types.Type) bool {
416437
}
417438
return false
418439
}
440+
441+
// isRangeIndex reports whether the instruction is a slice indexing
442+
// operation slice[i] within a "for range slice" loop. The operation
443+
// could be explicit, such as slice[i] within (or even after) the
444+
// loop, or it could be implicit, such as "for i, v := range slice {}".
445+
// (These cannot be reliably distinguished.)
446+
func isRangeIndex(instr *ssa.IndexAddr) bool {
447+
// Here we reverse-engineer the go/ssa lowering of range-over-slice:
448+
//
449+
// n = len(x)
450+
// jump loop
451+
// loop: "rangeindex.loop"
452+
// phi = φ(-1, incr) #rangeindex
453+
// incr = phi + 1
454+
// cond = incr < n
455+
// if cond goto body else done
456+
// body: "rangeindex.body"
457+
// instr = &x[incr]
458+
// ...
459+
// done:
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+
}

go/analysis/passes/nilness/testdata/src/a/a.go

Lines changed: 62 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ func f2(ptr *[3]int, i interface{}) {
5454
}
5555
}
5656

57-
func g() error
57+
func g() error { return nil }
5858

5959
func f3() error {
6060
err := g()
@@ -242,3 +242,64 @@ func f18(x any) {
242242
}
243243
println(*ptr)
244244
}
245+
246+
// Regression test for https://github.com/golang/go/issues/65674:
247+
// spurious "nil deference in slice index operation" when the
248+
// index was subject to a range loop.
249+
func f19(slice []int, array *[2]int, m map[string]int, ch chan int) {
250+
if slice == nil {
251+
// A range over a nil slice is dynamically benign,
252+
// but still signifies a programmer mistake.
253+
//
254+
// Since SSA has melted down the control structure,
255+
// so we can only report a diagnostic about the
256+
// index operation, with heuristics for "range".
257+
258+
for range slice { // nothing to report here
259+
}
260+
for _, v := range slice { // want "range of nil slice"
261+
_ = v
262+
}
263+
for i := range slice {
264+
_ = slice[i] // want "range of nil slice"
265+
}
266+
{
267+
var i int
268+
for i = range slice {
269+
}
270+
_ = slice[i] // want "index of nil slice"
271+
}
272+
for i := range slice {
273+
if i < len(slice) {
274+
_ = slice[i] // want "range of nil slice"
275+
}
276+
}
277+
if len(slice) > 3 {
278+
_ = slice[2] // want "index of nil slice"
279+
}
280+
for i := 0; i < len(slice); i++ {
281+
_ = slice[i] // want "index of nil slice"
282+
}
283+
}
284+
285+
if array == nil {
286+
// (The v var is necessary, otherwise the SSA
287+
// code doesn't dereference the pointer.)
288+
for _, v := range array { // want "nil dereference in array index operation"
289+
_ = v
290+
}
291+
}
292+
293+
if m == nil {
294+
for range m { // want "range over nil map"
295+
}
296+
m["one"] = 1 // want "nil dereference in map update"
297+
}
298+
299+
if ch == nil {
300+
for range ch { // want "receive from nil channel"
301+
}
302+
<-ch // want "receive from nil channel"
303+
ch <- 0 // want "send to nil channel"
304+
}
305+
}

0 commit comments

Comments
 (0)