golang.org/x/tools@v0.21.0/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/astutil" 18 "golang.org/x/tools/go/ast/inspector" 19 ) 20 21 const Doc = "check for common mistakes involving boolean operators" 22 23 var Analyzer = &analysis.Analyzer{ 24 Name: "bools", 25 Doc: Doc, 26 URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/bools", 27 Requires: []*analysis.Analyzer{inspect.Analyzer}, 28 Run: run, 29 } 30 31 func run(pass *analysis.Pass) (interface{}, error) { 32 inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) 33 34 nodeFilter := []ast.Node{ 35 (*ast.BinaryExpr)(nil), 36 } 37 seen := make(map[*ast.BinaryExpr]bool) 38 inspect.Preorder(nodeFilter, func(n ast.Node) { 39 e := n.(*ast.BinaryExpr) 40 if seen[e] { 41 // Already processed as a subexpression of an earlier node. 42 return 43 } 44 45 var op boolOp 46 switch e.Op { 47 case token.LOR: 48 op = or 49 case token.LAND: 50 op = and 51 default: 52 return 53 } 54 55 comm := op.commutativeSets(pass.TypesInfo, e, seen) 56 for _, exprs := range comm { 57 op.checkRedundant(pass, exprs) 58 op.checkSuspect(pass, exprs) 59 } 60 }) 61 return nil, nil 62 } 63 64 type boolOp struct { 65 name string 66 tok token.Token // token corresponding to this operator 67 badEq token.Token // token corresponding to the equality test that should not be used with this operator 68 } 69 70 var ( 71 or = boolOp{"or", token.LOR, token.NEQ} 72 and = boolOp{"and", token.LAND, token.EQL} 73 ) 74 75 // commutativeSets returns all side effect free sets of 76 // expressions in e that are connected by op. 77 // For example, given 'a || b || f() || c || d' with the or op, 78 // commutativeSets returns {{b, a}, {d, c}}. 79 // commutativeSets adds any expanded BinaryExprs to seen. 80 func (op boolOp) commutativeSets(info *types.Info, e *ast.BinaryExpr, seen map[*ast.BinaryExpr]bool) [][]ast.Expr { 81 exprs := op.split(e, seen) 82 83 // Partition the slice of expressions into commutative sets. 84 i := 0 85 var sets [][]ast.Expr 86 for j := 0; j <= len(exprs); j++ { 87 if j == len(exprs) || analysisutil.HasSideEffects(info, exprs[j]) { 88 if i < j { 89 sets = append(sets, exprs[i:j]) 90 } 91 i = j + 1 92 } 93 } 94 95 return sets 96 } 97 98 // checkRedundant checks for expressions of the form 99 // 100 // e && e 101 // e || e 102 // 103 // Exprs must contain only side effect free expressions. 104 func (op boolOp) checkRedundant(pass *analysis.Pass, exprs []ast.Expr) { 105 seen := make(map[string]bool) 106 for _, e := range exprs { 107 efmt := analysisutil.Format(pass.Fset, e) 108 if seen[efmt] { 109 pass.ReportRangef(e, "redundant %s: %s %s %s", op.name, efmt, op.tok, efmt) 110 } else { 111 seen[efmt] = true 112 } 113 } 114 } 115 116 // checkSuspect checks for expressions of the form 117 // 118 // x != c1 || x != c2 119 // x == c1 && x == c2 120 // 121 // where c1 and c2 are constant expressions. 122 // If c1 and c2 are the same then it's redundant; 123 // if c1 and c2 are different then it's always true or always false. 124 // Exprs must contain only side effect free expressions. 125 func (op boolOp) checkSuspect(pass *analysis.Pass, exprs []ast.Expr) { 126 // seen maps from expressions 'x' to equality expressions 'x != c'. 127 seen := make(map[string]string) 128 129 for _, e := range exprs { 130 bin, ok := e.(*ast.BinaryExpr) 131 if !ok || bin.Op != op.badEq { 132 continue 133 } 134 135 // In order to avoid false positives, restrict to cases 136 // in which one of the operands is constant. We're then 137 // interested in the other operand. 138 // In the rare case in which both operands are constant 139 // (e.g. runtime.GOOS and "windows"), we'll only catch 140 // mistakes if the LHS is repeated, which is how most 141 // code is written. 142 var x ast.Expr 143 switch { 144 case pass.TypesInfo.Types[bin.Y].Value != nil: 145 x = bin.X 146 case pass.TypesInfo.Types[bin.X].Value != nil: 147 x = bin.Y 148 default: 149 continue 150 } 151 152 // e is of the form 'x != c' or 'x == c'. 153 xfmt := analysisutil.Format(pass.Fset, x) 154 efmt := analysisutil.Format(pass.Fset, e) 155 if prev, found := seen[xfmt]; found { 156 // checkRedundant handles the case in which efmt == prev. 157 if efmt != prev { 158 pass.ReportRangef(e, "suspect %s: %s %s %s", op.name, efmt, op.tok, prev) 159 } 160 } else { 161 seen[xfmt] = efmt 162 } 163 } 164 } 165 166 // split returns a slice of all subexpressions in e that are connected by op. 167 // For example, given 'a || (b || c) || d' with the or op, 168 // split returns []{d, c, b, a}. 169 // seen[e] is already true; any newly processed exprs are added to seen. 170 func (op boolOp) split(e ast.Expr, seen map[*ast.BinaryExpr]bool) (exprs []ast.Expr) { 171 for { 172 e = astutil.Unparen(e) 173 if b, ok := e.(*ast.BinaryExpr); ok && b.Op == op.tok { 174 seen[b] = true 175 exprs = append(exprs, op.split(b.Y, seen)...) 176 e = b.X 177 } else { 178 exprs = append(exprs, e) 179 break 180 } 181 } 182 return 183 }