src

Go monorepo.
git clone git://code.dwrz.net/src
Log | Files | Refs

sa4003.go (6031B)


      1 package sa4003
      2 
      3 import (
      4 	"fmt"
      5 	"go/ast"
      6 	"go/constant"
      7 	"go/token"
      8 	"go/types"
      9 	"math"
     10 
     11 	"honnef.co/go/tools/analysis/code"
     12 	"honnef.co/go/tools/analysis/facts/generated"
     13 	"honnef.co/go/tools/analysis/lint"
     14 	"honnef.co/go/tools/analysis/report"
     15 	"honnef.co/go/tools/go/types/typeutil"
     16 
     17 	"golang.org/x/tools/go/analysis"
     18 	"golang.org/x/tools/go/analysis/passes/inspect"
     19 )
     20 
     21 var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
     22 	Analyzer: &analysis.Analyzer{
     23 		Name:     "SA4003",
     24 		Run:      run,
     25 		Requires: []*analysis.Analyzer{inspect.Analyzer, generated.Analyzer},
     26 	},
     27 	Doc: &lint.RawDocumentation{
     28 		Title:    `Comparing unsigned values against negative values is pointless`,
     29 		Since:    "2017.1",
     30 		Severity: lint.SeverityWarning,
     31 		MergeIf:  lint.MergeIfAll,
     32 	},
     33 })
     34 
     35 var Analyzer = SCAnalyzer.Analyzer
     36 
     37 func run(pass *analysis.Pass) (any, error) {
     38 	isobj := func(expr ast.Expr, name string) bool {
     39 		if name == "" {
     40 			return false
     41 		}
     42 		sel, ok := expr.(*ast.SelectorExpr)
     43 		if !ok {
     44 			return false
     45 		}
     46 		return typeutil.IsObject(pass.TypesInfo.ObjectOf(sel.Sel), name)
     47 	}
     48 
     49 	fn := func(node ast.Node) {
     50 		expr := node.(*ast.BinaryExpr)
     51 		tx := pass.TypesInfo.TypeOf(expr.X)
     52 		tset := typeutil.NewTypeSet(tx)
     53 
     54 		// We only check for the math constants and integer literals, not for
     55 		// all constant expressions. This is to avoid
     56 		// false positives when constant values differ under different build tags.
     57 		var (
     58 			maxMathConst string
     59 			minMathConst string
     60 			maxLiteral   constant.Value
     61 			minLiteral   constant.Value
     62 		)
     63 
     64 		allUnsigned := tset.All(func(t *types.Term) bool {
     65 			if basic, ok := t.Type().Underlying().(*types.Basic); ok {
     66 				return basic.Info()&types.IsUnsigned != 0
     67 			}
     68 			return false
     69 		})
     70 
     71 		if allUnsigned {
     72 			isZeroLiteral := func(expr ast.Expr) bool {
     73 				return code.IsIntegerLiteral(pass, expr, constant.MakeInt64(0))
     74 			}
     75 			if (expr.Op == token.LSS && isZeroLiteral(expr.Y)) ||
     76 				(expr.Op == token.GTR && isZeroLiteral(expr.X)) {
     77 				report.Report(
     78 					pass,
     79 					expr,
     80 					fmt.Sprintf("no value of type %s is less than 0", tx),
     81 					report.FilterGenerated(),
     82 				)
     83 			}
     84 			if expr.Op == token.GEQ && isZeroLiteral(expr.Y) ||
     85 				expr.Op == token.LEQ && isZeroLiteral(expr.X) {
     86 				report.Report(
     87 					pass,
     88 					expr,
     89 					fmt.Sprintf("every value of type %s is >= 0", tx),
     90 					report.FilterGenerated(),
     91 				)
     92 			}
     93 		}
     94 
     95 		core := tset.CoreType()
     96 		if core == nil {
     97 			// All remaining checks are only relevant when the type set
     98 			// contains a single underlying type.
     99 			//
    100 			// If we had a 'var x uint8 | uint16',
    101 			// then the type checker wouldn't permit a check such as
    102 			// 'if x <= math.MaxUint16', because the constant cannot be converted to all
    103 			// types in the type set.
    104 			return
    105 		}
    106 
    107 		basic, ok := core.(*types.Basic)
    108 		if !ok {
    109 			return
    110 		}
    111 
    112 		switch basic.Kind() {
    113 		case types.Uint8:
    114 			maxMathConst = "math.MaxUint8"
    115 			minLiteral = constant.MakeUint64(0)
    116 			maxLiteral = constant.MakeUint64(math.MaxUint8)
    117 		case types.Uint16:
    118 			maxMathConst = "math.MaxUint16"
    119 			minLiteral = constant.MakeUint64(0)
    120 			maxLiteral = constant.MakeUint64(math.MaxUint16)
    121 		case types.Uint32:
    122 			maxMathConst = "math.MaxUint32"
    123 			minLiteral = constant.MakeUint64(0)
    124 			maxLiteral = constant.MakeUint64(math.MaxUint32)
    125 		case types.Uint64:
    126 			maxMathConst = "math.MaxUint64"
    127 			minLiteral = constant.MakeUint64(0)
    128 			maxLiteral = constant.MakeUint64(math.MaxUint64)
    129 		case types.Uint:
    130 			// TODO(dh): we could chose 32 bit vs 64 bit depending on the
    131 			// file's build tags
    132 			maxMathConst = "math.MaxUint64"
    133 			minLiteral = constant.MakeUint64(0)
    134 			maxLiteral = constant.MakeUint64(math.MaxUint64)
    135 
    136 		case types.Int8:
    137 			minMathConst = "math.MinInt8"
    138 			maxMathConst = "math.MaxInt8"
    139 			minLiteral = constant.MakeInt64(math.MinInt8)
    140 			maxLiteral = constant.MakeInt64(math.MaxInt8)
    141 		case types.Int16:
    142 			minMathConst = "math.MinInt16"
    143 			maxMathConst = "math.MaxInt16"
    144 			minLiteral = constant.MakeInt64(math.MinInt16)
    145 			maxLiteral = constant.MakeInt64(math.MaxInt16)
    146 		case types.Int32:
    147 			minMathConst = "math.MinInt32"
    148 			maxMathConst = "math.MaxInt32"
    149 			minLiteral = constant.MakeInt64(math.MinInt32)
    150 			maxLiteral = constant.MakeInt64(math.MaxInt32)
    151 		case types.Int64:
    152 			minMathConst = "math.MinInt64"
    153 			maxMathConst = "math.MaxInt64"
    154 			minLiteral = constant.MakeInt64(math.MinInt64)
    155 			maxLiteral = constant.MakeInt64(math.MaxInt64)
    156 		case types.Int:
    157 			// TODO(dh): we could chose 32 bit vs 64 bit depending on the
    158 			// file's build tags
    159 			minMathConst = "math.MinInt64"
    160 			maxMathConst = "math.MaxInt64"
    161 			minLiteral = constant.MakeInt64(math.MinInt64)
    162 			maxLiteral = constant.MakeInt64(math.MaxInt64)
    163 		}
    164 
    165 		isLiteral := func(expr ast.Expr, c constant.Value) bool {
    166 			if c == nil {
    167 				return false
    168 			}
    169 			return code.IsIntegerLiteral(pass, expr, c)
    170 		}
    171 
    172 		x, y, op := expr.X, expr.Y, expr.Op
    173 		switch op {
    174 		case token.GEQ, token.GTR:
    175 		case token.LEQ:
    176 			x, y = y, x
    177 			op = token.GEQ
    178 		case token.LSS:
    179 			x, y = y, x
    180 			op = token.GTR
    181 		default:
    182 			return
    183 		}
    184 
    185 		if isobj(y, maxMathConst) || isLiteral(y, maxLiteral) {
    186 			report.Report(
    187 				pass,
    188 				expr,
    189 				fmt.Sprintf("no value of type %s is greater than %s", tx, maxMathConst),
    190 				report.FilterGenerated(),
    191 			)
    192 		}
    193 		if op == token.GEQ && (isobj(x, maxMathConst) || isLiteral(x, maxLiteral)) {
    194 			report.Report(
    195 				pass,
    196 				expr,
    197 				fmt.Sprintf("every value of type %s is <= %s", tx, maxMathConst),
    198 				report.FilterGenerated(),
    199 			)
    200 		}
    201 
    202 		if !allUnsigned {
    203 			if isobj(x, minMathConst) || isLiteral(x, minLiteral) {
    204 				report.Report(
    205 					pass,
    206 					expr,
    207 					fmt.Sprintf("no value of type %s is less than %s", tx, minMathConst),
    208 					report.FilterGenerated(),
    209 				)
    210 			}
    211 			if op == token.GEQ && (isobj(y, minMathConst) || isLiteral(y, minLiteral)) {
    212 				report.Report(
    213 					pass,
    214 					expr,
    215 					fmt.Sprintf("every value of type %s is >= %s", tx, minMathConst),
    216 					report.FilterGenerated(),
    217 				)
    218 			}
    219 		}
    220 
    221 	}
    222 	code.Preorder(pass, fn, (*ast.BinaryExpr)(nil))
    223 	return nil, nil
    224 }