package sa1025 import ( "go/types" "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: "SA1025", Run: run, Requires: []*analysis.Analyzer{buildir.Analyzer}, }, Doc: &lint.RawDocumentation{ Title: `It is not possible to use \'(*time.Timer).Reset\''s return value correctly`, Since: "2019.1", Severity: lint.SeverityWarning, MergeIf: lint.MergeIfAny, }, }) var Analyzer = SCAnalyzer.Analyzer func run(pass *analysis.Pass) (interface{}, error) { for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs { for _, block := range fn.Blocks { for _, ins := range block.Instrs { call, ok := ins.(*ir.Call) if !ok { continue } if !irutil.IsCallTo(call.Common(), "(*time.Timer).Reset") { continue } refs := call.Referrers() if refs == nil { continue } for _, ref := range irutil.FilterDebug(*refs) { ifstmt, ok := ref.(*ir.If) if !ok { continue } found := false for _, succ := range ifstmt.Block().Succs { if len(succ.Preds) != 1 { // Merge point, not a branch in the // syntactical sense. // FIXME(dh): this is broken for if // statements a la "if x || y" continue } irutil.Walk(succ, func(b *ir.BasicBlock) bool { if !succ.Dominates(b) { // We've reached the end of the branch return false } for _, ins := range b.Instrs { // TODO(dh): we should check that we're receiving from the // channel of a time.Timer to further reduce false // positives. Not a key priority, considering the rarity // of Reset and the tiny likeliness of a false positive // // We intentionally don't handle aliases here, because // we're only interested in time.Timer.C. if ins, ok := ins.(*ir.Recv); ok && types.TypeString(ins.Chan.Type(), nil) == "<-chan time.Time" { found = true return false } } return true }) } if found { report.Report(pass, call, "it is not possible to use Reset's return value correctly, as there is a race condition between draining the channel and the new timer expiring") } } } } } return nil, nil }