github.com/powerman/golang-tools@v0.1.11-0.20220410185822-5ad214d8d803/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  	"github.com/powerman/golang-tools/go/analysis"
    15  	"github.com/powerman/golang-tools/go/analysis/passes/inspect"
    16  	"github.com/powerman/golang-tools/go/analysis/passes/internal/analysisutil"
    17  	"github.com/powerman/golang-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  //   e && e
    98  //   e || e
    99  // Exprs must contain only side effect free expressions.
   100  func (op boolOp) checkRedundant(pass *analysis.Pass, exprs []ast.Expr) {
   101  	seen := make(map[string]bool)
   102  	for _, e := range exprs {
   103  		efmt := analysisutil.Format(pass.Fset, e)
   104  		if seen[efmt] {
   105  			pass.ReportRangef(e, "redundant %s: %s %s %s", op.name, efmt, op.tok, efmt)
   106  		} else {
   107  			seen[efmt] = true
   108  		}
   109  	}
   110  }
   111  
   112  // checkSuspect checks for expressions of the form
   113  //   x != c1 || x != c2
   114  //   x == c1 && x == c2
   115  // where c1 and c2 are constant expressions.
   116  // If c1 and c2 are the same then it's redundant;
   117  // if c1 and c2 are different then it's always true or always false.
   118  // Exprs must contain only side effect free expressions.
   119  func (op boolOp) checkSuspect(pass *analysis.Pass, exprs []ast.Expr) {
   120  	// seen maps from expressions 'x' to equality expressions 'x != c'.
   121  	seen := make(map[string]string)
   122  
   123  	for _, e := range exprs {
   124  		bin, ok := e.(*ast.BinaryExpr)
   125  		if !ok || bin.Op != op.badEq {
   126  			continue
   127  		}
   128  
   129  		// In order to avoid false positives, restrict to cases
   130  		// in which one of the operands is constant. We're then
   131  		// interested in the other operand.
   132  		// In the rare case in which both operands are constant
   133  		// (e.g. runtime.GOOS and "windows"), we'll only catch
   134  		// mistakes if the LHS is repeated, which is how most
   135  		// code is written.
   136  		var x ast.Expr
   137  		switch {
   138  		case pass.TypesInfo.Types[bin.Y].Value != nil:
   139  			x = bin.X
   140  		case pass.TypesInfo.Types[bin.X].Value != nil:
   141  			x = bin.Y
   142  		default:
   143  			continue
   144  		}
   145  
   146  		// e is of the form 'x != c' or 'x == c'.
   147  		xfmt := analysisutil.Format(pass.Fset, x)
   148  		efmt := analysisutil.Format(pass.Fset, e)
   149  		if prev, found := seen[xfmt]; found {
   150  			// checkRedundant handles the case in which efmt == prev.
   151  			if efmt != prev {
   152  				pass.ReportRangef(e, "suspect %s: %s %s %s", op.name, efmt, op.tok, prev)
   153  			}
   154  		} else {
   155  			seen[xfmt] = efmt
   156  		}
   157  	}
   158  }
   159  
   160  // hasSideEffects reports whether evaluation of e has side effects.
   161  func hasSideEffects(info *types.Info, e ast.Expr) bool {
   162  	safe := true
   163  	ast.Inspect(e, func(node ast.Node) bool {
   164  		switch n := node.(type) {
   165  		case *ast.CallExpr:
   166  			typVal := info.Types[n.Fun]
   167  			switch {
   168  			case typVal.IsType():
   169  				// Type conversion, which is safe.
   170  			case typVal.IsBuiltin():
   171  				// Builtin func, conservatively assumed to not
   172  				// be safe for now.
   173  				safe = false
   174  				return false
   175  			default:
   176  				// A non-builtin func or method call.
   177  				// Conservatively assume that all of them have
   178  				// side effects for now.
   179  				safe = false
   180  				return false
   181  			}
   182  		case *ast.UnaryExpr:
   183  			if n.Op == token.ARROW {
   184  				safe = false
   185  				return false
   186  			}
   187  		}
   188  		return true
   189  	})
   190  	return !safe
   191  }
   192  
   193  // split returns a slice of all subexpressions in e that are connected by op.
   194  // For example, given 'a || (b || c) || d' with the or op,
   195  // split returns []{d, c, b, a}.
   196  // seen[e] is already true; any newly processed exprs are added to seen.
   197  func (op boolOp) split(e ast.Expr, seen map[*ast.BinaryExpr]bool) (exprs []ast.Expr) {
   198  	for {
   199  		e = unparen(e)
   200  		if b, ok := e.(*ast.BinaryExpr); ok && b.Op == op.tok {
   201  			seen[b] = true
   202  			exprs = append(exprs, op.split(b.Y, seen)...)
   203  			e = b.X
   204  		} else {
   205  			exprs = append(exprs, e)
   206  			break
   207  		}
   208  	}
   209  	return
   210  }
   211  
   212  // unparen returns e with any enclosing parentheses stripped.
   213  func unparen(e ast.Expr) ast.Expr {
   214  	for {
   215  		p, ok := e.(*ast.ParenExpr)
   216  		if !ok {
   217  			return e
   218  		}
   219  		e = p.X
   220  	}
   221  }