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