1 // Copyright 2013 The Go Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style
3 // license that can be found in the LICENSE file.
5 // Package unreachable defines an Analyzer that checks for unreachable code.
8 // TODO(adonovan): use the new cfg package, which is more precise.
15 "golang.org/x/tools/go/analysis"
16 "golang.org/x/tools/go/analysis/passes/inspect"
17 "golang.org/x/tools/go/ast/inspector"
20 const Doc = `check for unreachable code
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.`
26 var Analyzer = &analysis.Analyzer{
29 Requires: []*analysis.Analyzer{inspect.Analyzer},
30 RunDespiteErrors: true,
34 func run(pass *analysis.Pass) (interface{}, error) {
35 inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
37 nodeFilter := []ast.Node{
41 inspect.Preorder(nodeFilter, func(n ast.Node) {
42 var body *ast.BlockStmt
43 switch n := n.(type) {
54 hasBreak: make(map[ast.Stmt]bool),
55 hasGoto: make(map[string]bool),
56 labels: make(map[string]ast.Stmt),
65 type deadState struct {
67 hasBreak map[ast.Stmt]bool
68 hasGoto map[string]bool
69 labels map[string]ast.Stmt
75 // findLabels gathers information about the labels defined and used by stmt
76 // and about which statements break, whether a label is involved or not.
77 func (d *deadState) findLabels(stmt ast.Stmt) {
78 switch x := stmt.(type) {
80 log.Fatalf("%s: internal error in findLabels: unexpected statement %T", d.pass.Fset.Position(x.Pos()), x)
92 // no statements inside
95 for _, stmt := range x.List {
103 d.hasGoto[x.Label.Name] = true
107 stmt := d.breakTarget
109 stmt = d.labels[x.Label.Name]
112 d.hasBreak[stmt] = true
122 case *ast.LabeledStmt:
123 d.labels[x.Label.Name] = x.Stmt
126 // These cases are all the same, but the x.Body only works
127 // when the specific type of x is known, so the cases cannot
130 outer := d.breakTarget
133 d.breakTarget = outer
136 outer := d.breakTarget
139 d.breakTarget = outer
141 case *ast.SelectStmt:
142 outer := d.breakTarget
145 d.breakTarget = outer
147 case *ast.SwitchStmt:
148 outer := d.breakTarget
151 d.breakTarget = outer
153 case *ast.TypeSwitchStmt:
154 outer := d.breakTarget
157 d.breakTarget = outer
159 case *ast.CommClause:
160 for _, stmt := range x.Body {
164 case *ast.CaseClause:
165 for _, stmt := range x.Body {
171 // findDead walks the statement looking for dead code.
172 // If d.reachable is false on entry, stmt itself is dead.
173 // When findDead returns, d.reachable tells whether the
174 // statement following stmt is reachable.
175 func (d *deadState) findDead(stmt ast.Stmt) {
176 // Is this a labeled goto target?
177 // If so, assume it is reachable due to the goto.
178 // This is slightly conservative, in that we don't
179 // check that the goto is reachable, so
181 // will not provoke a warning.
182 // But it's good enough.
183 if x, isLabel := stmt.(*ast.LabeledStmt); isLabel && d.hasGoto[x.Label.Name] {
190 // do not warn about unreachable empty statements
192 d.pass.Report(analysis.Diagnostic{
195 Message: "unreachable code",
196 SuggestedFixes: []analysis.SuggestedFix{{
198 TextEdits: []analysis.TextEdit{{
204 d.reachable = true // silence error about next statement
208 switch x := stmt.(type) {
210 log.Fatalf("%s: internal error in findDead: unexpected statement %T", d.pass.Fset.Position(x.Pos()), x)
212 case *ast.AssignStmt,
223 for _, stmt := range x.List {
227 case *ast.BranchStmt:
229 case token.BREAK, token.GOTO, token.FALLTHROUGH:
232 // NOTE: We accept "continue" statements as terminating.
233 // They are not necessary in the spec definition of terminating,
234 // because a continue statement cannot be the final statement
235 // before a return. But for the more general problem of syntactically
236 // identifying dead code, continue redirects control flow just
237 // like the other terminating statements.
243 call, ok := x.X.(*ast.CallExpr)
245 name, ok := call.Fun.(*ast.Ident)
246 if ok && name.Name == "panic" && name.Obj == nil {
253 d.reachable = x.Cond != nil || d.hasBreak[x]
261 d.reachable = d.reachable || r
263 // might not have executed if statement
267 case *ast.LabeledStmt:
274 case *ast.ReturnStmt:
277 case *ast.SelectStmt:
278 // NOTE: Unlike switch and type switch below, we don't care
279 // whether a select has a default, because a select without a
280 // default blocks until one of the cases can run. That's different
281 // from a switch without a default, which behaves like it has
282 // a default with an empty body.
283 anyReachable := false
284 for _, comm := range x.Body.List {
286 for _, stmt := range comm.(*ast.CommClause).Body {
289 anyReachable = anyReachable || d.reachable
291 d.reachable = anyReachable || d.hasBreak[x]
293 case *ast.SwitchStmt:
294 anyReachable := false
296 for _, cas := range x.Body.List {
297 cc := cas.(*ast.CaseClause)
302 for _, stmt := range cc.Body {
305 anyReachable = anyReachable || d.reachable
307 d.reachable = anyReachable || d.hasBreak[x] || !hasDefault
309 case *ast.TypeSwitchStmt:
310 anyReachable := false
312 for _, cas := range x.Body.List {
313 cc := cas.(*ast.CaseClause)
318 for _, stmt := range cc.Body {
321 anyReachable = anyReachable || d.reachable
323 d.reachable = anyReachable || d.hasBreak[x] || !hasDefault