Skip to content

Commit 253431f

Browse files
committed
SA9009: flag defer foo() where foo returns a closure
Add a new checker to flag cases where a deferred function returns exactly one function, and that function isn't called. For example: func f() func() { // Do stuff. return func() { // Do stuff } } func main() { defer f() // Error: should be f()() } As mentioned in #466, there's a change for false positives. I ran the check on Go stdlib, all my own code, and about 200 popular Go packages, and haven't found any false positives (and one match: grpc/grpc-go#7270) Fixes #466
1 parent d39a04f commit 253431f

File tree

5 files changed

+237
-0
lines changed

5 files changed

+237
-0
lines changed

staticcheck/analysis.go

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

staticcheck/sa9009/sa9009.go

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
package sa9009
2+
3+
import (
4+
"go/ast"
5+
"go/types"
6+
7+
"honnef.co/go/tools/analysis/code"
8+
"honnef.co/go/tools/analysis/lint"
9+
"honnef.co/go/tools/analysis/report"
10+
11+
"golang.org/x/tools/go/analysis"
12+
"golang.org/x/tools/go/analysis/passes/inspect"
13+
)
14+
15+
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
16+
Analyzer: &analysis.Analyzer{
17+
Name: "SA9009",
18+
Run: run,
19+
Requires: []*analysis.Analyzer{inspect.Analyzer},
20+
},
21+
Doc: &lint.Documentation{
22+
Title: `Returned function should be called in defer`,
23+
Text: `
24+
If you have a function such as:
25+
26+
func f() func() {
27+
// Do something.
28+
return func() {
29+
// Do something.
30+
}
31+
}
32+
33+
Then calling that in defer:
34+
35+
defer f()
36+
37+
Is almost always a mistake, since you typically want to call the returned
38+
function:
39+
40+
defer f()()
41+
`,
42+
Since: "Unreleased",
43+
Severity: lint.SeverityWarning,
44+
MergeIf: lint.MergeIfAny,
45+
},
46+
})
47+
48+
var Analyzer = SCAnalyzer.Analyzer
49+
50+
func run(pass *analysis.Pass) (any, error) {
51+
checkIdent := func(c *ast.Ident) bool {
52+
var (
53+
obj = pass.TypesInfo.ObjectOf(c)
54+
sig *types.Signature
55+
)
56+
switch f := obj.(type) {
57+
case *types.Builtin:
58+
return false
59+
case *types.Func:
60+
sig = f.Type().(*types.Signature)
61+
case *types.Var:
62+
switch ff := f.Type().(type) {
63+
case *types.Signature:
64+
sig = ff
65+
case *types.Named:
66+
sig = ff.Underlying().(*types.Signature)
67+
}
68+
}
69+
r := sig.Results()
70+
if r != nil && r.Len() == 1 {
71+
_, ok := r.At(0).Type().(*types.Signature)
72+
return ok
73+
}
74+
return false
75+
}
76+
77+
fn := func(n ast.Node) {
78+
var (
79+
returnsFunc bool
80+
def = n.(*ast.DeferStmt)
81+
)
82+
switch c := def.Call.Fun.(type) {
83+
case *ast.FuncLit: // defer func() { }()
84+
r := c.Type.Results
85+
if r != nil && len(r.List) == 1 {
86+
_, returnsFunc = r.List[0].Type.(*ast.FuncType)
87+
}
88+
case *ast.Ident: // defer f()
89+
returnsFunc = checkIdent(c)
90+
case *ast.SelectorExpr: // defer t.f()
91+
returnsFunc = checkIdent(c.Sel)
92+
case *ast.IndexExpr: // defer f[int](0)
93+
if id, ok := c.X.(*ast.Ident); ok {
94+
returnsFunc = checkIdent(id)
95+
}
96+
}
97+
if returnsFunc {
98+
report.Report(pass, def, "defered return function not called")
99+
}
100+
}
101+
102+
code.Preorder(pass, fn, (*ast.DeferStmt)(nil))
103+
return nil, nil
104+
}

staticcheck/sa9009/sa9009_test.go

