src

Go monorepo.
git clone git://code.dwrz.net/src
Log | Files | Refs

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 }