1
0
mirror of https://github.com/golang/go synced 2024-10-01 07:38:32 -06:00
go/cmd/vet/deadcode.go
David Symonds f895b43688 vet: Rearrange checkers to use a registration system.
This removes much of the AST logic out of main.go,
and makes it easier to build custom vet binaries

The trade-off in this change is for flexibility.
There's very little change in the per-check files,
a lot less code in main.go (specifically the AST walking
logic has shrunk), and it makes it much easier to build
custom vet binaries simply by dropping new source files
in the directory.

LGTM=josharian, r
R=r, josharian, kamil.kisiel
CC=golang-codereviews
https://golang.org/cl/83400043
2014-06-13 15:04:45 +10:00

295 lines
6.6 KiB
Go

// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Check for syntactically unreachable code.
package main
import (
"go/ast"
"go/token"
)
func init() {
register("unreachable",
"check for unreachable code",
checkUnreachable,
funcDecl, funcLit)
}
type deadState struct {
f *File
hasBreak map[ast.Stmt]bool
hasGoto map[string]bool
labels map[string]ast.Stmt
breakTarget ast.Stmt
reachable bool
}
// checkUnreachable checks a function body for dead code.
func checkUnreachable(f *File, node ast.Node) {
var body *ast.BlockStmt
switch n := node.(type) {
case *ast.FuncDecl:
body = n.Body
case *ast.FuncLit:
body = n.Body
}
if body == nil {
return
}
d := &deadState{
f: f,
hasBreak: make(map[ast.Stmt]bool),
hasGoto: make(map[string]bool),
labels: make(map[string]ast.Stmt),
}
d.findLabels(body)
d.reachable = true
d.findDead(body)
}
// findLabels gathers information about the labels defined and used by stmt
// and about which statements break, whether a label is involved or not.
func (d *deadState) findLabels(stmt ast.Stmt) {
switch x := stmt.(type) {
default:
d.f.Warnf(x.Pos(), "internal error in findLabels: unexpected statement %T", x)
case *ast.AssignStmt,
*ast.BadStmt,
*ast.DeclStmt,
*ast.DeferStmt,
*ast.EmptyStmt,
*ast.ExprStmt,
*ast.GoStmt,
*ast.IncDecStmt,
*ast.ReturnStmt,
*ast.SendStmt:
// no statements inside
case *ast.BlockStmt:
for _, stmt := range x.List {
d.findLabels(stmt)
}
case *ast.BranchStmt:
switch x.Tok {
case token.GOTO:
d.hasGoto[x.Label.Name] = true
case token.BREAK:
stmt := d.breakTarget
if x.Label != nil {
stmt = d.labels[x.Label.Name]
}
if stmt != nil {
d.hasBreak[stmt] = true
}
}
case *ast.IfStmt:
d.findLabels(x.Body)
if x.Else != nil {
d.findLabels(x.Else)
}
case *ast.LabeledStmt:
d.labels[x.Label.Name] = x.Stmt
d.findLabels(x.Stmt)
// These cases are all the same, but the x.Body only works
// when the specific type of x is known, so the cases cannot
// be merged.
case *ast.ForStmt:
outer := d.breakTarget
d.breakTarget = x
d.findLabels(x.Body)
d.breakTarget = outer
case *ast.RangeStmt:
outer := d.breakTarget
d.breakTarget = x
d.findLabels(x.Body)
d.breakTarget = outer
case *ast.SelectStmt:
outer := d.breakTarget
d.breakTarget = x
d.findLabels(x.Body)
d.breakTarget = outer
case *ast.SwitchStmt:
outer := d.breakTarget
d.breakTarget = x
d.findLabels(x.Body)
d.breakTarget = outer
case *ast.TypeSwitchStmt:
outer := d.breakTarget
d.breakTarget = x
d.findLabels(x.Body)
d.breakTarget = outer
case *ast.CommClause:
for _, stmt := range x.Body {
d.findLabels(stmt)
}
case *ast.CaseClause:
for _, stmt := range x.Body {
d.findLabels(stmt)
}
}
}
// findDead walks the statement looking for dead code.
// If d.reachable is false on entry, stmt itself is dead.
// When findDead returns, d.reachable tells whether the
// statement following stmt is reachable.
func (d *deadState) findDead(stmt ast.Stmt) {
// Is this a labeled goto target?
// If so, assume it is reachable due to the goto.
// This is slightly conservative, in that we don't
// check that the goto is reachable, so
// L: goto L
// will not provoke a warning.
// But it's good enough.
if x, isLabel := stmt.(*ast.LabeledStmt); isLabel && d.hasGoto[x.Label.Name] {
d.reachable = true
}
if !d.reachable {
switch stmt.(type) {
case *ast.EmptyStmt:
// do not warn about unreachable empty statements
default:
d.f.Bad(stmt.Pos(), "unreachable code")
d.reachable = true // silence error about next statement
}
}
switch x := stmt.(type) {
default:
d.f.Warnf(x.Pos(), "internal error in findDead: unexpected statement %T", x)
case *ast.AssignStmt,
*ast.BadStmt,
*ast.DeclStmt,
*ast.DeferStmt,
*ast.EmptyStmt,
*ast.GoStmt,
*ast.IncDecStmt,
*ast.SendStmt:
// no control flow
case *ast.BlockStmt:
for _, stmt := range x.List {
d.findDead(stmt)
}
case *ast.BranchStmt:
switch x.Tok {
case token.BREAK, token.GOTO, token.FALLTHROUGH:
d.reachable = false
case token.CONTINUE:
// NOTE: We accept "continue" statements as terminating.
// They are not necessary in the spec definition of terminating,
// because a continue statement cannot be the final statement
// before a return. But for the more general problem of syntactically
// identifying dead code, continue redirects control flow just
// like the other terminating statements.
d.reachable = false
}
case *ast.ExprStmt:
// Call to panic?
call, ok := x.X.(*ast.CallExpr)
if ok {
name, ok := call.Fun.(*ast.Ident)
if ok && name.Name == "panic" && name.Obj == nil {
d.reachable = false
}
}
case *ast.ForStmt:
d.findDead(x.Body)
d.reachable = x.Cond != nil || d.hasBreak[x]
case *ast.IfStmt:
d.findDead(x.Body)
if x.Else != nil {
r := d.reachable
d.reachable = true
d.findDead(x.Else)
d.reachable = d.reachable || r
} else {
// might not have executed if statement
d.reachable = true
}
case *ast.LabeledStmt:
d.findDead(x.Stmt)
case *ast.RangeStmt:
d.findDead(x.Body)
d.reachable = true
case *ast.ReturnStmt:
d.reachable = false
case *ast.SelectStmt:
// NOTE: Unlike switch and type switch below, we don't care
// whether a select has a default, because a select without a
// default blocks until one of the cases can run. That's different
// from a switch without a default, which behaves like it has
// a default with an empty body.
anyReachable := false
for _, comm := range x.Body.List {
d.reachable = true
for _, stmt := range comm.(*ast.CommClause).Body {
d.findDead(stmt)
}
anyReachable = anyReachable || d.reachable
}
d.reachable = anyReachable || d.hasBreak[x]
case *ast.SwitchStmt:
anyReachable := false
hasDefault := false
for _, cas := range x.Body.List {
cc := cas.(*ast.CaseClause)
if cc.List == nil {
hasDefault = true
}
d.reachable = true
for _, stmt := range cc.Body {
d.findDead(stmt)
}
anyReachable = anyReachable || d.reachable
}
d.reachable = anyReachable || d.hasBreak[x] || !hasDefault
case *ast.TypeSwitchStmt:
anyReachable := false
hasDefault := false
for _, cas := range x.Body.List {
cc := cas.(*ast.CaseClause)
if cc.List == nil {
hasDefault = true
}
d.reachable = true
for _, stmt := range cc.Body {
d.findDead(stmt)
}
anyReachable = anyReachable || d.reachable
}
d.reachable = anyReachable || d.hasBreak[x] || !hasDefault
}
}