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

     1  package sa4030
     2  
     3  import (
     4  	"fmt"
     5  	"go/ast"
     6  
     7  	"github.com/amarpal/go-tools/analysis/code"
     8  	"github.com/amarpal/go-tools/analysis/lint"
     9  	"github.com/amarpal/go-tools/analysis/report"
    10  	"github.com/amarpal/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.Documentation{
    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  }