package sa4006 import ( "fmt" "go/ast" "honnef.co/go/tools/analysis/code" "honnef.co/go/tools/analysis/facts/generated" "honnef.co/go/tools/analysis/lint" "honnef.co/go/tools/analysis/report" "honnef.co/go/tools/go/ir" "honnef.co/go/tools/go/ir/irutil" "honnef.co/go/tools/internal/passes/buildir" "golang.org/x/tools/go/analysis" ) var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ Analyzer: &analysis.Analyzer{ Name: "SA4006", Run: run, Requires: []*analysis.Analyzer{buildir.Analyzer, generated.Analyzer}, }, Doc: &lint.RawDocumentation{ Title: `A value assigned to a variable is never read before being overwritten. Forgotten error check or dead code?`, Since: "2017.1", Severity: lint.SeverityWarning, MergeIf: lint.MergeIfAll, }, }) var Analyzer = SCAnalyzer.Analyzer func run(pass *analysis.Pass) (interface{}, error) { for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs { if irutil.IsExample(fn) { continue } node := fn.Source() if node == nil { continue } if gen, ok := code.Generator(pass, node.Pos()); ok && gen == generated.Goyacc { // Don't flag unused values in code generated by goyacc. // There may be hundreds of those due to the way the state // machine is constructed. continue } switchTags := map[ir.Value]struct{}{} ast.Inspect(node, func(node ast.Node) bool { s, ok := node.(*ast.SwitchStmt) if !ok { return true } v, _ := fn.ValueForExpr(s.Tag) switchTags[v] = struct{}{} return true }) // OPT(dh): don't use a map, possibly use a bitset var hasUse func(v ir.Value, seen map[ir.Value]struct{}) bool hasUse = func(v ir.Value, seen map[ir.Value]struct{}) bool { if _, ok := seen[v]; ok { return false } if _, ok := switchTags[v]; ok { return true } refs := v.Referrers() if refs == nil { // TODO investigate why refs can be nil return true } for _, ref := range *refs { switch ref := ref.(type) { case *ir.DebugRef: case *ir.Sigma: if seen == nil { seen = map[ir.Value]struct{}{} } seen[v] = struct{}{} if hasUse(ref, seen) { return true } case *ir.Phi: if seen == nil { seen = map[ir.Value]struct{}{} } seen[v] = struct{}{} if hasUse(ref, seen) { return true } default: return true } } return false } ast.Inspect(node, func(node ast.Node) bool { assign, ok := node.(*ast.AssignStmt) if !ok { return true } if len(assign.Lhs) > 1 && len(assign.Rhs) == 1 { // Either a function call with multiple return values, // or a comma-ok assignment val, _ := fn.ValueForExpr(assign.Rhs[0]) if val == nil { return true } refs := val.Referrers() if refs == nil { return true } for _, ref := range *refs { ex, ok := ref.(*ir.Extract) if !ok { continue } if !hasUse(ex, nil) { lhs := assign.Lhs[ex.Index] if ident, ok := lhs.(*ast.Ident); !ok || ok && ident.Name == "_" { continue } report.Report(pass, assign, fmt.Sprintf("this value of %s is never used", lhs)) } } return true } for i, lhs := range assign.Lhs { rhs := assign.Rhs[i] if ident, ok := lhs.(*ast.Ident); !ok || ok && ident.Name == "_" { continue } val, _ := fn.ValueForExpr(rhs) if val == nil { continue } if _, ok := val.(*ir.Const); ok { // a zero-valued constant, for example in 'foo := []string(nil)' continue } if !hasUse(val, nil) { report.Report(pass, assign, fmt.Sprintf("this value of %s is never used", lhs)) } } return true }) } return nil, nil }