src

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

sa1025.go (2508B)


      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) (any, 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 *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 }