rebase: bump k8s.io/kubernetes from 1.26.2 to 1.27.2

Bumps [k8s.io/kubernetes](https://github.com/kubernetes/kubernetes) from 1.26.2 to 1.27.2.
- [Release notes](https://github.com/kubernetes/kubernetes/releases)
- [Commits](https://github.com/kubernetes/kubernetes/compare/v1.26.2...v1.27.2)

---
updated-dependencies:
- dependency-name: k8s.io/kubernetes
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
This commit is contained in:
dependabot[bot]
2023-05-29 21:03:29 +00:00
committed by mergify[bot]
parent 0e79135419
commit 07b05616a0
1072 changed files with 208716 additions and 198880 deletions

View File

@ -0,0 +1,72 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
package(
default_visibility = ["//visibility:public"],
licenses = ["notice"], # Apache 2.0
)
go_library(
name = "go_default_library",
srcs = [
"activation.go",
"attribute_patterns.go",
"attributes.go",
"coster.go",
"decorators.go",
"dispatcher.go",
"evalstate.go",
"interpretable.go",
"interpreter.go",
"optimizations.go",
"planner.go",
"prune.go",
"runtimecost.go",
],
importpath = "github.com/google/cel-go/interpreter",
deps = [
"//common:go_default_library",
"//common/containers: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",
"@org_golang_google_protobuf//types/known/structpb:go_default_library",
"@org_golang_google_protobuf//types/known/timestamppb:go_default_library",
"@org_golang_google_protobuf//types/known/wrapperspb:go_default_library",
],
)
go_test(
name = "go_default_test",
srcs = [
"activation_test.go",
"attribute_patterns_test.go",
"attributes_test.go",
"interpreter_test.go",
"prune_test.go",
],
embed = [
":go_default_library",
],
deps = [
"//checker:go_default_library",
"//checker/decls:go_default_library",
"//common/containers:go_default_library",
"//common/debug:go_default_library",
"//common/operators: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",
"//test/proto3pb: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/anypb:go_default_library",
],
)

View File

@ -0,0 +1,201 @@
// 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 interpreter
import (
"errors"
"fmt"
"sync"
"github.com/google/cel-go/common/types/ref"
)
// Activation used to resolve identifiers by name and references by id.
//
// An Activation is the primary mechanism by which a caller supplies input into a CEL program.
type Activation interface {
// ResolveName returns a value from the activation by qualified name, or false if the name
// could not be found.
ResolveName(name string) (interface{}, bool)
// Parent returns the parent of the current activation, may be nil.
// If non-nil, the parent will be searched during resolve calls.
Parent() Activation
}
// EmptyActivation returns a variable-free activation.
func EmptyActivation() Activation {
return emptyActivation{}
}
// emptyActivation is a variable-free activation.
type emptyActivation struct{}
func (emptyActivation) ResolveName(string) (interface{}, bool) { return nil, false }
func (emptyActivation) Parent() Activation { return nil }
// NewActivation returns an activation based on a map-based binding where the map keys are
// expected to be qualified names used with ResolveName calls.
//
// The input `bindings` may either be of type `Activation` or `map[string]interface{}`.
//
// Lazy bindings may be supplied within the map-based input in either of the following forms:
// - func() interface{}
// - func() ref.Val
//
// 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.
func NewActivation(bindings interface{}) (Activation, error) {
if bindings == nil {
return nil, errors.New("bindings must be non-nil")
}
a, isActivation := bindings.(Activation)
if isActivation {
return a, nil
}
m, isMap := bindings.(map[string]interface{})
if !isMap {
return nil, fmt.Errorf(
"activation input must be an activation or map[string]interface: got %T",
bindings)
}
return &mapActivation{bindings: m}, nil
}
// mapActivation which implements Activation and maps of named values.
//
// Named bindings may lazily supply values by providing a function which accepts no arguments and
// produces an interface value.
type mapActivation struct {
bindings map[string]interface{}
}
// Parent implements the Activation interface method.
func (a *mapActivation) Parent() Activation {
return nil
}
// ResolveName implements the Activation interface method.
func (a *mapActivation) ResolveName(name string) (interface{}, bool) {
obj, found := a.bindings[name]
if !found {
return nil, false
}
fn, isLazy := obj.(func() ref.Val)
if isLazy {
obj = fn()
a.bindings[name] = obj
}
fnRaw, isLazy := obj.(func() interface{})
if isLazy {
obj = fnRaw()
a.bindings[name] = obj
}
return obj, found
}
// hierarchicalActivation which implements Activation and contains a parent and
// child activation.
type hierarchicalActivation struct {
parent Activation
child Activation
}
// Parent implements the Activation interface method.
func (a *hierarchicalActivation) Parent() Activation {
return a.parent
}
// ResolveName implements the Activation interface method.
func (a *hierarchicalActivation) ResolveName(name string) (interface{}, bool) {
if object, found := a.child.ResolveName(name); found {
return object, found
}
return a.parent.ResolveName(name)
}
// NewHierarchicalActivation takes two activations and produces a new one which prioritizes
// resolution in the child first and parent(s) second.
func NewHierarchicalActivation(parent Activation, child Activation) Activation {
return &hierarchicalActivation{parent, child}
}
// NewPartialActivation returns an Activation which contains a list of AttributePattern values
// representing field and index operations that should result in a 'types.Unknown' result.
//
// The `bindings` value may be any value type supported by the interpreter.NewActivation call,
// but is typically either an existing Activation or map[string]interface{}.
func NewPartialActivation(bindings interface{},
unknowns ...*AttributePattern) (PartialActivation, error) {
a, err := NewActivation(bindings)
if err != nil {
return nil, err
}
return &partActivation{Activation: a, unknowns: unknowns}, nil
}
// PartialActivation extends the Activation interface with a set of UnknownAttributePatterns.
type PartialActivation interface {
Activation
// UnknownAttributePaths returns a set of AttributePattern values which match Attribute
// expressions for data accesses whose values are not yet known.
UnknownAttributePatterns() []*AttributePattern
}
// partActivation is the default implementations of the PartialActivation interface.
type partActivation struct {
Activation
unknowns []*AttributePattern
}
// UnknownAttributePatterns implements the PartialActivation interface method.
func (a *partActivation) UnknownAttributePatterns() []*AttributePattern {
return a.unknowns
}
// varActivation represents a single mutable variable binding.
//
// This activation type should only be used within folds as the fold loop controls the object
// life-cycle.
type varActivation struct {
parent Activation
name string
val ref.Val
}
// Parent implements the Activation interface method.
func (v *varActivation) Parent() Activation {
return v.parent
}
// ResolveName implements the Activation interface method.
func (v *varActivation) ResolveName(name string) (interface{}, bool) {
if name == v.name {
return v.val, true
}
return v.parent.ResolveName(name)
}
var (
// pool of var activations to reduce allocations during folds.
varActivationPool = &sync.Pool{
New: func() interface{} {
return &varActivation{}
},
}
)

View File

@ -0,0 +1,404 @@
// Copyright 2020 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 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"
)
// AttributePattern represents a top-level variable with an optional set of qualifier patterns.
//
// When using a CEL expression within a container, e.g. a package or namespace, the variable name
// in the pattern must match the qualified name produced during the variable namespace resolution.
// For example, if variable `c` appears in an expression whose container is `a.b`, the variable
// name supplied to the pattern must be `a.b.c`
//
// The qualifier patterns for attribute matching must be one of the following:
//
// - valid map key type: string, int, uint, bool
// - wildcard (*)
//
// Examples:
//
// 1. ns.myvar["complex-value"]
// 2. ns.myvar["complex-value"][0]
// 3. ns.myvar["complex-value"].*.name
//
// The first example is simple: match an attribute where the variable is 'ns.myvar' with a
// field access on 'complex-value'. The second example expands the match to indicate that only
// a specific index `0` should match. And lastly, the third example matches any indexed access
// that later selects the 'name' field.
type AttributePattern struct {
variable string
qualifierPatterns []*AttributeQualifierPattern
}
// NewAttributePattern produces a new mutable AttributePattern based on a variable name.
func NewAttributePattern(variable string) *AttributePattern {
return &AttributePattern{
variable: variable,
qualifierPatterns: []*AttributeQualifierPattern{},
}
}
// QualString adds a string qualifier pattern to the AttributePattern. The string may be a valid
// identifier, or string map key including empty string.
func (apat *AttributePattern) QualString(pattern string) *AttributePattern {
apat.qualifierPatterns = append(apat.qualifierPatterns,
&AttributeQualifierPattern{value: pattern})
return apat
}
// QualInt adds an int qualifier pattern to the AttributePattern. The index may be either a map or
// list index.
func (apat *AttributePattern) QualInt(pattern int64) *AttributePattern {
apat.qualifierPatterns = append(apat.qualifierPatterns,
&AttributeQualifierPattern{value: pattern})
return apat
}
// QualUint adds an uint qualifier pattern for a map index operation to the AttributePattern.
func (apat *AttributePattern) QualUint(pattern uint64) *AttributePattern {
apat.qualifierPatterns = append(apat.qualifierPatterns,
&AttributeQualifierPattern{value: pattern})
return apat
}
// QualBool adds a bool qualifier pattern for a map index operation to the AttributePattern.
func (apat *AttributePattern) QualBool(pattern bool) *AttributePattern {
apat.qualifierPatterns = append(apat.qualifierPatterns,
&AttributeQualifierPattern{value: pattern})
return apat
}
// Wildcard adds a special sentinel qualifier pattern that will match any single qualifier.
func (apat *AttributePattern) Wildcard() *AttributePattern {
apat.qualifierPatterns = append(apat.qualifierPatterns,
&AttributeQualifierPattern{wildcard: true})
return apat
}
// VariableMatches returns true if the fully qualified variable matches the AttributePattern
// fully qualified variable name.
func (apat *AttributePattern) VariableMatches(variable string) bool {
return apat.variable == variable
}
// QualifierPatterns returns the set of AttributeQualifierPattern values on the AttributePattern.
func (apat *AttributePattern) QualifierPatterns() []*AttributeQualifierPattern {
return apat.qualifierPatterns
}
// AttributeQualifierPattern holds a wildcard or valued qualifier pattern.
type AttributeQualifierPattern struct {
wildcard bool
value interface{}
}
// Matches returns true if the qualifier pattern is a wildcard, or the Qualifier implements the
// qualifierValueEquator interface and its IsValueEqualTo returns true for the qualifier pattern.
func (qpat *AttributeQualifierPattern) Matches(q Qualifier) bool {
if qpat.wildcard {
return true
}
qve, ok := q.(qualifierValueEquator)
return ok && qve.QualifierValueEquals(qpat.value)
}
// qualifierValueEquator defines an interface for determining if an input value, of valid map key
// type, is equal to the value held in the Qualifier. This interface is used by the
// AttributeQualifierPattern to determine pattern matches for non-wildcard qualifier patterns.
//
// Note: Attribute values are also Qualifier values; however, Attributes are resolved before
// qualification happens. This is an implementation detail, but one relevant to why the Attribute
// types do not surface in the list of implementations.
//
// See: partialAttributeFactory.matchesUnknownPatterns for more details on how this interface is
// used.
type qualifierValueEquator interface {
// QualifierValueEquals returns true if the input value is equal to the value held in the
// Qualifier.
QualifierValueEquals(value interface{}) bool
}
// QualifierValueEquals implementation for boolean qualifiers.
func (q *boolQualifier) QualifierValueEquals(value interface{}) bool {
bval, ok := value.(bool)
return ok && q.value == bval
}
// QualifierValueEquals implementation for field qualifiers.
func (q *fieldQualifier) QualifierValueEquals(value interface{}) bool {
sval, ok := value.(string)
return ok && q.Name == sval
}
// QualifierValueEquals implementation for string qualifiers.
func (q *stringQualifier) QualifierValueEquals(value interface{}) bool {
sval, ok := value.(string)
return ok && q.value == sval
}
// QualifierValueEquals implementation for int qualifiers.
func (q *intQualifier) QualifierValueEquals(value interface{}) bool {
return numericValueEquals(value, q.celValue)
}
// QualifierValueEquals implementation for uint qualifiers.
func (q *uintQualifier) QualifierValueEquals(value interface{}) bool {
return numericValueEquals(value, q.celValue)
}
// QualifierValueEquals implementation for double qualifiers.
func (q *doubleQualifier) QualifierValueEquals(value interface{}) bool {
return numericValueEquals(value, q.celValue)
}
// numericValueEquals uses CEL equality to determine whether two number values are
func numericValueEquals(value interface{}, celValue ref.Val) bool {
val := types.DefaultTypeAdapter.NativeToValue(value)
return celValue.Equal(val) == types.True
}
// 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 {
fac := NewAttributeFactory(container, adapter, provider)
return &partialAttributeFactory{
AttributeFactory: fac,
container: container,
adapter: adapter,
provider: provider,
}
}
type partialAttributeFactory struct {
AttributeFactory
container *containers.Container
adapter ref.TypeAdapter
provider ref.TypeProvider
}
// AbsoluteAttribute implementation of the AttributeFactory interface which wraps the
// NamespacedAttribute resolution in an internal attributeMatcher object to dynamically match
// unknown patterns from PartialActivation inputs if given.
func (fac *partialAttributeFactory) AbsoluteAttribute(id int64, names ...string) NamespacedAttribute {
attr := fac.AttributeFactory.AbsoluteAttribute(id, names...)
return &attributeMatcher{fac: fac, NamespacedAttribute: attr}
}
// MaybeAttribute implementation of the AttributeFactory interface which ensure that the set of
// 'maybe' NamespacedAttribute values are produced using the partialAttributeFactory rather than
// the base AttributeFactory implementation.
func (fac *partialAttributeFactory) MaybeAttribute(id int64, name string) Attribute {
return &maybeAttribute{
id: id,
attrs: []NamespacedAttribute{
fac.AbsoluteAttribute(id, fac.container.ResolveCandidateNames(name)...),
},
adapter: fac.adapter,
provider: fac.provider,
fac: fac,
}
}
// matchesUnknownPatterns returns true if the variable names and qualifiers for a given
// Attribute value match any of the ActivationPattern objects in the set of unknown activation
// patterns on the given PartialActivation.
//
// For example, in the expression `a.b`, the Attribute is composed of variable `a`, with string
// qualifier `b`. When a PartialActivation is supplied, it indicates that some or all of the data
// provided in the input is unknown by specifying unknown AttributePatterns. An AttributePattern
// that refers to variable `a` with a string qualifier of `c` will not match `a.b`; however, any
// of the following patterns will match Attribute `a.b`:
//
// - `AttributePattern("a")`
// - `AttributePattern("a").Wildcard()`
// - `AttributePattern("a").QualString("b")`
// - `AttributePattern("a").QualString("b").QualInt(0)`
//
// Any AttributePattern which overlaps an Attribute or vice-versa will produce an Unknown result
// for the last pattern matched variable or qualifier in the Attribute. In the first matching
// example, the expression id representing variable `a` would be listed in the Unknown result,
// whereas in the other pattern examples, the qualifier `b` would be returned as the Unknown.
func (fac *partialAttributeFactory) matchesUnknownPatterns(
vars PartialActivation,
attrID int64,
variableNames []string,
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) {
candidateIndices[i] = struct{}{}
}
}
}
// Determine whether to return early if there are no candidate unknown patterns.
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.
newQuals := make([]Qualifier, len(qualifiers))
for i, qual := range qualifiers {
attr, isAttr := qual.(Attribute)
if isAttr {
val, err := attr.Resolve(vars)
if err != nil {
return nil, err
}
unk, isUnk := val.(types.Unknown)
if isUnk {
return unk, nil
}
// If this resolution behavior ever changes, new implementations of the
// qualifierValueEquator may be required to handle proper resolution.
qual, err = fac.NewQualifier(nil, qual.ID(), val)
if err != nil {
return nil, err
}
}
newQuals[i] = qual
}
// Determine whether any of the unknown patterns match.
for patIdx := range candidateIndices {
pat := patterns[patIdx]
isUnk := true
matchExprID := attrID
qualPats := pat.QualifierPatterns()
for i, qual := range newQuals {
if i >= len(qualPats) {
break
}
matchExprID = qual.ID()
qualPat := qualPats[i]
// Note, the AttributeQualifierPattern relies on the input Qualifier not being an
// Attribute, since there is no way to resolve the Attribute with the information
// provided to the Matches call.
if !qualPat.Matches(qual) {
isUnk = false
break
}
}
if isUnk {
return types.Unknown{matchExprID}, nil
}
}
return nil, nil
}
// attributeMatcher embeds the NamespacedAttribute interface which allows it to participate in
// AttributePattern matching against Attribute values without having to modify the code paths that
// identify Attributes in expressions.
type attributeMatcher struct {
NamespacedAttribute
qualifiers []Qualifier
fac *partialAttributeFactory
}
// AddQualifier implements the Attribute interface method.
func (m *attributeMatcher) AddQualifier(qual Qualifier) (Attribute, error) {
// Add the qualifier to the embedded NamespacedAttribute. If the input to the Resolve
// method is not a PartialActivation, or does not match an unknown attribute pattern, the
// Resolve method is directly invoked on the underlying NamespacedAttribute.
_, err := m.NamespacedAttribute.AddQualifier(qual)
if err != nil {
return nil, err
}
// The attributeMatcher overloads TryResolve and will attempt to match unknown patterns against
// the variable name and qualifier set contained within the Attribute. These values are not
// directly inspectable on the top-level NamespacedAttribute interface and so are tracked within
// the attributeMatcher.
m.qualifiers = append(m.qualifiers, qual)
return m, nil
}
// Resolve is an implementation of the Attribute interface method which uses the
// attributeMatcher TryResolve implementation rather than the embedded NamespacedAttribute
// Resolve implementation.
func (m *attributeMatcher) Resolve(vars Activation) (interface{}, error) {
obj, found, err := m.TryResolve(vars)
if err != nil {
return nil, err
}
if !found {
return nil, fmt.Errorf("no such attribute: %v", m.NamespacedAttribute)
}
return obj, nil
}
// TryResolve is an implementation of the NamespacedAttribute interface method which tests
// for matching unknown attribute patterns and returns types.Unknown if present. Otherwise,
// the standard Resolve logic applies.
func (m *attributeMatcher) TryResolve(vars Activation) (interface{}, bool, error) {
id := m.NamespacedAttribute.ID()
// Bug in how partial activation is resolved, should search parents as well.
partial, isPartial := toPartialActivation(vars)
if isPartial {
unk, err := m.fac.matchesUnknownPatterns(
partial,
id,
m.CandidateVariableNames(),
m.qualifiers)
if err != nil {
return nil, true, err
}
if unk != nil {
return unk, true, nil
}
}
return m.NamespacedAttribute.TryResolve(vars)
}
// Qualify is an implementation of the Qualifier interface method.
func (m *attributeMatcher) Qualify(vars Activation, obj interface{}) (interface{}, error) {
val, err := m.Resolve(vars)
if err != nil {
return nil, err
}
unk, isUnk := val.(types.Unknown)
if isUnk {
return unk, nil
}
qual, err := m.fac.NewQualifier(nil, m.ID(), val)
if err != nil {
return nil, err
}
return qual.Qualify(vars, obj)
}
func toPartialActivation(vars Activation) (PartialActivation, bool) {
pv, ok := vars.(PartialActivation)
if ok {
return pv, true
}
if vars.Parent() != nil {
return toPartialActivation(vars.Parent())
}
return nil, false
}

