sa4003.go raw
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/lint"
13 "honnef.co/go/tools/analysis/report"
14 "honnef.co/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.RawDocumentation{
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 }
160