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