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 }