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 }