sa4004.go raw
1 package sa4004
2
3 import (
4 "go/ast"
5 "go/token"
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 "honnef.co/go/tools/go/types/typeutil"
12
13 "golang.org/x/tools/go/analysis"
14 "golang.org/x/tools/go/analysis/passes/inspect"
15 )
16
17 var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
18 Analyzer: &analysis.Analyzer{
19 Name: "SA4004",
20 Run: run,
21 Requires: []*analysis.Analyzer{inspect.Analyzer},
22 },
23 Doc: &lint.RawDocumentation{
24 Title: `The loop exits unconditionally after one iteration`,
25 Since: "2017.1",
26 Severity: lint.SeverityWarning,
27 MergeIf: lint.MergeIfAll,
28 },
29 })
30
31 var Analyzer = SCAnalyzer.Analyzer
32
33 func run(pass *analysis.Pass) (interface{}, error) {
34 // This check detects some, but not all unconditional loop exits.
35 // We give up in the following cases:
36 //
37 // - a goto anywhere in the loop. The goto might skip over our
38 // return, and we don't check that it doesn't.
39 //
40 // - any nested, unlabelled continue, even if it is in another
41 // loop or closure.
42 fn := func(node ast.Node) {
43 var body *ast.BlockStmt
44 switch fn := node.(type) {
45 case *ast.FuncDecl:
46 body = fn.Body
47 case *ast.FuncLit:
48 body = fn.Body
49 default:
50 lint.ExhaustiveTypeSwitch(node)
51 }
52 if body == nil {
53 return
54 }
55 labels := map[types.Object]ast.Stmt{}
56 ast.Inspect(body, func(node ast.Node) bool {
57 label, ok := node.(*ast.LabeledStmt)
58 if !ok {
59 return true
60 }
61 labels[pass.TypesInfo.ObjectOf(label.Label)] = label.Stmt
62 return true
63 })
64
65 ast.Inspect(body, func(node ast.Node) bool {
66 var loop ast.Node
67 var body *ast.BlockStmt
68 switch node := node.(type) {
69 case *ast.ForStmt:
70 body = node.Body
71 loop = node
72 case *ast.RangeStmt:
73 ok := typeutil.All(pass.TypesInfo.TypeOf(node.X), func(term *types.Term) bool {
74 switch term.Type().Underlying().(type) {
75 case *types.Slice, *types.Chan, *types.Basic, *types.Pointer, *types.Array:
76 return true
77 case *types.Map:
78 // looping once over a map is a valid pattern for
79 // getting an arbitrary element.
80 return false
81 case *types.Signature:
82 // we have no idea what semantics the function implements
83 return false
84 default:
85 lint.ExhaustiveTypeSwitch(term.Type().Underlying())
86 return false
87 }
88 })
89 if !ok {
90 return true
91 }
92 body = node.Body
93 loop = node
94 default:
95 return true
96 }
97 if len(body.List) < 2 {
98 // TODO(dh): is this check needed? when body.List < 2,
99 // then we can't find both an unconditional exit and a
100 // branching statement (if, ...). and we don't flag
101 // unconditional exits if there has been no branching
102 // in the loop body.
103
104 // avoid flagging the somewhat common pattern of using
105 // a range loop to get the first element in a slice,
106 // or the first rune in a string.
107 return true
108 }
109 var unconditionalExit ast.Node
110 hasBranching := false
111 for _, stmt := range body.List {
112 switch stmt := stmt.(type) {
113 case *ast.BranchStmt:
114 switch stmt.Tok {
115 case token.BREAK:
116 if stmt.Label == nil || labels[pass.TypesInfo.ObjectOf(stmt.Label)] == loop {
117 unconditionalExit = stmt
118 }
119 case token.CONTINUE:
120 if stmt.Label == nil || labels[pass.TypesInfo.ObjectOf(stmt.Label)] == loop {
121 unconditionalExit = nil
122 return false
123 }
124 }
125 case *ast.ReturnStmt:
126 unconditionalExit = stmt
127 case *ast.IfStmt, *ast.ForStmt, *ast.RangeStmt, *ast.SwitchStmt, *ast.SelectStmt:
128 hasBranching = true
129 }
130 }
131 if unconditionalExit == nil || !hasBranching {
132 return false
133 }
134 ast.Inspect(body, func(node ast.Node) bool {
135 if branch, ok := node.(*ast.BranchStmt); ok {
136
137 switch branch.Tok {
138 case token.GOTO:
139 unconditionalExit = nil
140 return false
141 case token.CONTINUE:
142 if branch.Label != nil && labels[pass.TypesInfo.ObjectOf(branch.Label)] != loop {
143 return true
144 }
145 unconditionalExit = nil
146 return false
147 }
148 }
149 return true
150 })
151 if unconditionalExit != nil {
152 report.Report(pass, unconditionalExit, "the surrounding loop is unconditionally terminated")
153 }
154 return true
155 })
156 }
157 code.Preorder(pass, fn, (*ast.FuncDecl)(nil), (*ast.FuncLit)(nil))
158 return nil, nil
159 }
160