Revamp the build system to be more inline with other Prometheus exporters. Notably add Darwin and Windows build targets, and add support for releases using tar files.
187 lines
4.4 KiB
Go
187 lines
4.4 KiB
Go
// Copyright 2014 The Go Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
// This file contains boolean condition tests.
|
|
|
|
package main
|
|
|
|
import (
|
|
"go/ast"
|
|
"go/token"
|
|
)
|
|
|
|
func init() {
|
|
register("bool",
|
|
"check for mistakes involving boolean operators",
|
|
checkBool,
|
|
binaryExpr)
|
|
}
|
|
|
|
func checkBool(f *File, n ast.Node) {
|
|
e := n.(*ast.BinaryExpr)
|
|
|
|
var op boolOp
|
|
switch e.Op {
|
|
case token.LOR:
|
|
op = or
|
|
case token.LAND:
|
|
op = and
|
|
default:
|
|
return
|
|
}
|
|
|
|
comm := op.commutativeSets(e)
|
|
for _, exprs := range comm {
|
|
op.checkRedundant(f, exprs)
|
|
op.checkSuspect(f, exprs)
|
|
}
|
|
}
|
|
|
|
type boolOp struct {
|
|
name string
|
|
tok token.Token // token corresponding to this operator
|
|
badEq token.Token // token corresponding to the equality test that should not be used with this operator
|
|
}
|
|
|
|
var (
|
|
or = boolOp{"or", token.LOR, token.NEQ}
|
|
and = boolOp{"and", token.LAND, token.EQL}
|
|
)
|
|
|
|
// commutativeSets returns all side effect free sets of
|
|
// expressions in e that are connected by op.
|
|
// For example, given 'a || b || f() || c || d' with the or op,
|
|
// commutativeSets returns {{b, a}, {d, c}}.
|
|
func (op boolOp) commutativeSets(e *ast.BinaryExpr) [][]ast.Expr {
|
|
exprs := op.split(e)
|
|
|
|
// Partition the slice of expressions into commutative sets.
|
|
i := 0
|
|
var sets [][]ast.Expr
|
|
for j := 0; j <= len(exprs); j++ {
|
|
if j == len(exprs) || hasSideEffects(exprs[j]) {
|
|
if i < j {
|
|
sets = append(sets, exprs[i:j])
|
|
}
|
|
i = j + 1
|
|
}
|
|
}
|
|
|
|
return sets
|
|
}
|
|
|
|
// checkRedundant checks for expressions of the form
|
|
// e && e
|
|
// e || e
|
|
// Exprs must contain only side effect free expressions.
|
|
func (op boolOp) checkRedundant(f *File, exprs []ast.Expr) {
|
|
seen := make(map[string]bool)
|
|
for _, e := range exprs {
|
|
efmt := f.gofmt(e)
|
|
if seen[efmt] {
|
|
f.Badf(e.Pos(), "redundant %s: %s %s %s", op.name, efmt, op.tok, efmt)
|
|
} else {
|
|
seen[efmt] = true
|
|
}
|
|
}
|
|
}
|
|
|
|
// checkSuspect checks for expressions of the form
|
|
// x != c1 || x != c2
|
|
// x == c1 && x == c2
|
|
// where c1 and c2 are constant expressions.
|
|
// If c1 and c2 are the same then it's redundant;
|
|
// if c1 and c2 are different then it's always true or always false.
|
|
// Exprs must contain only side effect free expressions.
|
|
func (op boolOp) checkSuspect(f *File, exprs []ast.Expr) {
|
|
// seen maps from expressions 'x' to equality expressions 'x != c'.
|
|
seen := make(map[string]string)
|
|
|
|
for _, e := range exprs {
|
|
bin, ok := e.(*ast.BinaryExpr)
|
|
if !ok || bin.Op != op.badEq {
|
|
continue
|
|
}
|
|
|
|
// In order to avoid false positives, restrict to cases
|
|
// in which one of the operands is constant. We're then
|
|
// interested in the other operand.
|
|
// In the rare case in which both operands are constant
|
|
// (e.g. runtime.GOOS and "windows"), we'll only catch
|
|
// mistakes if the LHS is repeated, which is how most
|
|
// code is written.
|
|
var x ast.Expr
|
|
switch {
|
|
case f.pkg.types[bin.Y].Value != nil:
|
|
x = bin.X
|
|
case f.pkg.types[bin.X].Value != nil:
|
|
x = bin.Y
|
|
default:
|
|
continue
|
|
}
|
|
|
|
// e is of the form 'x != c' or 'x == c'.
|
|
xfmt := f.gofmt(x)
|
|
efmt := f.gofmt(e)
|
|
if prev, found := seen[xfmt]; found {
|
|
// checkRedundant handles the case in which efmt == prev.
|
|
if efmt != prev {
|
|
f.Badf(e.Pos(), "suspect %s: %s %s %s", op.name, efmt, op.tok, prev)
|
|
}
|
|
} else {
|
|
seen[xfmt] = efmt
|
|
}
|
|
}
|
|
}
|
|
|
|
// hasSideEffects reports whether evaluation of e has side effects.
|
|
func hasSideEffects(e ast.Expr) bool {
|
|
safe := true
|
|
ast.Inspect(e, func(node ast.Node) bool {
|
|
switch n := node.(type) {
|
|
// Using CallExpr here will catch conversions
|
|
// as well as function and method invocations.
|
|
// We'll live with the false negatives for now.
|
|
case *ast.CallExpr:
|
|
safe = false
|
|
return false
|
|
case *ast.UnaryExpr:
|
|
if n.Op == token.ARROW {
|
|
safe = false
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
})
|
|
return !safe
|
|
}
|
|
|
|
// split returns a slice of all subexpressions in e that are connected by op.
|
|
// For example, given 'a || (b || c) || d' with the or op,
|
|
// split returns []{d, c, b, a}.
|
|
func (op boolOp) split(e ast.Expr) (exprs []ast.Expr) {
|
|
for {
|
|
e = unparen(e)
|
|
if b, ok := e.(*ast.BinaryExpr); ok && b.Op == op.tok {
|
|
exprs = append(exprs, op.split(b.Y)...)
|
|
e = b.X
|
|
} else {
|
|
exprs = append(exprs, e)
|
|
break
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
// unparen returns e with any enclosing parentheses stripped.
|
|
func unparen(e ast.Expr) ast.Expr {
|
|
for {
|
|
p, ok := e.(*ast.ParenExpr)
|
|
if !ok {
|
|
return e
|
|
}
|
|
e = p.X
|
|
}
|
|
}
|