GoPLS Viewer

Home|gopls/go/analysis/passes/bools/bools.go
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.
4
5// Package bools defines an Analyzer that detects common mistakes
6// involving boolean operators.
7package bools
8
9import (
10    "go/ast"
11    "go/token"
12    "go/types"
13
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"
18)
19
20const Doc = "check for common mistakes involving boolean operators"
21
22var Analyzer = &analysis.Analyzer{
23    Name:     "bools",
24    Doc:      Doc,
25    Requires: []*analysis.Analyzer{inspect.Analyzer},
26    Run:      run,
27}
28
29func run(pass *analysis.Pass) (interface{}, error) {
30    inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
31
32    nodeFilter := []ast.Node{
33        (*ast.BinaryExpr)(nil),
34    }
35    seen := make(map[*ast.BinaryExpr]bool)
36    inspect.Preorder(nodeFilter, func(n ast.Node) {
37        e := n.(*ast.BinaryExpr)
38        if seen[e] {
39            // Already processed as a subexpression of an earlier node.
40            return
41        }
42
43        var op boolOp
44        switch e.Op {
45        case token.LOR:
46            op = or
47        case token.LAND:
48            op = and
49        default:
50            return
51        }
52
53        comm := op.commutativeSets(pass.TypesInfoeseen)
54        for _exprs := range comm {
55            op.checkRedundant(passexprs)
56            op.checkSuspect(passexprs)
57        }
58    })
59    return nilnil
60}
61
62type boolOp struct {
63    name  string
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
66}
67
68var (
69    or  = boolOp{"or"token.LORtoken.NEQ}
70    and = boolOp{"and"token.LANDtoken.EQL}
71)
72
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.
78func (op boolOpcommutativeSets(info *types.Infoe *ast.BinaryExprseen map[*ast.BinaryExpr]bool) [][]ast.Expr {
79    exprs := op.split(eseen)
80
81    // Partition the slice of expressions into commutative sets.
82    i := 0
83    var sets [][]ast.Expr
84    for j := 0j <= len(exprs); j++ {
85        if j == len(exprs) || hasSideEffects(infoexprs[j]) {
86            if i < j {
87                sets = append(setsexprs[i:j])
88            }
89            i = j + 1
90        }
91    }
92
93    return sets
94}
95
96// checkRedundant checks for expressions of the form
97//
98//    e && e
99//    e || e
100//
101// Exprs must contain only side effect free expressions.
102func (op boolOpcheckRedundant(pass *analysis.Passexprs []ast.Expr) {
103    seen := make(map[string]bool)
104    for _e := range exprs {
105        efmt := analysisutil.Format(pass.Fsete)
106        if seen[efmt] {
107            pass.ReportRangef(e"redundant %s: %s %s %s"op.nameefmtop.tokefmt)
108        } else {
109            seen[efmt] = true
110        }
111    }
112}
113
114// checkSuspect checks for expressions of the form
115//
116//    x != c1 || x != c2
117//    x == c1 && x == c2
118//
119// where c1 and c2 are constant expressions.
120// If c1 and c2 are the same then it's redundant;
121// if c1 and c2 are different then it's always true or always false.
122// Exprs must contain only side effect free expressions.
123func (op boolOpcheckSuspect(pass *analysis.Passexprs []ast.Expr) {
124    // seen maps from expressions 'x' to equality expressions 'x != c'.
125    seen := make(map[string]string)
126
127    for _e := range exprs {
128        binok := e.(*ast.BinaryExpr)
129        if !ok || bin.Op != op.badEq {
130            continue
131        }
132
133        // In order to avoid false positives, restrict to cases
134        // in which one of the operands is constant. We're then
135        // interested in the other operand.
136        // In the rare case in which both operands are constant
137        // (e.g. runtime.GOOS and "windows"), we'll only catch
138        // mistakes if the LHS is repeated, which is how most
139        // code is written.
140        var x ast.Expr
141        switch {
142        case pass.TypesInfo.Types[bin.Y].Value != nil:
143            x = bin.X
144        case pass.TypesInfo.Types[bin.X].Value != nil:
145            x = bin.Y
146        default:
147            continue
148        }
149
150        // e is of the form 'x != c' or 'x == c'.
151        xfmt := analysisutil.Format(pass.Fsetx)
152        efmt := analysisutil.Format(pass.Fsete)
153        if prevfound := seen[xfmt]; found {
154            // checkRedundant handles the case in which efmt == prev.
155            if efmt != prev {
156                pass.ReportRangef(e"suspect %s: %s %s %s"op.nameefmtop.tokprev)
157            }
158        } else {
159            seen[xfmt] = efmt
160        }
161    }
162}
163
164// hasSideEffects reports whether evaluation of e has side effects.
165func hasSideEffects(info *types.Infoe ast.Exprbool {
166    safe := true
167    ast.Inspect(e, func(node ast.Nodebool {
168        switch n := node.(type) {
169        case *ast.CallExpr:
170            typVal := info.Types[n.Fun]
171            switch {
172            case typVal.IsType():
173                // Type conversion, which is safe.
174            case typVal.IsBuiltin():
175                // Builtin func, conservatively assumed to not
176                // be safe for now.
177                safe = false
178                return false
179            default:
180                // A non-builtin func or method call.
181                // Conservatively assume that all of them have
182                // side effects for now.
183                safe = false
184                return false
185            }
186        case *ast.UnaryExpr:
187            if n.Op == token.ARROW {
188                safe = false
189                return false
190            }
191        }
192        return true
193    })
194    return !safe
195}
196
197// split returns a slice of all subexpressions in e that are connected by op.
198// For example, given 'a || (b || c) || d' with the or op,
199// split returns []{d, c, b, a}.
200// seen[e] is already true; any newly processed exprs are added to seen.
201func (op boolOpsplit(e ast.Exprseen map[*ast.BinaryExpr]bool) (exprs []ast.Expr) {
202    for {
203        e = unparen(e)
204        if bok := e.(*ast.BinaryExpr); ok && b.Op == op.tok {
205            seen[b] = true
206            exprs = append(exprsop.split(b.Yseen)...)
207            e = b.X
208        } else {
209            exprs = append(exprse)
210            break
211        }
212    }
213    return
214}
215
216// unparen returns e with any enclosing parentheses stripped.
217func unparen(e ast.Exprast.Expr {
218    for {
219        pok := e.(*ast.ParenExpr)
220        if !ok {
221            return e
222        }
223        e = p.X
224    }
225}
226
MembersX
boolOp.commutativeSets.info
boolOp.commutativeSets.e
boolOp.checkSuspect.RangeStmt_3336.e
Doc
run.pass
run.BlockStmt.RangeStmt_1276.exprs
hasSideEffects
ast
inspect
run.BlockStmt.op
analysisutil
boolOp.split.op
unparen
boolOp.checkSuspect.RangeStmt_3336.BlockStmt.xfmt
boolOp.checkSuspect.RangeStmt_3336.BlockStmt.efmt
boolOp.name
boolOp.commutativeSets.i
boolOp.commutativeSets.j
boolOp.checkRedundant.op
boolOp.checkRedundant.seen
boolOp.checkSuspect.pass
inspector
token
boolOp.checkSuspect
boolOp.checkSuspect.exprs
types
boolOp.commutativeSets
hasSideEffects.e
boolOp.split.seen
run.BlockStmt.comm
boolOp.tok
boolOp.split.exprs
boolOp
boolOp.commutativeSets.exprs
boolOp.checkRedundant.exprs
boolOp.checkRedundant.RangeStmt_2630.BlockStmt.efmt
hasSideEffects.safe
boolOp.commutativeSets.op
boolOp.checkRedundant.RangeStmt_2630.e
unparen.e
boolOp.checkSuspect.seen
boolOp.checkSuspect.RangeStmt_3336.BlockStmt.x
boolOp.split.e
run.seen
boolOp.badEq
boolOp.checkRedundant
boolOp.checkRedundant.pass
run.nodeFilter
boolOp.commutativeSets.seen
boolOp.commutativeSets.sets
boolOp.checkSuspect.op
hasSideEffects.info
analysis
run
boolOp.split
Members
X