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

     1  package sa4025
     2  
     3  import (
     4  	"fmt"
     5  	"go/ast"
     6  	"go/constant"
     7  
     8  	"github.com/amarpal/go-tools/analysis/code"
     9  	"github.com/amarpal/go-tools/analysis/lint"
    10  	"github.com/amarpal/go-tools/analysis/report"
    11  	"github.com/amarpal/go-tools/pattern"
    12  
    13  	"golang.org/x/tools/go/analysis"
    14  	"golang.org/x/tools/go/analysis/passes/inspect"
    15  )
    16  
    17  var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
    18  	Analyzer: &analysis.Analyzer{
    19  		Name:     "SA4025",
    20  		Run:      run,
    21  		Requires: []*analysis.Analyzer{inspect.Analyzer},
    22  	},
    23  	Doc: &lint.Documentation{
    24  		Title: "Integer division of literals that results in zero",
    25  		Text: `When dividing two integer constants, the result will
    26  also be an integer. Thus, a division such as \'2 / 3\' results in \'0\'.
    27  This is true for all of the following examples:
    28  
    29  	_ = 2 / 3
    30  	const _ = 2 / 3
    31  	const _ float64 = 2 / 3
    32  	_ = float64(2 / 3)
    33  
    34  Staticcheck will flag such divisions if both sides of the division are
    35  integer literals, as it is highly unlikely that the division was
    36  intended to truncate to zero. Staticcheck will not flag integer
    37  division involving named constants, to avoid noisy positives.
    38  `,
    39  		Since:    "2021.1",
    40  		Severity: lint.SeverityWarning,
    41  		MergeIf:  lint.MergeIfAny,
    42  	},
    43  })
    44  
    45  var Analyzer = SCAnalyzer.Analyzer
    46  
    47  var integerDivisionQ = pattern.MustParse(`(BinaryExpr (IntegerLiteral _) "/" (IntegerLiteral _))`)
    48  
    49  func run(pass *analysis.Pass) (interface{}, error) {
    50  	fn := func(node ast.Node) {
    51  		_, ok := code.Match(pass, integerDivisionQ, node)
    52  		if !ok {
    53  			return
    54  		}
    55  
    56  		val := constant.ToInt(pass.TypesInfo.Types[node.(ast.Expr)].Value)
    57  		if v, ok := constant.Uint64Val(val); ok && v == 0 {
    58  			report.Report(pass, node, fmt.Sprintf("the integer division '%s' results in zero", report.Render(pass, node)))
    59  		}
    60  
    61  		// TODO: we could offer a suggested fix here, but I am not
    62  		// sure what it should be. There are many options to choose
    63  		// from.
    64  
    65  		// Note: we experimented with flagging divisions that truncate
    66  		// (e.g. 4 / 3), but it ran into false positives in Go's
    67  		// 'time' package, which does this, deliberately:
    68  		//
    69  		//   unixToInternal int64 = (1969*365 + 1969/4 - 1969/100 + 1969/400) * secondsPerDay
    70  		//
    71  		// The check also found a real bug in other code, but I don't
    72  		// think we can outright ban this kind of division.
    73  	}
    74  	code.Preorder(pass, fn, (*ast.BinaryExpr)(nil))
    75  
    76  	return nil, nil
    77  }