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) (interface{}, 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 }
70