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

View as plain text