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