Files
cloudpods/pkg/util/conditionparser/parser.go
Jian Qiu 26089ffde0 fix: make condition eval compatible with go 1.18 (#14534)
* fix: make condition eval compatible with go 1.18

* fix: update circle-ci image to yunion/centos-build:go-1.18.3-0

* update go,mod go version to 1.18

* update vendor

Co-authored-by: Qiu Jian <qiujian@yunionyun.com>
2022-06-28 00:29:56 +08:00

749 lines
18 KiB
Go

// Copyright 2019 Yunion
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package conditionparser
import (
"fmt"
"go/ast"
"go/parser"
"go/token"
"reflect"
"strconv"
"strings"
"yunion.io/x/jsonutils"
"yunion.io/x/pkg/errors"
"yunion.io/x/pkg/utils"
)
var (
ErrInvalidOp = errors.Error("invalid operation")
ErrFieldNotFound = errors.Error("field not found")
ErrOutOfIndex = errors.Error("out of index")
ErrFuncArgument = errors.Error("invalid function arguments")
ErrMethodNotFound = errors.Error("method not found")
)
func IsValid(exprStr string) bool {
_, err := parser.ParseExpr(exprStr)
if err != nil {
return false
}
return true
}
func EvalString(exprStr string, input interface{}) (string, error) {
if len(exprStr) == 0 {
return "", nil
}
expr, err := parser.ParseExpr(exprStr)
if err != nil {
return "", errors.Wrapf(err, "parse expr %s error", exprStr)
}
result, err := eval(expr, input)
if err != nil {
return "", errors.Wrap(err, "eval")
}
switch strVal := result.(type) {
case string:
return strVal, nil
case *jsonutils.JSONString:
return strVal.GetString()
default:
return fmt.Sprintf("%s", strVal), nil
}
}
func EvalBool(exprStr string, input interface{}) (bool, error) {
if len(exprStr) == 0 {
return true, nil
}
expr, err := parser.ParseExpr(exprStr)
if err != nil {
return false, errors.Wrapf(err, "parse expr %s", exprStr)
}
result, err := eval(expr, input)
if err != nil {
return false, errors.Wrap(err, "eval")
}
switch bVal := result.(type) {
case bool:
return bVal, nil
case *jsonutils.JSONBool:
return bVal.Bool()
case []interface{}, *jsonutils.JSONArray:
arrX := getArray(result)
for i := 0; i < len(arrX); i += 1 {
val := getBool(arrX[i])
if val {
return true, nil
}
}
}
return false, nil
}
func eval(expr ast.Expr, input interface{}) (interface{}, error) {
switch expr.(type) {
case *ast.BinaryExpr:
v, err := evalBinary(expr.(*ast.BinaryExpr), input)
if err != nil {
return nil, errors.Wrap(err, "evalBinary")
}
return v, nil
case *ast.UnaryExpr:
v, err := evalUnary(expr.(*ast.UnaryExpr), input)
if err != nil {
return nil, errors.Wrap(err, "evalUnary")
}
return v, nil
case *ast.SelectorExpr:
v, err := evalSelector(expr.(*ast.SelectorExpr), input)
if err != nil {
return nil, errors.Wrap(err, "evalSelector")
}
return v, nil
case *ast.Ident:
v, err := evalIdent(expr.(*ast.Ident), input)
if err != nil {
return nil, errors.Wrap(err, "evalIdent")
}
return v, nil
case *ast.ParenExpr:
v, err := evalParen(expr.(*ast.ParenExpr), input)
if err != nil {
return nil, errors.Wrap(err, "evalParen")
}
return v, nil
case *ast.CallExpr:
v, err := evalCall(expr.(*ast.CallExpr), input)
if err != nil {
return nil, errors.Wrap(err, "evalCall")
}
return v, nil
case *ast.IndexExpr:
v, err := evalIndex(expr.(*ast.IndexExpr), input)
if err != nil {
return nil, errors.Wrap(err, "evalIndex")
}
return v, nil
case *ast.BasicLit:
v, err := evalBasicLit(expr.(*ast.BasicLit), input)
if err != nil {
return nil, errors.Wrap(err, "evalBasicLit")
}
return v, nil
default:
return nil, errors.Wrapf(ErrInvalidOp, "unsupported expr type %s", reflect.TypeOf(expr))
}
}
func evalBasicLit(expr *ast.BasicLit, input interface{}) (interface{}, error) {
switch expr.Kind {
case token.IDENT:
switch input.(type) {
case *jsonutils.JSONDict:
jsonX := input.(*jsonutils.JSONDict)
return jsonX.Get(expr.Value)
default:
return nil, errors.Wrapf(ErrInvalidOp, "evalBasicLit unsupported type %s", reflect.TypeOf(input))
}
case token.INT:
return strconv.Atoi(expr.Value)
case token.FLOAT:
return strconv.ParseFloat(expr.Value, 64)
case token.CHAR:
return expr.Value[1 : len(expr.Value)-1][0], nil
case token.STRING:
return expr.Value[1 : len(expr.Value)-1], nil
default:
return nil, errors.Wrapf(ErrInvalidOp, "evalBasicLit unsupported kind %s", expr.Kind)
}
}
func evalIndex(expr *ast.IndexExpr, input interface{}) (interface{}, error) {
X, err := eval(expr.X, input)
if err != nil {
return nil, err
}
indexV, err := eval(expr.Index, input)
if err != nil {
return nil, err
}
switch X.(type) {
case []interface{}, *jsonutils.JSONArray:
arrX := getArray(X)
indexI := getInt(indexV)
if indexI >= 0 && indexI < int64(len(arrX)) {
return arrX[indexI], nil
} else {
return nil, ErrOutOfIndex
}
case *jsonutils.JSONDict:
jsonX := X.(*jsonutils.JSONDict)
field := getString(indexV)
return getJSONProperty(jsonX, field)
case string:
strX := X.(string)
indexI := getInt(indexV)
if indexI >= 0 && indexI < int64(len(strX)) {
return strX[indexI], nil
} else {
return nil, ErrOutOfIndex
}
default:
return nil, err
}
}
func args2Strings(args []interface{}) []string {
strs := make([]string, len(args))
for i := 0; i < len(args); i += 1 {
strs[i] = getString(args[i])
}
return strs
}
func args2Ints(args []interface{}) []int {
ints := make([]int, len(args))
for i := 0; i < len(args); i += 1 {
ints[i] = int(getInt(args[i]))
}
return ints
}
func evalCallInternal(funcV interface{}, args []interface{}) (interface{}, error) {
switch funcV.(type) {
case []interface{}, *jsonutils.JSONArray:
arrFuncV := getArray(funcV)
ret := make([]interface{}, len(arrFuncV))
for i := 0; i < len(arrFuncV); i += 1 {
reti, err := evalCallInternal(arrFuncV[i], args)
if err != nil {
return nil, err
}
ret[i] = reti
}
return ret, nil
case *funcCaller:
caller := funcV.(*funcCaller)
switch caller.caller.(type) {
case string:
strX := caller.caller.(string)
switch caller.method {
case "startswith":
strArgs := args2Strings(args)
if len(strArgs) != 1 {
return nil, ErrFuncArgument
}
return strings.HasPrefix(strX, strArgs[0]), nil
case "endswith":
strArgs := args2Strings(args)
if len(strArgs) != 1 {
return nil, ErrFuncArgument
}
return strings.HasSuffix(strX, strArgs[0]), nil
case "contains":
strArgs := args2Strings(args)
if len(strArgs) != 1 {
return nil, ErrFuncArgument
}
return strings.Contains(strX, strArgs[0]), nil
case "in":
var strArgs []string
if len(args) == 0 {
return nil, ErrFuncArgument
} else if len(args) == 1 {
switch args[0].(type) {
case string, *jsonutils.JSONString:
strArgs = []string{args[0].(string)}
case []interface{}, *jsonutils.JSONArray:
strArgs = args2Strings(getArray(args[0]))
default:
return nil, ErrFuncArgument
}
} else {
strArgs = args2Strings(args)
}
return utils.IsInStringArray(strX, strArgs), nil
case "len":
if len(args) > 0 {
return nil, ErrFuncArgument
}
return len(strX), nil
case "substr":
intArgs := args2Ints(args)
var o1, o2 int
if len(intArgs) == 1 {
o1 = 0
o2 = intArgs[0]
} else if len(intArgs) == 2 {
o1 = intArgs[0]
o2 = intArgs[1]
} else {
return nil, ErrFuncArgument
}
if o1 < 0 {
o1 = len(strX) + o1
}
if o2 < 0 {
o2 = len(strX) + o2
}
if o1 < 0 || o1 >= len(strX) {
return nil, ErrFuncArgument
}
if o2 <= o1 || o2 > len(strX) {
return nil, ErrFuncArgument
}
return strX[o1:o2], nil
default:
return nil, ErrInvalidOp
}
case *jsonutils.JSONDict:
jsonX := caller.caller.(*jsonutils.JSONDict)
switch caller.method {
case "contains":
strArgs := args2Strings(args)
return jsonX.Contains(strArgs...), nil
case "len":
if len(args) > 0 {
return nil, ErrFuncArgument
}
return jsonX.Size(), nil
case "keys":
if len(args) > 0 {
return nil, ErrFuncArgument
}
return jsonutils.NewStringArray(jsonX.SortedKeys()), nil
default:
return nil, ErrMethodNotFound
}
case []interface{}, *jsonutils.JSONArray:
array := getArray(caller.caller)
switch caller.method {
case "len":
if len(args) > 0 {
return nil, ErrFuncArgument
}
return len(array), nil
case "contains":
if len(args) < 1 {
return nil, ErrFuncArgument
}
for j := 0; j < len(args); j += 1 {
find := false
for i := 0; i < len(array); i += 1 {
findInf, err := evalBinaryInternal(array[i], args[j], token.EQL)
if err != nil {
return nil, err
}
switch findInf.(type) {
case bool:
if findInf.(bool) {
find = true
}
default:
return nil, ErrInvalidOp
}
}
if !find {
return false, nil
}
}
return true, nil
default:
return nil, ErrMethodNotFound
}
default:
return nil, ErrInvalidOp
}
default:
return nil, ErrInvalidOp
}
}
func evalCall(expr *ast.CallExpr, input interface{}) (interface{}, error) {
funcV, err := eval(expr.Fun, input)
if err != nil {
return nil, err
}
args := make([]interface{}, len(expr.Args))
for i := 0; i < len(expr.Args); i += 1 {
args[i], err = eval(expr.Args[i], input)
if err != nil {
return nil, err
}
}
return evalCallInternal(funcV, args)
}
func evalParen(expr *ast.ParenExpr, input interface{}) (interface{}, error) {
return eval(expr.X, input)
}
func getJSONProperty(json *jsonutils.JSONDict, identStr string) (jsonutils.JSONObject, error) {
if json.Contains(identStr) {
return json.Get(identStr)
} else {
identArray := jsonutils.GetArrayOfPrefix(json, identStr)
if len(identArray) > 0 {
return jsonutils.NewArray(identArray...), nil
} else {
return nil, ErrFieldNotFound
}
}
}
func evalIdent(expr *ast.Ident, input interface{}) (interface{}, error) {
if input == nil {
return expr.Name, nil
} else {
switch input.(type) {
case *jsonutils.JSONDict:
json := input.(*jsonutils.JSONDict)
return getJSONProperty(json, expr.Name)
default:
return nil, errors.Wrapf(ErrInvalidOp, "evalIdent unsupported type %s", reflect.TypeOf(input))
}
}
}
type funcCaller struct {
caller interface{}
method string
}
func getArray(X interface{}) []interface{} {
switch X.(type) {
case *jsonutils.JSONArray:
arr := X.(*jsonutils.JSONArray)
ret := make([]interface{}, arr.Size())
for i := 0; i < arr.Size(); i += 1 {
ret[i], _ = arr.GetAt(i)
}
return ret
default:
return X.([]interface{})
}
}
func evalSelectorInternal(X interface{}, identStr string) (interface{}, error) {
switch X.(type) {
case *jsonutils.JSONDict:
json := X.(*jsonutils.JSONDict)
ret, err := getJSONProperty(json, identStr)
if err == ErrFieldNotFound {
return &funcCaller{caller: X, method: identStr}, nil
} else {
return ret, errors.Wrapf(err, "getJSONProperty %s", identStr)
}
case []interface{}, *jsonutils.JSONArray:
if identStr == "len" || identStr == "contains" {
return &funcCaller{caller: X, method: identStr}, nil
}
arrX := getArray(X)
ret := make([]interface{}, len(arrX))
for i := 0; i < len(arrX); i += 1 {
reti, err := evalSelectorInternal(arrX[i], identStr)
if err != nil {
return nil, err
}
ret[i] = reti
}
return ret, nil
case string, *jsonutils.JSONString:
return &funcCaller{caller: getString(X), method: identStr}, nil
default:
return nil, errors.Wrapf(ErrInvalidOp, "unsupported type %s", reflect.TypeOf(X))
}
}
func evalSelector(expr *ast.SelectorExpr, input interface{}) (interface{}, error) {
X, err := eval(expr.X, input)
if err != nil {
return nil, errors.Wrap(ErrInvalidOp, "eval selector X")
}
ident, err := evalIdent(expr.Sel, nil)
if err != nil {
return nil, errors.Wrap(ErrInvalidOp, "eval selector Sel")
}
identStr := ident.(string)
return evalSelectorInternal(X, identStr)
}
func evalUnaryInternal(X interface{}, op token.Token) (interface{}, error) {
switch X.(type) {
case []interface{}, *jsonutils.JSONArray:
arrX := getArray(X)
ret := make([]interface{}, len(arrX))
for i := 0; i < len(arrX); i += 1 {
reti, err := evalUnaryInternal(arrX[i], op)
if err != nil {
return nil, err
}
ret[i] = reti
}
return ret, nil
case bool, *jsonutils.JSONBool:
boolX := getBool(X)
switch op {
case token.NOT:
return !boolX, nil
default:
return nil, ErrInvalidOp
}
case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, *jsonutils.JSONInt:
intX := getInt(X)
switch op {
case token.SUB:
return -intX, nil
default:
return nil, ErrInvalidOp
}
case float32, float64, *jsonutils.JSONFloat:
floatX := getFloat(X)
switch op {
case token.SUB:
return -floatX, nil
default:
return nil, ErrInvalidOp
}
default:
return nil, ErrInvalidOp
}
}
func evalUnary(expr *ast.UnaryExpr, input interface{}) (interface{}, error) {
X, err := eval(expr.X, input)
if err != nil {
return nil, err
}
op := expr.Op
return evalUnaryInternal(X, op)
}
func getString(val interface{}) string {
switch val.(type) {
case *jsonutils.JSONString:
jsonVal, _ := val.(*jsonutils.JSONString).GetString()
return jsonVal
default:
return val.(string)
}
}
func getBool(val interface{}) bool {
switch val.(type) {
case *jsonutils.JSONBool:
jsonVal, _ := val.(*jsonutils.JSONBool).Bool()
return jsonVal
default:
return val.(bool)
}
}
func getInt(val interface{}) int64 {
switch val.(type) {
case *jsonutils.JSONInt:
jsonVal, _ := val.(*jsonutils.JSONInt).Int()
return jsonVal
default:
return reflect.ValueOf(val).Int()
}
}
func getFloat(val interface{}) float64 {
switch val.(type) {
case *jsonutils.JSONFloat:
jsonVal, _ := val.(*jsonutils.JSONFloat).Float()
return jsonVal
default:
return reflect.ValueOf(val).Float()
}
}
func evalBinaryInternal(X, Y interface{}, op token.Token) (interface{}, error) {
switch X.(type) {
case []interface{}, *jsonutils.JSONArray:
arrX := getArray(X)
ret := make([]interface{}, len(arrX))
for i := 0; i < len(arrX); i += 1 {
reti, err := evalBinaryInternal(arrX[i], Y, op)
if err != nil {
return nil, errors.Wrapf(err, "X.evalBinaryInternal at [%d]", i)
}
ret[i] = reti
}
return ret, nil
}
switch Y.(type) {
case []interface{}, *jsonutils.JSONArray:
arrY := getArray(Y)
ret := make([]interface{}, len(arrY))
for i := 0; i < len(arrY); i += 1 {
reti, err := evalBinaryInternal(X, arrY[i], op)
if err != nil {
return nil, errors.Wrapf(err, "Y.evalBinaryInternal at [%d]", i)
}
ret[i] = reti
}
return ret, nil
}
switch X.(type) {
case bool, *jsonutils.JSONBool:
switch Y.(type) {
case bool, *jsonutils.JSONBool:
boolX := getBool(X)
boolY := getBool(Y)
switch op {
case token.LAND:
return boolX && boolY, nil
case token.LOR:
return boolX || boolY, nil
default:
return nil, errors.Wrapf(ErrInvalidOp, "bool op %v", op)
}
default:
return nil, errors.Wrap(ErrInvalidOp, "mismatch bool type")
}
case string, *jsonutils.JSONString:
switch Y.(type) {
case string, *jsonutils.JSONString:
strX := getString(X)
strY := getString(Y)
switch op {
case token.ADD:
return strX + strY, nil
case token.EQL:
return strX == strY, nil
case token.NEQ:
return strX != strY, nil
default:
return nil, errors.Wrapf(ErrInvalidOp, "string op %v", op)
}
default:
return nil, errors.Wrap(ErrInvalidOp, "mismatch string type")
}
case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, *jsonutils.JSONInt:
intX := getInt(X)
switch Y.(type) {
case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, *jsonutils.JSONInt:
intY := getInt(Y)
return evalIntegerOp(intX, intY, op)
case float32, float64, jsonutils.JSONFloat:
floatX := float64(intX)
floatY := getFloat(Y)
return evalFloatOp(floatX, floatY, op)
default:
return nil, errors.Wrap(ErrInvalidOp, "mismatch type int")
}
case float32, float64, *jsonutils.JSONFloat:
floatX := getFloat(X)
switch Y.(type) {
case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, *jsonutils.JSONInt:
floatY := float64(getInt(Y))
return evalFloatOp(floatX, floatY, op)
case float32, float64, *jsonutils.JSONFloat:
floatY := getFloat(Y)
return evalFloatOp(floatX, floatY, op)
default:
return nil, errors.Wrap(ErrInvalidOp, "mismatch type float")
}
default:
return nil, errors.Wrapf(ErrInvalidOp, "unsupported op type %v", reflect.TypeOf(X))
}
}
func evalBinary(bExpr *ast.BinaryExpr, input interface{}) (interface{}, error) {
X, err := eval(bExpr.X, input)
if err != nil {
return nil, errors.Wrap(err, "eval binary left")
}
Y, err := eval(bExpr.Y, input)
if err != nil {
return nil, errors.Wrap(err, "eval binary right")
}
return evalBinaryInternal(X, Y, bExpr.Op)
}
func evalIntegerOp(X, Y int64, op token.Token) (interface{}, error) {
switch op {
case token.ADD:
return X + Y, nil
case token.SUB:
return X - Y, nil
case token.MUL:
return X * Y, nil
case token.QUO:
return X / Y, nil
case token.REM:
return X % Y, nil
case token.AND:
return X & Y, nil
case token.OR:
return X | Y, nil
case token.XOR:
return X ^ Y, nil
case token.SHL:
return X << uint64(Y), nil
case token.SHR:
return X >> uint64(Y), nil
case token.AND_NOT:
return X &^ Y, nil
case token.EQL:
return X == Y, nil
case token.LSS:
return X < Y, nil
case token.GTR:
return X > Y, nil
case token.NEQ:
return X != Y, nil
case token.LEQ:
return X <= Y, nil
case token.GEQ:
return X >= Y, nil
default:
return nil, ErrInvalidOp
}
}
func evalFloatOp(X, Y float64, op token.Token) (interface{}, error) {
switch op {
case token.ADD:
return X + Y, nil
case token.SUB:
return X - Y, nil
case token.MUL:
return X * Y, nil
case token.QUO:
return X / Y, nil
case token.EQL:
return X == Y, nil
case token.LSS:
return X < Y, nil
case token.GTR:
return X > Y, nil
case token.NEQ:
return X != Y, nil
case token.LEQ:
return X <= Y, nil
case token.GEQ:
return X >= Y, nil
default:
return nil, ErrInvalidOp
}
}