1 // Copyright 2014 The Go Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style
3 // license that can be found in the LICENSE file.
5 // Package bools defines an Analyzer that detects common mistakes
6 // involving boolean operators.
14 "golang.org/x/tools/go/analysis"
15 "golang.org/x/tools/go/analysis/passes/inspect"
16 "golang.org/x/tools/go/analysis/passes/internal/analysisutil"
17 "golang.org/x/tools/go/ast/inspector"
20 const Doc = "check for common mistakes involving boolean operators"
22 var Analyzer = &analysis.Analyzer{
25 Requires: []*analysis.Analyzer{inspect.Analyzer},
29 func run(pass *analysis.Pass) (interface{}, error) {
30 inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
32 nodeFilter := []ast.Node{
33 (*ast.BinaryExpr)(nil),
35 seen := make(map[*ast.BinaryExpr]bool)
36 inspect.Preorder(nodeFilter, func(n ast.Node) {
37 e := n.(*ast.BinaryExpr)
39 // Already processed as a subexpression of an earlier node.
53 comm := op.commutativeSets(pass.TypesInfo, e, seen)
54 for _, exprs := range comm {
55 op.checkRedundant(pass, exprs)
56 op.checkSuspect(pass, exprs)
64 tok token.Token // token corresponding to this operator
65 badEq token.Token // token corresponding to the equality test that should not be used with this operator
69 or = boolOp{"or", token.LOR, token.NEQ}
70 and = boolOp{"and", token.LAND, token.EQL}
73 // commutativeSets returns all side effect free sets of
74 // expressions in e that are connected by op.
75 // For example, given 'a || b || f() || c || d' with the or op,
76 // commutativeSets returns {{b, a}, {d, c}}.
77 // commutativeSets adds any expanded BinaryExprs to seen.
78 func (op boolOp) commutativeSets(info *types.Info, e *ast.BinaryExpr, seen map[*ast.BinaryExpr]bool) [][]ast.Expr {
79 exprs := op.split(e, seen)
81 // Partition the slice of expressions into commutative sets.
84 for j := 0; j <= len(exprs); j++ {
85 if j == len(exprs) || hasSideEffects(info, exprs[j]) {
87 sets = append(sets, exprs[i:j])
96 // checkRedundant checks for expressions of the form
99 // Exprs must contain only side effect free expressions.
100 func (op boolOp) checkRedundant(pass *analysis.Pass, exprs []ast.Expr) {
101 seen := make(map[string]bool)
102 for _, e := range exprs {
103 efmt := analysisutil.Format(pass.Fset, e)
105 pass.ReportRangef(e, "redundant %s: %s %s %s", op.name, efmt, op.tok, efmt)
112 // checkSuspect checks for expressions of the form
113 // x != c1 || x != c2
114 // x == c1 && x == c2
115 // where c1 and c2 are constant expressions.
116 // If c1 and c2 are the same then it's redundant;
117 // if c1 and c2 are different then it's always true or always false.
118 // Exprs must contain only side effect free expressions.
119 func (op boolOp) checkSuspect(pass *analysis.Pass, exprs []ast.Expr) {
120 // seen maps from expressions 'x' to equality expressions 'x != c'.
121 seen := make(map[string]string)
123 for _, e := range exprs {
124 bin, ok := e.(*ast.BinaryExpr)
125 if !ok || bin.Op != op.badEq {
129 // In order to avoid false positives, restrict to cases
130 // in which one of the operands is constant. We're then
131 // interested in the other operand.
132 // In the rare case in which both operands are constant
133 // (e.g. runtime.GOOS and "windows"), we'll only catch
134 // mistakes if the LHS is repeated, which is how most
138 case pass.TypesInfo.Types[bin.Y].Value != nil:
140 case pass.TypesInfo.Types[bin.X].Value != nil:
146 // e is of the form 'x != c' or 'x == c'.
147 xfmt := analysisutil.Format(pass.Fset, x)
148 efmt := analysisutil.Format(pass.Fset, e)
149 if prev, found := seen[xfmt]; found {
150 // checkRedundant handles the case in which efmt == prev.
152 pass.ReportRangef(e, "suspect %s: %s %s %s", op.name, efmt, op.tok, prev)
160 // hasSideEffects reports whether evaluation of e has side effects.
161 func hasSideEffects(info *types.Info, e ast.Expr) bool {
163 ast.Inspect(e, func(node ast.Node) bool {
164 switch n := node.(type) {
166 typVal := info.Types[n.Fun]
168 case typVal.IsType():
169 // Type conversion, which is safe.
170 case typVal.IsBuiltin():
171 // Builtin func, conservatively assumed to not
176 // A non-builtin func or method call.
177 // Conservatively assume that all of them have
178 // side effects for now.
183 if n.Op == token.ARROW {
193 // split returns a slice of all subexpressions in e that are connected by op.
194 // For example, given 'a || (b || c) || d' with the or op,
195 // split returns []{d, c, b, a}.
196 // seen[e] is already true; any newly processed exprs are added to seen.
197 func (op boolOp) split(e ast.Expr, seen map[*ast.BinaryExpr]bool) (exprs []ast.Expr) {
200 if b, ok := e.(*ast.BinaryExpr); ok && b.Op == op.tok {
202 exprs = append(exprs, op.split(b.Y, seen)...)
205 exprs = append(exprs, e)
212 // unparen returns e with any enclosing parentheses stripped.
213 func unparen(e ast.Expr) ast.Expr {
215 p, ok := e.(*ast.ParenExpr)