1
2
3
4
5
6 package unreachable
7
8
9
10 import (
11 "go/ast"
12 "go/token"
13 "log"
14
15 "golang.org/x/tools/go/analysis"
16 "golang.org/x/tools/go/analysis/passes/inspect"
17 "golang.org/x/tools/go/ast/inspector"
18 )
19
20 const Doc = `check for unreachable code
21
22 The unreachable analyzer finds statements that execution can never reach
23 because they are preceded by an return statement, a call to panic, an
24 infinite loop, or similar constructs.`
25
26 var Analyzer = &analysis.Analyzer{
27 Name: "unreachable",
28 Doc: Doc,
29 Requires: []*analysis.Analyzer{inspect.Analyzer},
30 RunDespiteErrors: true,
31 Run: run,
32 }
33
34 func run(pass *analysis.Pass) (interface{}, error) {
35 inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
36
37 nodeFilter := []ast.Node{
38 (*ast.FuncDecl)(nil),
39 (*ast.FuncLit)(nil),
40 }
41 inspect.Preorder(nodeFilter, func(n ast.Node) {
42 var body *ast.BlockStmt
43 switch n := n.(type) {
44 case *ast.FuncDecl:
45 body = n.Body
46 case *ast.FuncLit:
47 body = n.Body
48 }
49 if body == nil {
50 return
51 }
52 d := &deadState{
53 pass: pass,
54 hasBreak: make(map[ast.Stmt]bool),
55 hasGoto: make(map[string]bool),
56 labels: make(map[string]ast.Stmt),
57 }
58 d.findLabels(body)
59 d.reachable = true
60 d.findDead(body)
61 })
62 return nil, nil
63 }
64
65 type deadState struct {
66 pass *analysis.Pass
67 hasBreak map[ast.Stmt]bool
68 hasGoto map[string]bool
69 labels map[string]ast.Stmt
70 breakTarget ast.Stmt
71
72 reachable bool
73 }
74
75
76
77 func (d *deadState) findLabels(stmt ast.Stmt) {
78 switch x := stmt.(type) {
79 default:
80 log.Fatalf("%s: internal error in findLabels: unexpected statement %T", d.pass.Fset.Position(x.Pos()), x)
81
82 case *ast.AssignStmt,
83 *ast.BadStmt,
84 *ast.DeclStmt,
85 *ast.DeferStmt,
86 *ast.EmptyStmt,
87 *ast.ExprStmt,
88 *ast.GoStmt,
89 *ast.IncDecStmt,
90 *ast.ReturnStmt,
91 *ast.SendStmt:
92
93
94 case *ast.BlockStmt:
95 for _, stmt := range x.List {
96 d.findLabels(stmt)
97 }
98
99 case *ast.BranchStmt:
100 switch x.Tok {
101 case token.GOTO:
102 if x.Label != nil {
103 d.hasGoto[x.Label.Name] = true
104 }
105
106 case token.BREAK:
107 stmt := d.breakTarget
108 if x.Label != nil {
109 stmt = d.labels[x.Label.Name]
110 }
111 if stmt != nil {
112 d.hasBreak[stmt] = true
113 }
114 }
115
116 case *ast.IfStmt:
117 d.findLabels(x.Body)
118 if x.Else != nil {
119 d.findLabels(x.Else)
120 }
121
122 case *ast.LabeledStmt:
123 d.labels[x.Label.Name] = x.Stmt
124 d.findLabels(x.Stmt)
125
126
127
128
129 case *ast.ForStmt:
130 outer := d.breakTarget
131 d.breakTarget = x
132 d.findLabels(x.Body)
133 d.breakTarget = outer
134
135 case *ast.RangeStmt:
136 outer := d.breakTarget
137 d.breakTarget = x
138 d.findLabels(x.Body)
139 d.breakTarget = outer
140
141 case *ast.SelectStmt:
142 outer := d.breakTarget
143 d.breakTarget = x
144 d.findLabels(x.Body)
145 d.breakTarget = outer
146
147 case *ast.SwitchStmt:
148 outer := d.breakTarget
149 d.breakTarget = x
150 d.findLabels(x.Body)
151 d.breakTarget = outer
152
153 case *ast.TypeSwitchStmt:
154 outer := d.breakTarget
155 d.breakTarget = x
156 d.findLabels(x.Body)
157 d.breakTarget = outer
158
159 case *ast.CommClause:
160 for _, stmt := range x.Body {
161 d.findLabels(stmt)
162 }
163
164 case *ast.CaseClause:
165 for _, stmt := range x.Body {
166 d.findLabels(stmt)
167 }
168 }
169 }
170
171
172
173
174
175 func (d *deadState) findDead(stmt ast.Stmt) {
176
177
178
179
180
181
182
183 if x, isLabel := stmt.(*ast.LabeledStmt); isLabel && d.hasGoto[x.Label.Name] {
184 d.reachable = true
185 }
186
187 if !d.reachable {
188 switch stmt.(type) {
189 case *ast.EmptyStmt:
190
191 default:
192 d.pass.Report(analysis.Diagnostic{
193 Pos: stmt.Pos(),
194 End: stmt.End(),
195 Message: "unreachable code",
196 SuggestedFixes: []analysis.SuggestedFix{{
197 Message: "Remove",
198 TextEdits: []analysis.TextEdit{{
199 Pos: stmt.Pos(),
200 End: stmt.End(),
201 }},
202 }},
203 })
204 d.reachable = true
205 }
206 }
207
208 switch x := stmt.(type) {
209 default:
210 log.Fatalf("%s: internal error in findDead: unexpected statement %T", d.pass.Fset.Position(x.Pos()), x)
211
212 case *ast.AssignStmt,
213 *ast.BadStmt,
214 *ast.DeclStmt,
215 *ast.DeferStmt,
216 *ast.EmptyStmt,
217 *ast.GoStmt,
218 *ast.IncDecStmt,
219 *ast.SendStmt:
220
221
222 case *ast.BlockStmt:
223 for _, stmt := range x.List {
224 d.findDead(stmt)
225 }
226
227 case *ast.BranchStmt:
228 switch x.Tok {
229 case token.BREAK, token.GOTO, token.FALLTHROUGH:
230 d.reachable = false
231 case token.CONTINUE:
232
233
234
235
236
237
238 d.reachable = false
239 }
240
241 case *ast.ExprStmt:
242
243 call, ok := x.X.(*ast.CallExpr)
244 if ok {
245 name, ok := call.Fun.(*ast.Ident)
246 if ok && name.Name == "panic" && name.Obj == nil {
247 d.reachable = false
248 }
249 }
250
251 case *ast.ForStmt:
252 d.findDead(x.Body)
253 d.reachable = x.Cond != nil || d.hasBreak[x]
254
255 case *ast.IfStmt:
256 d.findDead(x.Body)
257 if x.Else != nil {
258 r := d.reachable
259 d.reachable = true
260 d.findDead(x.Else)
261 d.reachable = d.reachable || r
262 } else {
263
264 d.reachable = true
265 }
266
267 case *ast.LabeledStmt:
268 d.findDead(x.Stmt)
269
270 case *ast.RangeStmt:
271 d.findDead(x.Body)
272 d.reachable = true
273
274 case *ast.ReturnStmt:
275 d.reachable = false
276
277 case *ast.SelectStmt:
278
279
280
281
282
283 anyReachable := false
284 for _, comm := range x.Body.List {
285 d.reachable = true
286 for _, stmt := range comm.(*ast.CommClause).Body {
287 d.findDead(stmt)
288 }
289 anyReachable = anyReachable || d.reachable
290 }
291 d.reachable = anyReachable || d.hasBreak[x]
292
293 case *ast.SwitchStmt:
294 anyReachable := false
295 hasDefault := false
296 for _, cas := range x.Body.List {
297 cc := cas.(*ast.CaseClause)
298 if cc.List == nil {
299 hasDefault = true
300 }
301 d.reachable = true
302 for _, stmt := range cc.Body {
303 d.findDead(stmt)
304 }
305 anyReachable = anyReachable || d.reachable
306 }
307 d.reachable = anyReachable || d.hasBreak[x] || !hasDefault
308
309 case *ast.TypeSwitchStmt:
310 anyReachable := false
311 hasDefault := false
312 for _, cas := range x.Body.List {
313 cc := cas.(*ast.CaseClause)
314 if cc.List == nil {
315 hasDefault = true
316 }
317 d.reachable = true
318 for _, stmt := range cc.Body {
319 d.findDead(stmt)
320 }
321 anyReachable = anyReachable || d.reachable
322 }
323 d.reachable = anyReachable || d.hasBreak[x] || !hasDefault
324 }
325 }
326
View as plain text