github.com/karrick/go@v0.0.0-20170817181416-d5b0ec858b37/src/cmd/vet/bool.go (about)

     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  // This file contains boolean condition tests.
     6  
     7  package main
     8  
     9  import (
    10  	"go/ast"
    11  	"go/token"
    12  )
    13  
    14  func init() {
    15  	register("bool",
    16  		"check for mistakes involving boolean operators",
    17  		checkBool,
    18  		binaryExpr)
    19  }
    20  
    21  func checkBool(f *File, n ast.Node) {
    22  	e := n.(*ast.BinaryExpr)
    23  
    24  	var op boolOp
    25  	switch e.Op {
    26  	case token.LOR:
    27  		op = or
    28  	case token.LAND:
    29  		op = and
    30  	default:
    31  		return
    32  	}
    33  
    34  	comm := op.commutativeSets(e)
    35  	for _, exprs := range comm {
    36  		op.checkRedundant(f, exprs)
    37  		op.checkSuspect(f, exprs)
    38  	}
    39  }
    40  
    41  type boolOp struct {
    42  	name  string
    43  	tok   token.Token // token corresponding to this operator
    44  	badEq token.Token // token corresponding to the equality test that should not be used with this operator
    45  }
    46  
    47  var (
    48  	or  = boolOp{"or", token.LOR, token.NEQ}
    49  	and = boolOp{"and", token.LAND, token.EQL}
    50  )
    51  
    52  // commutativeSets returns all side effect free sets of
    53  // expressions in e that are connected by op.
    54  // For example, given 'a || b || f() || c || d' with the or op,
    55  // commutativeSets returns {{b, a}, {d, c}}.
    56  func (op boolOp) commutativeSets(e *ast.BinaryExpr) [][]ast.Expr {
    57  	exprs := op.split(e)
    58  
    59  	// Partition the slice of expressions into commutative sets.
    60  	i := 0
    61  	var sets [][]ast.Expr
    62  	for j := 0; j <= len(exprs); j++ {
    63  		if j == len(exprs) || hasSideEffects(exprs[j]) {
    64  			if i < j {
    65  				sets = append(sets, exprs[i:j])
    66  			}
    67  			i = j + 1
    68  		}
    69  	}
    70  
    71  	return sets
    72  }
    73  
    74  // checkRedundant checks for expressions of the form
    75  //   e && e
    76  //   e || e
    77  // Exprs must contain only side effect free expressions.
    78  func (op boolOp) checkRedundant(f *File, exprs []ast.Expr) {
    79  	seen := make(map[string]bool)
    80  	for _, e := range exprs {
    81  		efmt := f.gofmt(e)
    82  		if seen[efmt] {
    83  			f.Badf(e.Pos(), "redundant %s: %s %s %s", op.name, efmt, op.tok, efmt)
    84  		} else {
    85  			seen[efmt] = true
    86  		}
    87  	}
    88  }
    89  
    90  // checkSuspect checks for expressions of the form
    91  //   x != c1 || x != c2
    92  //   x == c1 && x == c2
    93  // where c1 and c2 are constant expressions.
    94  // If c1 and c2 are the same then it's redundant;
    95  // if c1 and c2 are different then it's always true or always false.
    96  // Exprs must contain only side effect free expressions.
    97  func (op boolOp) checkSuspect(f *File, exprs []ast.Expr) {
    98  	// seen maps from expressions 'x' to equality expressions 'x != c'.
    99  	seen := make(map[string]string)
   100  
   101  	for _, e := range exprs {
   102  		bin, ok := e.(*ast.BinaryExpr)
   103  		if !ok || bin.Op != op.badEq {
   104  			continue
   105  		}
   106  
   107  		// In order to avoid false positives, restrict to cases
   108  		// in which one of the operands is constant. We're then
   109  		// interested in the other operand.
   110  		// In the rare case in which both operands are constant
   111  		// (e.g. runtime.GOOS and "windows"), we'll only catch
   112  		// mistakes if the LHS is repeated, which is how most
   113  		// code is written.
   114  		var x ast.Expr
   115  		switch {
   116  		case f.pkg.types[bin.Y].Value != nil:
   117  			x = bin.X
   118  		case f.pkg.types[bin.X].Value != nil:
   119  			x = bin.Y
   120  		default:
   121  			continue
   122  		}
   123  
   124  		// e is of the form 'x != c' or 'x == c'.
   125  		xfmt := f.gofmt(x)
   126  		efmt := f.gofmt(e)
   127  		if prev, found := seen[xfmt]; found {
   128  			// checkRedundant handles the case in which efmt == prev.
   129  			if efmt != prev {
   130  				f.Badf(e.Pos(), "suspect %s: %s %s %s", op.name, efmt, op.tok, prev)
   131  			}
   132  		} else {
   133  			seen[xfmt] = efmt
   134  		}
   135  	}
   136  }
   137  
   138  // hasSideEffects reports whether evaluation of e has side effects.
   139  func hasSideEffects(e ast.Expr) bool {
   140  	safe := true
   141  	ast.Inspect(e, func(node ast.Node) bool {
   142  		switch n := node.(type) {
   143  		// Using CallExpr here will catch conversions
   144  		// as well as function and method invocations.
   145  		// We'll live with the false negatives for now.
   146  		case *ast.CallExpr:
   147  			safe = false
   148  			return false
   149  		case *ast.UnaryExpr:
   150  			if n.Op == token.ARROW {
   151  				safe = false
   152  				return false
   153  			}
   154  		}
   155  		return true
   156  	})
   157  	return !safe
   158  }
   159  
   160  // split returns a slice of all subexpressions in e that are connected by op.
   161  // For example, given 'a || (b || c) || d' with the or op,
   162  // split returns []{d, c, b, a}.
   163  func (op boolOp) split(e ast.Expr) (exprs []ast.Expr) {
   164  	for {
   165  		e = unparen(e)
   166  		if b, ok := e.(*ast.BinaryExpr); ok && b.Op == op.tok {
   167  			exprs = append(exprs, op.split(b.Y)...)
   168  			e = b.X
   169  		} else {
   170  			exprs = append(exprs, e)
   171  			break
   172  		}
   173  	}
   174  	return
   175  }
   176  
   177  // unparen returns e with any enclosing parentheses stripped.
   178  func unparen(e ast.Expr) ast.Expr {
   179  	for {
   180  		p, ok := e.(*ast.ParenExpr)
   181  		if !ok {
   182  			return e
   183  		}
   184  		e = p.X
   185  	}
   186  }