github.com/amarpal/go-tools@v0.0.0-20240422043104-40142f59f616/staticcheck/sa1025/sa1025.go (about)

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