1051
vendor/github.com/google/cel-go/interpreter/attributes.go generated vendored Normal file

File diff suppressed because it is too large Load Diff

35
vendor/github.com/google/cel-go/interpreter/coster.go generated vendored Normal file
View File

@ -0,0 +1,35 @@
// Copyright 2020 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 interpreter
import "math"
// TODO: remove Coster.
// Coster calculates the heuristic cost incurred during evaluation.
// Deprecated: Please migrate cel.EstimateCost, it supports length estimates for input data and cost estimates for
// extension functions.
type Coster interface {
Cost() (min, max int64)
}
// estimateCost returns the heuristic cost interval for the program.
func estimateCost(i interface{}) (min, max int64) {
c, ok := i.(Coster)
if !ok {
return 0, math.MaxInt64
}
return c.Cost()
}

View File

@ -0,0 +1,269 @@
// 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 interpreter
import (
"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"
)
// InterpretableDecorator is a functional interface for decorating or replacing
// Interpretable expression nodes at construction time.
type InterpretableDecorator func(Interpretable) (Interpretable, error)
// decObserveEval records evaluation state into an EvalState object.
func decObserveEval(observer EvalObserver) InterpretableDecorator {
return func(i Interpretable) (Interpretable, error) {
switch inst := i.(type) {
case *evalWatch, *evalWatchAttr, *evalWatchConst:
// these instruction are already watching, return straight-away.
return i, nil
case InterpretableAttribute:
return &evalWatchAttr{
InterpretableAttribute: inst,
observer: observer,
}, nil
case InterpretableConst:
return &evalWatchConst{
InterpretableConst: inst,
observer: observer,
}, nil
default:
return &evalWatch{
Interpretable: i,
observer: observer,
}, nil
}
}
}
// decInterruptFolds creates an intepretable decorator which marks comprehensions as interruptable
// where the interrupt state is communicated via a hidden variable on the Activation.
func decInterruptFolds() InterpretableDecorator {
return func(i Interpretable) (Interpretable, error) {
fold, ok := i.(*evalFold)
if !ok {
return i, nil
}
fold.interruptable = true
return fold, nil
}
}
// decDisableShortcircuits ensures that all branches of an expression will be evaluated, no short-circuiting.
func decDisableShortcircuits() InterpretableDecorator {
return func(i Interpretable) (Interpretable, error) {
switch expr := i.(type) {
case *evalOr:
return &evalExhaustiveOr{
id: expr.id,
lhs: expr.lhs,
rhs: expr.rhs,
}, nil
case *evalAnd:
return &evalExhaustiveAnd{
id: expr.id,
lhs: expr.lhs,
rhs: expr.rhs,
}, nil
case *evalFold:
expr.exhaustive = true
return expr, nil
case InterpretableAttribute:
cond, isCond := expr.Attr().(*conditionalAttribute)
if isCond {
return &evalExhaustiveConditional{
id: cond.id,
attr: cond,
adapter: expr.Adapter(),
}, nil
}
}
return i, nil
}
}
// decOptimize optimizes the program plan by looking for common evaluation patterns and
// conditionally precomputing the result.
// - build list and map values with constant elements.
// - convert 'in' operations to set membership tests if possible.
func decOptimize() InterpretableDecorator {
return func(i Interpretable) (Interpretable, error) {
switch inst := i.(type) {
case *evalList:
return maybeBuildListLiteral(i, inst)
case *evalMap:
return maybeBuildMapLiteral(i, inst)
case InterpretableCall:
if inst.OverloadID() == overloads.InList {
return maybeOptimizeSetMembership(i, inst)
}
if overloads.IsTypeConversionFunction(inst.Function()) {
return maybeOptimizeConstUnary(i, inst)
}
}
return i, nil
}
}
// decRegexOptimizer compiles regex pattern string constants.
func decRegexOptimizer(regexOptimizations ...*RegexOptimization) InterpretableDecorator {
functionMatchMap := make(map[string]*RegexOptimization)
overloadMatchMap := make(map[string]*RegexOptimization)
for _, m := range regexOptimizations {
functionMatchMap[m.Function] = m
if m.OverloadID != "" {
overloadMatchMap[m.OverloadID] = m
}
}
return func(i Interpretable) (Interpretable, error) {
call, ok := i.(InterpretableCall)
if !ok {
return i, nil
}
var matcher *RegexOptimization
var found bool
if call.OverloadID() != "" {
matcher, found = overloadMatchMap[call.OverloadID()]
}
if !found {
matcher, found = functionMatchMap[call.Function()]
}
if !found || matcher.RegexIndex >= len(call.Args()) {
return i, nil
}
args := call.Args()
regexArg := args[matcher.RegexIndex]
regexStr, isConst := regexArg.(InterpretableConst)
if !isConst {
return i, nil
}
pattern, ok := regexStr.Value().(types.String)
if !ok {
return i, nil
}
return matcher.Factory(call, string(pattern))
}
}
func maybeOptimizeConstUnary(i Interpretable, call InterpretableCall) (Interpretable, error) {
args := call.Args()
if len(args) != 1 {
return i, nil
}
_, isConst := args[0].(InterpretableConst)
if !isConst {
return i, nil
}
val := call.Eval(EmptyActivation())
if types.IsError(val) {
return nil, val.(*types.Err)
}
return NewConstValue(call.ID(), val), nil
}
func maybeBuildListLiteral(i Interpretable, l *evalList) (Interpretable, error) {
for _, elem := range l.elems {
_, isConst := elem.(InterpretableConst)
if !isConst {
return i, nil
}
}
return NewConstValue(l.ID(), l.Eval(EmptyActivation())), nil
}
func maybeBuildMapLiteral(i Interpretable, mp *evalMap) (Interpretable, error) {
for idx, key := range mp.keys {
_, isConst := key.(InterpretableConst)
if !isConst {
return i, nil
}
_, isConst = mp.vals[idx].(InterpretableConst)
if !isConst {
return i, nil
}
}
return NewConstValue(mp.ID(), mp.Eval(EmptyActivation())), nil
}
// maybeOptimizeSetMembership may convert an 'in' operation against a list to map key membership
// test if the following conditions are true:
// - the list is a constant with homogeneous element types.
// - the elements are all of primitive type.
func maybeOptimizeSetMembership(i Interpretable, inlist InterpretableCall) (Interpretable, error) {
args := inlist.Args()
lhs := args[0]
rhs := args[1]
l, isConst := rhs.(InterpretableConst)
if !isConst {
return i, nil
}
// When the incoming binary call is flagged with as the InList overload, the value will
// always be convertible to a `traits.Lister` type.
list := l.Value().(traits.Lister)
if list.Size() == types.IntZero {
return NewConstValue(inlist.ID(), types.False), nil
}
it := list.Iterator()
valueSet := make(map[ref.Val]ref.Val)
for it.HasNext() == types.True {
elem := it.Next()
if !types.IsPrimitiveType(elem) {
// Note, non-primitive type are not yet supported.
return i, nil
}
valueSet[elem] = types.True
switch ev := elem.(type) {
case types.Double:
iv := ev.ConvertToType(types.IntType)
// Ensure that only lossless conversions are added to the set
if !types.IsError(iv) && iv.Equal(ev) == types.True {
valueSet[iv] = types.True
}
// Ensure that only lossless conversions are added to the set
uv := ev.ConvertToType(types.UintType)
if !types.IsError(uv) && uv.Equal(ev) == types.True {
valueSet[uv] = types.True
}
case types.Int:
dv := ev.ConvertToType(types.DoubleType)
if !types.IsError(dv) {
valueSet[dv] = types.True
}
uv := ev.ConvertToType(types.UintType)
if !types.IsError(uv) {
valueSet[uv] = types.True
}
case types.Uint:
dv := ev.ConvertToType(types.DoubleType)
if !types.IsError(dv) {
valueSet[dv] = types.True
}
iv := ev.ConvertToType(types.IntType)
if !types.IsError(iv) {
valueSet[iv] = types.True
}
}
}
return &evalSetMembership{
inst: inlist,
arg: lhs,
valueSet: valueSet,
}, nil
}

