mirror of
https://github.com/golang/go
synced 2024-11-18 16:14:46 -07:00
01f8cd246d
They will be deleted from their current homes once this has landed. Changes made to import paths to make the code compile, and to find errchk in the right place in cmd/vet's Makefile. TODO in a later CL: tidy up vet. R=golang-dev, gri CC=golang-dev https://golang.org/cl/9495043
281 lines
6.4 KiB
Go
281 lines
6.4 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"
|
|
)
|
|
|
|
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 (f *File) checkUnreachable(body *ast.BlockStmt) {
|
|
if !vet("unreachable") || 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.Warnf(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
|
|
}
|
|
}
|