Lines changed: 13 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
package deferr
2+
3+
func x() {
4+
var (
5+
t t1
6+
tt t2
7+
varReturnNothing = func() {}
8+
varReturnInt = func() int { return 0 }
9+
varReturnFunc = func() func() { return func() {} }
10+
varReturnFuncInt = func(int) func(int) int { return func(int) int { return 0 } }
11+
varReturnMulti = func() (int, func()) { return 0, func() {} }
12+
13+
namedReturnNothing = named(func() {})
14+
namedReturnFunc = namedReturn(func() func() { return func() {} })
15+
)
16+
17+
// Correct.
18+
defer returnNothing()
19+
defer varReturnNothing()
20+
defer namedReturnNothing()
21+
defer t.returnNothing()
22+
defer tt.returnNothing()
23+
defer tt.t.returnNothing()
24+
defer func() {}()
25+
defer close(make(chan int))
26+
27+
defer returnInt()
28+
defer varReturnInt()
29+
defer t.returnInt()
30+
defer tt.returnInt()
31+
defer tt.t.returnInt()
32+
defer func() int { return 0 }()
33+
34+
defer returnFunc()()
35+
defer varReturnFunc()()
36+
defer namedReturnFunc()()
37+
defer t.returnFunc()()
38+
defer tt.returnFunc()()
39+
defer tt.t.returnFunc()()
40+
defer func() func() { return func() {} }()()
41+
42+
defer returnFuncInt(0)(0)
43+
defer varReturnFuncInt(0)(0)
44+
defer t.returnFuncInt(0)(0)
45+
defer tt.returnFuncInt(0)(0)
46+
defer tt.t.returnFuncInt(0)(0)
47+
defer func(int) func(int) int { return func(int) int { return 0 } }(0)(0)
48+
49+
defer returnMulti()
50+
defer varReturnMulti()
51+
defer t.returnMulti()
52+
defer tt.returnMulti()
53+
defer tt.t.returnMulti()
54+
defer func() (int, func()) { return 0, func() {} }()
55+
56+
// Wrong.
57+
defer returnFunc() //@ diag(`defered return function not called`)
58+
defer varReturnFunc() //@ diag(`defered return function not called`)
59+
defer namedReturnFunc() //@ diag(`defered return function not called`)
60+
defer t.returnFunc() //@ diag(`defered return function not called`)
61+
defer tt.returnFunc() //@ diag(`defered return function not called`)
62+
defer tt.t.returnFunc() //@ diag(`defered return function not called`)
63+
defer func() func() { return func() {} }() //@ diag(`defered return function not called`)
64+
defer returnFuncInt(0) //@ diag(`defered return function not called`)
65+
defer t.returnFuncInt(0) //@ diag(`defered return function not called`)
66+
defer tt.returnFuncInt(0) //@ diag(`defered return function not called`)
67+
defer tt.t.returnFuncInt(0) //@ diag(`defered return function not called`)
68+
defer func(int) func(int) int { return func(int) int { return 0 } }(0) //@ diag(`defered return function not called`)
69+
70+
// Function returns a function which returns another function. This is
71+
// getting silly and is not checked.
72+
defer silly1()()
73+
defer func() func() func() {
74+
return func() func() {
75+
return func() {}
76+
}
77+
}()()
78+
}
79+
80+
func returnNothing() {}
81+
func returnInt() int { return 0 }
82+
func returnFunc() func() { return func() {} }
83+
func returnFuncInt(int) func(int) int { return func(int) int { return 0 } }
84+
func returnMulti() (int, func()) { return 0, func() {} }
85+
86+
type (
87+
t1 struct{}
88+
t2 struct{ t t1 }
89+
named func()
90+
namedReturn func() func()
91+
)
92+
93+
func (t1) returnNothing() {}
94+
func (t1) returnInt() int { return 0 }
95+
func (t1) returnFunc() func() { return func() {} }
96+
func (t1) returnFuncInt(int) func(int) int { return func(int) int { return 0 } }
97+
func (t1) returnMulti() (int, func()) { return 0, func() {} }
98+
99+
func (*t2) returnNothing() {}
100+
func (*t2) returnInt() int { return 0 }
101+
func (*t2) returnFunc() func() { return func() {} }
102+
func (*t2) returnFuncInt(int) func(int) int { return func(int) int { return 0 } }
103+
func (*t2) returnMulti() (int, func()) { return 0, func() {} }
104+
105+
func silly1() func() func() {
106+
return func() func() {
107+
return func() {}
108+
}
109+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package deferr
2+
3+
func x() {
4+
5+
defer tpReturnFuncInt[int](0) //@ diag(`defered return function not called`)
6+
defer tpReturnFuncInt(0)(0)
7+
}
8+
9+
func tpReturnFuncInt[T any](T) func(int) int { return func(int) int { return 0 } }

0 commit comments

Comments
 (0)