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