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 }