github.com/cockroachdb/tools@v0.0.0-20230222021103-a6d27438930d/go/analysis/passes/bools/bools.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 // Package bools defines an Analyzer that detects common mistakes 6 // involving boolean operators. 7 package bools 8 9 import ( 10 "go/ast" 11 "go/token" 12 "go/types" 13 14 "golang.org/x/tools/go/analysis" 15 "golang.org/x/tools/go/analysis/passes/inspect" 16 "golang.org/x/tools/go/analysis/passes/internal/analysisutil" 17 "golang.org/x/tools/go/ast/inspector" 18 ) 19 20 const Doc = "check for common mistakes involving boolean operators" 21 22 var Analyzer = &analysis.Analyzer{ 23 Name: "bools", 24 Doc: Doc, 25 Requires: []*analysis.Analyzer{inspect.Analyzer}, 26 Run: run, 27 } 28 29 func run(pass *analysis.Pass) (interface{}, error) { 30 inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) 31 32 nodeFilter := []ast.Node{ 33 (*ast.BinaryExpr)(nil), 34 } 35 seen := make(map[*ast.BinaryExpr]bool) 36 inspect.Preorder(nodeFilter, func(n ast.Node) { 37 e := n.(*ast.BinaryExpr) 38 if seen[e] { 39 // Already processed as a subexpression of an earlier node. 40 return 41 } 42 43 var op boolOp 44 switch e.Op { 45 case token.LOR: 46 op = or 47 case token.LAND: 48 op = and 49 default: 50 return 51 } 52 53 comm := op.commutativeSets(pass.TypesInfo, e, seen) 54 for _, exprs := range comm { 55 op.checkRedundant(pass, exprs) 56 op.checkSuspect(pass, exprs) 57 } 58 }) 59 return nil, nil 60 } 61 62 type boolOp struct { 63 name string 64 tok token.Token // token corresponding to this operator 65 badEq token.Token // token corresponding to the equality test that should not be used with this operator 66 } 67 68 var ( 69 or = boolOp{"or", token.LOR, token.NEQ} 70 and = boolOp{"and", token.LAND, token.EQL} 71 ) 72 73 // commutativeSets returns all side effect free sets of 74 // expressions in e that are connected by op. 75 // For example, given 'a || b || f() || c || d' with the or op, 76 // commutativeSets returns {{b, a}, {d, c}}. 77 // commutativeSets adds any expanded BinaryExprs to seen. 78 func (op boolOp) commutativeSets(info *types.Info, e *ast.BinaryExpr, seen map[*ast.BinaryExpr]bool) [][]ast.Expr { 79 exprs := op.split(e, seen) 80 81 // Partition the slice of expressions into commutative sets. 82 i := 0 83 var sets [][]ast.Expr 84 for j := 0; j <= len(exprs); j++ { 85 if j == len(exprs) || hasSideEffects(info, exprs[j]) { 86 if i < j { 87 sets = append(sets, exprs[i:j]) 88 } 89 i = j + 1 90 } 91 } 92 93 return sets 94 } 95 96 // checkRedundant checks for expressions of the form 97 // 98 // e && e 99 // e || e 100 // 101 // Exprs must contain only side effect free expressions. 102 func (op boolOp) checkRedundant(pass *analysis.Pass, exprs []ast.Expr) { 103 seen := make(map[string]bool) 104 for _, e := range exprs { 105 efmt := analysisutil.Format(pass.Fset, e) 106 if seen[efmt] { 107 pass.ReportRangef(e, "redundant %s: %s %s %s", op.name, efmt, op.tok, efmt) 108 } else { 109 seen[efmt] = true 110 } 111 } 112 } 113 114 // checkSuspect checks for expressions of the form 115 // 116 // x != c1 || x != c2 117 // x == c1 && x == c2 118 // 119 // where c1 and c2 are constant expressions. 120 // If c1 and c2 are the same then it's redundant; 121 // if c1 and c2 are different then it's always true or always false. 122 // Exprs must contain only side effect free expressions. 123 func (op boolOp) checkSuspect(pass *analysis.Pass, exprs []ast.Expr) { 124 // seen maps from expressions 'x' to equality expressions 'x != c'. 125 seen := make(map[string]string) 126 127 for _, e := range exprs { 128 bin, ok := e.(*ast.BinaryExpr) 129 if !ok || bin.Op != op.badEq { 130 continue 131 } 132 133 // In order to avoid false positives, restrict to cases 134 // in which one of the operands is constant. We're then 135 // interested in the other operand. 136 // In the rare case in which both operands are constant 137 // (e.g. runtime.GOOS and "windows"), we'll only catch 138 // mistakes if the LHS is repeated, which is how most 139 // code is written. 140 var x ast.Expr 141 switch { 142 case pass.TypesInfo.Types[bin.Y].Value != nil: 143 x = bin.X 144 case pass.TypesInfo.Types[bin.X].Value != nil: 145 x = bin.Y 146 default: 147 continue 148 } 149 150 // e is of the form 'x != c' or 'x == c'. 151 xfmt := analysisutil.Format(pass.Fset, x) 152 efmt := analysisutil.Format(pass.Fset, e) 153 if prev, found := seen[xfmt]; found { 154 // checkRedundant handles the case in which efmt == prev. 155 if efmt != prev { 156 pass.ReportRangef(e, "suspect %s: %s %s %s", op.name, efmt, op.tok, prev) 157 } 158 } else { 159 seen[xfmt] = efmt 160 } 161 } 162 } 163 164 // hasSideEffects reports whether evaluation of e has side effects. 165 func hasSideEffects(info *types.Info, e ast.Expr) bool { 166 safe := true 167 ast.Inspect(e, func(node ast.Node) bool { 168 switch n := node.(type) { 169 case *ast.CallExpr: 170 typVal := info.Types[n.Fun] 171 switch { 172 case typVal.IsType(): 173 // Type conversion, which is safe. 174 case typVal.IsBuiltin(): 175 // Builtin func, conservatively assumed to not 176 // be safe for now. 177 safe = false 178 return false 179 default: 180 // A non-builtin func or method call. 181 // Conservatively assume that all of them have 182 // side effects for now. 183 safe = false 184 return false 185 } 186 case *ast.UnaryExpr: 187 if n.Op == token.ARROW { 188 safe = false 189 return false 190 } 191 } 192 return true 193 }) 194 return !safe 195 } 196 197 // split returns a slice of all subexpressions in e that are connected by op. 198 // For example, given 'a || (b || c) || d' with the or op, 199 // split returns []{d, c, b, a}. 200 // seen[e] is already true; any newly processed exprs are added to seen. 201 func (op boolOp) split(e ast.Expr, seen map[*ast.BinaryExpr]bool) (exprs []ast.Expr) { 202 for { 203 e = unparen(e) 204 if b, ok := e.(*ast.BinaryExpr); ok && b.Op == op.tok { 205 seen[b] = true 206 exprs = append(exprs, op.split(b.Y, seen)...) 207 e = b.X 208 } else { 209 exprs = append(exprs, e) 210 break 211 } 212 } 213 return 214 } 215 216 // unparen returns e with any enclosing parentheses stripped. 217 func unparen(e ast.Expr) ast.Expr { 218 for { 219 p, ok := e.(*ast.ParenExpr) 220 if !ok { 221 return e 222 } 223 e = p.X 224 } 225 }