github.com/miolini/go@v0.0.0-20160405192216-fca68c8cb408/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  }