-
Notifications
You must be signed in to change notification settings - Fork 113
/
deferInLoop_checker.go
87 lines (76 loc) · 2.02 KB
/
deferInLoop_checker.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
package checkers
import (
"go/ast"
"golang.org/x/tools/go/ast/astutil"
"github.com/go-critic/go-critic/checkers/internal/astwalk"
"github.com/go-critic/go-critic/framework/linter"
)
func init() {
var info linter.CheckerInfo
info.Name = "deferInLoop"
info.Tags = []string{"diagnostic", "experimental"}
info.Summary = "Detects loops inside functions that use defer"
info.Before = `
for _, filename := range []string{"foo", "bar"} {
f, err := os.Open(filename)
defer f.Close()
}
`
info.After = `
func process(filename string) {
f, err := os.Open(filename)
defer f.Close()
}
/* ... */
for _, filename := range []string{"foo", "bar"} {
process(filename)
}`
collection.AddChecker(&info, func(ctx *linter.CheckerContext) (linter.FileWalker, error) {
return astwalk.WalkerForFuncDecl(&deferInLoopChecker{ctx: ctx}), nil
})
}
type deferInLoopChecker struct {
astwalk.WalkHandler
ctx *linter.CheckerContext
}
func (c *deferInLoopChecker) VisitFuncDecl(fn *ast.FuncDecl) {
// TODO: add func args check
// example: t.Run(123, func() { for { defer println(); break } })
var blockParser func(*ast.BlockStmt, bool)
blockParser = func(block *ast.BlockStmt, inFor bool) {
for _, cur := range block.List {
switch n := cur.(type) {
case *ast.DeferStmt:
if inFor {
c.warn(n)
}
case *ast.RangeStmt:
blockParser(n.Body, true)
case *ast.ForStmt:
blockParser(n.Body, true)
case *ast.GoStmt:
if f, ok := n.Call.Fun.(*ast.FuncLit); ok {
blockParser(f.Body, false)
}
case *ast.ExprStmt:
if f, ok := n.X.(*ast.CallExpr); ok {
if anon, ok := f.Fun.(*ast.FuncLit); ok {
blockParser(anon.Body, false)
}
}
case *ast.BlockStmt:
blockParser(n, inFor)
}
}
}
pre := func(cur *astutil.Cursor) bool {
if n, ok := cur.Node().(*ast.BlockStmt); ok {
blockParser(n, false)
}
return false
}
astutil.Apply(fn.Body, pre, nil)
}
func (c *deferInLoopChecker) warn(cause *ast.DeferStmt) {
c.ctx.Warn(cause, "Possible resource leak, 'defer' is called in the 'for' loop")
}