github.com/amarpal/go-tools@v0.0.0-20240422043104-40142f59f616/staticcheck/sa1025/sa1025.go (about) 1 package sa1025 2 3 import ( 4 "github.com/amarpal/go-tools/analysis/lint" 5 "github.com/amarpal/go-tools/analysis/report" 6 "github.com/amarpal/go-tools/go/ir" 7 "github.com/amarpal/go-tools/go/ir/irutil" 8 "github.com/amarpal/go-tools/go/types/typeutil" 9 "github.com/amarpal/go-tools/internal/passes/buildir" 10 11 "golang.org/x/tools/go/analysis" 12 ) 13 14 var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ 15 Analyzer: &analysis.Analyzer{ 16 Name: "SA1025", 17 Run: run, 18 Requires: []*analysis.Analyzer{buildir.Analyzer}, 19 }, 20 Doc: &lint.Documentation{ 21 Title: `It is not possible to use \'(*time.Timer).Reset\''s return value correctly`, 22 Since: "2019.1", 23 Severity: lint.SeverityWarning, 24 MergeIf: lint.MergeIfAny, 25 }, 26 }) 27 28 var Analyzer = SCAnalyzer.Analyzer 29 30 func run(pass *analysis.Pass) (interface{}, error) { 31 for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs { 32 for _, block := range fn.Blocks { 33 for _, ins := range block.Instrs { 34 call, ok := ins.(*ir.Call) 35 if !ok { 36 continue 37 } 38 if !irutil.IsCallTo(call.Common(), "(*time.Timer).Reset") { 39 continue 40 } 41 refs := call.Referrers() 42 if refs == nil { 43 continue 44 } 45 for _, ref := range irutil.FilterDebug(*refs) { 46 ifstmt, ok := ref.(*ir.If) 47 if !ok { 48 continue 49 } 50 51 found := false 52 for _, succ := range ifstmt.Block().Succs { 53 if len(succ.Preds) != 1 { 54 // Merge point, not a branch in the 55 // syntactical sense. 56 57 // FIXME(dh): this is broken for if 58 // statements a la "if x || y" 59 continue 60 } 61 irutil.Walk(succ, func(b *ir.BasicBlock) bool { 62 if !succ.Dominates(b) { 63 // We've reached the end of the branch 64 return false 65 } 66 for _, ins := range b.Instrs { 67 // TODO(dh): we should check that 68 // we're receiving from the channel of 69 // a time.Timer to further reduce 70 // false positives. Not a key 71 // priority, considering the rarity of 72 // Reset and the tiny likeliness of a 73 // false positive 74 if ins, ok := ins.(*ir.Recv); ok && typeutil.IsType(ins.Chan.Type(), "<-chan time.Time") { 75 found = true 76 return false 77 } 78 } 79 return true 80 }) 81 } 82 83 if found { 84 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") 85 } 86 } 87 } 88 } 89 } 90 return nil, nil 91 }