-
Notifications
You must be signed in to change notification settings - Fork 0
/
floatCompare_checker.go
107 lines (94 loc) · 2.7 KB
/
floatCompare_checker.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
package checkers
import (
"go/ast"
"go/token"
"go/types"
"github.com/go-lintpack/lintpack"
"github.com/go-lintpack/lintpack/astwalk"
"github.com/go-toolsmith/astequal"
"github.com/go-toolsmith/astp"
"golang.org/x/tools/go/ast/astutil"
)
func init() {
var info lintpack.CheckerInfo
info.Name = "floatCompare"
info.Tags = []string{"diagnostic", "experimental"}
info.Summary = "Detects fragile float variables comparisons"
info.Before = `
// x and y are floats
return x == y`
info.After = `
// x and y are floats
return math.Abs(x - y) < eps`
collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker {
return astwalk.WalkerForLocalExpr(&floatCompareChecker{ctx: ctx})
})
}
type floatCompareChecker struct {
astwalk.WalkHandler
ctx *lintpack.CheckerContext
}
func (c *floatCompareChecker) VisitLocalExpr(expr ast.Expr) {
binexpr, ok := expr.(*ast.BinaryExpr)
if !ok {
return
}
if binexpr.Op == token.EQL || binexpr.Op == token.NEQ {
if c.isFloatExpr(binexpr) &&
c.isMultiBinaryExpr(binexpr) &&
!c.isNaNCheckExpr(binexpr) &&
!c.isInfCheckExpr(binexpr) {
c.warn(binexpr)
}
}
}
func (c *floatCompareChecker) isFloatExpr(binexpr *ast.BinaryExpr) bool {
exprx := astutil.Unparen(binexpr.X)
typx, ok := c.ctx.TypesInfo.TypeOf(exprx).(*types.Basic)
return ok && typx.Info()&types.IsFloat != 0
}
func (c *floatCompareChecker) isNaNCheckExpr(binexpr *ast.BinaryExpr) bool {
exprx := astutil.Unparen(binexpr.X)
expry := astutil.Unparen(binexpr.Y)
return astequal.Expr(exprx, expry)
}
func (c *floatCompareChecker) isInfCheckExpr(binexpr *ast.BinaryExpr) bool {
binx := astutil.Unparen(binexpr.X)
biny := astutil.Unparen(binexpr.Y)
expr, bin, ok := c.identExpr(binx, biny)
if !ok {
return false
}
x := astutil.Unparen(expr)
y := astutil.Unparen(bin.X)
z := astutil.Unparen(bin.Y)
return astequal.Expr(x, y) && astequal.Expr(y, z)
}
func (c *floatCompareChecker) isMultiBinaryExpr(binexpr *ast.BinaryExpr) bool {
exprx := astutil.Unparen(binexpr.X)
expry := astutil.Unparen(binexpr.Y)
return astp.IsBinaryExpr(exprx) || astp.IsBinaryExpr(expry)
}
func (c *floatCompareChecker) identExpr(x, y ast.Node) (ast.Expr, *ast.BinaryExpr, bool) {
expr1, ok1 := x.(*ast.BinaryExpr)
expr2, ok2 := y.(*ast.BinaryExpr)
switch {
case ok1 && !ok2:
return y.(ast.Expr), expr1, true
case !ok1 && ok2:
return x.(ast.Expr), expr2, true
default:
return nil, nil, false
}
}
func (c *floatCompareChecker) warn(expr *ast.BinaryExpr) {
op := ">="
if expr.Op == token.EQL {
op = "<"
}
format := "change `%s` to `math.Abs(%s - %s) %s eps`"
if astp.IsBinaryExpr(expr.Y) {
format = "change `%s` to `math.Abs(%s - (%s)) %s eps`"
}
c.ctx.Warn(expr, format, expr, expr.X, expr.Y, op)
}