Home | History | Annotate | Download | only in 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(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 }
    187