Skip to content

Commit 86ea9d7

Browse files
adonovandennypenta
authored andcommitted
gopls/internal/analysis/yield: analyzer for iterator (yield) mistakes
This analyzer detects failure to check the result of a call to yield (which can cause a range loop to run beyond the sequence, leading to a panic). It is not always a mistake to ignore the result of a call to yield; it depends on whether control can reach another call to yield without checking that the first call returned true. Consequently, this analyzer uses SSA for control flow analysis. We plan to add this analyzer to gopls before we promote it to vet. + test, relnote Fixes golang/go#65795 Change-Id: I75fa272e2f546be0c2acb10a1978c82bc19db5bd Reviewed-on: https://go-review.googlesource.com/c/tools/+/609617 Auto-Submit: Alan Donovan <[email protected]> Reviewed-by: Robert Findley <[email protected]> Commit-Queue: Alan Donovan <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]>
1 parent 2f73c61 commit 86ea9d7

File tree

5 files changed

+20
-1
lines changed

5 files changed

+20
-1
lines changed

gopls/internal/analysis/yield/testdata/src/a/a.go

+3
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ func tricky(in io.ReadCloser) func(yield func(string, error) bool) {
8383
}
8484
}
8585
}
86+
<<<<<<< HEAD
8687

8788
// Regression test for issue #70598.
8889
func shortCircuitAND(yield func(int) bool) {
@@ -118,3 +119,5 @@ func tricky3(yield func(int) bool) {
118119
yield(3)
119120
}
120121
}
122+
=======
123+
>>>>>>> 9b6e4f21d (gopls/internal/analysis/yield: analyzer for iterator (yield) mistakes)

gopls/internal/analysis/yield/yield.go

+12
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,10 @@ import (
2020
_ "embed"
2121
"fmt"
2222
"go/ast"
23+
<<<<<<< HEAD
2324
"go/constant"
25+
=======
26+
>>>>>>> 9b6e4f21d (gopls/internal/analysis/yield: analyzer for iterator (yield) mistakes)
2427
"go/token"
2528
"go/types"
2629

@@ -120,6 +123,7 @@ func run(pass *analysis.Pass) (interface{}, error) {
120123
// In that case visit only the "if !yield()" block.
121124
cond := instr.Cond
122125
t, f := b.Succs[0], b.Succs[1]
126+
<<<<<<< HEAD
123127

124128
// Strip off any NOT operator.
125129
cond, t, f = unnegate(cond, t, f)
@@ -148,6 +152,11 @@ func run(pass *analysis.Pass) (interface{}, error) {
148152
}
149153
}
150154

155+
=======
156+
if unop, ok := cond.(*ssa.UnOp); ok && unop.Op == token.NOT {
157+
cond, t, f = unop.X, f, t
158+
}
159+
>>>>>>> 9b6e4f21d (gopls/internal/analysis/yield: analyzer for iterator (yield) mistakes)
151160
if cond, ok := cond.(*ssa.Call); ok && ssaYieldCalls[cond] != nil {
152161
// Skip the successor reached by "if yield() { ... }".
153162
} else {
@@ -169,10 +178,13 @@ func run(pass *analysis.Pass) (interface{}, error) {
169178

170179
return nil, nil
171180
}
181+
<<<<<<< HEAD
172182

173183
func unnegate(cond ssa.Value, t, f *ssa.BasicBlock) (_ ssa.Value, _, _ *ssa.BasicBlock) {
174184
if unop, ok := cond.(*ssa.UnOp); ok && unop.Op == token.NOT {
175185
return unop.X, f, t
176186
}
177187
return cond, t, f
178188
}
189+
=======
190+
>>>>>>> 9b6e4f21d (gopls/internal/analysis/yield: analyzer for iterator (yield) mistakes)

gopls/internal/analysis/yield/yield_test.go

+1
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,4 @@ func Test(t *testing.T) {
1515
testdata := analysistest.TestData()
1616
analysistest.Run(t, testdata, yield.Analyzer, "a")
1717
}
18+
e

gopls/internal/doc/api.json

+3
Original file line numberDiff line numberDiff line change
@@ -1304,12 +1304,15 @@
13041304
"Default": false
13051305
},
13061306
{
1307+
<<<<<<< HEAD
13071308
"Name": "waitgroup",
13081309
"Doc": "check for misuses of sync.WaitGroup\n\nThis analyzer detects mistaken calls to the (*sync.WaitGroup).Add\nmethod from inside a new goroutine, causing Add to race with Wait:\n\n\t// WRONG\n\tvar wg sync.WaitGroup\n\tgo func() {\n\t wg.Add(1) // \"WaitGroup.Add called from inside new goroutine\"\n\t defer wg.Done()\n\t ...\n\t}()\n\twg.Wait() // (may return prematurely before new goroutine starts)\n\nThe correct code calls Add before starting the goroutine:\n\n\t// RIGHT\n\tvar wg sync.WaitGroup\n\twg.Add(1)\n\tgo func() {\n\t\tdefer wg.Done()\n\t\t...\n\t}()\n\twg.Wait()",
13091310
"URL": "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/waitgroup",
13101311
"Default": true
13111312
},
13121313
{
1314+
=======
1315+
>>>>>>> 9b6e4f21d (gopls/internal/analysis/yield: analyzer for iterator (yield) mistakes)
13131316
"Name": "yield",
13141317
"Doc": "report calls to yield where the result is ignored\n\nAfter a yield function returns false, the caller should not call\nthe yield function again; generally the iterator should return\npromptly.\n\nThis example fails to check the result of the call to yield,\ncausing this analyzer to report a diagnostic:\n\n\tyield(1) // yield may be called again (on L2) after returning false\n\tyield(2)\n\nThe corrected code is either this:\n\n\tif yield(1) { yield(2) }\n\nor simply:\n\n\t_ = yield(1) \u0026\u0026 yield(2)\n\nIt is not always a mistake to ignore the result of yield.\nFor example, this is a valid single-element iterator:\n\n\tyield(1) // ok to ignore result\n\treturn\n\nIt is only a mistake when the yield call that returned false may be\nfollowed by another call.",
13151318
"URL": "https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/yield",

gopls/internal/golang/assembly.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright 2024 The Go Authors. All rights reserved.
1+
ew// Copyright 2024 The Go Authors. All rights reserved.
22
// Use of this source code is governed by a BSD-style
33
// license that can be found in the LICENSE file.
44

0 commit comments

Comments
 (0)