View File

@ -0,0 +1,100 @@
// 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 interpreter
import (
"fmt"
"github.com/google/cel-go/interpreter/functions"
)
// Dispatcher resolves function calls to their appropriate overload.
type Dispatcher interface {
// Add one or more overloads, returning an error if any Overload has the same Overload#Name.
Add(overloads ...*functions.Overload) error
// FindOverload returns an Overload definition matching the provided name.
FindOverload(overload string) (*functions.Overload, bool)
// OverloadIds returns the set of all overload identifiers configured for dispatch.
OverloadIds() []string
}
// NewDispatcher returns an empty Dispatcher instance.
func NewDispatcher() Dispatcher {
return &defaultDispatcher{
overloads: make(map[string]*functions.Overload)}
}
// ExtendDispatcher returns a Dispatcher which inherits the overloads of its parent, and
// provides an isolation layer between built-ins and extension functions which is useful
// for forward compatibility.
func ExtendDispatcher(parent Dispatcher) Dispatcher {
return &defaultDispatcher{
parent: parent,
overloads: make(map[string]*functions.Overload)}
}
// overloadMap helper type for indexing overloads by function name.
type overloadMap map[string]*functions.Overload
// defaultDispatcher struct which contains an overload map.
type defaultDispatcher struct {
parent Dispatcher
overloads overloadMap
}
// Add implements the Dispatcher.Add interface method.
func (d *defaultDispatcher) Add(overloads ...*functions.Overload) error {
for _, o := range overloads {
// add the overload unless an overload of the same name has already been provided.
if _, found := d.overloads[o.Operator]; found {
return fmt.Errorf("overload already exists '%s'", o.Operator)
}
// index the overload by function name.
d.overloads[o.Operator] = o
}
return nil
}
// FindOverload implements the Dispatcher.FindOverload interface method.
func (d *defaultDispatcher) FindOverload(overload string) (*functions.Overload, bool) {
o, found := d.overloads[overload]
// Attempt to dispatch to an overload defined in the parent.
if !found && d.parent != nil {
return d.parent.FindOverload(overload)
}
return o, found
}
// OverloadIds implements the Dispatcher interface method.
func (d *defaultDispatcher) OverloadIds() []string {
i := 0
overloads := make([]string, len(d.overloads))
for name := range d.overloads {
overloads[i] = name
i++
}
if d.parent == nil {
return overloads
}
parentOverloads := d.parent.OverloadIds()
for _, pName := range parentOverloads {
if _, found := d.overloads[pName]; !found {
overloads = append(overloads, pName)
}
}
return overloads
}

View File

