...
Run Format

Source file src/cmd/vet/bool.go

Documentation: cmd/vet

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

View as plain text