| // 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 | |
| } | |
| } |