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