Home | History | Annotate | Download | only in vet
      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 }
    297