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

     1  package sa1004
     2  
     3  import (
     4  	"fmt"
     5  	"go/ast"
     6  	"go/constant"
     7  	"go/types"
     8  
     9  	"github.com/amarpal/go-tools/analysis/code"
    10  	"github.com/amarpal/go-tools/analysis/edit"
    11  	"github.com/amarpal/go-tools/analysis/lint"
    12  	"github.com/amarpal/go-tools/analysis/report"
    13  	"github.com/amarpal/go-tools/pattern"
    14  
    15  	"golang.org/x/tools/go/analysis"
    16  	"golang.org/x/tools/go/analysis/passes/inspect"
    17  )
    18  
    19  var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
    20  	Analyzer: &analysis.Analyzer{
    21  		Name:     "SA1004",
    22  		Run:      run,
    23  		Requires: []*analysis.Analyzer{inspect.Analyzer},
    24  	},
    25  	Doc: &lint.Documentation{
    26  		Title: `Suspiciously small untyped constant in \'time.Sleep\'`,
    27  		Text: `The \'time\'.Sleep function takes a \'time.Duration\' as its only argument.
    28  Durations are expressed in nanoseconds. Thus, calling \'time.Sleep(1)\'
    29  will sleep for 1 nanosecond. This is a common source of bugs, as sleep
    30  functions in other languages often accept seconds or milliseconds.
    31  
    32  The \'time\' package provides constants such as \'time.Second\' to express
    33  large durations. These can be combined with arithmetic to express
    34  arbitrary durations, for example \'5 * time.Second\' for 5 seconds.
    35  
    36  If you truly meant to sleep for a tiny amount of time, use
    37  \'n * time.Nanosecond\' to signal to Staticcheck that you did mean to sleep
    38  for some amount of nanoseconds.`,
    39  		Since:    "2017.1",
    40  		Severity: lint.SeverityWarning,
    41  		MergeIf:  lint.MergeIfAny,
    42  	},
    43  })
    44  
    45  var Analyzer = SCAnalyzer.Analyzer
    46  
    47  var (
    48  	checkTimeSleepConstantPatternQ   = pattern.MustParse(`(CallExpr (Symbol "time.Sleep") lit@(IntegerLiteral value))`)
    49  	checkTimeSleepConstantPatternRns = pattern.MustParse(`(BinaryExpr duration "*" (SelectorExpr (Ident "time") (Ident "Nanosecond")))`)
    50  	checkTimeSleepConstantPatternRs  = pattern.MustParse(`(BinaryExpr duration "*" (SelectorExpr (Ident "time") (Ident "Second")))`)
    51  )
    52  
    53  func run(pass *analysis.Pass) (interface{}, error) {
    54  	fn := func(node ast.Node) {
    55  		m, ok := code.Match(pass, checkTimeSleepConstantPatternQ, node)
    56  		if !ok {
    57  			return
    58  		}
    59  		n, ok := constant.Int64Val(m.State["value"].(types.TypeAndValue).Value)
    60  		if !ok {
    61  			return
    62  		}
    63  		if n == 0 || n > 120 {
    64  			// time.Sleep(0) is a seldom used pattern in concurrency
    65  			// tests. >120 might be intentional. 120 was chosen
    66  			// because the user could've meant 2 minutes.
    67  			return
    68  		}
    69  
    70  		lit := m.State["lit"].(ast.Node)
    71  		report.Report(pass, lit,
    72  			fmt.Sprintf("sleeping for %d nanoseconds is probably a bug; be explicit if it isn't", n), report.Fixes(
    73  				edit.Fix("explicitly use nanoseconds", edit.ReplaceWithPattern(pass.Fset, lit, checkTimeSleepConstantPatternRns, pattern.State{"duration": lit})),
    74  				edit.Fix("use seconds", edit.ReplaceWithPattern(pass.Fset, lit, checkTimeSleepConstantPatternRs, pattern.State{"duration": lit}))))
    75  	}
    76  	code.Preorder(pass, fn, (*ast.CallExpr)(nil))
    77  	return nil, nil
    78  }