github.com/amarpal/go-tools@v0.0.0-20240422043104-40142f59f616/staticcheck/sa4006/sa4006.go (about)

     1  package sa4006
     2  
     3  import (
     4  	"fmt"
     5  	"go/ast"
     6  
     7  	"github.com/amarpal/go-tools/analysis/code"
     8  	"github.com/amarpal/go-tools/analysis/facts/generated"
     9  	"github.com/amarpal/go-tools/analysis/lint"
    10  	"github.com/amarpal/go-tools/analysis/report"
    11  	"github.com/amarpal/go-tools/go/ir"
    12  	"github.com/amarpal/go-tools/go/ir/irutil"
    13  	"github.com/amarpal/go-tools/internal/passes/buildir"
    14  
    15  	"golang.org/x/tools/go/analysis"
    16  )
    17  
    18  var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
    19  	Analyzer: &analysis.Analyzer{
    20  		Name:     "SA4006",
    21  		Run:      run,
    22  		Requires: []*analysis.Analyzer{buildir.Analyzer, generated.Analyzer},
    23  	},
    24  	Doc: &lint.Documentation{
    25  		Title:    `A value assigned to a variable is never read before being overwritten. Forgotten error check or dead code?`,
    26  		Since:    "2017.1",
    27  		Severity: lint.SeverityWarning,
    28  		MergeIf:  lint.MergeIfAll,
    29  	},
    30  })
    31  
    32  var Analyzer = SCAnalyzer.Analyzer
    33  
    34  func run(pass *analysis.Pass) (interface{}, error) {
    35  	for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs {
    36  		if irutil.IsExample(fn) {
    37  			continue
    38  		}
    39  		node := fn.Source()
    40  		if node == nil {
    41  			continue
    42  		}
    43  		if gen, ok := code.Generator(pass, node.Pos()); ok && gen == generated.Goyacc {
    44  			// Don't flag unused values in code generated by goyacc.
    45  			// There may be hundreds of those due to the way the state
    46  			// machine is constructed.
    47  			continue
    48  		}
    49  
    50  		switchTags := map[ir.Value]struct{}{}
    51  		ast.Inspect(node, func(node ast.Node) bool {
    52  			s, ok := node.(*ast.SwitchStmt)
    53  			if !ok {
    54  				return true
    55  			}
    56  			v, _ := fn.ValueForExpr(s.Tag)
    57  			switchTags[v] = struct{}{}
    58  			return true
    59  		})
    60  
    61  		// OPT(dh): don't use a map, possibly use a bitset
    62  		var hasUse func(v ir.Value, seen map[ir.Value]struct{}) bool
    63  		hasUse = func(v ir.Value, seen map[ir.Value]struct{}) bool {
    64  			if _, ok := seen[v]; ok {
    65  				return false
    66  			}
    67  			if _, ok := switchTags[v]; ok {
    68  				return true
    69  			}
    70  			refs := v.Referrers()
    71  			if refs == nil {
    72  				// TODO investigate why refs can be nil
    73  				return true
    74  			}
    75  			for _, ref := range *refs {
    76  				switch ref := ref.(type) {
    77  				case *ir.DebugRef:
    78  				case *ir.Sigma:
    79  					if seen == nil {
    80  						seen = map[ir.Value]struct{}{}
    81  					}
    82  					seen[v] = struct{}{}
    83  					if hasUse(ref, seen) {
    84  						return true
    85  					}
    86  				case *ir.Phi:
    87  					if seen == nil {
    88  						seen = map[ir.Value]struct{}{}
    89  					}
    90  					seen[v] = struct{}{}
    91  					if hasUse(ref, seen) {
    92  						return true
    93  					}
    94  				default:
    95  					return true
    96  				}
    97  			}
    98  			return false
    99  		}
   100  
   101  		ast.Inspect(node, func(node ast.Node) bool {
   102  			assign, ok := node.(*ast.AssignStmt)
   103  			if !ok {
   104  				return true
   105  			}
   106  			if len(assign.Lhs) > 1 && len(assign.Rhs) == 1 {
   107  				// Either a function call with multiple return values,
   108  				// or a comma-ok assignment
   109  
   110  				val, _ := fn.ValueForExpr(assign.Rhs[0])
   111  				if val == nil {
   112  					return true
   113  				}
   114  				refs := val.Referrers()
   115  				if refs == nil {
   116  					return true
   117  				}
   118  				for _, ref := range *refs {
   119  					ex, ok := ref.(*ir.Extract)
   120  					if !ok {
   121  						continue
   122  					}
   123  					if !hasUse(ex, nil) {
   124  						lhs := assign.Lhs[ex.Index]
   125  						if ident, ok := lhs.(*ast.Ident); !ok || ok && ident.Name == "_" {
   126  							continue
   127  						}
   128  						report.Report(pass, assign, fmt.Sprintf("this value of %s is never used", lhs))
   129  					}
   130  				}
   131  				return true
   132  			}
   133  			for i, lhs := range assign.Lhs {
   134  				rhs := assign.Rhs[i]
   135  				if ident, ok := lhs.(*ast.Ident); !ok || ok && ident.Name == "_" {
   136  					continue
   137  				}
   138  				val, _ := fn.ValueForExpr(rhs)
   139  				if val == nil {
   140  					continue
   141  				}
   142  
   143  				if _, ok := val.(*ir.Const); ok {
   144  					// a zero-valued constant, for example in 'foo := []string(nil)'
   145  					continue
   146  				}
   147  				if !hasUse(val, nil) {
   148  					report.Report(pass, assign, fmt.Sprintf("this value of %s is never used", lhs))
   149  				}
   150  			}
   151  			return true
   152  		})
   153  	}
   154  	return nil, nil
   155  }