sa4006.go raw

   1  package sa4006
   2  
   3  import (
   4  	"fmt"
   5  	"go/ast"
   6  
   7  	"honnef.co/go/tools/analysis/code"
   8  	"honnef.co/go/tools/analysis/facts/generated"
   9  	"honnef.co/go/tools/analysis/lint"
  10  	"honnef.co/go/tools/analysis/report"
  11  	"honnef.co/go/tools/go/ir"
  12  	"honnef.co/go/tools/go/ir/irutil"
  13  	"honnef.co/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.RawDocumentation{
  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  }
 156