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

     1  package sa4003
     2  
     3  import (
     4  	"fmt"
     5  	"go/ast"
     6  	"go/constant"
     7  	"go/token"
     8  	"go/types"
     9  	"math"
    10  
    11  	"github.com/amarpal/go-tools/analysis/code"
    12  	"github.com/amarpal/go-tools/analysis/lint"
    13  	"github.com/amarpal/go-tools/analysis/report"
    14  	"github.com/amarpal/go-tools/go/types/typeutil"
    15  
    16  	"golang.org/x/tools/go/analysis"
    17  	"golang.org/x/tools/go/analysis/passes/inspect"
    18  )
    19  
    20  var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
    21  	Analyzer: &analysis.Analyzer{
    22  		Name:     "SA4003",
    23  		Run:      run,
    24  		Requires: []*analysis.Analyzer{inspect.Analyzer},
    25  	},
    26  	Doc: &lint.Documentation{
    27  		Title:    `Comparing unsigned values against negative values is pointless`,
    28  		Since:    "2017.1",
    29  		Severity: lint.SeverityWarning,
    30  		MergeIf:  lint.MergeIfAll,
    31  	},
    32  })
    33  
    34  var Analyzer = SCAnalyzer.Analyzer
    35  
    36  func run(pass *analysis.Pass) (interface{}, error) {
    37  	isobj := func(expr ast.Expr, name string) bool {
    38  		if name == "" {
    39  			return false
    40  		}
    41  		sel, ok := expr.(*ast.SelectorExpr)
    42  		if !ok {
    43  			return false
    44  		}
    45  		return typeutil.IsObject(pass.TypesInfo.ObjectOf(sel.Sel), name)
    46  	}
    47  
    48  	fn := func(node ast.Node) {
    49  		expr := node.(*ast.BinaryExpr)
    50  		tx := pass.TypesInfo.TypeOf(expr.X)
    51  		basic, ok := tx.Underlying().(*types.Basic)
    52  		if !ok {
    53  			return
    54  		}
    55  
    56  		// We only check for the math constants and integer literals, not for all constant expressions. This is to avoid
    57  		// false positives when constant values differ under different build tags.
    58  		var (
    59  			maxMathConst string
    60  			minMathConst string
    61  			maxLiteral   constant.Value
    62  			minLiteral   constant.Value
    63  		)
    64  
    65  		switch basic.Kind() {
    66  		case types.Uint8:
    67  			maxMathConst = "math.MaxUint8"
    68  			minLiteral = constant.MakeUint64(0)
    69  			maxLiteral = constant.MakeUint64(math.MaxUint8)
    70  		case types.Uint16:
    71  			maxMathConst = "math.MaxUint16"
    72  			minLiteral = constant.MakeUint64(0)
    73  			maxLiteral = constant.MakeUint64(math.MaxUint16)
    74  		case types.Uint32:
    75  			maxMathConst = "math.MaxUint32"
    76  			minLiteral = constant.MakeUint64(0)
    77  			maxLiteral = constant.MakeUint64(math.MaxUint32)
    78  		case types.Uint64:
    79  			maxMathConst = "math.MaxUint64"
    80  			minLiteral = constant.MakeUint64(0)
    81  			maxLiteral = constant.MakeUint64(math.MaxUint64)
    82  		case types.Uint:
    83  			// TODO(dh): we could chose 32 bit vs 64 bit depending on the file's build tags
    84  			maxMathConst = "math.MaxUint64"
    85  			minLiteral = constant.MakeUint64(0)
    86  			maxLiteral = constant.MakeUint64(math.MaxUint64)
    87  
    88  		case types.Int8:
    89  			minMathConst = "math.MinInt8"
    90  			maxMathConst = "math.MaxInt8"
    91  			minLiteral = constant.MakeInt64(math.MinInt8)
    92  			maxLiteral = constant.MakeInt64(math.MaxInt8)
    93  		case types.Int16:
    94  			minMathConst = "math.MinInt16"
    95  			maxMathConst = "math.MaxInt16"
    96  			minLiteral = constant.MakeInt64(math.MinInt16)
    97  			maxLiteral = constant.MakeInt64(math.MaxInt16)
    98  		case types.Int32:
    99  			minMathConst = "math.MinInt32"
   100  			maxMathConst = "math.MaxInt32"
   101  			minLiteral = constant.MakeInt64(math.MinInt32)
   102  			maxLiteral = constant.MakeInt64(math.MaxInt32)
   103  		case types.Int64:
   104  			minMathConst = "math.MinInt64"
   105  			maxMathConst = "math.MaxInt64"
   106  			minLiteral = constant.MakeInt64(math.MinInt64)
   107  			maxLiteral = constant.MakeInt64(math.MaxInt64)
   108  		case types.Int:
   109  			// TODO(dh): we could chose 32 bit vs 64 bit depending on the file's build tags
   110  			minMathConst = "math.MinInt64"
   111  			maxMathConst = "math.MaxInt64"
   112  			minLiteral = constant.MakeInt64(math.MinInt64)
   113  			maxLiteral = constant.MakeInt64(math.MaxInt64)
   114  		}
   115  
   116  		isLiteral := func(expr ast.Expr, c constant.Value) bool {
   117  			if c == nil {
   118  				return false
   119  			}
   120  			return code.IsIntegerLiteral(pass, expr, c)
   121  		}
   122  		isZeroLiteral := func(expr ast.Expr) bool {
   123  			return code.IsIntegerLiteral(pass, expr, constant.MakeInt64(0))
   124  		}
   125  
   126  		if (expr.Op == token.GTR || expr.Op == token.GEQ) && (isobj(expr.Y, maxMathConst) || isLiteral(expr.Y, maxLiteral)) ||
   127  			(expr.Op == token.LSS || expr.Op == token.LEQ) && (isobj(expr.X, maxMathConst) || isLiteral(expr.X, maxLiteral)) {
   128  			report.Report(pass, expr, fmt.Sprintf("no value of type %s is greater than %s", basic, maxMathConst))
   129  		}
   130  
   131  		if expr.Op == token.LEQ && (isobj(expr.Y, maxMathConst) || isLiteral(expr.Y, maxLiteral)) ||
   132  			expr.Op == token.GEQ && (isobj(expr.X, maxMathConst) || isLiteral(expr.X, maxLiteral)) {
   133  			report.Report(pass, expr, fmt.Sprintf("every value of type %s is <= %s", basic, maxMathConst))
   134  		}
   135  
   136  		if (basic.Info() & types.IsUnsigned) != 0 {
   137  			if (expr.Op == token.LSS && isZeroLiteral(expr.Y)) ||
   138  				(expr.Op == token.GTR && isZeroLiteral(expr.X)) {
   139  				report.Report(pass, expr, fmt.Sprintf("no value of type %s is less than 0", basic))
   140  			}
   141  			if expr.Op == token.GEQ && isZeroLiteral(expr.Y) ||
   142  				expr.Op == token.LEQ && isZeroLiteral(expr.X) {
   143  				report.Report(pass, expr, fmt.Sprintf("every value of type %s is >= 0", basic))
   144  			}
   145  		} else {
   146  			if (expr.Op == token.LSS || expr.Op == token.LEQ) && (isobj(expr.Y, minMathConst) || isLiteral(expr.Y, minLiteral)) ||
   147  				(expr.Op == token.GTR || expr.Op == token.GEQ) && (isobj(expr.X, minMathConst) || isLiteral(expr.X, minLiteral)) {
   148  				report.Report(pass, expr, fmt.Sprintf("no value of type %s is less than %s", basic, minMathConst))
   149  			}
   150  			if expr.Op == token.GEQ && (isobj(expr.Y, minMathConst) || isLiteral(expr.Y, minLiteral)) ||
   151  				expr.Op == token.LEQ && (isobj(expr.X, minMathConst) || isLiteral(expr.X, minLiteral)) {
   152  				report.Report(pass, expr, fmt.Sprintf("every value of type %s is >= %s", basic, minMathConst))
   153  			}
   154  		}
   155  
   156  	}
   157  	code.Preorder(pass, fn, (*ast.BinaryExpr)(nil))
   158  	return nil, nil
   159  }