rebase: bump the golang-dependencies group with 1 update

Bumps the golang-dependencies group with 1 update: [golang.org/x/crypto](https://github.com/golang/crypto).


Updates `golang.org/x/crypto` from 0.16.0 to 0.17.0
- [Commits](https://github.com/golang/crypto/compare/v0.16.0...v0.17.0)

---
updated-dependencies:
- dependency-name: golang.org/x/crypto
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: golang-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
This commit is contained in:
dependabot[bot]
2023-12-18 20:31:00 +00:00
committed by mergify[bot]
parent 1ad79314f9
commit e5d9b68d36
398 changed files with 33924 additions and 10753 deletions

View File

@ -25,13 +25,14 @@ go_library(
importpath = "github.com/google/cel-go/interpreter",
deps = [
"//common:go_default_library",
"//common/ast:go_default_library",
"//common/containers:go_default_library",
"//common/functions:go_default_library",
"//common/operators:go_default_library",
"//common/overloads:go_default_library",
"//common/types:go_default_library",
"//common/types/ref:go_default_library",
"//common/types/traits:go_default_library",
"//interpreter/functions:go_default_library",
"@org_golang_google_genproto_googleapis_api//expr/v1alpha1:go_default_library",
"@org_golang_google_protobuf//proto:go_default_library",
"@org_golang_google_protobuf//types/known/durationpb:go_default_library",
@ -56,12 +57,13 @@ go_test(
],
deps = [
"//checker:go_default_library",
"//checker/decls:go_default_library",
"//common/containers:go_default_library",
"//common/debug:go_default_library",
"//common/decls:go_default_library",
"//common/functions:go_default_library",
"//common/operators:go_default_library",
"//common/stdlib:go_default_library",
"//common/types:go_default_library",
"//interpreter/functions:go_default_library",
"//parser:go_default_library",
"//test:go_default_library",
"//test/proto2pb:go_default_library",

View File

@ -58,7 +58,7 @@ func (emptyActivation) Parent() Activation { return nil }
// The output of the lazy binding will overwrite the variable reference in the internal map.
//
// Values which are not represented as ref.Val types on input may be adapted to a ref.Val using
// the ref.TypeAdapter configured in the environment.
// the types.Adapter configured in the environment.
func NewActivation(bindings any) (Activation, error) {
if bindings == nil {
return nil, errors.New("bindings must be non-nil")

View File

@ -15,6 +15,8 @@
package interpreter
import (
"fmt"
"github.com/google/cel-go/common/containers"
"github.com/google/cel-go/common/types"
"github.com/google/cel-go/common/types/ref"
@ -177,8 +179,8 @@ func numericValueEquals(value any, celValue ref.Val) bool {
// NewPartialAttributeFactory returns an AttributeFactory implementation capable of performing
// AttributePattern matches with PartialActivation inputs.
func NewPartialAttributeFactory(container *containers.Container,
adapter ref.TypeAdapter,
provider ref.TypeProvider) AttributeFactory {
adapter types.Adapter,
provider types.Provider) AttributeFactory {
fac := NewAttributeFactory(container, adapter, provider)
return &partialAttributeFactory{
AttributeFactory: fac,
@ -191,8 +193,8 @@ func NewPartialAttributeFactory(container *containers.Container,
type partialAttributeFactory struct {
AttributeFactory
container *containers.Container
adapter ref.TypeAdapter
provider ref.TypeProvider
adapter types.Adapter
provider types.Provider
}
// AbsoluteAttribute implementation of the AttributeFactory interface which wraps the
@ -241,12 +243,15 @@ func (fac *partialAttributeFactory) matchesUnknownPatterns(
vars PartialActivation,
attrID int64,
variableNames []string,
qualifiers []Qualifier) (types.Unknown, error) {
qualifiers []Qualifier) (*types.Unknown, error) {
patterns := vars.UnknownAttributePatterns()
candidateIndices := map[int]struct{}{}
for _, variable := range variableNames {
for i, pat := range patterns {
if pat.VariableMatches(variable) {
if len(qualifiers) == 0 {
return types.NewUnknown(attrID, types.NewAttributeTrail(variable)), nil
}
candidateIndices[i] = struct{}{}
}
}
@ -255,10 +260,6 @@ func (fac *partialAttributeFactory) matchesUnknownPatterns(
if len(candidateIndices) == 0 {
return nil, nil
}
// Determine whether to return early if there are no qualifiers.
if len(qualifiers) == 0 {
return types.Unknown{attrID}, nil
}
// Resolve the attribute qualifiers into a static set. This prevents more dynamic
// Attribute resolutions than necessary when there are multiple unknown patterns
// that traverse the same Attribute-based qualifier field.
@ -300,7 +301,28 @@ func (fac *partialAttributeFactory) matchesUnknownPatterns(
}
}
if isUnk {
return types.Unknown{matchExprID}, nil
attr := types.NewAttributeTrail(pat.variable)
for i := 0; i < len(qualPats) && i < len(newQuals); i++ {
if qual, ok := newQuals[i].(ConstantQualifier); ok {
switch v := qual.Value().Value().(type) {
case bool:
types.QualifyAttribute[bool](attr, v)
case float64:
types.QualifyAttribute[int64](attr, int64(v))
case int64:
types.QualifyAttribute[int64](attr, v)
case string:
types.QualifyAttribute[string](attr, v)
case uint64:
types.QualifyAttribute[uint64](attr, v)
default:
types.QualifyAttribute[string](attr, fmt.Sprintf("%v", v))
}
} else {
types.QualifyAttribute[string](attr, "*")
}
}
return types.NewUnknown(matchExprID, attr), nil
}
}
return nil, nil

View File

@ -22,8 +22,6 @@ import (
"github.com/google/cel-go/common/types"
"github.com/google/cel-go/common/types/ref"
"github.com/google/cel-go/common/types/traits"
exprpb "google.golang.org/genproto/googleapis/api/expr/v1alpha1"
)
// AttributeFactory provides methods creating Attribute and Qualifier values.
@ -61,7 +59,7 @@ type AttributeFactory interface {
// The qualifier may consider the object type being qualified, if present. If absent, the
// qualification should be considered dynamic and the qualification should still work, though
// it may be sub-optimal.
NewQualifier(objType *exprpb.Type, qualID int64, val any, opt bool) (Qualifier, error)
NewQualifier(objType *types.Type, qualID int64, val any, opt bool) (Qualifier, error)
}
// Qualifier marker interface for designating different qualifier values and where they appear
@ -131,7 +129,7 @@ type NamespacedAttribute interface {
// NewAttributeFactory returns a default AttributeFactory which is produces Attribute values
// capable of resolving types by simple names and qualify the values using the supported qualifier
// types: bool, int, string, and uint.
func NewAttributeFactory(cont *containers.Container, a ref.TypeAdapter, p ref.TypeProvider) AttributeFactory {
func NewAttributeFactory(cont *containers.Container, a types.Adapter, p types.Provider) AttributeFactory {
return &attrFactory{
container: cont,
adapter: a,
@ -141,8 +139,8 @@ func NewAttributeFactory(cont *containers.Container, a ref.TypeAdapter, p ref.Ty
type attrFactory struct {
container *containers.Container
adapter ref.TypeAdapter
provider ref.TypeProvider
adapter types.Adapter
provider types.Provider
}
// AbsoluteAttribute refers to a variable value and an optional qualifier path.
@ -199,13 +197,13 @@ func (r *attrFactory) RelativeAttribute(id int64, operand Interpretable) Attribu
}
// NewQualifier is an implementation of the AttributeFactory interface.
func (r *attrFactory) NewQualifier(objType *exprpb.Type, qualID int64, val any, opt bool) (Qualifier, error) {
func (r *attrFactory) NewQualifier(objType *types.Type, qualID int64, val any, opt bool) (Qualifier, error) {
// Before creating a new qualifier check to see if this is a protobuf message field access.
// If so, use the precomputed GetFrom qualification method rather than the standard
// stringQualifier.
str, isStr := val.(string)
if isStr && objType != nil && objType.GetMessageType() != "" {
ft, found := r.provider.FindFieldType(objType.GetMessageType(), str)
if isStr && objType != nil && objType.Kind() == types.StructKind {
ft, found := r.provider.FindStructFieldType(objType.TypeName(), str)
if found && ft.IsSet != nil && ft.GetFrom != nil {
return &fieldQualifier{
id: qualID,
@ -225,8 +223,8 @@ type absoluteAttribute struct {
// (package) of the expression.
namespaceNames []string
qualifiers []Qualifier
adapter ref.TypeAdapter
provider ref.TypeProvider
adapter types.Adapter
provider types.Provider
fac AttributeFactory
}
@ -325,7 +323,7 @@ type conditionalAttribute struct {
expr Interpretable
truthy Attribute
falsy Attribute
adapter ref.TypeAdapter
adapter types.Adapter
fac AttributeFactory
}
@ -393,8 +391,8 @@ func (a *conditionalAttribute) String() string {
type maybeAttribute struct {
id int64
attrs []NamespacedAttribute
adapter ref.TypeAdapter
provider ref.TypeProvider
adapter types.Adapter
provider types.Provider
fac AttributeFactory
}
@ -511,7 +509,7 @@ type relativeAttribute struct {
id int64
operand Interpretable
qualifiers []Qualifier
adapter ref.TypeAdapter
adapter types.Adapter
fac AttributeFactory
}
@ -576,7 +574,7 @@ func (a *relativeAttribute) String() string {
return fmt.Sprintf("id: %v, operand: %v", a.id, a.operand)
}
func newQualifier(adapter ref.TypeAdapter, id int64, v any, opt bool) (Qualifier, error) {
func newQualifier(adapter types.Adapter, id int64, v any, opt bool) (Qualifier, error) {
var qual Qualifier
switch val := v.(type) {
case Attribute:
@ -657,7 +655,7 @@ func newQualifier(adapter ref.TypeAdapter, id int64, v any, opt bool) (Qualifier
qual = &doubleQualifier{
id: id, value: float64(val), celValue: val, adapter: adapter, optional: opt,
}
case types.Unknown:
case *types.Unknown:
qual = &unknownQualifier{id: id, value: val}
default:
if q, ok := v.(Qualifier); ok {
@ -689,7 +687,7 @@ type stringQualifier struct {
id int64
value string
celValue ref.Val
adapter ref.TypeAdapter
adapter types.Adapter
optional bool
}
@ -790,7 +788,7 @@ type intQualifier struct {
id int64
value int64
celValue ref.Val
adapter ref.TypeAdapter
adapter types.Adapter
optional bool
}
@ -917,7 +915,7 @@ type uintQualifier struct {
id int64
value uint64
celValue ref.Val
adapter ref.TypeAdapter
adapter types.Adapter
optional bool
}
@ -982,7 +980,7 @@ type boolQualifier struct {
id int64
value bool
celValue ref.Val
adapter ref.TypeAdapter
adapter types.Adapter
optional bool
}
@ -1035,8 +1033,8 @@ func (q *boolQualifier) Value() ref.Val {
type fieldQualifier struct {
id int64
Name string
FieldType *ref.FieldType
adapter ref.TypeAdapter
FieldType *types.FieldType
adapter types.Adapter
optional bool
}
@ -1094,7 +1092,7 @@ type doubleQualifier struct {
id int64
value float64
celValue ref.Val
adapter ref.TypeAdapter
adapter types.Adapter
optional bool
}
@ -1131,7 +1129,7 @@ func (q *doubleQualifier) Value() ref.Val {
// for any value subject to qualification. This is consistent with CEL's unknown handling elsewhere.
type unknownQualifier struct {
id int64
value types.Unknown
value *types.Unknown
}
// ID is an implementation of the Qualifier interface method.
@ -1225,10 +1223,10 @@ func attrQualifyIfPresent(fac AttributeFactory, vars Activation, obj any, qualAt
// refQualify attempts to convert the value to a CEL value and then uses reflection methods to try and
// apply the qualifier with the option to presence test field accesses before retrieving field values.
func refQualify(adapter ref.TypeAdapter, obj any, idx ref.Val, presenceTest, presenceOnly bool) (ref.Val, bool, error) {
func refQualify(adapter types.Adapter, obj any, idx ref.Val, presenceTest, presenceOnly bool) (ref.Val, bool, error) {
celVal := adapter.NativeToValue(obj)
switch v := celVal.(type) {
case types.Unknown:
case *types.Unknown:
return v, true, nil
case *types.Err:
return nil, false, v

View File

@ -75,15 +75,13 @@ func decDisableShortcircuits() InterpretableDecorator {
switch expr := i.(type) {
case *evalOr:
return &evalExhaustiveOr{
id: expr.id,
lhs: expr.lhs,
rhs: expr.rhs,
id: expr.id,
terms: expr.terms,
}, nil
case *evalAnd:
return &evalExhaustiveAnd{
id: expr.id,
lhs: expr.lhs,
rhs: expr.rhs,
id: expr.id,
terms: expr.terms,
}, nil
case *evalFold:
expr.exhaustive = true

View File

@ -17,7 +17,7 @@ package interpreter
import (
"fmt"
"github.com/google/cel-go/interpreter/functions"
"github.com/google/cel-go/common/functions"
)
// Dispatcher resolves function calls to their appropriate overload.

View File

@ -66,7 +66,11 @@ func (s *evalState) Value(exprID int64) (ref.Val, bool) {
// SetValue is an implementation of the EvalState interface method.
func (s *evalState) SetValue(exprID int64, val ref.Val) {
s.values[exprID] = val
if val == nil {
delete(s.values, exprID)
} else {
s.values[exprID] = val
}
}
// Reset implements the EvalState interface method.

View File

@ -25,7 +25,7 @@ import (
"github.com/google/cel-go/common/types/ref"
)
type typeVerifier func(int64, ...*types.TypeValue) (bool, error)
type typeVerifier func(int64, ...ref.Type) (bool, error)
// InterpolateFormattedString checks the syntax and cardinality of any string.format calls present in the expression and reports
// any errors at compile time.

View File

@ -7,16 +7,11 @@ package(
go_library(
name = "go_default_library",
srcs = [
srcs = [
"functions.go",
"standard.go",
],
importpath = "github.com/google/cel-go/interpreter/functions",
deps = [
"//common/operators:go_default_library",
"//common/overloads:go_default_library",
"//common/types:go_default_library",
"//common/types/ref:go_default_library",
"//common/types/traits:go_default_library",
"//common/functions:go_default_library",
],
)

View File

@ -16,7 +16,7 @@
// interpreter and as declared within the checker#StandardDeclarations.
package functions
import "github.com/google/cel-go/common/types/ref"
import fn "github.com/google/cel-go/common/functions"
// Overload defines a named overload of a function, indicating an operand trait
// which must be present on the first argument to the overload as well as one
@ -26,37 +26,14 @@ import "github.com/google/cel-go/common/types/ref"
// and the specializations simplify the call contract for implementers of
// types with operator overloads. Any added complexity is assumed to be handled
// by the generic FunctionOp.
type Overload struct {
// Operator name as written in an expression or defined within
// operators.go.
Operator string
// Operand trait used to dispatch the call. The zero-value indicates a
// global function overload or that one of the Unary / Binary / Function
// definitions should be used to execute the call.
OperandTrait int
// Unary defines the overload with a UnaryOp implementation. May be nil.
Unary UnaryOp
// Binary defines the overload with a BinaryOp implementation. May be nil.
Binary BinaryOp
// Function defines the overload with a FunctionOp implementation. May be
// nil.
Function FunctionOp
// NonStrict specifies whether the Overload will tolerate arguments that
// are types.Err or types.Unknown.
NonStrict bool
}
type Overload = fn.Overload
// UnaryOp is a function that takes a single value and produces an output.
type UnaryOp func(value ref.Val) ref.Val
type UnaryOp = fn.UnaryOp
// BinaryOp is a function that takes two values and produces an output.
type BinaryOp func(lhs ref.Val, rhs ref.Val) ref.Val
type BinaryOp = fn.BinaryOp
// FunctionOp is a function with accepts zero or more arguments and produces
// a value or error as a result.
type FunctionOp func(values ...ref.Val) ref.Val
type FunctionOp = fn.FunctionOp

View File

@ -1,270 +0,0 @@
// Copyright 2018 Google LLC
//
// 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 functions
import (
"github.com/google/cel-go/common/operators"
"github.com/google/cel-go/common/overloads"
"github.com/google/cel-go/common/types"
"github.com/google/cel-go/common/types/ref"
"github.com/google/cel-go/common/types/traits"
)
// StandardOverloads returns the definitions of the built-in overloads.
func StandardOverloads() []*Overload {
return []*Overload{
// Logical not (!a)
{
Operator: operators.LogicalNot,
OperandTrait: traits.NegatorType,
Unary: func(value ref.Val) ref.Val {
if !types.IsBool(value) {
return types.ValOrErr(value, "no such overload")
}
return value.(traits.Negater).Negate()
}},
// Not strictly false: IsBool(a) ? a : true
{
Operator: operators.NotStrictlyFalse,
Unary: notStrictlyFalse},
// Deprecated: not strictly false, may be overridden in the environment.
{
Operator: operators.OldNotStrictlyFalse,
Unary: notStrictlyFalse},
// Less than operator
{Operator: operators.Less,
OperandTrait: traits.ComparerType,
Binary: func(lhs ref.Val, rhs ref.Val) ref.Val {
cmp := lhs.(traits.Comparer).Compare(rhs)
if cmp == types.IntNegOne {
return types.True
}
if cmp == types.IntOne || cmp == types.IntZero {
return types.False
}
return cmp
}},
// Less than or equal operator
{Operator: operators.LessEquals,
OperandTrait: traits.ComparerType,
Binary: func(lhs ref.Val, rhs ref.Val) ref.Val {
cmp := lhs.(traits.Comparer).Compare(rhs)
if cmp == types.IntNegOne || cmp == types.IntZero {
return types.True
}
if cmp == types.IntOne {
return types.False
}
return cmp
}},
// Greater than operator
{Operator: operators.Greater,
OperandTrait: traits.ComparerType,
Binary: func(lhs ref.Val, rhs ref.Val) ref.Val {
cmp := lhs.(traits.Comparer).Compare(rhs)
if cmp == types.IntOne {
return types.True
}
if cmp == types.IntNegOne || cmp == types.IntZero {
return types.False
}
return cmp
}},
// Greater than equal operators
{Operator: operators.GreaterEquals,
OperandTrait: traits.ComparerType,
Binary: func(lhs ref.Val, rhs ref.Val) ref.Val {
cmp := lhs.(traits.Comparer).Compare(rhs)
if cmp == types.IntOne || cmp == types.IntZero {
return types.True
}
if cmp == types.IntNegOne {
return types.False
}
return cmp
}},
// Add operator
{Operator: operators.Add,
OperandTrait: traits.AdderType,
Binary: func(lhs ref.Val, rhs ref.Val) ref.Val {
return lhs.(traits.Adder).Add(rhs)
}},
// Subtract operators
{Operator: operators.Subtract,
OperandTrait: traits.SubtractorType,
Binary: func(lhs ref.Val, rhs ref.Val) ref.Val {
return lhs.(traits.Subtractor).Subtract(rhs)
}},
// Multiply operator
{Operator: operators.Multiply,
OperandTrait: traits.MultiplierType,
Binary: func(lhs ref.Val, rhs ref.Val) ref.Val {
return lhs.(traits.Multiplier).Multiply(rhs)
}},
// Divide operator
{Operator: operators.Divide,
OperandTrait: traits.DividerType,
Binary: func(lhs ref.Val, rhs ref.Val) ref.Val {
return lhs.(traits.Divider).Divide(rhs)
}},
// Modulo operator
{Operator: operators.Modulo,
OperandTrait: traits.ModderType,
Binary: func(lhs ref.Val, rhs ref.Val) ref.Val {
return lhs.(traits.Modder).Modulo(rhs)
}},
// Negate operator
{Operator: operators.Negate,
OperandTrait: traits.NegatorType,
Unary: func(value ref.Val) ref.Val {
if types.IsBool(value) {
return types.ValOrErr(value, "no such overload")
}
return value.(traits.Negater).Negate()
}},
// Index operator
{Operator: operators.Index,
OperandTrait: traits.IndexerType,
Binary: func(lhs ref.Val, rhs ref.Val) ref.Val {
return lhs.(traits.Indexer).Get(rhs)
}},
// Size function
{Operator: overloads.Size,
OperandTrait: traits.SizerType,
Unary: func(value ref.Val) ref.Val {
return value.(traits.Sizer).Size()
}},
// In operator
{Operator: operators.In, Binary: inAggregate},
// Deprecated: in operator, may be overridden in the environment.
{Operator: operators.OldIn, Binary: inAggregate},
// Matches function
{Operator: overloads.Matches,
OperandTrait: traits.MatcherType,
Binary: func(lhs ref.Val, rhs ref.Val) ref.Val {
return lhs.(traits.Matcher).Match(rhs)
}},
// Type conversion functions
// TODO: verify type conversion safety of numeric values.
// Int conversions.
{Operator: overloads.TypeConvertInt,
Unary: func(value ref.Val) ref.Val {
return value.ConvertToType(types.IntType)
}},
// Uint conversions.
{Operator: overloads.TypeConvertUint,
Unary: func(value ref.Val) ref.Val {
return value.ConvertToType(types.UintType)
}},
// Double conversions.
{Operator: overloads.TypeConvertDouble,
Unary: func(value ref.Val) ref.Val {
return value.ConvertToType(types.DoubleType)
}},
// Bool conversions.
{Operator: overloads.TypeConvertBool,
Unary: func(value ref.Val) ref.Val {
return value.ConvertToType(types.BoolType)
}},
// Bytes conversions.
{Operator: overloads.TypeConvertBytes,
Unary: func(value ref.Val) ref.Val {
return value.ConvertToType(types.BytesType)
}},
// String conversions.
{Operator: overloads.TypeConvertString,
Unary: func(value ref.Val) ref.Val {
return value.ConvertToType(types.StringType)
}},
// Timestamp conversions.
{Operator: overloads.TypeConvertTimestamp,
Unary: func(value ref.Val) ref.Val {
return value.ConvertToType(types.TimestampType)
}},
// Duration conversions.
{Operator: overloads.TypeConvertDuration,
Unary: func(value ref.Val) ref.Val {
return value.ConvertToType(types.DurationType)
}},
// Type operations.
{Operator: overloads.TypeConvertType,
Unary: func(value ref.Val) ref.Val {
return value.ConvertToType(types.TypeType)
}},
// Dyn conversion (identity function).
{Operator: overloads.TypeConvertDyn,
Unary: func(value ref.Val) ref.Val {
return value
}},
{Operator: overloads.Iterator,
OperandTrait: traits.IterableType,
Unary: func(value ref.Val) ref.Val {
return value.(traits.Iterable).Iterator()
}},
{Operator: overloads.HasNext,
OperandTrait: traits.IteratorType,
Unary: func(value ref.Val) ref.Val {
return value.(traits.Iterator).HasNext()
}},
{Operator: overloads.Next,
OperandTrait: traits.IteratorType,
Unary: func(value ref.Val) ref.Val {
return value.(traits.Iterator).Next()
}},
}
}
func notStrictlyFalse(value ref.Val) ref.Val {
if types.IsBool(value) {
return value
}
return types.True
}
func inAggregate(lhs ref.Val, rhs ref.Val) ref.Val {
if rhs.Type().HasTrait(traits.ContainerType) {
return rhs.(traits.Container).Contains(lhs)
}
return types.ValOrErr(rhs, "no such overload")
}

View File

@ -17,12 +17,12 @@ package interpreter
import (
"fmt"
"github.com/google/cel-go/common/functions"
"github.com/google/cel-go/common/operators"
"github.com/google/cel-go/common/overloads"
"github.com/google/cel-go/common/types"
"github.com/google/cel-go/common/types/ref"
"github.com/google/cel-go/common/types/traits"
"github.com/google/cel-go/interpreter/functions"
)
// Interpretable can accept a given Activation and produce a value along with
@ -52,7 +52,7 @@ type InterpretableAttribute interface {
Attr() Attribute
// Adapter returns the type adapter to be used for adapting resolved Attribute values.
Adapter() ref.TypeAdapter
Adapter() types.Adapter
// AddQualifier proxies the Attribute.AddQualifier method.
//
@ -202,9 +202,8 @@ func (cons *evalConst) Value() ref.Val {
}
type evalOr struct {
id int64
lhs Interpretable
rhs Interpretable
id int64
terms []Interpretable
}
// ID implements the Interpretable interface method.
@ -214,41 +213,39 @@ func (or *evalOr) ID() int64 {
// Eval implements the Interpretable interface method.
func (or *evalOr) Eval(ctx Activation) ref.Val {
// short-circuit lhs.
lVal := or.lhs.Eval(ctx)
lBool, lok := lVal.(types.Bool)
if lok && lBool == types.True {
return types.True
var err ref.Val = nil
var unk *types.Unknown
for _, term := range or.terms {
val := term.Eval(ctx)
boolVal, ok := val.(types.Bool)
// short-circuit on true.
if ok && boolVal == types.True {
return types.True
}
if !ok {
isUnk := false
unk, isUnk = types.MaybeMergeUnknowns(val, unk)
if !isUnk && err == nil {
if types.IsError(val) {
err = val
} else {
err = types.MaybeNoSuchOverloadErr(val)
}
}
}
}
// short-circuit on rhs.
rVal := or.rhs.Eval(ctx)
rBool, rok := rVal.(types.Bool)
if rok && rBool == types.True {
return types.True
if unk != nil {
return unk
}
// return if both sides are bool false.
if lok && rok {
return types.False
if err != nil {
return err
}
// TODO: return both values as a set if both are unknown or error.
// prefer left unknown to right unknown.
if types.IsUnknown(lVal) {
return lVal
}
if types.IsUnknown(rVal) {
return rVal
}
// If the left-hand side is non-boolean return it as the error.
if types.IsError(lVal) {
return lVal
}
return types.ValOrErr(rVal, "no such overload")
return types.False
}
type evalAnd struct {
id int64
lhs Interpretable
rhs Interpretable
id int64
terms []Interpretable
}
// ID implements the Interpretable interface method.
@ -258,35 +255,34 @@ func (and *evalAnd) ID() int64 {
// Eval implements the Interpretable interface method.
func (and *evalAnd) Eval(ctx Activation) ref.Val {
// short-circuit lhs.
lVal := and.lhs.Eval(ctx)
lBool, lok := lVal.(types.Bool)
if lok && lBool == types.False {
return types.False
var err ref.Val = nil
var unk *types.Unknown
for _, term := range and.terms {
val := term.Eval(ctx)
boolVal, ok := val.(types.Bool)
// short-circuit on false.
if ok && boolVal == types.False {
return types.False
}
if !ok {
isUnk := false
unk, isUnk = types.MaybeMergeUnknowns(val, unk)
if !isUnk && err == nil {
if types.IsError(val) {
err = val
} else {
err = types.MaybeNoSuchOverloadErr(val)
}
}
}
}
// short-circuit on rhs.
rVal := and.rhs.Eval(ctx)
rBool, rok := rVal.(types.Bool)
if rok && rBool == types.False {
return types.False
if unk != nil {
return unk
}
// return if both sides are bool true.
if lok && rok {
return types.True
if err != nil {
return err
}
// TODO: return both values as a set if both are unknown or error.
// prefer left unknown to right unknown.
if types.IsUnknown(lVal) {
return lVal
}
if types.IsUnknown(rVal) {
return rVal
}
// If the left-hand side is non-boolean return it as the error.
if types.IsError(lVal) {
return lVal
}
return types.ValOrErr(rVal, "no such overload")
return types.True
}
type evalEq struct {
@ -579,7 +575,7 @@ type evalList struct {
elems []Interpretable
optionals []bool
hasOptionals bool
adapter ref.TypeAdapter
adapter types.Adapter
}
// ID implements the Interpretable interface method.
@ -625,7 +621,7 @@ type evalMap struct {
vals []Interpretable
optionals []bool
hasOptionals bool
adapter ref.TypeAdapter
adapter types.Adapter
}
// ID implements the Interpretable interface method.
@ -689,7 +685,7 @@ type evalObj struct {
vals []Interpretable
optionals []bool
hasOptionals bool
provider ref.TypeProvider
provider types.Provider
}
// ID implements the Interpretable interface method.
@ -739,7 +735,7 @@ type evalFold struct {
cond Interpretable
step Interpretable
result Interpretable
adapter ref.TypeAdapter
adapter types.Adapter
exhaustive bool
interruptable bool
}
@ -865,18 +861,40 @@ type evalWatchAttr struct {
// AddQualifier creates a wrapper over the incoming qualifier which observes the qualification
// result.
func (e *evalWatchAttr) AddQualifier(q Qualifier) (Attribute, error) {
cq, isConst := q.(ConstantQualifier)
if isConst {
switch qual := q.(type) {
// By default, the qualifier is either a constant or an attribute
// There may be some custom cases where the attribute is neither.
case ConstantQualifier:
// Expose a method to test whether the qualifier matches the input pattern.
q = &evalWatchConstQual{
ConstantQualifier: cq,
ConstantQualifier: qual,
observer: e.observer,
adapter: e.InterpretableAttribute.Adapter(),
adapter: e.Adapter(),
}
} else {
q = &evalWatchQual{
Qualifier: q,
case *evalWatchAttr:
// Unwrap the evalWatchAttr since the observation will be applied during Qualify or
// QualifyIfPresent rather than Eval.
q = &evalWatchAttrQual{
Attribute: qual.InterpretableAttribute,
observer: e.observer,
adapter: e.InterpretableAttribute.Adapter(),
adapter: e.Adapter(),
}
case Attribute:
// Expose methods which intercept the qualification prior to being applied as a qualifier.
// Using this interface ensures that the qualifier is converted to a constant value one
// time during attribute pattern matching as the method embeds the Attribute interface
// needed to trip the conversion to a constant.
q = &evalWatchAttrQual{
Attribute: qual,
observer: e.observer,
adapter: e.Adapter(),
}
default:
// This is likely a custom qualifier type.
q = &evalWatchQual{
Qualifier: qual,
observer: e.observer,
adapter: e.Adapter(),
}
}
_, err := e.InterpretableAttribute.AddQualifier(q)
@ -895,7 +913,7 @@ func (e *evalWatchAttr) Eval(vars Activation) ref.Val {
type evalWatchConstQual struct {
ConstantQualifier
observer EvalObserver
adapter ref.TypeAdapter
adapter types.Adapter
}
// Qualify observes the qualification of a object via a constant boolean, int, string, or uint.
@ -934,11 +952,48 @@ func (e *evalWatchConstQual) QualifierValueEquals(value any) bool {
return ok && qve.QualifierValueEquals(value)
}
// evalWatchAttrQual observes the qualification of an object by a value computed at runtime.
type evalWatchAttrQual struct {
Attribute
observer EvalObserver
adapter ref.TypeAdapter
}
// Qualify observes the qualification of a object via a value computed at runtime.
func (e *evalWatchAttrQual) Qualify(vars Activation, obj any) (any, error) {
out, err := e.Attribute.Qualify(vars, obj)
var val ref.Val
if err != nil {
val = types.WrapErr(err)
} else {
val = e.adapter.NativeToValue(out)
}
e.observer(e.ID(), e.Attribute, val)
return out, err
}
// QualifyIfPresent conditionally qualifies the variable and only records a value if one is present.
func (e *evalWatchAttrQual) QualifyIfPresent(vars Activation, obj any, presenceOnly bool) (any, bool, error) {
out, present, err := e.Attribute.QualifyIfPresent(vars, obj, presenceOnly)
var val ref.Val
if err != nil {
val = types.WrapErr(err)
} else if out != nil {
val = e.adapter.NativeToValue(out)
} else if presenceOnly {
val = types.Bool(present)
}
if present || presenceOnly {
e.observer(e.ID(), e.Attribute, val)
}
return out, present, err
}
// evalWatchQual observes the qualification of an object by a value computed at runtime.
type evalWatchQual struct {
Qualifier
observer EvalObserver
adapter ref.TypeAdapter
adapter types.Adapter
}
// Qualify observes the qualification of a object via a value computed at runtime.
@ -986,9 +1041,8 @@ func (e *evalWatchConst) Eval(vars Activation) ref.Val {
// evalExhaustiveOr is just like evalOr, but does not short-circuit argument evaluation.
type evalExhaustiveOr struct {
id int64
lhs Interpretable
rhs Interpretable
id int64
terms []Interpretable
}
// ID implements the Interpretable interface method.
@ -998,38 +1052,44 @@ func (or *evalExhaustiveOr) ID() int64 {
// Eval implements the Interpretable interface method.
func (or *evalExhaustiveOr) Eval(ctx Activation) ref.Val {
lVal := or.lhs.Eval(ctx)
rVal := or.rhs.Eval(ctx)
lBool, lok := lVal.(types.Bool)
if lok && lBool == types.True {
var err ref.Val = nil
var unk *types.Unknown
isTrue := false
for _, term := range or.terms {
val := term.Eval(ctx)
boolVal, ok := val.(types.Bool)
// flag the result as true
if ok && boolVal == types.True {
isTrue = true
}
if !ok && !isTrue {
isUnk := false
unk, isUnk = types.MaybeMergeUnknowns(val, unk)
if !isUnk && err == nil {
if types.IsError(val) {
err = val
} else {
err = types.MaybeNoSuchOverloadErr(val)
}
}
}
}
if isTrue {
return types.True
}
rBool, rok := rVal.(types.Bool)
if rok && rBool == types.True {
return types.True
if unk != nil {
return unk
}
if lok && rok {
return types.False
if err != nil {
return err
}
if types.IsUnknown(lVal) {
return lVal
}
if types.IsUnknown(rVal) {
return rVal
}
// TODO: Combine the errors into a set in the future.
// If the left-hand side is non-boolean return it as the error.
if types.IsError(lVal) {
return lVal
}
return types.MaybeNoSuchOverloadErr(rVal)
return types.False
}
// evalExhaustiveAnd is just like evalAnd, but does not short-circuit argument evaluation.
type evalExhaustiveAnd struct {
id int64
lhs Interpretable
rhs Interpretable
id int64
terms []Interpretable
}
// ID implements the Interpretable interface method.
@ -1039,38 +1099,45 @@ func (and *evalExhaustiveAnd) ID() int64 {
// Eval implements the Interpretable interface method.
func (and *evalExhaustiveAnd) Eval(ctx Activation) ref.Val {
lVal := and.lhs.Eval(ctx)
rVal := and.rhs.Eval(ctx)
lBool, lok := lVal.(types.Bool)
if lok && lBool == types.False {
var err ref.Val = nil
var unk *types.Unknown
isFalse := false
for _, term := range and.terms {
val := term.Eval(ctx)
boolVal, ok := val.(types.Bool)
// short-circuit on false.
if ok && boolVal == types.False {
isFalse = true
}
if !ok && !isFalse {
isUnk := false
unk, isUnk = types.MaybeMergeUnknowns(val, unk)
if !isUnk && err == nil {
if types.IsError(val) {
err = val
} else {
err = types.MaybeNoSuchOverloadErr(val)
}
}
}
}
if isFalse {
return types.False
}
rBool, rok := rVal.(types.Bool)
if rok && rBool == types.False {
return types.False
if unk != nil {
return unk
}
if lok && rok {
return types.True
if err != nil {
return err
}
if types.IsUnknown(lVal) {
return lVal
}
if types.IsUnknown(rVal) {
return rVal
}
// TODO: Combine the errors into a set in the future.
// If the left-hand side is non-boolean return it as the error.
if types.IsError(lVal) {
return lVal
}
return types.MaybeNoSuchOverloadErr(rVal)
return types.True
}
// evalExhaustiveConditional is like evalConditional, but does not short-circuit argument
// evaluation.
type evalExhaustiveConditional struct {
id int64
adapter ref.TypeAdapter
adapter types.Adapter
attr *conditionalAttribute
}
@ -1102,7 +1169,7 @@ func (cond *evalExhaustiveConditional) Eval(ctx Activation) ref.Val {
// evalAttr evaluates an Attribute value.
type evalAttr struct {
adapter ref.TypeAdapter
adapter types.Adapter
attr Attribute
optional bool
}
@ -1127,7 +1194,7 @@ func (a *evalAttr) Attr() Attribute {
}
// Adapter implements the InterpretableAttribute interface method.
func (a *evalAttr) Adapter() ref.TypeAdapter {
func (a *evalAttr) Adapter() types.Adapter {
return a.adapter
}

View File

@ -18,9 +18,10 @@
package interpreter
import (
"github.com/google/cel-go/common/ast"
"github.com/google/cel-go/common/containers"
"github.com/google/cel-go/common/types"
"github.com/google/cel-go/common/types/ref"
"github.com/google/cel-go/interpreter/functions"
exprpb "google.golang.org/genproto/googleapis/api/expr/v1alpha1"
)
@ -29,7 +30,7 @@ import (
type Interpreter interface {
// NewInterpretable creates an Interpretable from a checked expression and an
// optional list of InterpretableDecorator values.
NewInterpretable(checked *exprpb.CheckedExpr, decorators ...InterpretableDecorator) (Interpretable, error)
NewInterpretable(checked *ast.CheckedAST, decorators ...InterpretableDecorator) (Interpretable, error)
// NewUncheckedInterpretable returns an Interpretable from a parsed expression
// and an optional list of InterpretableDecorator values.
@ -154,8 +155,8 @@ func CompileRegexConstants(regexOptimizations ...*RegexOptimization) Interpretab
type exprInterpreter struct {
dispatcher Dispatcher
container *containers.Container
provider ref.TypeProvider
adapter ref.TypeAdapter
provider types.Provider
adapter types.Adapter
attrFactory AttributeFactory
}
@ -163,8 +164,8 @@ type exprInterpreter struct {
// throughout the Eval of all Interpretable instances generated from it.
func NewInterpreter(dispatcher Dispatcher,
container *containers.Container,
provider ref.TypeProvider,
adapter ref.TypeAdapter,
provider types.Provider,
adapter types.Adapter,
attrFactory AttributeFactory) Interpreter {
return &exprInterpreter{
dispatcher: dispatcher,
@ -174,20 +175,9 @@ func NewInterpreter(dispatcher Dispatcher,
attrFactory: attrFactory}
}
// NewStandardInterpreter builds a Dispatcher and TypeProvider with support for all of the CEL
// builtins defined in the language definition.
func NewStandardInterpreter(container *containers.Container,
provider ref.TypeProvider,
adapter ref.TypeAdapter,
resolver AttributeFactory) Interpreter {
dispatcher := NewDispatcher()
dispatcher.Add(functions.StandardOverloads()...)
return NewInterpreter(dispatcher, container, provider, adapter, resolver)
}
// NewIntepretable implements the Interpreter interface method.
func (i *exprInterpreter) NewInterpretable(
checked *exprpb.CheckedExpr,
checked *ast.CheckedAST,
decorators ...InterpretableDecorator) (Interpretable, error) {
p := newPlanner(
i.dispatcher,
@ -197,7 +187,7 @@ func (i *exprInterpreter) NewInterpretable(
i.container,
checked,
decorators...)
return p.Plan(checked.GetExpr())
return p.Plan(checked.Expr)
}
// NewUncheckedIntepretable implements the Interpreter interface method.

View File

@ -18,10 +18,12 @@ import (
"fmt"
"strings"
"github.com/google/cel-go/common/ast"
"github.com/google/cel-go/common/containers"
"github.com/google/cel-go/common/functions"
"github.com/google/cel-go/common/operators"
"github.com/google/cel-go/common/types"
"github.com/google/cel-go/common/types/ref"
"github.com/google/cel-go/interpreter/functions"
exprpb "google.golang.org/genproto/googleapis/api/expr/v1alpha1"
)
@ -37,11 +39,11 @@ type interpretablePlanner interface {
// functions, types, and namespaced identifiers at plan time rather than at runtime since
// it only needs to be done once and may be semi-expensive to compute.
func newPlanner(disp Dispatcher,
provider ref.TypeProvider,
adapter ref.TypeAdapter,
provider types.Provider,
adapter types.Adapter,
attrFactory AttributeFactory,
cont *containers.Container,
checked *exprpb.CheckedExpr,
checked *ast.CheckedAST,
decorators ...InterpretableDecorator) interpretablePlanner {
return &planner{
disp: disp,
@ -49,8 +51,8 @@ func newPlanner(disp Dispatcher,
adapter: adapter,
attrFactory: attrFactory,
container: cont,
refMap: checked.GetReferenceMap(),
typeMap: checked.GetTypeMap(),
refMap: checked.ReferenceMap,
typeMap: checked.TypeMap,
decorators: decorators,
}
}
@ -59,8 +61,8 @@ func newPlanner(disp Dispatcher,
// TypeAdapter, and Container to resolve functions and types at plan time. Namespaces present in
// Select expressions are resolved lazily at evaluation time.
func newUncheckedPlanner(disp Dispatcher,
provider ref.TypeProvider,
adapter ref.TypeAdapter,
provider types.Provider,
adapter types.Adapter,
attrFactory AttributeFactory,
cont *containers.Container,
decorators ...InterpretableDecorator) interpretablePlanner {
@ -70,8 +72,8 @@ func newUncheckedPlanner(disp Dispatcher,
adapter: adapter,
attrFactory: attrFactory,
container: cont,
refMap: make(map[int64]*exprpb.Reference),
typeMap: make(map[int64]*exprpb.Type),
refMap: make(map[int64]*ast.ReferenceInfo),
typeMap: make(map[int64]*types.Type),
decorators: decorators,
}
}
@ -79,12 +81,12 @@ func newUncheckedPlanner(disp Dispatcher,
// planner is an implementation of the interpretablePlanner interface.
type planner struct {
disp Dispatcher
provider ref.TypeProvider
adapter ref.TypeAdapter
provider types.Provider
adapter types.Adapter
attrFactory AttributeFactory
container *containers.Container
refMap map[int64]*exprpb.Reference
typeMap map[int64]*exprpb.Type
refMap map[int64]*ast.ReferenceInfo
typeMap map[int64]*types.Type
decorators []InterpretableDecorator
}
@ -143,22 +145,19 @@ func (p *planner) planIdent(expr *exprpb.Expr) (Interpretable, error) {
}, nil
}
func (p *planner) planCheckedIdent(id int64, identRef *exprpb.Reference) (Interpretable, error) {
func (p *planner) planCheckedIdent(id int64, identRef *ast.ReferenceInfo) (Interpretable, error) {
// Plan a constant reference if this is the case for this simple identifier.
if identRef.GetValue() != nil {
return p.Plan(&exprpb.Expr{Id: id,
ExprKind: &exprpb.Expr_ConstExpr{
ConstExpr: identRef.GetValue(),
}})
if identRef.Value != nil {
return NewConstValue(id, identRef.Value), nil
}
// Check to see whether the type map indicates this is a type name. All types should be
// registered with the provider.
cType := p.typeMap[id]
if cType.GetType() != nil {
cVal, found := p.provider.FindIdent(identRef.GetName())
if cType.Kind() == types.TypeKind {
cVal, found := p.provider.FindIdent(identRef.Name)
if !found {
return nil, fmt.Errorf("reference to undefined type: %s", identRef.GetName())
return nil, fmt.Errorf("reference to undefined type: %s", identRef.Name)
}
return NewConstValue(id, cVal), nil
}
@ -166,7 +165,7 @@ func (p *planner) planCheckedIdent(id int64, identRef *exprpb.Reference) (Interp
// Otherwise, return the attribute for the resolved identifier name.
return &evalAttr{
adapter: p.adapter,
attr: p.attrFactory.AbsoluteAttribute(id, identRef.GetName()),
attr: p.attrFactory.AbsoluteAttribute(id, identRef.Name),
}, nil
}
@ -429,18 +428,16 @@ func (p *planner) planCallNotEqual(expr *exprpb.Expr, args []Interpretable) (Int
// planCallLogicalAnd generates a logical and (&&) Interpretable.
func (p *planner) planCallLogicalAnd(expr *exprpb.Expr, args []Interpretable) (Interpretable, error) {
return &evalAnd{
id: expr.GetId(),
lhs: args[0],
rhs: args[1],
id: expr.GetId(),
terms: args,
}, nil
}
// planCallLogicalOr generates a logical or (||) Interpretable.
func (p *planner) planCallLogicalOr(expr *exprpb.Expr, args []Interpretable) (Interpretable, error) {
return &evalOr{
id: expr.GetId(),
lhs: args[0],
rhs: args[1],
id: expr.GetId(),
terms: args,
}, nil
}
@ -476,7 +473,7 @@ func (p *planner) planCallConditional(expr *exprpb.Expr, args []Interpretable) (
func (p *planner) planCallIndex(expr *exprpb.Expr, args []Interpretable, optional bool) (Interpretable, error) {
op := args[0]
ind := args[1]
opType := p.typeMap[expr.GetCallExpr().GetTarget().GetId()]
opType := p.typeMap[op.ID()]
// Establish the attribute reference.
var err error
@ -675,7 +672,7 @@ func (p *planner) constValue(c *exprpb.Constant) (ref.Val, error) {
// namespace resolution rules to it in a scan over possible matching types in the TypeProvider.
func (p *planner) resolveTypeName(typeName string) (string, bool) {
for _, qualifiedTypeName := range p.container.ResolveCandidateNames(typeName) {
if _, found := p.provider.FindType(qualifiedTypeName); found {
if _, found := p.provider.FindStructType(qualifiedTypeName); found {
return qualifiedTypeName, true
}
}
@ -702,8 +699,8 @@ func (p *planner) resolveFunction(expr *exprpb.Expr) (*exprpb.Expr, string, stri
// function name as the fnName value.
oRef, hasOverload := p.refMap[expr.GetId()]
if hasOverload {
if len(oRef.GetOverloadId()) == 1 {
return target, fnName, oRef.GetOverloadId()[0]
if len(oRef.OverloadIDs) == 1 {
return target, fnName, oRef.OverloadIDs[0]
}
// Note, this namespaced function name will not appear as a fully qualified name in ASTs
// built and stored before cel-go v0.5.0; however, this functionality did not work at all

View File

@ -341,6 +341,11 @@ func (p *astPruner) prune(node *exprpb.Expr) (*exprpb.Expr, bool) {
}
}
if macro, found := p.macroCalls[node.GetId()]; found {
// Ensure that intermediate values for the comprehension are cleared during pruning
compre := node.GetComprehensionExpr()
if compre != nil {
visit(macro, clearIterVarVisitor(compre.IterVar, p.state))
}
// prune the expression in terms of the macro call instead of the expanded form.
if newMacro, pruned := p.prune(macro); pruned {
p.macroCalls[node.GetId()] = newMacro
@ -488,6 +493,27 @@ func (p *astPruner) prune(node *exprpb.Expr) (*exprpb.Expr, bool) {
},
}, true
}
case *exprpb.Expr_ComprehensionExpr:
compre := node.GetComprehensionExpr()
// Only the range of the comprehension is pruned since the state tracking only records
// the last iteration of the comprehension and not each step in the evaluation which
// means that the any residuals computed in between might be inaccurate.
if newRange, pruned := p.maybePrune(compre.GetIterRange()); pruned {
return &exprpb.Expr{
Id: node.GetId(),
ExprKind: &exprpb.Expr_ComprehensionExpr{
ComprehensionExpr: &exprpb.Expr_Comprehension{
IterVar: compre.GetIterVar(),
IterRange: newRange,
AccuVar: compre.GetAccuVar(),
AccuInit: compre.GetAccuInit(),
LoopCondition: compre.GetLoopCondition(),
LoopStep: compre.GetLoopStep(),
Result: compre.GetResult(),
},
},
}, true
}
}
return node, false
}
@ -524,6 +550,17 @@ func getMaxID(expr *exprpb.Expr) int64 {
return maxID
}
func clearIterVarVisitor(varName string, state EvalState) astVisitor {
return astVisitor{
visitExpr: func(e *exprpb.Expr) {
ident := e.GetIdentExpr()
if ident != nil && ident.GetName() == varName {
state.SetValue(e.GetId(), nil)
}
},
}
}
func maxIDVisitor(maxID *int64) astVisitor {
return astVisitor{
visitExpr: func(e *exprpb.Expr) {
@ -543,7 +580,9 @@ func visit(expr *exprpb.Expr, visitor astVisitor) {
exprs := []*exprpb.Expr{expr}
for len(exprs) != 0 {
e := exprs[0]
visitor.visitExpr(e)
if visitor.visitExpr != nil {
visitor.visitExpr(e)
}
exprs = exprs[1:]
switch e.GetExprKind().(type) {
case *exprpb.Expr_SelectExpr:
@ -567,7 +606,9 @@ func visit(expr *exprpb.Expr, visitor astVisitor) {
exprs = append(exprs, list.GetElements()...)
case *exprpb.Expr_StructExpr:
for _, entry := range e.GetStructExpr().GetEntries() {
visitor.visitEntry(entry)
if visitor.visitEntry != nil {
visitor.visitEntry(entry)
}
if entry.GetMapKey() != nil {
exprs = append(exprs, entry.GetMapKey())
}

View File

@ -65,13 +65,21 @@ func CostObserver(tracker *CostTracker) EvalObserver {
// While the field names are identical, the boolean operation eval structs do not share an interface and so
// must be handled individually.
case *evalOr:
tracker.stack.drop(t.rhs.ID(), t.lhs.ID())
for _, term := range t.terms {
tracker.stack.drop(term.ID())
}
case *evalAnd:
tracker.stack.drop(t.rhs.ID(), t.lhs.ID())
for _, term := range t.terms {
tracker.stack.drop(term.ID())
}
case *evalExhaustiveOr:
tracker.stack.drop(t.rhs.ID(), t.lhs.ID())
for _, term := range t.terms {
tracker.stack.drop(term.ID())
}
case *evalExhaustiveAnd:
tracker.stack.drop(t.rhs.ID(), t.lhs.ID())
for _, term := range t.terms {
tracker.stack.drop(term.ID())
}
case *evalFold:
tracker.stack.drop(t.iterRange.ID())
case Qualifier:
@ -125,6 +133,7 @@ func PresenceTestHasCost(hasCost bool) CostTrackerOption {
func NewCostTracker(estimator ActualCostEstimator, opts ...CostTrackerOption) (*CostTracker, error) {
tracker := &CostTracker{
Estimator: estimator,
overloadTrackers: map[string]FunctionTracker{},
presenceTestHasCost: true,
}
for _, opt := range opts {
@ -136,9 +145,24 @@ func NewCostTracker(estimator ActualCostEstimator, opts ...CostTrackerOption) (*
return tracker, nil
}
// OverloadCostTracker binds an overload ID to a runtime FunctionTracker implementation.
//
// OverloadCostTracker instances augment or override ActualCostEstimator decisions, allowing for versioned and/or
// optional cost tracking changes.
func OverloadCostTracker(overloadID string, fnTracker FunctionTracker) CostTrackerOption {
return func(tracker *CostTracker) error {
tracker.overloadTrackers[overloadID] = fnTracker
return nil
}
}
// FunctionTracker computes the actual cost of evaluating the functions with the given arguments and result.
type FunctionTracker func(args []ref.Val, result ref.Val) *uint64
// CostTracker represents the information needed for tracking runtime cost.
type CostTracker struct {
Estimator ActualCostEstimator
overloadTrackers map[string]FunctionTracker
Limit *uint64
presenceTestHasCost bool
@ -151,10 +175,19 @@ func (c *CostTracker) ActualCost() uint64 {
return c.cost
}
func (c *CostTracker) costCall(call InterpretableCall, argValues []ref.Val, result ref.Val) uint64 {
func (c *CostTracker) costCall(call InterpretableCall, args []ref.Val, result ref.Val) uint64 {
var cost uint64
if len(c.overloadTrackers) != 0 {
if tracker, found := c.overloadTrackers[call.OverloadID()]; found {
callCost := tracker(args, result)
if callCost != nil {
cost += *callCost
return cost
}
}
}
if c.Estimator != nil {
callCost := c.Estimator.CallCost(call.Function(), call.OverloadID(), argValues, result)
callCost := c.Estimator.CallCost(call.Function(), call.OverloadID(), args, result)
if callCost != nil {
cost += *callCost
return cost
@ -165,11 +198,11 @@ func (c *CostTracker) costCall(call InterpretableCall, argValues []ref.Val, resu
switch call.OverloadID() {
// O(n) functions
case overloads.StartsWithString, overloads.EndsWithString, overloads.StringToBytes, overloads.BytesToString, overloads.ExtQuoteString, overloads.ExtFormatString:
cost += uint64(math.Ceil(float64(c.actualSize(argValues[0])) * common.StringTraversalCostFactor))
cost += uint64(math.Ceil(float64(c.actualSize(args[0])) * common.StringTraversalCostFactor))
case overloads.InList:
// If a list is composed entirely of constant values this is O(1), but we don't account for that here.
// We just assume all list containment checks are O(n).
cost += c.actualSize(argValues[1])
cost += c.actualSize(args[1])
// O(min(m, n)) functions
case overloads.LessString, overloads.GreaterString, overloads.LessEqualsString, overloads.GreaterEqualsString,
overloads.LessBytes, overloads.GreaterBytes, overloads.LessEqualsBytes, overloads.GreaterEqualsBytes,
@ -177,8 +210,8 @@ func (c *CostTracker) costCall(call InterpretableCall, argValues []ref.Val, resu
// When we check the equality of 2 scalar values (e.g. 2 integers, 2 floating-point numbers, 2 booleans etc.),
// the CostTracker.actualSize() function by definition returns 1 for each operand, resulting in an overall cost
// of 1.
lhsSize := c.actualSize(argValues[0])
rhsSize := c.actualSize(argValues[1])
lhsSize := c.actualSize(args[0])
rhsSize := c.actualSize(args[1])
minSize := lhsSize
if rhsSize < minSize {
minSize = rhsSize
@ -187,23 +220,23 @@ func (c *CostTracker) costCall(call InterpretableCall, argValues []ref.Val, resu
// O(m+n) functions
case overloads.AddString, overloads.AddBytes:
// In the worst case scenario, we would need to reallocate a new backing store and copy both operands over.
cost += uint64(math.Ceil(float64(c.actualSize(argValues[0])+c.actualSize(argValues[1])) * common.StringTraversalCostFactor))
cost += uint64(math.Ceil(float64(c.actualSize(args[0])+c.actualSize(args[1])) * common.StringTraversalCostFactor))
// O(nm) functions
case overloads.MatchesString:
// https://swtch.com/~rsc/regexp/regexp1.html applies to RE2 implementation supported by CEL
// Add one to string length for purposes of cost calculation to prevent product of string and regex to be 0
// in case where string is empty but regex is still expensive.
strCost := uint64(math.Ceil((1.0 + float64(c.actualSize(argValues[0]))) * common.StringTraversalCostFactor))
strCost := uint64(math.Ceil((1.0 + float64(c.actualSize(args[0]))) * common.StringTraversalCostFactor))
// We don't know how many expressions are in the regex, just the string length (a huge
// improvement here would be to somehow get a count the number of expressions in the regex or
// how many states are in the regex state machine and use that to measure regex cost).
// For now, we're making a guess that each expression in a regex is typically at least 4 chars
// in length.
regexCost := uint64(math.Ceil(float64(c.actualSize(argValues[1])) * common.RegexStringLengthCostFactor))
regexCost := uint64(math.Ceil(float64(c.actualSize(args[1])) * common.RegexStringLengthCostFactor))
cost += strCost * regexCost
case overloads.ContainsString:
strCost := uint64(math.Ceil(float64(c.actualSize(argValues[0])) * common.StringTraversalCostFactor))
substrCost := uint64(math.Ceil(float64(c.actualSize(argValues[1])) * common.StringTraversalCostFactor))
strCost := uint64(math.Ceil(float64(c.actualSize(args[0])) * common.StringTraversalCostFactor))
substrCost := uint64(math.Ceil(float64(c.actualSize(args[1])) * common.StringTraversalCostFactor))
cost += strCost * substrCost
default: