mirror of
https://github.com/ceph/ceph-csi.git
synced 2025-06-13 18:43:34 +00:00
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:
committed by
mergify[bot]
parent
0e79135419
commit
07b05616a0
72
vendor/github.com/google/cel-go/interpreter/BUILD.bazel
generated
vendored
Normal file
72
vendor/github.com/google/cel-go/interpreter/BUILD.bazel
generated
vendored
Normal 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",
|
||||
],
|
||||
)
|
201
vendor/github.com/google/cel-go/interpreter/activation.go
generated
vendored
Normal file
201
vendor/github.com/google/cel-go/interpreter/activation.go
generated
vendored
Normal 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{}
|
||||
},
|
||||
}
|
||||
)
|
404
vendor/github.com/google/cel-go/interpreter/attribute_patterns.go
generated
vendored
Normal file
404
vendor/github.com/google/cel-go/interpreter/attribute_patterns.go
generated
vendored
Normal 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
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
35
vendor/github.com/google/cel-go/interpreter/coster.go
generated
vendored
Normal 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()
|
||||
}
|
269
vendor/github.com/google/cel-go/interpreter/decorators.go
generated
vendored
Normal file
269
vendor/github.com/google/cel-go/interpreter/decorators.go
generated
vendored
Normal 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
|
||||
}
|
100
vendor/github.com/google/cel-go/interpreter/dispatcher.go
generated
vendored
Normal file
100
vendor/github.com/google/cel-go/interpreter/dispatcher.go
generated
vendored
Normal 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
|
||||
}
|
75
vendor/github.com/google/cel-go/interpreter/evalstate.go
generated
vendored
Normal file
75
vendor/github.com/google/cel-go/interpreter/evalstate.go
generated
vendored
Normal 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{}
|
||||
}
|
22
vendor/github.com/google/cel-go/interpreter/functions/BUILD.bazel
generated
vendored
Normal file
22
vendor/github.com/google/cel-go/interpreter/functions/BUILD.bazel
generated
vendored
Normal 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",
|
||||
],
|
||||
)
|
62
vendor/github.com/google/cel-go/interpreter/functions/functions.go
generated
vendored
Normal file
62
vendor/github.com/google/cel-go/interpreter/functions/functions.go
generated
vendored
Normal 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
|
270
vendor/github.com/google/cel-go/interpreter/functions/standard.go
generated
vendored
Normal file
270
vendor/github.com/google/cel-go/interpreter/functions/standard.go
generated
vendored
Normal 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")
|
||||
}
|
1230
vendor/github.com/google/cel-go/interpreter/interpretable.go
generated
vendored
Normal file
1230
vendor/github.com/google/cel-go/interpreter/interpretable.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
217
vendor/github.com/google/cel-go/interpreter/interpreter.go
generated
vendored
Normal file
217
vendor/github.com/google/cel-go/interpreter/interpreter.go
generated
vendored
Normal 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)
|
||||
}
|
46
vendor/github.com/google/cel-go/interpreter/optimizations.go
generated
vendored
Normal file
46
vendor/github.com/google/cel-go/interpreter/optimizations.go
generated
vendored
Normal 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
794
vendor/github.com/google/cel-go/interpreter/planner.go
generated
vendored
Normal 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
397
vendor/github.com/google/cel-go/interpreter/prune.go
generated
vendored
Normal 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++
|
||||
}
|
||||
}
|
241
vendor/github.com/google/cel-go/interpreter/runtimecost.go
generated
vendored
Normal file
241
vendor/github.com/google/cel-go/interpreter/runtimecost.go
generated
vendored
Normal 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
|
||||
}
|
Reference in New Issue
Block a user