golang.org/x/tools@v0.21.0/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 6 7 // TODO(adonovan): use the new cfg package, which is more precise. 8 9 import ( 10 _ "embed" 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/analysis/passes/internal/analysisutil" 18 "golang.org/x/tools/go/ast/inspector" 19 ) 20 21 //go:embed doc.go 22 var doc string 23 24 var Analyzer = &analysis.Analyzer{ 25 Name: "unreachable", 26 Doc: analysisutil.MustExtractDoc(doc, "unreachable"), 27 URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/unreachable", 28 Requires: []*analysis.Analyzer{inspect.Analyzer}, 29 RunDespiteErrors: true, 30 Run: run, 31 } 32 33 func run(pass *analysis.Pass) (interface{}, error) { 34 inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) 35 36 nodeFilter := []ast.Node{ 37 (*ast.FuncDecl)(nil), 38 (*ast.FuncLit)(nil), 39 } 40 inspect.Preorder(nodeFilter, func(n ast.Node) { 41 var body *ast.BlockStmt 42 switch n := n.(type) { 43 case *ast.FuncDecl: 44 body = n.Body 45 case *ast.FuncLit: 46 body = n.Body 47 } 48 if body == nil { 49 return 50 } 51 d := &deadState{ 52 pass: pass, 53 hasBreak: make(map[ast.Stmt]bool), 54 hasGoto: make(map[string]bool), 55 labels: make(map[string]ast.Stmt), 56 } 57 d.findLabels(body) 58 d.reachable = true 59 d.findDead(body) 60 }) 61 return nil, nil 62 } 63 64 type deadState struct { 65 pass *analysis.Pass 66 hasBreak map[ast.Stmt]bool 67 hasGoto map[string]bool 68 labels map[string]ast.Stmt 69 breakTarget ast.Stmt 70 71 reachable bool 72 } 73 74 // findLabels gathers information about the labels defined and used by stmt 75 // and about which statements break, whether a label is involved or not. 76 func (d *deadState) findLabels(stmt ast.Stmt) { 77 switch x := stmt.(type) { 78 default: 79 log.Fatalf("%s: internal error in findLabels: unexpected statement %T", d.pass.Fset.Position(x.Pos()), x) 80 81 case *ast.AssignStmt, 82 *ast.BadStmt, 83 *ast.DeclStmt, 84 *ast.DeferStmt, 85 *ast.EmptyStmt, 86 *ast.ExprStmt, 87 *ast.GoStmt, 88 *ast.IncDecStmt, 89 *ast.ReturnStmt, 90 *ast.SendStmt: 91 // no statements inside 92 93 case *ast.BlockStmt: 94 for _, stmt := range x.List { 95 d.findLabels(stmt) 96 } 97 98 case *ast.BranchStmt: 99 switch x.Tok { 100 case token.GOTO: 101 if x.Label != nil { 102 d.hasGoto[x.Label.Name] = true 103 } 104 105 case token.BREAK: 106 stmt := d.breakTarget 107 if x.Label != nil { 108 stmt = d.labels[x.Label.Name] 109 } 110 if stmt != nil { 111 d.hasBreak[stmt] = true 112 } 113 } 114 115 case *ast.IfStmt: 116 d.findLabels(x.Body) 117 if x.Else != nil { 118 d.findLabels(x.Else) 119 } 120 121 case *ast.LabeledStmt: 122 d.labels[x.Label.Name] = x.Stmt 123 d.findLabels(x.Stmt) 124 125 // These cases are all the same, but the x.Body only works 126 // when the specific type of x is known, so the cases cannot 127 // be merged. 128 case *ast.ForStmt: 129 outer := d.breakTarget 130 d.breakTarget = x 131 d.findLabels(x.Body) 132 d.breakTarget = outer 133 134 case *ast.RangeStmt: 135 outer := d.breakTarget 136 d.breakTarget = x 137 d.findLabels(x.Body) 138 d.breakTarget = outer 139 140 case *ast.SelectStmt: 141 outer := d.breakTarget 142 d.breakTarget = x 143 d.findLabels(x.Body) 144 d.breakTarget = outer 145 146 case *ast.SwitchStmt: 147 outer := d.breakTarget 148 d.breakTarget = x 149 d.findLabels(x.Body) 150 d.breakTarget = outer 151 152 case *ast.TypeSwitchStmt: 153 outer := d.breakTarget 154 d.breakTarget = x 155 d.findLabels(x.Body) 156 d.breakTarget = outer 157 158 case *ast.CommClause: 159 for _, stmt := range x.Body { 160 d.findLabels(stmt) 161 } 162 163 case *ast.CaseClause: 164 for _, stmt := range x.Body { 165 d.findLabels(stmt) 166 } 167 } 168 } 169 170 // findDead walks the statement looking for dead code. 171 // If d.reachable is false on entry, stmt itself is dead. 172 // When findDead returns, d.reachable tells whether the 173 // statement following stmt is reachable. 174 func (d *deadState) findDead(stmt ast.Stmt) { 175 // Is this a labeled goto target? 176 // If so, assume it is reachable due to the goto. 177 // This is slightly conservative, in that we don't 178 // check that the goto is reachable, so 179 // L: goto L 180 // will not provoke a warning. 181 // But it's good enough. 182 if x, isLabel := stmt.(*ast.LabeledStmt); isLabel && d.hasGoto[x.Label.Name] { 183 d.reachable = true 184 } 185 186 if !d.reachable { 187 switch stmt.(type) { 188 case *ast.EmptyStmt: 189 // do not warn about unreachable empty statements 190 default: 191 d.pass.Report(analysis.Diagnostic{ 192 Pos: stmt.Pos(), 193 End: stmt.End(), 194 Message: "unreachable code", 195 SuggestedFixes: []analysis.SuggestedFix{{ 196 Message: "Remove", 197 TextEdits: []analysis.TextEdit{{ 198 Pos: stmt.Pos(), 199 End: stmt.End(), 200 }}, 201 }}, 202 }) 203 d.reachable = true // silence error about next statement 204 } 205 } 206 207 switch x := stmt.(type) { 208 default: 209 log.Fatalf("%s: internal error in findDead: unexpected statement %T", d.pass.Fset.Position(x.Pos()), x) 210 211 case *ast.AssignStmt, 212 *ast.BadStmt, 213 *ast.DeclStmt, 214 *ast.DeferStmt, 215 *ast.EmptyStmt, 216 *ast.GoStmt, 217 *ast.IncDecStmt, 218 *ast.SendStmt: 219 // no control flow 220 221 case *ast.BlockStmt: 222 for _, stmt := range x.List { 223 d.findDead(stmt) 224 } 225 226 case *ast.BranchStmt: 227 switch x.Tok { 228 case token.BREAK, token.GOTO, token.FALLTHROUGH: 229 d.reachable = false 230 case token.CONTINUE: 231 // NOTE: We accept "continue" statements as terminating. 232 // They are not necessary in the spec definition of terminating, 233 // because a continue statement cannot be the final statement 234 // before a return. But for the more general problem of syntactically 235 // identifying dead code, continue redirects control flow just 236 // like the other terminating statements. 237 d.reachable = false 238 } 239 240 case *ast.ExprStmt: 241 // Call to panic? 242 call, ok := x.X.(*ast.CallExpr) 243 if ok { 244 name, ok := call.Fun.(*ast.Ident) 245 if ok && name.Name == "panic" && name.Obj == nil { 246 d.reachable = false 247 } 248 } 249 250 case *ast.ForStmt: 251 d.findDead(x.Body) 252 d.reachable = x.Cond != nil || d.hasBreak[x] 253 254 case *ast.IfStmt: 255 d.findDead(x.Body) 256 if x.Else != nil { 257 r := d.reachable 258 d.reachable = true 259 d.findDead(x.Else) 260 d.reachable = d.reachable || r 261 } else { 262 // might not have executed if statement 263 d.reachable = true 264 } 265 266 case *ast.LabeledStmt: 267 d.findDead(x.Stmt) 268 269 case *ast.RangeStmt: 270 d.findDead(x.Body) 271 d.reachable = true 272 273 case *ast.ReturnStmt: 274 d.reachable = false 275 276 case *ast.SelectStmt: 277 // NOTE: Unlike switch and type switch below, we don't care 278 // whether a select has a default, because a select without a 279 // default blocks until one of the cases can run. That's different 280 // from a switch without a default, which behaves like it has 281 // a default with an empty body. 282 anyReachable := false 283 for _, comm := range x.Body.List { 284 d.reachable = true 285 for _, stmt := range comm.(*ast.CommClause).Body { 286 d.findDead(stmt) 287 } 288 anyReachable = anyReachable || d.reachable 289 } 290 d.reachable = anyReachable || d.hasBreak[x] 291 292 case *ast.SwitchStmt: 293 anyReachable := false 294 hasDefault := false 295 for _, cas := range x.Body.List { 296 cc := cas.(*ast.CaseClause) 297 if cc.List == nil { 298 hasDefault = true 299 } 300 d.reachable = true 301 for _, stmt := range cc.Body { 302 d.findDead(stmt) 303 } 304 anyReachable = anyReachable || d.reachable 305 } 306 d.reachable = anyReachable || d.hasBreak[x] || !hasDefault 307 308 case *ast.TypeSwitchStmt: 309 anyReachable := false 310 hasDefault := false 311 for _, cas := range x.Body.List { 312 cc := cas.(*ast.CaseClause) 313 if cc.List == nil { 314 hasDefault = true 315 } 316 d.reachable = true 317 for _, stmt := range cc.Body { 318 d.findDead(stmt) 319 } 320 anyReachable = anyReachable || d.reachable 321 } 322 d.reachable = anyReachable || d.hasBreak[x] || !hasDefault 323 } 324 }