Skip to content

Commit 8ffeaba

Browse files
adonovangopherbot
authored andcommitted
gopls/internal/settings: enable 'waitgroup' analyzer
This analyzer (equivalent to staticcheck SA2000) will appear in gopls in advance of its appearance in cmd/vet after go1.24. + Test, relnote Change-Id: I6bf571f18c46b6ecedb711170adfddc829f1b859 Reviewed-on: https://go-review.googlesource.com/c/tools/+/633035 LUCI-TryBot-Result: Go LUCI <[email protected]> Auto-Submit: Alan Donovan <[email protected]> Reviewed-by: Robert Findley <[email protected]>
1 parent 4317959 commit 8ffeaba

File tree

5 files changed

+61
-0
lines changed

5 files changed

+61
-0
lines changed

gopls/doc/analyzers.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -976,6 +976,37 @@ Default: off. Enable by setting `"analyses": {"useany": true}`.
976976

977977
Package documentation: [useany](https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/useany)
978978

979+
<a id='waitgroup'></a>
980+
## `waitgroup`: check for misuses of sync.WaitGroup
981+
982+
983+
This analyzer detects mistaken calls to the (*sync.WaitGroup).Add
984+
method from inside a new goroutine, causing Add to race with Wait:
985+
986+
// WRONG
987+
var wg sync.WaitGroup
988+
go func() {
989+
wg.Add(1) // "WaitGroup.Add called from inside new goroutine"
990+
defer wg.Done()
991+
...
992+
}()
993+
wg.Wait() // (may return prematurely before new goroutine starts)
994+
995+
The correct code calls Add before starting the goroutine:
996+
997+
// RIGHT
998+
var wg sync.WaitGroup
999+
wg.Add(1)
1000+
go func() {
1001+
defer wg.Done()
1002+
...
1003+
}()
1004+
wg.Wait()
1005+
1006+
Default: on.
1007+
1008+
Package documentation: [waitgroup](https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/waitgroup)
1009+
9791010
<a id='yield'></a>
9801011
## `yield`: report calls to yield where the result is ignored
9811012

gopls/doc/release/v0.17.0.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,15 @@ The new `yield` analyzer detects mistakes using the `yield` function
9494
in a Go 1.23 iterator, such as failure to check its boolean result and
9595
break out of a loop.
9696

97+
## `waitgroup` analyzer
98+
99+
The new `waitgroup` analyzer detects calls to the `Add` method of
100+
`sync.WaitGroup` that are (mistakenly) made within the new goroutine,
101+
causing `Add` to race with `Wait`.
102+
(This check is equivalent to
103+
[staticcheck's SA2000](https://staticcheck.dev/docs/checks#SA2000),
104+
but is enabled by default.)
105+
97106
## Add test for function or method
98107

99108
If the selected chunk of code is part of a function or method declaration F,

gopls/internal/doc/api.json

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -609,6 +609,11 @@
609609
"Doc": "check for constraints that could be simplified to \"any\"",
610610
"Default": "false"
611611
},
612+
{
613+
"Name": "\"waitgroup\"",
614+
"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()",
615+
"Default": "true"
616+
},
612617
{
613618
"Name": "\"yield\"",
614619
"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.",
@@ -1298,6 +1303,12 @@
12981303
"URL": "https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/useany",
12991304
"Default": false
13001305
},
1306+
{
1307+
"Name": "waitgroup",
1308+
"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()",
1309+
"URL": "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/waitgroup",
1310+
"Default": true
1311+
},
13011312
{
13021313
"Name": "yield",
13031314
"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.",

gopls/internal/settings/analysis.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ import (
4545
"golang.org/x/tools/go/analysis/passes/unsafeptr"
4646
"golang.org/x/tools/go/analysis/passes/unusedresult"
4747
"golang.org/x/tools/go/analysis/passes/unusedwrite"
48+
"golang.org/x/tools/go/analysis/passes/waitgroup"
4849
"golang.org/x/tools/gopls/internal/analysis/deprecated"
4950
"golang.org/x/tools/gopls/internal/analysis/embeddirective"
5051
"golang.org/x/tools/gopls/internal/analysis/fillreturns"
@@ -154,6 +155,7 @@ func init() {
154155
{analyzer: yield.Analyzer, enabled: true}, // uses go/ssa
155156
{analyzer: sortslice.Analyzer, enabled: true},
156157
{analyzer: embeddirective.Analyzer, enabled: true},
158+
{analyzer: waitgroup.Analyzer, enabled: true}, // to appear in cmd/[email protected]
157159

158160
// disabled due to high false positives
159161
{analyzer: shadow.Analyzer, enabled: false}, // very noisy

gopls/internal/test/marker/testdata/diagnostics/analyzers.txt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,14 @@ func _() {
6666
slog.Info("msg", 1) //@diag("1", re`slog.Info arg "1" should be a string or a slog.Attr`)
6767
}
6868

69+
// waitgroup
70+
func _() {
71+
var wg sync.WaitGroup
72+
go func() {
73+
wg.Add(1) //@diag("(", re"WaitGroup.Add called from inside new goroutine")
74+
}()
75+
}
76+
6977
-- cgocall/cgocall.go --
7078
package cgocall
7179

0 commit comments

Comments
 (0)