@ -0,0 +1,75 @@
// 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 interpreter
import (
"github.com/google/cel-go/common/types/ref"
)
// EvalState tracks the values associated with expression ids during execution.
type EvalState interface {
// IDs returns the list of ids with recorded values.
IDs() []int64
// Value returns the observed value of the given expression id if found, and a nil false
// result if not.
Value(int64) (ref.Val, bool)
// SetValue sets the observed value of the expression id.
SetValue(int64, ref.Val)
// Reset clears the previously recorded expression values.
Reset()
}
// evalState permits the mutation of evaluation state for a given expression id.
type evalState struct {
values map[int64]ref.Val
}
// NewEvalState returns an EvalState instanced used to observe the intermediate
// evaluations of an expression.
func NewEvalState() EvalState {
return &evalState{
values: make(map[int64]ref.Val),
}
}
// IDs implements the EvalState interface method.
func (s *evalState) IDs() []int64 {
var ids []int64
for k, v := range s.values {
if v != nil {
ids = append(ids, k)
}
}
return ids
}
// Value is an implementation of the EvalState interface method.
func (s *evalState) Value(exprID int64) (ref.Val, bool) {
val, found := s.values[exprID]
return val, found
}
// SetValue is an implementation of the EvalState interface method.
func (s *evalState) SetValue(exprID int64, val ref.Val) {
s.values[exprID] = val
}
// Reset implements the EvalState interface method.
func (s *evalState) Reset() {
s.values = map[int64]ref.Val{}
}

View File

@ -0,0 +1,22 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library")
package(
default_visibility = ["//visibility:public"],
licenses = ["notice"], # Apache 2.0
)
go_library(
name = "go_default_library",
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",
],
)

View File

@ -0,0 +1,62 @@
// 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 defines the standard builtin functions supported by the
// interpreter and as declared within the checker#StandardDeclarations.
package functions
import "github.com/google/cel-go/common/types/ref"
// 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
// of either a unary, binary, or function implementation.
//
// The majority of operators within the expression language are unary or binary
// 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
}
// UnaryOp is a function that takes a single value and produces an output.
type UnaryOp func(value ref.Val) ref.Val
// BinaryOp is a function that takes two values and produces an output.
type BinaryOp func(lhs ref.Val, rhs ref.Val) ref.Val
// FunctionOp is a function with accepts zero or more arguments and produces
// an value (as interface{}) or error as a result.
type FunctionOp func(values ...ref.Val) ref.Val

View File

