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