aboutsummaryrefslogtreecommitdiff
path: root/libgo/go/cmd/vet/bool.go
diff options
context:
space:
mode:
Diffstat (limited to 'libgo/go/cmd/vet/bool.go')
-rw-r--r--libgo/go/cmd/vet/bool.go186
1 files changed, 186 insertions, 0 deletions
diff --git a/libgo/go/cmd/vet/bool.go b/libgo/go/cmd/vet/bool.go
new file mode 100644
index 0000000..07c2a93
--- /dev/null
+++ b/libgo/go/cmd/vet/bool.go
@@ -0,0 +1,186 @@
+// Copyright 2014 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// This file contains boolean condition tests.
+
+package main
+
+import (
+ "go/ast"
+ "go/token"
+)
+
+func init() {
+ register("bool",
+ "check for mistakes involving boolean operators",
+ checkBool,
+ binaryExpr)
+}
+
+func checkBool(f *File, n ast.Node) {
+ e := n.(*ast.BinaryExpr)
+
+ var op boolOp
+ switch e.Op {
+ case token.LOR:
+ op = or
+ case token.LAND:
+ op = and
+ default:
+ return
+ }
+
+ comm := op.commutativeSets(e)
+ for _, exprs := range comm {
+ op.checkRedundant(f, exprs)
+ op.checkSuspect(f, exprs)
+ }
+}
+
+type boolOp struct {
+ name string
+ tok token.Token // token corresponding to this operator
+ badEq token.Token // token corresponding to the equality test that should not be used with this operator
+}
+
+var (
+ or = boolOp{"or", token.LOR, token.NEQ}
+ and = boolOp{"and", token.LAND, token.EQL}
+)
+
+// commutativeSets returns all side effect free sets of
+// expressions in e that are connected by op.
+// For example, given 'a || b || f() || c || d' with the or op,
+// commutativeSets returns {{b, a}, {d, c}}.
+func (op boolOp) commutativeSets(e *ast.BinaryExpr) [][]ast.Expr {
+ exprs := op.split(e)
+
+ // Partition the slice of expressions into commutative sets.
+ i := 0
+ var sets [][]ast.Expr
+ for j := 0; j <= len(exprs); j++ {
+ if j == len(exprs) || hasSideEffects(exprs[j]) {
+ if i < j {
+ sets = append(sets, exprs[i:j])
+ }
+ i = j + 1
+ }
+ }
+
+ return sets
+}
+
+// checkRedundant checks for expressions of the form
+// e && e
+// e || e
+// Exprs must contain only side effect free expressions.
+func (op boolOp) checkRedundant(f *File, exprs []ast.Expr) {
+ seen := make(map[string]bool)
+ for _, e := range exprs {
+ efmt := f.gofmt(e)
+ if seen[efmt] {
+ f.Badf(e.Pos(), "redundant %s: %s %s %s", op.name, efmt, op.tok, efmt)
+ } else {
+ seen[efmt] = true
+ }
+ }
+}
+
+// checkSuspect checks for expressions of the form
+// x != c1 || x != c2
+// x == c1 && x == c2
+// where c1 and c2 are constant expressions.
+// If c1 and c2 are the same then it's redundant;
+// if c1 and c2 are different then it's always true or always false.
+// Exprs must contain only side effect free expressions.
+func (op boolOp) checkSuspect(f *File, exprs []ast.Expr) {
+ // seen maps from expressions 'x' to equality expressions 'x != c'.
+ seen := make(map[string]string)
+
+ for _, e := range exprs {
+ bin, ok := e.(*ast.BinaryExpr)
+ if !ok || bin.Op != op.badEq {
+ continue
+ }
+
+ // In order to avoid false positives, restrict to cases
+ // in which one of the operands is constant. We're then
+ // interested in the other operand.
+ // In the rare case in which both operands are constant
+ // (e.g. runtime.GOOS and "windows"), we'll only catch
+ // mistakes if the LHS is repeated, which is how most
+ // code is written.
+ var x ast.Expr
+ switch {
+ case f.pkg.types[bin.Y].Value != nil:
+ x = bin.X
+ case f.pkg.types[bin.X].Value != nil:
+ x = bin.Y
+ default:
+ continue
+ }
+
+ // e is of the form 'x != c' or 'x == c'.
+ xfmt := f.gofmt(x)
+ efmt := f.gofmt(e)
+ if prev, found := seen[xfmt]; found {
+ // checkRedundant handles the case in which efmt == prev.
+ if efmt != prev {
+ f.Badf(e.Pos(), "suspect %s: %s %s %s", op.name, efmt, op.tok, prev)
+ }
+ } else {
+ seen[xfmt] = efmt
+ }
+ }
+}
+
+// hasSideEffects reports whether evaluation of e has side effects.
+func hasSideEffects(e ast.Expr) bool {
+ safe := true
+ ast.Inspect(e, func(node ast.Node) bool {
+ switch n := node.(type) {
+ // Using CallExpr here will catch conversions
+ // as well as function and method invocations.
+ // We'll live with the false negatives for now.
+ case *ast.CallExpr:
+ safe = false
+ return false
+ case *ast.UnaryExpr:
+ if n.Op == token.ARROW {
+ safe = false
+ return false
+ }
+ }
+ return true
+ })
+ return !safe
+}
+
+// split returns a slice of all subexpressions in e that are connected by op.
+// For example, given 'a || (b || c) || d' with the or op,
+// split returns []{d, c, b, a}.
+func (op boolOp) split(e ast.Expr) (exprs []ast.Expr) {
+ for {
+ e = unparen(e)
+ if b, ok := e.(*ast.BinaryExpr); ok && b.Op == op.tok {
+ exprs = append(exprs, op.split(b.Y)...)
+ e = b.X
+ } else {
+ exprs = append(exprs, e)
+ break
+ }
+ }
+ return
+}
+
+// unparen returns e with any enclosing parentheses stripped.
+func unparen(e ast.Expr) ast.Expr {
+ for {
+ p, ok := e.(*ast.ParenExpr)
+ if !ok {
+ return e
+ }
+ e = p.X
+ }
+}