@ -0,0 +1,270 @@
// 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")
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,217 @@
// 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 interpreter provides functions to evaluate parsed expressions with
// the option to augment the evaluation with inputs and functions supplied at
// evaluation time.
package interpreter
import (
"github.com/google/cel-go/common/containers"
"github.com/google/cel-go/common/types/ref"
"github.com/google/cel-go/interpreter/functions"
exprpb "google.golang.org/genproto/googleapis/api/expr/v1alpha1"
)
// Interpreter generates a new Interpretable from a checked or unchecked expression.
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)
// NewUncheckedInterpretable returns an Interpretable from a parsed expression
// and an optional list of InterpretableDecorator values.
NewUncheckedInterpretable(expr *exprpb.Expr,
decorators ...InterpretableDecorator) (Interpretable, error)
}
// EvalObserver is a functional interface that accepts an expression id and an observed value.
// The id identifies the expression that was evaluated, the programStep is the Interpretable or Qualifier that
// was evaluated and value is the result of the evaluation.
type EvalObserver func(id int64, programStep interface{}, value ref.Val)
// Observe constructs a decorator that calls all the provided observers in order after evaluating each Interpretable
// or Qualifier during program evaluation.
func Observe(observers ...EvalObserver) InterpretableDecorator {
if len(observers) == 1 {
return decObserveEval(observers[0])
}
observeFn := func(id int64, programStep interface{}, val ref.Val) {
for _, observer := range observers {
observer(id, programStep, val)
}
}
return decObserveEval(observeFn)
}
// EvalCancelledError represents a cancelled program evaluation operation.
type EvalCancelledError struct {
Message string
// Type identifies the cause of the cancellation.
Cause CancellationCause
}
func (e EvalCancelledError) Error() string {
return e.Message
}
// CancellationCause enumerates the ways a program evaluation operation can be cancelled.
type CancellationCause int
const (
// ContextCancelled indicates that the operation was cancelled in response to a Golang context cancellation.
ContextCancelled CancellationCause = iota
// CostLimitExceeded indicates that the operation was cancelled in response to the actual cost limit being
// exceeded.
CostLimitExceeded
)
// TODO: Replace all usages of TrackState with EvalStateObserver
// TrackState decorates each expression node with an observer which records the value
// associated with the given expression id. EvalState must be provided to the decorator.
// This decorator is not thread-safe, and the EvalState must be reset between Eval()
// calls.
// DEPRECATED: Please use EvalStateObserver instead. It composes gracefully with additional observers.
func TrackState(state EvalState) InterpretableDecorator {
return Observe(EvalStateObserver(state))
}
// EvalStateObserver provides an observer which records the value
// associated with the given expression id. EvalState must be provided to the observer.
// This decorator is not thread-safe, and the EvalState must be reset between Eval()
// calls.
func EvalStateObserver(state EvalState) EvalObserver {
return func(id int64, programStep interface{}, val ref.Val) {
state.SetValue(id, val)
}
}
// ExhaustiveEval replaces operations that short-circuit with versions that evaluate
// expressions and couples this behavior with the TrackState() decorator to provide
// insight into the evaluation state of the entire expression. EvalState must be
// provided to the decorator. This decorator is not thread-safe, and the EvalState
// must be reset between Eval() calls.
func ExhaustiveEval() InterpretableDecorator {
ex := decDisableShortcircuits()
return func(i Interpretable) (Interpretable, error) {
return ex(i)
}
}
// InterruptableEval annotates comprehension loops with information that indicates they
// should check the `#interrupted` state within a custom Activation.
//
// The custom activation is currently managed higher up in the stack within the 'cel' package
// and should not require any custom support on behalf of callers.
func InterruptableEval() InterpretableDecorator {
return decInterruptFolds()
}
// Optimize will pre-compute operations such as list and map construction and optimize
// call arguments to set membership tests. The set of optimizations will increase over time.
func Optimize() InterpretableDecorator {
return decOptimize()
}
// RegexOptimization provides a way to replace an InterpretableCall for a regex function when the
// RegexIndex argument is a string constant. Typically, the Factory would compile the regex pattern at
// RegexIndex and report any errors (at program creation time) and then use the compiled regex for
// all regex function invocations.
type RegexOptimization struct {
// Function is the name of the function to optimize.
Function string
// OverloadID is the ID of the overload to optimize.
OverloadID string
// RegexIndex is the index position of the regex pattern argument. Only calls to the function where this argument is
// a string constant will be delegated to this optimizer.
RegexIndex int
// Factory constructs a replacement InterpretableCall node that optimizes the regex function call. Factory is
// provided with the unoptimized regex call and the string constant at the RegexIndex argument.
// The Factory may compile the regex for use across all invocations of the call, return any errors and
// return an interpreter.NewCall with the desired regex optimized function impl.
Factory func(call InterpretableCall, regexPattern string) (InterpretableCall, error)
}
// CompileRegexConstants compiles regex pattern string constants at program creation time and reports any regex pattern
// compile errors.
func CompileRegexConstants(regexOptimizations ...*RegexOptimization) InterpretableDecorator {
return decRegexOptimizer(regexOptimizations...)
}
type exprInterpreter struct {
dispatcher Dispatcher
container *containers.Container
provider ref.TypeProvider
adapter ref.TypeAdapter
attrFactory AttributeFactory
}
// NewInterpreter builds an Interpreter from a Dispatcher and TypeProvider which will be used
// throughout the Eval of all Interpretable instances generated from it.
func NewInterpreter(dispatcher Dispatcher,
container *containers.Container,
provider ref.TypeProvider,
adapter ref.TypeAdapter,
attrFactory AttributeFactory) Interpreter {
return &exprInterpreter{
dispatcher: dispatcher,
container: container,
provider: provider,
adapter: adapter,
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,
decorators ...InterpretableDecorator) (Interpretable, error) {
p := newPlanner(
i.dispatcher,
i.provider,
i.adapter,
i.attrFactory,
i.container,
checked,
decorators...)
return p.Plan(checked.GetExpr())
}
// NewUncheckedIntepretable implements the Interpreter interface method.
func (i *exprInterpreter) NewUncheckedInterpretable(
expr *exprpb.Expr,
decorators ...InterpretableDecorator) (Interpretable, error) {
p := newUncheckedPlanner(
i.dispatcher,
i.provider,
i.adapter,
i.attrFactory,
i.container,
decorators...)
return p.Plan(expr)
}

View File

@ -0,0 +1,46 @@
// Copyright 2022 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 interpreter
import (
"regexp"
"github.com/google/cel-go/common/types"
"github.com/google/cel-go/common/types/ref"
)
// MatchesRegexOptimization optimizes the 'matches' standard library function by compiling the regex pattern and
// reporting any compilation errors at program creation time, and using the compiled regex pattern for all function
// call invocations.
var MatchesRegexOptimization = &RegexOptimization{
Function: "matches",
RegexIndex: 1,
Factory: func(call InterpretableCall, regexPattern string) (InterpretableCall, error) {
compiledRegex, err := regexp.Compile(regexPattern)
if err != nil {
return nil, err
}
return NewCall(call.ID(), call.Function(), call.OverloadID(), call.Args(), func(values ...ref.Val) ref.Val {
if len(values) != 2 {
return types.NoSuchOverloadErr()
}
in, ok := values[0].Value().(string)
if !ok {
return types.NoSuchOverloadErr()
}
return types.Bool(compiledRegex.MatchString(in))
}), nil
},
}

794
vendor/github.com/google/cel-go/interpreter/planner.go generated vendored Normal file
View File

@ -0,0 +1,794 @@
// 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 interpreter
import (
"fmt"
"strings"
"github.com/google/cel-go/common/containers"
"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"
)
// interpretablePlanner creates an Interpretable evaluation plan from a proto Expr value.
type interpretablePlanner interface {
// Plan generates an Interpretable value (or error) from the input proto Expr.
Plan(expr *exprpb.Expr) (Interpretable, error)
}
// newPlanner creates an interpretablePlanner which references a Dispatcher, TypeProvider,
// TypeAdapter, Container, and CheckedExpr value. These pieces of data are used to resolve
// 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,
attrFactory AttributeFactory,
cont *containers.Container,
checked *exprpb.CheckedExpr,
decorators ...InterpretableDecorator) interpretablePlanner {
return &planner{
disp: disp,
provider: provider,
adapter: adapter,
attrFactory: attrFactory,
container: cont,
refMap: checked.GetReferenceMap(),
typeMap: checked.GetTypeMap(),
decorators: decorators,
}
}
// newUncheckedPlanner creates an interpretablePlanner which references a Dispatcher, TypeProvider,
// 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,
attrFactory AttributeFactory,
cont *containers.Container,
decorators ...InterpretableDecorator) interpretablePlanner {
return &planner{
disp: disp,
provider: provider,
adapter: adapter,
attrFactory: attrFactory,
container: cont,
refMap: make(map[int64]*exprpb.Reference),
typeMap: make(map[int64]*exprpb.Type),
decorators: decorators,
}
}
// planner is an implementation of the interpretablePlanner interface.
type planner struct {
disp Dispatcher
provider ref.TypeProvider
adapter ref.TypeAdapter
attrFactory AttributeFactory
container *containers.Container
refMap map[int64]*exprpb.Reference
typeMap map[int64]*exprpb.Type
decorators []InterpretableDecorator
}
// Plan implements the interpretablePlanner interface. This implementation of the Plan method also
// applies decorators to each Interpretable generated as part of the overall plan. Decorators are
// useful for layering functionality into the evaluation that is not natively understood by CEL,
// such as state-tracking, expression re-write, and possibly efficient thread-safe memoization of
// repeated expressions.
func (p *planner) Plan(expr *exprpb.Expr) (Interpretable, error) {
switch expr.GetExprKind().(type) {
case *exprpb.Expr_CallExpr:
return p.decorate(p.planCall(expr))
case *exprpb.Expr_IdentExpr:
return p.decorate(p.planIdent(expr))
case *exprpb.Expr_SelectExpr:
return p.decorate(p.planSelect(expr))
case *exprpb.Expr_ListExpr:
return p.decorate(p.planCreateList(expr))
case *exprpb.Expr_StructExpr:
return p.decorate(p.planCreateStruct(expr))
case *exprpb.Expr_ComprehensionExpr:
return p.decorate(p.planComprehension(expr))
case *exprpb.Expr_ConstExpr:
return p.decorate(p.planConst(expr))
}
return nil, fmt.Errorf("unsupported expr: %v", expr)
}
// decorate applies the InterpretableDecorator functions to the given Interpretable.
// Both the Interpretable and error generated by a Plan step are accepted as arguments
// for convenience.
func (p *planner) decorate(i Interpretable, err error) (Interpretable, error) {
if err != nil {
return nil, err
}
for _, dec := range p.decorators {
i, err = dec(i)
if err != nil {
return nil, err
}
}
return i, nil
}
// planIdent creates an Interpretable that resolves an identifier from an Activation.
func (p *planner) planIdent(expr *exprpb.Expr) (Interpretable, error) {
// Establish whether the identifier is in the reference map.
if identRef, found := p.refMap[expr.GetId()]; found {
return p.planCheckedIdent(expr.GetId(), identRef)
}
// Create the possible attribute list for the unresolved reference.
ident := expr.GetIdentExpr()
return &evalAttr{
adapter: p.adapter,
attr: p.attrFactory.MaybeAttribute(expr.GetId(), ident.Name),
}, nil
}
func (p *planner) planCheckedIdent(id int64, identRef *exprpb.Reference) (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(),
}})
}
// 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 !found {
return nil, fmt.Errorf("reference to undefined type: %s", identRef.GetName())
}
return NewConstValue(id, cVal), nil
}
// Otherwise, return the attribute for the resolved identifier name.
return &evalAttr{
adapter: p.adapter,
attr: p.attrFactory.AbsoluteAttribute(id, identRef.GetName()),
}, nil
}
// planSelect creates an Interpretable with either:
//
// a) selects a field from a map or proto.
// b) creates a field presence test for a select within a has() macro.
// c) resolves the select expression to a namespaced identifier.
func (p *planner) planSelect(expr *exprpb.Expr) (Interpretable, error) {
// If the Select id appears in the reference map from the CheckedExpr proto then it is either
// a namespaced identifier or enum value.
if identRef, found := p.refMap[expr.GetId()]; found {
return p.planCheckedIdent(expr.GetId(), identRef)
}
sel := expr.GetSelectExpr()
// Plan the operand evaluation.
op, err := p.Plan(sel.GetOperand())
if err != nil {
return nil, err
}
// Determine the field type if this is a proto message type.
var fieldType *ref.FieldType
opType := p.typeMap[sel.GetOperand().GetId()]
if opType.GetMessageType() != "" {
ft, found := p.provider.FindFieldType(opType.GetMessageType(), sel.GetField())
if found && ft.IsSet != nil && ft.GetFrom != nil {
fieldType = ft
}
}
// If the Select was marked TestOnly, this is a presence test.
//
// Note: presence tests are defined for structured (e.g. proto) and dynamic values (map, json)
// as follows:
// - True if the object field has a non-default value, e.g. obj.str != ""
// - True if the dynamic value has the field defined, e.g. key in map
//
// However, presence tests are not defined for qualified identifier names with primitive types.
// If a string named 'a.b.c' is declared in the environment and referenced within `has(a.b.c)`,
// it is not clear whether has should error or follow the convention defined for structured
// values.
if sel.TestOnly {
// Return the test only eval expression.
return &evalTestOnly{
id: expr.GetId(),
field: types.String(sel.GetField()),
fieldType: fieldType,
op: op,
}, nil
}
// Build a qualifier.
qual, err := p.attrFactory.NewQualifier(
opType, expr.GetId(), sel.GetField())
if err != nil {
return nil, err
}
// Lastly, create a field selection Interpretable.
attr, isAttr := op.(InterpretableAttribute)
if isAttr {
_, err = attr.AddQualifier(qual)
return attr, err
}
relAttr, err := p.relativeAttr(op.ID(), op)
if err != nil {
return nil, err
}
_, err = relAttr.AddQualifier(qual)
if err != nil {
return nil, err
}
return relAttr, nil
}
// planCall creates a callable Interpretable while specializing for common functions and invocation
// patterns. Specifically, conditional operators &&, ||, ?:, and (in)equality functions result in
// optimized Interpretable values.
func (p *planner) planCall(expr *exprpb.Expr) (Interpretable, error) {
call := expr.GetCallExpr()
target, fnName, oName := p.resolveFunction(expr)
argCount := len(call.GetArgs())
var offset int
if target != nil {
argCount++
offset++
}
args := make([]Interpretable, argCount)
if target != nil {
arg, err := p.Plan(target)
if err != nil {
return nil, err
}
args[0] = arg
}
for i, argExpr := range call.GetArgs() {
arg, err := p.Plan(argExpr)
if err != nil {
return nil, err
}
args[i+offset] = arg
}
// Generate specialized Interpretable operators by function name if possible.
switch fnName {
case operators.LogicalAnd:
return p.planCallLogicalAnd(expr, args)
case operators.LogicalOr:
return p.planCallLogicalOr(expr, args)
case operators.Conditional:
return p.planCallConditional(expr, args)
case operators.Equals:
return p.planCallEqual(expr, args)
case operators.NotEquals:
return p.planCallNotEqual(expr, args)
case operators.Index:
return p.planCallIndex(expr, args)
}
// Otherwise, generate Interpretable calls specialized by argument count.
// Try to find the specific function by overload id.
var fnDef *functions.Overload
if oName != "" {
fnDef, _ = p.disp.FindOverload(oName)
}
// If the overload id couldn't resolve the function, try the simple function name.
if fnDef == nil {
fnDef, _ = p.disp.FindOverload(fnName)
}
switch argCount {
case 0:
return p.planCallZero(expr, fnName, oName, fnDef)
case 1:
// If the FunctionOp has been used, then use it as it may exist for the purposes
// of dynamic dispatch within a singleton function implementation.
if fnDef != nil && fnDef.Unary == nil && fnDef.Function != nil {
return p.planCallVarArgs(expr, fnName, oName, fnDef, args)
}
return p.planCallUnary(expr, fnName, oName, fnDef, args)
case 2:
// If the FunctionOp has been used, then use it as it may exist for the purposes
// of dynamic dispatch within a singleton function implementation.
if fnDef != nil && fnDef.Binary == nil && fnDef.Function != nil {
return p.planCallVarArgs(expr, fnName, oName, fnDef, args)
}
return p.planCallBinary(expr, fnName, oName, fnDef, args)
default:
return p.planCallVarArgs(expr, fnName, oName, fnDef, args)
}
}
// planCallZero generates a zero-arity callable Interpretable.
func (p *planner) planCallZero(expr *exprpb.Expr,
function string,
overload string,
impl *functions.Overload) (Interpretable, error) {
if impl == nil || impl.Function == nil {
return nil, fmt.Errorf("no such overload: %s()", function)
}
return &evalZeroArity{
id: expr.GetId(),
function: function,
overload: overload,
impl: impl.Function,
}, nil
}
// planCallUnary generates a unary callable Interpretable.
func (p *planner) planCallUnary(expr *exprpb.Expr,
function string,
overload string,
impl *functions.Overload,
args []Interpretable) (Interpretable, error) {
var fn functions.UnaryOp
var trait int
var nonStrict bool
if impl != nil {
if impl.Unary == nil {
return nil, fmt.Errorf("no such overload: %s(arg)", function)
}
fn = impl.Unary
trait = impl.OperandTrait
nonStrict = impl.NonStrict
}
return &evalUnary{
id: expr.GetId(),
function: function,
overload: overload,
arg: args[0],
trait: trait,
impl: fn,
nonStrict: nonStrict,
}, nil
}
// planCallBinary generates a binary callable Interpretable.
func (p *planner) planCallBinary(expr *exprpb.Expr,
function string,
overload string,
impl *functions.Overload,
args []Interpretable) (Interpretable, error) {
var fn functions.BinaryOp
var trait int
var nonStrict bool
if impl != nil {
if impl.Binary == nil {
return nil, fmt.Errorf("no such overload: %s(lhs, rhs)", function)
}
fn = impl.Binary
trait = impl.OperandTrait
nonStrict = impl.NonStrict
}
return &evalBinary{
id: expr.GetId(),
function: function,
overload: overload,
lhs: args[0],
rhs: args[1],
trait: trait,
impl: fn,
nonStrict: nonStrict,
}, nil
}
// planCallVarArgs generates a variable argument callable Interpretable.
func (p *planner) planCallVarArgs(expr *exprpb.Expr,
function string,
overload string,
impl *functions.Overload,
args []Interpretable) (Interpretable, error) {
var fn functions.FunctionOp
var trait int
var nonStrict bool
if impl != nil {
if impl.Function == nil {
return nil, fmt.Errorf("no such overload: %s(...)", function)
}
fn = impl.Function
trait = impl.OperandTrait
nonStrict = impl.NonStrict
}
return &evalVarArgs{
id: expr.GetId(),
function: function,
overload: overload,
args: args,
trait: trait,
impl: fn,
nonStrict: nonStrict,
}, nil
}
// planCallEqual generates an equals (==) Interpretable.
func (p *planner) planCallEqual(expr *exprpb.Expr,
args []Interpretable) (Interpretable, error) {
return &evalEq{
id: expr.GetId(),
lhs: args[0],
rhs: args[1],
}, nil
}
// planCallNotEqual generates a not equals (!=) Interpretable.
func (p *planner) planCallNotEqual(expr *exprpb.Expr,
args []Interpretable) (Interpretable, error) {
return &evalNe{
id: expr.GetId(),
lhs: args[0],
rhs: args[1],
}, nil
}
// 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],
}, 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],
}, nil
}
// planCallConditional generates a conditional / ternary (c ? t : f) Interpretable.
func (p *planner) planCallConditional(expr *exprpb.Expr,
args []Interpretable) (Interpretable, error) {
cond := args[0]
t := args[1]
var tAttr Attribute
truthyAttr, isTruthyAttr := t.(InterpretableAttribute)
if isTruthyAttr {
tAttr = truthyAttr.Attr()
} else {
tAttr = p.attrFactory.RelativeAttribute(t.ID(), t)
}
f := args[2]
var fAttr Attribute
falsyAttr, isFalsyAttr := f.(InterpretableAttribute)
if isFalsyAttr {
fAttr = falsyAttr.Attr()
} else {
fAttr = p.attrFactory.RelativeAttribute(f.ID(), f)
}
return &evalAttr{
adapter: p.adapter,
attr: p.attrFactory.ConditionalAttribute(expr.GetId(), cond, tAttr, fAttr),
}, nil
}
// planCallIndex either extends an attribute with the argument to the index operation, or creates
// a relative attribute based on the return of a function call or operation.
func (p *planner) planCallIndex(expr *exprpb.Expr,
args []Interpretable) (Interpretable, error) {
op := args[0]
ind := args[1]
opAttr, err := p.relativeAttr(op.ID(), op)
if err != nil {
return nil, err
}
opType := p.typeMap[expr.GetCallExpr().GetTarget().GetId()]
indConst, isIndConst := ind.(InterpretableConst)
if isIndConst {
qual, err := p.attrFactory.NewQualifier(
opType, expr.GetId(), indConst.Value())
if err != nil {
return nil, err
}
_, err = opAttr.AddQualifier(qual)
return opAttr, err
}
indAttr, isIndAttr := ind.(InterpretableAttribute)
if isIndAttr {
qual, err := p.attrFactory.NewQualifier(
opType, expr.GetId(), indAttr)
if err != nil {
return nil, err
}
_, err = opAttr.AddQualifier(qual)
return opAttr, err
}
indQual, err := p.relativeAttr(expr.GetId(), ind)
if err != nil {
return nil, err
}
_, err = opAttr.AddQualifier(indQual)
return opAttr, err
}
// planCreateList generates a list construction Interpretable.
func (p *planner) planCreateList(expr *exprpb.Expr) (Interpretable, error) {
list := expr.GetListExpr()
elems := make([]Interpretable, len(list.GetElements()))
for i, elem := range list.GetElements() {
elemVal, err := p.Plan(elem)
if err != nil {
return nil, err
}
elems[i] = elemVal
}
return &evalList{
id: expr.GetId(),
elems: elems,
adapter: p.adapter,
}, nil
}
// planCreateStruct generates a map or object construction Interpretable.
func (p *planner) planCreateStruct(expr *exprpb.Expr) (Interpretable, error) {
str := expr.GetStructExpr()
if len(str.MessageName) != 0 {
return p.planCreateObj(expr)
}
entries := str.GetEntries()
keys := make([]Interpretable, len(entries))
vals := make([]Interpretable, len(entries))
for i, entry := range entries {
keyVal, err := p.Plan(entry.GetMapKey())
if err != nil {
return nil, err
}
keys[i] = keyVal
valVal, err := p.Plan(entry.GetValue())
if err != nil {
return nil, err
}
vals[i] = valVal
}
return &evalMap{
id: expr.GetId(),
keys: keys,
vals: vals,
adapter: p.adapter,
}, nil
}
// planCreateObj generates an object construction Interpretable.
func (p *planner) planCreateObj(expr *exprpb.Expr) (Interpretable, error) {
obj := expr.GetStructExpr()
typeName, defined := p.resolveTypeName(obj.MessageName)
if !defined {
return nil, fmt.Errorf("unknown type: %s", typeName)
}
entries := obj.GetEntries()
fields := make([]string, len(entries))
vals := make([]Interpretable, len(entries))
for i, entry := range entries {
fields[i] = entry.GetFieldKey()
val, err := p.Plan(entry.GetValue())
if err != nil {
return nil, err
}
vals[i] = val
}
return &evalObj{
id: expr.GetId(),
typeName: typeName,
fields: fields,
vals: vals,
provider: p.provider,
}, nil
}
// planComprehension generates an Interpretable fold operation.
func (p *planner) planComprehension(expr *exprpb.Expr) (Interpretable, error) {
fold := expr.GetComprehensionExpr()
accu, err := p.Plan(fold.GetAccuInit())
if err != nil {
return nil, err
}
iterRange, err := p.Plan(fold.GetIterRange())
if err != nil {
return nil, err
}
cond, err := p.Plan(fold.GetLoopCondition())
if err != nil {
return nil, err
}
step, err := p.Plan(fold.GetLoopStep())
if err != nil {
return nil, err
}
result, err := p.Plan(fold.GetResult())
if err != nil {
return nil, err
}
return &evalFold{
id: expr.GetId(),
accuVar: fold.AccuVar,
accu: accu,
iterVar: fold.IterVar,
iterRange: iterRange,
cond: cond,
step: step,
result: result,
adapter: p.adapter,
}, nil
}
// planConst generates a constant valued Interpretable.
func (p *planner) planConst(expr *exprpb.Expr) (Interpretable, error) {
val, err := p.constValue(expr.GetConstExpr())
if err != nil {
return nil, err
}
return NewConstValue(expr.GetId(), val), nil
}
// constValue converts a proto Constant value to a ref.Val.
func (p *planner) constValue(c *exprpb.Constant) (ref.Val, error) {
switch c.GetConstantKind().(type) {
case *exprpb.Constant_BoolValue:
return p.adapter.NativeToValue(c.GetBoolValue()), nil
case *exprpb.Constant_BytesValue:
return p.adapter.NativeToValue(c.GetBytesValue()), nil
case *exprpb.Constant_DoubleValue:
return p.adapter.NativeToValue(c.GetDoubleValue()), nil
case *exprpb.Constant_DurationValue:
return p.adapter.NativeToValue(c.GetDurationValue().AsDuration()), nil
case *exprpb.Constant_Int64Value:
return p.adapter.NativeToValue(c.GetInt64Value()), nil
case *exprpb.Constant_NullValue:
return p.adapter.NativeToValue(c.GetNullValue()), nil
case *exprpb.Constant_StringValue:
return p.adapter.NativeToValue(c.GetStringValue()), nil
case *exprpb.Constant_TimestampValue:
return p.adapter.NativeToValue(c.GetTimestampValue().AsTime()), nil
case *exprpb.Constant_Uint64Value:
return p.adapter.NativeToValue(c.GetUint64Value()), nil
}
return nil, fmt.Errorf("unknown constant type: %v", c)
}
// resolveTypeName takes a qualified string constructed at parse time, applies the proto
// 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 {
return qualifiedTypeName, true
}
}
return "", false
}
// resolveFunction determines the call target, function name, and overload name from a given Expr
// value.
//
// The resolveFunction resolves ambiguities where a function may either be a receiver-style
// invocation or a qualified global function name.
// - The target expression may only consist of ident and select expressions.
// - The function is declared in the environment using its fully-qualified name.
// - The fully-qualified function name matches the string serialized target value.
func (p *planner) resolveFunction(expr *exprpb.Expr) (*exprpb.Expr, string, string) {
// Note: similar logic exists within the `checker/checker.go`. If making changes here
// please consider the impact on checker.go and consolidate implementations or mirror code
// as appropriate.
call := expr.GetCallExpr()
target := call.GetTarget()
fnName := call.GetFunction()
// Checked expressions always have a reference map entry, and _should_ have the fully qualified
// 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]
}
// 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
// before the v0.5.0 release.
return target, fnName, ""
}
// Parse-only expressions need to handle the same logic as is normally performed at check time,
// but with potentially much less information. The only reliable source of information about
// which functions are configured is the dispatcher.
if target == nil {
// If the user has a parse-only expression, then it should have been configured as such in
// the interpreter dispatcher as it may have been omitted from the checker environment.
for _, qualifiedName := range p.container.ResolveCandidateNames(fnName) {
_, found := p.disp.FindOverload(qualifiedName)
if found {
return nil, qualifiedName, ""
}
}
// It's possible that the overload was not found, but this situation is accounted for in
// the planCall phase; however, the leading dot used for denoting fully-qualified
// namespaced identifiers must be stripped, as all declarations already use fully-qualified
// names. This stripping behavior is handled automatically by the ResolveCandidateNames
// call.
return target, stripLeadingDot(fnName), ""
}
// Handle the situation where the function target actually indicates a qualified function name.
qualifiedPrefix, maybeQualified := p.toQualifiedName(target)
if maybeQualified {
maybeQualifiedName := qualifiedPrefix + "." + fnName
for _, qualifiedName := range p.container.ResolveCandidateNames(maybeQualifiedName) {
_, found := p.disp.FindOverload(qualifiedName)
if found {
// Clear the target to ensure the proper arity is used for finding the
// implementation.
return nil, qualifiedName, ""
}
}
}
// In the default case, the function is exactly as it was advertised: a receiver call on with
// an expression-based target with the given simple function name.
return target, fnName, ""
}
func (p *planner) relativeAttr(id int64, eval Interpretable) (InterpretableAttribute, error) {
eAttr, ok := eval.(InterpretableAttribute)
if !ok {
eAttr = &evalAttr{
adapter: p.adapter,
attr: p.attrFactory.RelativeAttribute(id, eval),
}
}
decAttr, err := p.decorate(eAttr, nil)
if err != nil {
return nil, err
}
eAttr, ok = decAttr.(InterpretableAttribute)
if !ok {
return nil, fmt.Errorf("invalid attribute decoration: %v(%T)", decAttr, decAttr)
}
return eAttr, nil
}
// toQualifiedName converts an expression AST into a qualified name if possible, with a boolean
// 'found' value that indicates if the conversion is successful.
func (p *planner) toQualifiedName(operand *exprpb.Expr) (string, bool) {
// If the checker identified the expression as an attribute by the type-checker, then it can't
// possibly be part of qualified name in a namespace.
_, isAttr := p.refMap[operand.GetId()]
if isAttr {
return "", false
}
// Since functions cannot be both namespaced and receiver functions, if the operand is not an
// qualified variable name, return the (possibly) qualified name given the expressions.
return containers.ToQualifiedName(operand)
}
func stripLeadingDot(name string) string {
if strings.HasPrefix(name, ".") {
return name[1:]
}
return name
}

