1 package sa5002
2 3 import (
4 "go/ast"
5 "go/constant"
6 "go/types"
7 8 "honnef.co/go/tools/analysis/code"
9 "honnef.co/go/tools/analysis/lint"
10 "honnef.co/go/tools/analysis/report"
11 12 "golang.org/x/tools/go/analysis"
13 "golang.org/x/tools/go/analysis/passes/inspect"
14 )
15 16 var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
17 Analyzer: &analysis.Analyzer{
18 Name: "SA5002",
19 Run: run,
20 Requires: []*analysis.Analyzer{inspect.Analyzer},
21 },
22 Doc: &lint.RawDocumentation{
23 Title: `The empty for loop (\"for {}\") spins and can block the scheduler`,
24 Since: "2017.1",
25 Severity: lint.SeverityWarning,
26 MergeIf: lint.MergeIfAny,
27 },
28 })
29 30 var Analyzer = SCAnalyzer.Analyzer
31 32 func run(pass *analysis.Pass) (interface{}, error) {
33 fn := func(node ast.Node) {
34 loop := node.(*ast.ForStmt)
35 if len(loop.Body.List) != 0 || loop.Post != nil {
36 return
37 }
38 39 if loop.Init != nil {
40 // TODO(dh): this isn't strictly necessary, it just makes
41 // the check easier.
42 return
43 }
44 // An empty loop is bad news in two cases: 1) The loop has no
45 // condition. In that case, it's just a loop that spins
46 // forever and as fast as it can, keeping a core busy. 2) The
47 // loop condition only consists of variable or field reads and
48 // operators on those. The only way those could change their
49 // value is with unsynchronised access, which constitutes a
50 // data race.
51 //
52 // If the condition contains any function calls, its behaviour
53 // is dynamic and the loop might terminate. Similarly for
54 // channel receives.
55 56 if loop.Cond != nil {
57 if code.MayHaveSideEffects(pass, loop.Cond, nil) {
58 return
59 }
60 if ident, ok := loop.Cond.(*ast.Ident); ok {
61 if k, ok := pass.TypesInfo.ObjectOf(ident).(*types.Const); ok {
62 if !constant.BoolVal(k.Val()) {
63 // don't flag `for false {}` loops. They're a debug aid.
64 return
65 }
66 }
67 }
68 report.Report(pass, loop, "loop condition never changes or has a race condition")
69 }
70 report.Report(pass, loop, "this loop will spin, using 100% CPU", report.ShortRange())
71 }
72 code.Preorder(pass, fn, (*ast.ForStmt)(nil))
73 return nil, nil
74 }
75