+++ /dev/null
-// 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.
-
-// Package bools defines an Analyzer that detects common mistakes
-// involving boolean operators.
-package bools
-
-import (
- "go/ast"
- "go/token"
- "go/types"
-
- "golang.org/x/tools/go/analysis"
- "golang.org/x/tools/go/analysis/passes/inspect"
- "golang.org/x/tools/go/analysis/passes/internal/analysisutil"
- "golang.org/x/tools/go/ast/inspector"
-)
-
-const Doc = "check for common mistakes involving boolean operators"
-
-var Analyzer = &analysis.Analyzer{
- Name: "bools",
- Doc: Doc,
- Requires: []*analysis.Analyzer{inspect.Analyzer},
- Run: run,
-}
-
-func run(pass *analysis.Pass) (interface{}, error) {
- inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
-
- nodeFilter := []ast.Node{
- (*ast.BinaryExpr)(nil),
- }
- seen := make(map[*ast.BinaryExpr]bool)
- inspect.Preorder(nodeFilter, func(n ast.Node) {
- e := n.(*ast.BinaryExpr)
- if seen[e] {
- // Already processed as a subexpression of an earlier node.
- return
- }
-
- var op boolOp
- switch e.Op {
- case token.LOR:
- op = or
- case token.LAND:
- op = and
- default:
- return
- }
-
- comm := op.commutativeSets(pass.TypesInfo, e, seen)
- for _, exprs := range comm {
- op.checkRedundant(pass, exprs)
- op.checkSuspect(pass, exprs)
- }
- })
- return nil, nil
-}
-
-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}}.
-// commutativeSets adds any expanded BinaryExprs to seen.
-func (op boolOp) commutativeSets(info *types.Info, e *ast.BinaryExpr, seen map[*ast.BinaryExpr]bool) [][]ast.Expr {
- exprs := op.split(e, seen)
-
- // 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(info, 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(pass *analysis.Pass, exprs []ast.Expr) {
- seen := make(map[string]bool)
- for _, e := range exprs {
- efmt := analysisutil.Format(pass.Fset, e)
- if seen[efmt] {
- pass.ReportRangef(e, "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(pass *analysis.Pass, 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 pass.TypesInfo.Types[bin.Y].Value != nil:
- x = bin.X
- case pass.TypesInfo.Types[bin.X].Value != nil:
- x = bin.Y
- default:
- continue
- }
-
- // e is of the form 'x != c' or 'x == c'.
- xfmt := analysisutil.Format(pass.Fset, x)
- efmt := analysisutil.Format(pass.Fset, e)
- if prev, found := seen[xfmt]; found {
- // checkRedundant handles the case in which efmt == prev.
- if efmt != prev {
- pass.ReportRangef(e, "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(info *types.Info, e ast.Expr) bool {
- safe := true
- ast.Inspect(e, func(node ast.Node) bool {
- switch n := node.(type) {
- case *ast.CallExpr:
- typVal := info.Types[n.Fun]
- switch {
- case typVal.IsType():
- // Type conversion, which is safe.
- case typVal.IsBuiltin():
- // Builtin func, conservatively assumed to not
- // be safe for now.
- safe = false
- return false
- default:
- // A non-builtin func or method call.
- // Conservatively assume that all of them have
- // side effects for now.
- 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}.
-// seen[e] is already true; any newly processed exprs are added to seen.
-func (op boolOp) split(e ast.Expr, seen map[*ast.BinaryExpr]bool) (exprs []ast.Expr) {
- for {
- e = unparen(e)
- if b, ok := e.(*ast.BinaryExpr); ok && b.Op == op.tok {
- seen[b] = true
- exprs = append(exprs, op.split(b.Y, seen)...)
- 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
- }
-}