sa1025.go raw

   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