status-go/vendor/github.com/hashicorp/go-bexpr/evaluate.go

310 lines
9.2 KiB
Go
Raw Normal View History

2022-10-25 14:25:08 +00:00
package bexpr
import (
"encoding/json"
"errors"
"fmt"
"reflect"
"regexp"
"strconv"
"strings"
"github.com/hashicorp/go-bexpr/grammar"
"github.com/mitchellh/pointerstructure"
)
var byteSliceTyp reflect.Type = reflect.TypeOf([]byte{})
func primitiveEqualityFn(kind reflect.Kind) func(first interface{}, second reflect.Value) bool {
switch kind {
case reflect.Bool:
return doEqualBool
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return doEqualInt64
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
return doEqualUint64
case reflect.Float32:
return doEqualFloat32
case reflect.Float64:
return doEqualFloat64
case reflect.String:
return doEqualString
default:
return nil
}
}
func doEqualBool(first interface{}, second reflect.Value) bool {
return first.(bool) == second.Bool()
}
func doEqualInt64(first interface{}, second reflect.Value) bool {
return first.(int64) == second.Int()
}
func doEqualUint64(first interface{}, second reflect.Value) bool {
return first.(uint64) == second.Uint()
}
func doEqualFloat32(first interface{}, second reflect.Value) bool {
return first.(float32) == float32(second.Float())
}
func doEqualFloat64(first interface{}, second reflect.Value) bool {
return first.(float64) == second.Float()
}
func doEqualString(first interface{}, second reflect.Value) bool {
return first.(string) == second.String()
}
// Get rid of 0 to many levels of pointers to get at the real type
func derefType(rtype reflect.Type) reflect.Type {
for rtype.Kind() == reflect.Ptr {
rtype = rtype.Elem()
}
return rtype
}
func doMatchMatches(expression *grammar.MatchExpression, value reflect.Value) (bool, error) {
if !value.Type().ConvertibleTo(byteSliceTyp) {
return false, fmt.Errorf("Value of type %s is not convertible to []byte", value.Type())
}
var re *regexp.Regexp
var ok bool
if expression.Value.Converted != nil {
re, ok = expression.Value.Converted.(*regexp.Regexp)
}
if !ok || re == nil {
var err error
re, err = regexp.Compile(expression.Value.Raw)
if err != nil {
return false, fmt.Errorf("Failed to compile regular expression %q: %v", expression.Value.Raw, err)
}
expression.Value.Converted = re
}
return re.Match(value.Convert(byteSliceTyp).Interface().([]byte)), nil
}
func doMatchEqual(expression *grammar.MatchExpression, value reflect.Value) (bool, error) {
// NOTE: see preconditions in evaluategrammar.MatchExpressionRecurse
eqFn := primitiveEqualityFn(value.Kind())
if eqFn == nil {
return false, errors.New("unable to find suitable primitive comparison function for matching")
}
matchValue, err := getMatchExprValue(expression, value.Kind())
if err != nil {
return false, fmt.Errorf("error getting match value in expression: %w", err)
}
return eqFn(matchValue, value), nil
}
func doMatchIn(expression *grammar.MatchExpression, value reflect.Value) (bool, error) {
matchValue, err := getMatchExprValue(expression, value.Kind())
if err != nil {
return false, fmt.Errorf("error getting match value in expression: %w", err)
}
switch kind := value.Kind(); kind {
case reflect.Map:
found := value.MapIndex(reflect.ValueOf(matchValue))
return found.IsValid(), nil
case reflect.Slice, reflect.Array:
itemType := derefType(value.Type().Elem())
kind := itemType.Kind()
switch kind {
case reflect.Interface:
// If it's an interface, that is, the type was []interface{}, we
// have to treat each element individually, checking each element's
// type/kind and rederiving the match value.
for i := 0; i < value.Len(); i++ {
item := value.Index(i).Elem()
itemType := derefType(item.Type())
kind := itemType.Kind()
// We need to special case errors here. The reason is that in an
// interface slice there can be a mix/match of types, but the
// coerce functions expect a certain type. So the expression
// passed in might be `"true" in "/my/slice"` but the value it's
// checking against might be an integer, thus it will try to
// coerce "true" to an integer and fail. However, all of the
// functions use strconv which has a specific error type for
// syntax errors, so as a special case in this situation, don't
// error on a strconv.ErrSyntax, just continue on to the next
// element.
matchValue, err = getMatchExprValue(expression, kind)
if err != nil {
if errors.Is(err, strconv.ErrSyntax) {
continue
}
return false, errors.New(`error getting interface slice match value in expression`)
}
eqFn := primitiveEqualityFn(kind)
if eqFn == nil {
return false, fmt.Errorf(`unable to find suitable primitive comparison function for "in" comparison in interface slice: %s`, kind)
}
// the value will be the correct type as we verified the itemType
if eqFn(matchValue, reflect.Indirect(item)) {
return true, nil
}
}
return false, nil
default:
// Otherwise it's a concrete type and we can essentially cache the
// answers. First we need to re-derive the match value for equality
// assertion.
matchValue, err = getMatchExprValue(expression, kind)
if err != nil {
return false, fmt.Errorf("error getting match value in expression: %w", err)
}
eqFn := primitiveEqualityFn(kind)
if eqFn == nil {
return false, errors.New(`unable to find suitable primitive comparison function for "in" comparison`)
}
for i := 0; i < value.Len(); i++ {
item := value.Index(i)
// the value will be the correct type as we verified the itemType
if eqFn(matchValue, reflect.Indirect(item)) {
return true, nil
}
}
return false, nil
}
case reflect.String:
return strings.Contains(value.String(), matchValue.(string)), nil
default:
return false, fmt.Errorf("Cannot perform in/contains operations on type %s for selector: %q", kind, expression.Selector)
}
}
func doMatchIsEmpty(matcher *grammar.MatchExpression, value reflect.Value) (bool, error) {
// NOTE: see preconditions in evaluategrammar.MatchExpressionRecurse
return value.Len() == 0, nil
}
func getMatchExprValue(expression *grammar.MatchExpression, rvalue reflect.Kind) (interface{}, error) {
if expression.Value == nil {
return nil, nil
}
switch rvalue {
case reflect.Bool:
return CoerceBool(expression.Value.Raw)
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return CoerceInt64(expression.Value.Raw)
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
return CoerceUint64(expression.Value.Raw)
case reflect.Float32:
return CoerceFloat32(expression.Value.Raw)
case reflect.Float64:
return CoerceFloat64(expression.Value.Raw)
default:
return expression.Value.Raw, nil
}
}
func evaluateMatchExpression(expression *grammar.MatchExpression, datum interface{}, opt ...Option) (bool, error) {
opts := getOpts(opt...)
ptr := pointerstructure.Pointer{
Parts: expression.Selector.Path,
Config: pointerstructure.Config{
TagName: opts.withTagName,
ValueTransformationHook: opts.withHookFn,
},
}
val, err := ptr.Get(datum)
if err != nil {
return false, fmt.Errorf("error finding value in datum: %w", err)
}
if jn, ok := val.(json.Number); ok {
if jni, err := jn.Int64(); err == nil {
val = jni
} else if jnf, err := jn.Float64(); err == nil {
val = jnf
} else {
return false, fmt.Errorf("unable to convert json number %s to int or float", jn)
}
}
rvalue := reflect.Indirect(reflect.ValueOf(val))
switch expression.Operator {
case grammar.MatchEqual:
return doMatchEqual(expression, rvalue)
case grammar.MatchNotEqual:
result, err := doMatchEqual(expression, rvalue)
if err == nil {
return !result, nil
}
return false, err
case grammar.MatchIn:
return doMatchIn(expression, rvalue)
case grammar.MatchNotIn:
result, err := doMatchIn(expression, rvalue)
if err == nil {
return !result, nil
}
return false, err
case grammar.MatchIsEmpty:
return doMatchIsEmpty(expression, rvalue)
case grammar.MatchIsNotEmpty:
result, err := doMatchIsEmpty(expression, rvalue)
if err == nil {
return !result, nil
}
return false, err
case grammar.MatchMatches:
return doMatchMatches(expression, rvalue)
case grammar.MatchNotMatches:
result, err := doMatchMatches(expression, rvalue)
if err == nil {
return !result, nil
}
return false, err
default:
return false, fmt.Errorf("Invalid match operation: %d", expression.Operator)
}
}
func evaluate(ast grammar.Expression, datum interface{}, opt ...Option) (bool, error) {
switch node := ast.(type) {
case *grammar.UnaryExpression:
switch node.Operator {
case grammar.UnaryOpNot:
result, err := evaluate(node.Operand, datum, opt...)
return !result, err
}
case *grammar.BinaryExpression:
switch node.Operator {
case grammar.BinaryOpAnd:
result, err := evaluate(node.Left, datum, opt...)
if err != nil || !result {
return result, err
}
return evaluate(node.Right, datum, opt...)
case grammar.BinaryOpOr:
result, err := evaluate(node.Left, datum, opt...)
if err != nil || result {
return result, err
}
return evaluate(node.Right, datum, opt...)
}
case *grammar.MatchExpression:
return evaluateMatchExpression(node, datum, opt...)
}
return false, fmt.Errorf("Invalid AST node")
}