github.com/powerman/golang-tools@v0.1.11-0.20220410185822-5ad214d8d803/go/analysis/passes/unreachable/unreachable.go (about) 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. 4 5 // Package unreachable defines an Analyzer that checks for unreachable code. 6 package unreachable 7 8 // TODO(adonovan): use the new cfg package, which is more precise. 9 10 import ( 11 "go/ast" 12 "go/token" 13 "log" 14 15 "github.com/powerman/golang-tools/go/analysis" 16 "github.com/powerman/golang-tools/go/analysis/passes/inspect" 17 "github.com/powerman/golang-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 // 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) { 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 // no statements inside 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 // 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 128 // be merged. 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 // 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 180 // L: goto L 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] { 184 d.reachable = true 185 } 186 187 if !d.reachable { 188 switch stmt.(type) { 189 case *ast.EmptyStmt: 190 // do not warn about unreachable empty statements 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 // silence error about next statement 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 // no control flow 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 // 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. 238 d.reachable = false 239 } 240 241 case *ast.ExprStmt: 242 // Call to panic? 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 // might not have executed if statement 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 // 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 { 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 }