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

View as plain text