sa4000.go raw
1 package sa4000
2
3 import (
4 "fmt"
5 "go/ast"
6 "go/token"
7 "go/types"
8 "reflect"
9
10 "honnef.co/go/tools/analysis/code"
11 "honnef.co/go/tools/analysis/facts/generated"
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: "SA4000",
23 Run: run,
24 Requires: []*analysis.Analyzer{inspect.Analyzer, generated.Analyzer},
25 },
26 Doc: &lint.RawDocumentation{
27 Title: `Binary operator has identical expressions on both sides`,
28 Since: "2017.1",
29 Severity: lint.SeverityWarning,
30 MergeIf: lint.MergeIfAny,
31 },
32 })
33
34 var Analyzer = SCAnalyzer.Analyzer
35
36 func run(pass *analysis.Pass) (interface{}, error) {
37 var isFloat func(T types.Type) bool
38 isFloat = func(T types.Type) bool {
39 tset := typeutil.NewTypeSet(T)
40 if len(tset.Terms) == 0 {
41 // no terms, so floats are a possibility
42 return true
43 }
44 return tset.Any(func(term *types.Term) bool {
45 switch typ := term.Type().Underlying().(type) {
46 case *types.Basic:
47 kind := typ.Kind()
48 return kind == types.Float32 || kind == types.Float64
49 case *types.Array:
50 return isFloat(typ.Elem())
51 case *types.Struct:
52 for i := 0; i < typ.NumFields(); i++ {
53 if !isFloat(typ.Field(i).Type()) {
54 return false
55 }
56 }
57 return true
58 default:
59 return false
60 }
61 })
62 }
63
64 // TODO(dh): this check ignores the existence of side-effects and
65 // happily flags fn() == fn() – so far, we've had nobody complain
66 // about a false positive, and it's caught several bugs in real
67 // code.
68 //
69 // We special case functions from the math/rand package. Someone ran
70 // into the following false positive: "rand.Intn(2) - rand.Intn(2), which I wrote to generate values {-1, 0, 1} with {0.25, 0.5, 0.25} probability."
71 fn := func(node ast.Node) {
72 op := node.(*ast.BinaryExpr)
73 switch op.Op {
74 case token.EQL, token.NEQ:
75 case token.SUB, token.QUO, token.AND, token.REM, token.OR, token.XOR, token.AND_NOT,
76 token.LAND, token.LOR, token.LSS, token.GTR, token.LEQ, token.GEQ:
77 default:
78 // For some ops, such as + and *, it can make sense to
79 // have identical operands
80 return
81 }
82
83 if isFloat(pass.TypesInfo.TypeOf(op.X)) {
84 // 'float <op> float' makes sense for several operators.
85 // We've tried keeping an exact list of operators to allow, but floats keep surprising us. Let's just give up instead.
86 return
87 }
88
89 if reflect.TypeOf(op.X) != reflect.TypeOf(op.Y) {
90 return
91 }
92 if report.Render(pass, op.X) != report.Render(pass, op.Y) {
93 return
94 }
95 l1, ok1 := op.X.(*ast.BasicLit)
96 l2, ok2 := op.Y.(*ast.BasicLit)
97 if ok1 && ok2 && l1.Kind == token.INT && l2.Kind == l1.Kind && l1.Value == "0" && l2.Value == l1.Value && code.IsGenerated(pass, l1.Pos()) {
98 // cgo generates the following function call:
99 // _cgoCheckPointer(_cgoBase0, 0 == 0) – it uses 0 == 0
100 // instead of true in case the user shadowed the
101 // identifier. Ideally we'd restrict this exception to
102 // calls of _cgoCheckPointer, but it's not worth the
103 // hassle of keeping track of the stack. <lit> <op> <lit>
104 // are very rare to begin with, and we're mostly checking
105 // for them to catch typos such as 1 == 1 where the user
106 // meant to type i == 1. The odds of a false negative for
107 // 0 == 0 are slim.
108 return
109 }
110
111 if expr, ok := op.X.(*ast.CallExpr); ok {
112 call := code.CallName(pass, expr)
113 switch call {
114 case "math/rand.Int",
115 "math/rand.Int31",
116 "math/rand.Int31n",
117 "math/rand.Int63",
118 "math/rand.Int63n",
119 "math/rand.Intn",
120 "math/rand.Uint32",
121 "math/rand.Uint64",
122 "math/rand.ExpFloat64",
123 "math/rand.Float32",
124 "math/rand.Float64",
125 "math/rand.NormFloat64",
126 "(*math/rand.Rand).Int",
127 "(*math/rand.Rand).Int31",
128 "(*math/rand.Rand).Int31n",
129 "(*math/rand.Rand).Int63",
130 "(*math/rand.Rand).Int63n",
131 "(*math/rand.Rand).Intn",
132 "(*math/rand.Rand).Uint32",
133 "(*math/rand.Rand).Uint64",
134 "(*math/rand.Rand).ExpFloat64",
135 "(*math/rand.Rand).Float32",
136 "(*math/rand.Rand).Float64",
137 "(*math/rand.Rand).NormFloat64":
138 return
139 }
140 }
141
142 report.Report(pass, op, fmt.Sprintf("identical expressions on the left and right side of the '%s' operator", op.Op))
143 }
144 code.Preorder(pass, fn, (*ast.BinaryExpr)(nil))
145 return nil, nil
146 }
147