honnef.co/go/tools@v0.5.0-0.dev.0.20240520180541-dcae280a5e87/staticcheck/sa1025/sa1025.go (about)

     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.Documentation{
    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  }