...

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

View as plain text