Source file src/cmd/vendor/golang.org/x/tools/go/analysis/passes/unreachable/unreachable.go

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

View as plain text