Source file src/cmd/vendor/golang.org/x/tools/go/analysis/passes/bools/bools.go

     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  }
   184  

View as plain text