sa4030.go raw

   1  package sa4030
   2  
   3  import (
   4  	"fmt"
   5  	"go/ast"
   6  
   7  	"honnef.co/go/tools/analysis/code"
   8  	"honnef.co/go/tools/analysis/lint"
   9  	"honnef.co/go/tools/analysis/report"
  10  	"honnef.co/go/tools/pattern"
  11  
  12  	"golang.org/x/tools/go/analysis"
  13  	"golang.org/x/tools/go/analysis/passes/inspect"
  14  )
  15  
  16  var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
  17  	Analyzer: &analysis.Analyzer{
  18  		Name:     "SA4030",
  19  		Run:      run,
  20  		Requires: []*analysis.Analyzer{inspect.Analyzer},
  21  	},
  22  	Doc: &lint.RawDocumentation{
  23  		Title: "Ineffective attempt at generating random number",
  24  		Text: `
  25  Functions in the \'math/rand\' package that accept upper limits, such
  26  as \'Intn\', generate random numbers in the half-open interval [0,n). In
  27  other words, the generated numbers will be \'>= 0\' and \'< n\' – they
  28  don't include \'n\'. \'rand.Intn(1)\' therefore doesn't generate \'0\'
  29  or \'1\', it always generates \'0\'.`,
  30  		Since:    "2022.1",
  31  		Severity: lint.SeverityWarning,
  32  		MergeIf:  lint.MergeIfAny,
  33  	},
  34  })
  35  
  36  var Analyzer = SCAnalyzer.Analyzer
  37  
  38  var ineffectiveRandIntQ = pattern.MustParse(`
  39  	(CallExpr
  40  		(Symbol
  41  			name@(Or
  42  				"math/rand.Int31n"
  43  				"math/rand.Int63n"
  44  				"math/rand.Intn"
  45  				"(*math/rand.Rand).Int31n"
  46  				"(*math/rand.Rand).Int63n"
  47  				"(*math/rand.Rand).Intn"))
  48  		[(IntegerLiteral "1")])`)
  49  
  50  func run(pass *analysis.Pass) (interface{}, error) {
  51  	fn := func(node ast.Node) {
  52  		m, ok := code.Match(pass, ineffectiveRandIntQ, node)
  53  		if !ok {
  54  			return
  55  		}
  56  
  57  		report.Report(pass, node,
  58  			fmt.Sprintf("%s(n) generates a random value 0 <= x < n; that is, the generated values don't include n; %s therefore always returns 0",
  59  				m.State["name"], report.Render(pass, node)))
  60  	}
  61  
  62  	code.Preorder(pass, fn, (*ast.CallExpr)(nil))
  63  	return nil, nil
  64  }
  65