...
Run Format

Source file src/cmd/vet/bool.go

Documentation: cmd/vet

     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(f, 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(f *File, 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(f, 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(f *File, e ast.Expr) bool {
   140  	safe := true
   141  	ast.Inspect(e, func(node ast.Node) bool {
   142  		switch n := node.(type) {
   143  		case *ast.CallExpr:
   144  			typVal := f.pkg.types[n.Fun]
   145  			switch {
   146  			case typVal.IsType():
   147  				// Type conversion, which is safe.
   148  			case typVal.IsBuiltin():
   149  				// Builtin func, conservatively assumed to not
   150  				// be safe for now.
   151  				safe = false
   152  				return false
   153  			default:
   154  				// A non-builtin func or method call.
   155  				// Conservatively assume that all of them have
   156  				// side effects for now.
   157  				safe = false
   158  				return false
   159  			}
   160  		case *ast.UnaryExpr:
   161  			if n.Op == token.ARROW {
   162  				safe = false
   163  				return false
   164  			}
   165  		}
   166  		return true
   167  	})
   168  	return !safe
   169  }
   170  
   171  // split returns a slice of all subexpressions in e that are connected by op.
   172  // For example, given 'a || (b || c) || d' with the or op,
   173  // split returns []{d, c, b, a}.
   174  func (op boolOp) split(e ast.Expr) (exprs []ast.Expr) {
   175  	for {
   176  		e = unparen(e)
   177  		if b, ok := e.(*ast.BinaryExpr); ok && b.Op == op.tok {
   178  			exprs = append(exprs, op.split(b.Y)...)
   179  			e = b.X
   180  		} else {
   181  			exprs = append(exprs, e)
   182  			break
   183  		}
   184  	}
   185  	return
   186  }
   187  
   188  // unparen returns e with any enclosing parentheses stripped.
   189  func unparen(e ast.Expr) ast.Expr {
   190  	for {
   191  		p, ok := e.(*ast.ParenExpr)
   192  		if !ok {
   193  			return e
   194  		}
   195  		e = p.X
   196  	}
   197  }
   198  

View as plain text