sa1015.go (2279B)
1 package sa1015 2 3 import ( 4 "go/token" 5 "go/version" 6 7 "honnef.co/go/tools/analysis/code" 8 "honnef.co/go/tools/analysis/lint" 9 "honnef.co/go/tools/analysis/report" 10 "honnef.co/go/tools/go/ir" 11 "honnef.co/go/tools/go/ir/irutil" 12 "honnef.co/go/tools/internal/passes/buildir" 13 14 "golang.org/x/tools/go/analysis" 15 ) 16 17 var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ 18 Analyzer: &analysis.Analyzer{ 19 Name: "SA1015", 20 Run: run, 21 Requires: []*analysis.Analyzer{buildir.Analyzer}, 22 }, 23 Doc: &lint.RawDocumentation{ 24 Title: `Using \'time.Tick\' in a way that will leak. Consider using \'time.NewTicker\', and only use \'time.Tick\' in tests, commands and endless functions`, 25 26 Text: `Before Go 1.23, \'time.Ticker\'s had to be closed to be able to be garbage 27 collected. Since \'time.Tick\' doesn't make it possible to close the underlying 28 ticker, using it repeatedly would leak memory. 29 30 Go 1.23 fixes this by allowing tickers to be collected even if they weren't closed.`, 31 Since: "2017.1", 32 Severity: lint.SeverityWarning, 33 MergeIf: lint.MergeIfAny, 34 }, 35 }) 36 37 var Analyzer = SCAnalyzer.Analyzer 38 39 func run(pass *analysis.Pass) (any, error) { 40 for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs { 41 if fn.Pos() == token.NoPos || version.Compare(code.StdlibVersion(pass, fn), "go1.23") >= 0 { 42 // Beginning with Go 1.23, the GC is able to collect unreferenced, unclosed 43 // tickers, which makes time.Tick safe(r) to use. 44 // 45 // When we don't have a valid position, we err on the side of false negatives. 46 // This shouldn't actually lead to any false negatives, as no functions 47 // without valid positions (such as the synthesized init function) should be 48 // able to use time.Tick. 49 continue 50 } 51 52 if code.IsMainLike(pass) || code.IsInTest(pass, fn) { 53 continue 54 } 55 for _, block := range fn.Blocks { 56 for _, ins := range block.Instrs { 57 call, ok := ins.(*ir.Call) 58 if !ok || !irutil.IsCallTo(call.Common(), "time.Tick") { 59 continue 60 } 61 if !irutil.Terminates(call.Parent()) { 62 continue 63 } 64 report.Report(pass, call, "using time.Tick leaks the underlying ticker, consider using it only in endless functions, tests and the main package, and use time.NewTicker here") 65 } 66 } 67 } 68 return nil, nil 69 }