397
vendor/github.com/google/cel-go/interpreter/prune.go generated vendored Normal file
View File

@ -0,0 +1,397 @@
// 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 interpreter
import (
"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/common/types/traits"
exprpb "google.golang.org/genproto/googleapis/api/expr/v1alpha1"
structpb "google.golang.org/protobuf/types/known/structpb"
)
type astPruner struct {
expr *exprpb.Expr
state EvalState
nextExprID int64
}
// TODO Consider having a separate walk of the AST that finds common
// subexpressions. This can be called before or after constant folding to find
// common subexpressions.
// PruneAst prunes the given AST based on the given EvalState and generates a new AST.
// Given AST is copied on write and a new AST is returned.
// Couple of typical use cases this interface would be:
//
// A)
// 1) Evaluate expr with some unknowns,
// 2) If result is unknown:
//
// a) PruneAst
// b) Goto 1
//
// Functional call results which are known would be effectively cached across
// iterations.
//
// B)
// 1) Compile the expression (maybe via a service and maybe after checking a
//
// compiled expression does not exists in local cache)
//
// 2) Prepare the environment and the interpreter. Activation might be empty.
// 3) Eval the expression. This might return unknown or error or a concrete
//
// value.
//
// 4) PruneAst
// 4) Maybe cache the expression
// This is effectively constant folding the expression. How the environment is
// prepared in step 2 is flexible. For example, If the caller caches the
// compiled and constant folded expressions, but is not willing to constant
// fold(and thus cache results of) some external calls, then they can prepare
// the overloads accordingly.
func PruneAst(expr *exprpb.Expr, state EvalState) *exprpb.Expr {
pruner := &astPruner{
expr: expr,
state: state,
nextExprID: 1}
newExpr, _ := pruner.prune(expr)
return newExpr
}
func (p *astPruner) createLiteral(id int64, val *exprpb.Constant) *exprpb.Expr {
return &exprpb.Expr{
Id: id,
ExprKind: &exprpb.Expr_ConstExpr{
ConstExpr: val,
},
}
}
func (p *astPruner) maybeCreateLiteral(id int64, val ref.Val) (*exprpb.Expr, bool) {
switch val.Type() {
case types.BoolType:
return p.createLiteral(id,
&exprpb.Constant{ConstantKind: &exprpb.Constant_BoolValue{BoolValue: val.Value().(bool)}}), true
case types.IntType:
return p.createLiteral(id,
&exprpb.Constant{ConstantKind: &exprpb.Constant_Int64Value{Int64Value: val.Value().(int64)}}), true
case types.UintType:
return p.createLiteral(id,
&exprpb.Constant{ConstantKind: &exprpb.Constant_Uint64Value{Uint64Value: val.Value().(uint64)}}), true
case types.StringType:
return p.createLiteral(id,
&exprpb.Constant{ConstantKind: &exprpb.Constant_StringValue{StringValue: val.Value().(string)}}), true
case types.DoubleType:
return p.createLiteral(id,
&exprpb.Constant{ConstantKind: &exprpb.Constant_DoubleValue{DoubleValue: val.Value().(float64)}}), true
case types.BytesType:
return p.createLiteral(id,
&exprpb.Constant{ConstantKind: &exprpb.Constant_BytesValue{BytesValue: val.Value().([]byte)}}), true
case types.NullType:
return p.createLiteral(id,
&exprpb.Constant{ConstantKind: &exprpb.Constant_NullValue{NullValue: val.Value().(structpb.NullValue)}}), true
}
// Attempt to build a list literal.
if list, isList := val.(traits.Lister); isList {
sz := list.Size().(types.Int)
elemExprs := make([]*exprpb.Expr, sz)
for i := types.Int(0); i < sz; i++ {
elem := list.Get(i)
if types.IsUnknownOrError(elem) {
return nil, false
}
elemExpr, ok := p.maybeCreateLiteral(p.nextID(), elem)
if !ok {
return nil, false
}
elemExprs[i] = elemExpr
}
return &exprpb.Expr{
Id: id,
ExprKind: &exprpb.Expr_ListExpr{
ListExpr: &exprpb.Expr_CreateList{
Elements: elemExprs,
},
},
}, true
}
// Create a map literal if possible.
if mp, isMap := val.(traits.Mapper); isMap {
it := mp.Iterator()
entries := make([]*exprpb.Expr_CreateStruct_Entry, mp.Size().(types.Int))
i := 0
for it.HasNext() != types.False {
key := it.Next()
val := mp.Get(key)
if types.IsUnknownOrError(key) || types.IsUnknownOrError(val) {
return nil, false
}
keyExpr, ok := p.maybeCreateLiteral(p.nextID(), key)
if !ok {
return nil, false
}
valExpr, ok := p.maybeCreateLiteral(p.nextID(), val)
if !ok {
return nil, false
}
entry := &exprpb.Expr_CreateStruct_Entry{
Id: p.nextID(),
KeyKind: &exprpb.Expr_CreateStruct_Entry_MapKey{
MapKey: keyExpr,
},
Value: valExpr,
}
entries[i] = entry
i++
}
return &exprpb.Expr{
Id: id,
ExprKind: &exprpb.Expr_StructExpr{
StructExpr: &exprpb.Expr_CreateStruct{
Entries: entries,
},
},
}, true
}
// TODO(issues/377) To construct message literals, the type provider will need to support
// the enumeration the fields for a given message.
return nil, false
}
func (p *astPruner) maybePruneAndOr(node *exprpb.Expr) (*exprpb.Expr, bool) {
if !p.existsWithUnknownValue(node.GetId()) {
return nil, false
}
call := node.GetCallExpr()
// We know result is unknown, so we have at least one unknown arg
// and if one side is a known value, we know we can ignore it.
if p.existsWithKnownValue(call.Args[0].GetId()) {
return call.Args[1], true
}
if p.existsWithKnownValue(call.Args[1].GetId()) {
return call.Args[0], true
}
return nil, false
}
func (p *astPruner) maybePruneConditional(node *exprpb.Expr) (*exprpb.Expr, bool) {
if !p.existsWithUnknownValue(node.GetId()) {
return nil, false
}
call := node.GetCallExpr()
condVal, condValueExists := p.value(call.Args[0].GetId())
if !condValueExists || types.IsUnknownOrError(condVal) {
return nil, false
}
if condVal.Value().(bool) {
return call.Args[1], true
}
return call.Args[2], true
}
func (p *astPruner) maybePruneFunction(node *exprpb.Expr) (*exprpb.Expr, bool) {
call := node.GetCallExpr()
if call.Function == operators.LogicalOr || call.Function == operators.LogicalAnd {
return p.maybePruneAndOr(node)
}
if call.Function == operators.Conditional {
return p.maybePruneConditional(node)
}
return nil, false
}
func (p *astPruner) prune(node *exprpb.Expr) (*exprpb.Expr, bool) {
if node == nil {
return node, false
}
val, valueExists := p.value(node.GetId())
if valueExists && !types.IsUnknownOrError(val) {
if newNode, ok := p.maybeCreateLiteral(node.GetId(), val); ok {
return newNode, true
}
}
// We have either an unknown/error value, or something we don't want to
// transform, or expression was not evaluated. If possible, drill down
// more.
switch node.GetExprKind().(type) {
case *exprpb.Expr_SelectExpr:
if operand, pruned := p.prune(node.GetSelectExpr().GetOperand()); pruned {
return &exprpb.Expr{
Id: node.GetId(),
ExprKind: &exprpb.Expr_SelectExpr{
SelectExpr: &exprpb.Expr_Select{
Operand: operand,
Field: node.GetSelectExpr().GetField(),
TestOnly: node.GetSelectExpr().GetTestOnly(),
},
},
}, true
}
case *exprpb.Expr_CallExpr:
if newExpr, pruned := p.maybePruneFunction(node); pruned {
newExpr, _ = p.prune(newExpr)
return newExpr, true
}
var prunedCall bool
call := node.GetCallExpr()
args := call.GetArgs()
newArgs := make([]*exprpb.Expr, len(args))
newCall := &exprpb.Expr_Call{
Function: call.GetFunction(),
Target: call.GetTarget(),
Args: newArgs,
}
for i, arg := range args {
newArgs[i] = arg
if newArg, prunedArg := p.prune(arg); prunedArg {
prunedCall = true
newArgs[i] = newArg
}
}
if newTarget, prunedTarget := p.prune(call.GetTarget()); prunedTarget {
prunedCall = true
newCall.Target = newTarget
}
if prunedCall {
return &exprpb.Expr{
Id: node.GetId(),
ExprKind: &exprpb.Expr_CallExpr{
CallExpr: newCall,
},
}, true
}
case *exprpb.Expr_ListExpr:
elems := node.GetListExpr().GetElements()
newElems := make([]*exprpb.Expr, len(elems))
var prunedList bool
for i, elem := range elems {
newElems[i] = elem
if newElem, prunedElem := p.prune(elem); prunedElem {
newElems[i] = newElem
prunedList = true
}
}
if prunedList {
return &exprpb.Expr{
Id: node.GetId(),
ExprKind: &exprpb.Expr_ListExpr{
ListExpr: &exprpb.Expr_CreateList{
Elements: newElems,
},
},
}, true
}
case *exprpb.Expr_StructExpr:
var prunedStruct bool
entries := node.GetStructExpr().GetEntries()
messageType := node.GetStructExpr().GetMessageName()
newEntries := make([]*exprpb.Expr_CreateStruct_Entry, len(entries))
for i, entry := range entries {
newEntries[i] = entry
newKey, prunedKey := p.prune(entry.GetMapKey())
newValue, prunedValue := p.prune(entry.GetValue())
if !prunedKey && !prunedValue {
continue
}
prunedStruct = true
newEntry := &exprpb.Expr_CreateStruct_Entry{
Value: newValue,
}
if messageType != "" {
newEntry.KeyKind = &exprpb.Expr_CreateStruct_Entry_FieldKey{
FieldKey: entry.GetFieldKey(),
}
} else {
newEntry.KeyKind = &exprpb.Expr_CreateStruct_Entry_MapKey{
MapKey: newKey,
}
}
newEntries[i] = newEntry
}
if prunedStruct {
return &exprpb.Expr{
Id: node.GetId(),
ExprKind: &exprpb.Expr_StructExpr{
StructExpr: &exprpb.Expr_CreateStruct{
MessageName: messageType,
Entries: newEntries,
},
},
}, 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.prune(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
}
func (p *astPruner) value(id int64) (ref.Val, bool) {
val, found := p.state.Value(id)
return val, (found && val != nil)
}
func (p *astPruner) existsWithUnknownValue(id int64) bool {
val, valueExists := p.value(id)
return valueExists && types.IsUnknown(val)
}
func (p *astPruner) existsWithKnownValue(id int64) bool {
val, valueExists := p.value(id)
return valueExists && !types.IsUnknown(val)
}
func (p *astPruner) nextID() int64 {
for {
_, found := p.state.Value(p.nextExprID)
if !found {
next := p.nextExprID
p.nextExprID++
return next
}
p.nextExprID++
}
}

View File

@ -0,0 +1,241 @@
// Copyright 2022 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 interpreter
import (
"math"
"github.com/google/cel-go/common"
"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"
)
// WARNING: Any changes to cost calculations in this file require a corresponding change in checker/cost.go
// ActualCostEstimator provides function call cost estimations at runtime
// CallCost returns an estimated cost for the function overload invocation with the given args, or nil if it has no
// estimate to provide. CEL attempts to provide reasonable estimates for its standard function library, so CallCost
// should typically not need to provide an estimate for CELs standard function.
type ActualCostEstimator interface {
CallCost(function, overloadID string, args []ref.Val, result ref.Val) *uint64
}
// CostObserver provides an observer that tracks runtime cost.
func CostObserver(tracker *CostTracker) EvalObserver {
observer := func(id int64, programStep interface{}, val ref.Val) {
switch t := programStep.(type) {
case ConstantQualifier:
// TODO: Push identifiers on to the stack before observing constant qualifiers that apply to them
// and enable the below pop. Once enabled this can case can be collapsed into the Qualifier case.
tracker.cost++
case InterpretableConst:
// zero cost
case InterpretableAttribute:
switch a := t.Attr().(type) {
case *conditionalAttribute:
// Ternary has no direct cost. All cost is from the conditional and the true/false branch expressions.
tracker.stack.drop(a.falsy.ID(), a.truthy.ID(), a.expr.ID())
default:
tracker.stack.drop(t.Attr().ID())
tracker.cost += common.SelectAndIdentCost
}
case *evalExhaustiveConditional:
// Ternary has no direct cost. All cost is from the conditional and the true/false branch expressions.
tracker.stack.drop(t.attr.falsy.ID(), t.attr.truthy.ID(), t.attr.expr.ID())
// 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())
case *evalAnd:
tracker.stack.drop(t.rhs.ID(), t.lhs.ID())
case *evalExhaustiveOr:
tracker.stack.drop(t.rhs.ID(), t.lhs.ID())
case *evalExhaustiveAnd:
tracker.stack.drop(t.rhs.ID(), t.lhs.ID())
case *evalFold:
tracker.stack.drop(t.iterRange.ID())
case Qualifier:
tracker.cost++
case InterpretableCall:
if argVals, ok := tracker.stack.dropArgs(t.Args()); ok {
tracker.cost += tracker.costCall(t, argVals, val)
}
case InterpretableConstructor:
tracker.stack.dropArgs(t.InitVals())
switch t.Type() {
case types.ListType:
tracker.cost += common.ListCreateBaseCost
case types.MapType:
tracker.cost += common.MapCreateBaseCost
default:
tracker.cost += common.StructCreateBaseCost
}
}
tracker.stack.push(val, id)
if tracker.Limit != nil && tracker.cost > *tracker.Limit {
panic(EvalCancelledError{Cause: CostLimitExceeded, Message: "operation cancelled: actual cost limit exceeded"})
}
}
return observer
}
// CostTracker represents the information needed for tacking runtime cost
type CostTracker struct {
Estimator ActualCostEstimator
Limit *uint64
cost uint64
stack refValStack
}
// ActualCost returns the runtime cost
func (c CostTracker) ActualCost() uint64 {
return c.cost
}
func (c CostTracker) costCall(call InterpretableCall, argValues []ref.Val, result ref.Val) uint64 {
var cost uint64
if c.Estimator != nil {
callCost := c.Estimator.CallCost(call.Function(), call.OverloadID(), argValues, result)
if callCost != nil {
cost += *callCost
return cost
}
}
// if user didn't specify, the default way of calculating runtime cost would be used.
// if user has their own implementation of ActualCostEstimator, make sure to cover the mapping between overloadId and cost calculation
switch call.OverloadID() {
// O(n) functions
case overloads.StartsWithString, overloads.EndsWithString, overloads.StringToBytes, overloads.BytesToString:
cost += uint64(math.Ceil(float64(c.actualSize(argValues[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])
// O(min(m, n)) functions
case overloads.LessString, overloads.GreaterString, overloads.LessEqualsString, overloads.GreaterEqualsString,
overloads.LessBytes, overloads.GreaterBytes, overloads.LessEqualsBytes, overloads.GreaterEqualsBytes,
overloads.Equals, overloads.NotEquals:
// 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])
minSize := lhsSize
if rhsSize < minSize {
minSize = rhsSize
}
cost += uint64(math.Ceil(float64(minSize) * common.StringTraversalCostFactor))
// 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))
// 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))
// 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))
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))
cost += strCost * substrCost
default:
// The following operations are assumed to have O(1) complexity.
// - AddList due to the implementation. Index lookup can be O(c) the
// number of concatenated lists, but we don't track that is cost calculations.
// - Conversions, since none perform a traversal of a type of unbound length.
// - Computing the size of strings, byte sequences, lists and maps.
// - Logical operations and all operators on fixed width scalars (comparisons, equality)
// - Any functions that don't have a declared cost either here or in provided ActualCostEstimator.
cost++
}
return cost
}
// actualSize returns the size of value
func (c CostTracker) actualSize(value ref.Val) uint64 {
if sz, ok := value.(traits.Sizer); ok {
return uint64(sz.Size().(types.Int))
}
return 1
}
type stackVal struct {
Val ref.Val
ID int64
}
// refValStack keeps track of values of the stack for cost calculation purposes
type refValStack []stackVal
func (s *refValStack) push(val ref.Val, id int64) {
value := stackVal{Val: val, ID: id}
*s = append(*s, value)
}
// TODO: Allowing drop and dropArgs to remove stack items above the IDs they are provided is a workaround. drop and dropArgs
// should find and remove only the stack items matching the provided IDs once all attributes are properly pushed and popped from stack.
// drop searches the stack for each ID and removes the ID and all stack items above it.
// If none of the IDs are found, the stack is not modified.
// WARNING: It is possible for multiple expressions with the same ID to exist (due to how macros are implemented) so it's
// possible that a dropped ID will remain on the stack. They should be removed when IDs on the stack are popped.
func (s *refValStack) drop(ids ...int64) {
for _, id := range ids {
for idx := len(*s) - 1; idx >= 0; idx-- {
if (*s)[idx].ID == id {
*s = (*s)[:idx]
break
}
}
}
}
// dropArgs searches the stack for all the args by their IDs, accumulates their associated ref.Vals and drops any
// stack items above any of the arg IDs. If any of the IDs are not found the stack, false is returned.
// Args are assumed to be found in the stack in reverse order, i.e. the last arg is expected to be found highest in
// the stack.
// WARNING: It is possible for multiple expressions with the same ID to exist (due to how macros are implemented) so it's
// possible that a dropped ID will remain on the stack. They should be removed when IDs on the stack are popped.
func (s *refValStack) dropArgs(args []Interpretable) ([]ref.Val, bool) {
result := make([]ref.Val, len(args))
argloop:
for nIdx := len(args) - 1; nIdx >= 0; nIdx-- {
for idx := len(*s) - 1; idx >= 0; idx-- {
if (*s)[idx].ID == args[nIdx].ID() {
el := (*s)[idx]
*s = (*s)[:idx]
result[nIdx] = el.Val
continue argloop
}
}
return nil, false
}
return result, true
}