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) (any, 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  			// (This call to pass.Report is a frequent source
   192  			// of diagnostics beyond EOF in a truncated file;
   193  			// see #71659.)
   194  			d.pass.Report(analysis.Diagnostic{
   195  				Pos:     stmt.Pos(),
   196  				End:     stmt.End(),
   197  				Message: "unreachable code",
   198  				SuggestedFixes: []analysis.SuggestedFix{{
   199  					Message: "Remove",
   200  					TextEdits: []analysis.TextEdit{{
   201  						Pos: stmt.Pos(),
   202  						End: stmt.End(),
   203  					}},
   204  				}},
   205  			})
   206  			d.reachable = true // silence error about next statement
   207  		}
   208  	}
   209  
   210  	switch x := stmt.(type) {
   211  	default:
   212  		log.Fatalf("%s: internal error in findDead: unexpected statement %T", d.pass.Fset.Position(x.Pos()), x)
   213  
   214  	case *ast.AssignStmt,
   215  		*ast.BadStmt,
   216  		*ast.DeclStmt,
   217  		*ast.DeferStmt,
   218  		*ast.EmptyStmt,
   219  		*ast.GoStmt,
   220  		*ast.IncDecStmt,
   221  		*ast.SendStmt:
   222  		// no control flow
   223  
   224  	case *ast.BlockStmt:
   225  		for _, stmt := range x.List {
   226  			d.findDead(stmt)
   227  		}
   228  
   229  	case *ast.BranchStmt:
   230  		switch x.Tok {
   231  		case token.BREAK, token.GOTO, token.FALLTHROUGH:
   232  			d.reachable = false
   233  		case token.CONTINUE:
   234  			// NOTE: We accept "continue" statements as terminating.
   235  			// They are not necessary in the spec definition of terminating,
   236  			// because a continue statement cannot be the final statement
   237  			// before a return. But for the more general problem of syntactically
   238  			// identifying dead code, continue redirects control flow just
   239  			// like the other terminating statements.
   240  			d.reachable = false
   241  		}
   242  
   243  	case *ast.ExprStmt:
   244  		// Call to panic?
   245  		call, ok := x.X.(*ast.CallExpr)
   246  		if ok {
   247  			name, ok := call.Fun.(*ast.Ident)
   248  			if ok && name.Name == "panic" && name.Obj == nil {
   249  				d.reachable = false
   250  			}
   251  		}
   252  
   253  	case *ast.ForStmt:
   254  		d.findDead(x.Body)
   255  		d.reachable = x.Cond != nil || d.hasBreak[x]
   256  
   257  	case *ast.IfStmt:
   258  		d.findDead(x.Body)
   259  		if x.Else != nil {
   260  			r := d.reachable
   261  			d.reachable = true
   262  			d.findDead(x.Else)
   263  			d.reachable = d.reachable || r
   264  		} else {
   265  			// might not have executed if statement
   266  			d.reachable = true
   267  		}
   268  
   269  	case *ast.LabeledStmt:
   270  		d.findDead(x.Stmt)
   271  
   272  	case *ast.RangeStmt:
   273  		d.findDead(x.Body)
   274  		d.reachable = true
   275  
   276  	case *ast.ReturnStmt:
   277  		d.reachable = false
   278  
   279  	case *ast.SelectStmt:
   280  		// NOTE: Unlike switch and type switch below, we don't care
   281  		// whether a select has a default, because a select without a
   282  		// default blocks until one of the cases can run. That's different
   283  		// from a switch without a default, which behaves like it has
   284  		// a default with an empty body.
   285  		anyReachable := false
   286  		for _, comm := range x.Body.List {
   287  			d.reachable = true
   288  			for _, stmt := range comm.(*ast.CommClause).Body {
   289  				d.findDead(stmt)
   290  			}
   291  			anyReachable = anyReachable || d.reachable
   292  		}
   293  		d.reachable = anyReachable || d.hasBreak[x]
   294  
   295  	case *ast.SwitchStmt:
   296  		anyReachable := false
   297  		hasDefault := false
   298  		for _, cas := range x.Body.List {
   299  			cc := cas.(*ast.CaseClause)
   300  			if cc.List == nil {
   301  				hasDefault = true
   302  			}
   303  			d.reachable = true
   304  			for _, stmt := range cc.Body {
   305  				d.findDead(stmt)
   306  			}
   307  			anyReachable = anyReachable || d.reachable
   308  		}
   309  		d.reachable = anyReachable || d.hasBreak[x] || !hasDefault
   310  
   311  	case *ast.TypeSwitchStmt:
   312  		anyReachable := false
   313  		hasDefault := false
   314  		for _, cas := range x.Body.List {
   315  			cc := cas.(*ast.CaseClause)
   316  			if cc.List == nil {
   317  				hasDefault = true
   318  			}
   319  			d.reachable = true
   320  			for _, stmt := range cc.Body {
   321  				d.findDead(stmt)
   322  			}
   323  			anyReachable = anyReachable || d.reachable
   324  		}
   325  		d.reachable = anyReachable || d.hasBreak[x] || !hasDefault
   326  	}
   327  }
   328  

View as plain text