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 }