diff --git a/go.mod b/go.mod
index d485c4d952..7c5e8f18ce 100644
--- a/go.mod
+++ b/go.mod
@@ -113,7 +113,7 @@ require (
github.com/golang/protobuf v1.5.4 // indirect
github.com/golang/snappy v1.0.0 // indirect
github.com/google/btree v1.1.3 // indirect
- github.com/google/cel-go v0.28.1 // indirect
+ github.com/google/cel-go v0.29.0 // indirect
github.com/google/certificate-transparency-go v1.3.3 // indirect
github.com/google/gnostic-models v0.7.0 // indirect
github.com/google/go-github/v73 v73.0.0 // indirect
diff --git a/go.sum b/go.sum
index 623b355acc..c02f0c535b 100644
--- a/go.sum
+++ b/go.sum
@@ -284,8 +284,8 @@ github.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs=
github.com/golang/snappy v1.0.0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg=
github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
-github.com/google/cel-go v0.28.1 h1:YWIwi77J4xIsYUwAF/iIuS6haffzIHS8yWI8glSbLWM=
-github.com/google/cel-go v0.28.1/go.mod h1:X0bD6iVNR8pkROSOoHVdgTkzmRcosof7WQqCD6wcMc8=
+github.com/google/cel-go v0.29.0 h1:fEG+Ja3YRwNOqnQxTyJwoByAUAvTuxUGiro/jhrm4F4=
+github.com/google/cel-go v0.29.0/go.mod h1:X0bD6iVNR8pkROSOoHVdgTkzmRcosof7WQqCD6wcMc8=
github.com/google/certificate-transparency-go v1.3.3 h1:hq/rSxztSkXN2tx/3jQqF6Xc0O565UQPdHrOWvZwybo=
github.com/google/certificate-transparency-go v1.3.3/go.mod h1:iR17ZgSaXRzSa5qvjFl8TnVD5h8ky2JMVio+dzoKMgA=
github.com/google/gnostic-models v0.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo=
diff --git a/vendor/github.com/google/cel-go/cel/env.go b/vendor/github.com/google/cel-go/cel/env.go
index e2de2ff6f8..7139e415ea 100644
--- a/vendor/github.com/google/cel-go/cel/env.go
+++ b/vendor/github.com/google/cel-go/cel/env.go
@@ -436,6 +436,15 @@ func (e *Env) Check(ast *Ast) (*Ast, *Issues) {
return ast, nil
}
+// configuredExpressionSizeLimit returns the effective expression size code point limit.
+// A zero value means "use the parser default".
+func (e *Env) configuredExpressionSizeLimit() int {
+ if l := e.limits[limitCodePointSize]; l != 0 {
+ return l
+ }
+ return 100_000
+}
+
// Compile combines the Parse and Check phases CEL program compilation to produce an Ast and
// associated issues.
//
@@ -445,7 +454,11 @@ func (e *Env) Check(ast *Ast) (*Ast, *Issues) {
//
// Note, for parse-only uses of CEL use Parse.
func (e *Env) Compile(txt string) (*Ast, *Issues) {
- return e.CompileSource(common.NewTextSource(txt))
+ src, err := common.NewTextSourceWithLimit(txt, e.configuredExpressionSizeLimit())
+ if err != nil {
+ return nil, ErrorAsIssues(err)
+ }
+ return e.CompileSource(src)
}
// CompileSource combines the Parse and Check phases CEL program compilation to produce an Ast and
@@ -650,7 +663,10 @@ func (e *Env) Validators() []ASTValidator {
// This form of Parse creates a Source value for the input `txt` and forwards to the
// ParseSource method.
func (e *Env) Parse(txt string) (*Ast, *Issues) {
- src := common.NewTextSource(txt)
+ src, err := common.NewTextSourceWithLimit(txt, e.configuredExpressionSizeLimit())
+ if err != nil {
+ return nil, ErrorAsIssues(err)
+ }
return e.ParseSource(src)
}
diff --git a/vendor/github.com/google/cel-go/cel/folding.go b/vendor/github.com/google/cel-go/cel/folding.go
index d1ea6b19db..0cff9517a4 100644
--- a/vendor/github.com/google/cel-go/cel/folding.go
+++ b/vendor/github.com/google/cel-go/cel/folding.go
@@ -97,7 +97,7 @@ func (opt *constantFoldingOptimizer) Optimize(ctx *OptimizerContext, a *ast.AST)
continue
}
// Late-bound function calls cannot be folded.
- if fold.Kind() == ast.CallKind && isLateBoundFunctionCall(ctx, a, fold) {
+ if fold.Kind() == ast.CallKind && isLateBoundFunctionCall(ctx, fold) {
continue
}
// Otherwise, assume all context is needed to evaluate the expression.
@@ -168,7 +168,7 @@ func (opt *constantFoldingOptimizer) tryFold(ctx *OptimizerContext, a *ast.AST,
return nil
}
-func isLateBoundFunctionCall(ctx *OptimizerContext, a *ast.AST, expr ast.Expr) bool {
+func isLateBoundFunctionCall(ctx *OptimizerContext, expr ast.Expr) bool {
call := expr.AsCall()
function := ctx.Functions()[call.FunctionName()]
if function == nil {
@@ -518,7 +518,7 @@ func (opt *constantFoldingOptimizer) constantExprMatcher(ctx *OptimizerContext,
constantExprs = false
}
// Late-bound function calls cannot be folded.
- if e.Kind() == ast.CallKind && isLateBoundFunctionCall(ctx, a, e) {
+ if e.Kind() == ast.CallKind && isLateBoundFunctionCall(ctx, e) {
constantExprs = false
}
})
diff --git a/vendor/github.com/google/cel-go/cel/library.go b/vendor/github.com/google/cel-go/cel/library.go
index 3c8b6ba343..332eb3f170 100644
--- a/vendor/github.com/google/cel-go/cel/library.go
+++ b/vendor/github.com/google/cel-go/cel/library.go
@@ -590,7 +590,7 @@ func (lib *optionalLib) CompileOptions() []EnvOption {
// ProgramOptions implements the Library interface method.
func (lib *optionalLib) ProgramOptions() []ProgramOption {
return []ProgramOption{
- CustomDecorator(decorateOptionalOr),
+ CustomDecoratorV2(decorateOptionalOr),
}
}
@@ -683,7 +683,7 @@ func EnableErrorOnBadPresenceTest(value bool) EnvOption {
return features(featureEnableErrorOnBadPresenceTest, value)
}
-func decorateOptionalOr(i interpreter.Interpretable) (interpreter.Interpretable, error) {
+func decorateOptionalOr(i interpreter.InterpretableV2) (interpreter.InterpretableV2, error) {
call, ok := i.(interpreter.InterpretableCall)
if !ok {
return i, nil
@@ -720,8 +720,8 @@ func decorateOptionalOr(i interpreter.Interpretable) (interpreter.Interpretable,
// the second optional expression is evaluated and returned.
type evalOptionalOr struct {
id int64
- lhs interpreter.Interpretable
- rhs interpreter.Interpretable
+ lhs interpreter.InterpretableV2
+ rhs interpreter.InterpretableV2
}
// ID implements the Interpretable interface method.
@@ -729,11 +729,9 @@ func (opt *evalOptionalOr) ID() int64 {
return opt.id
}
-// Eval evaluates the left-hand side optional to determine whether it contains a value, else
-// proceeds with the right-hand side evaluation.
-func (opt *evalOptionalOr) Eval(ctx interpreter.Activation) ref.Val {
+func (opt *evalOptionalOr) Exec(frame *interpreter.ExecutionFrame) ref.Val {
// short-circuit lhs.
- optLHS := opt.lhs.Eval(ctx)
+ optLHS := opt.lhs.Exec(frame)
switch val := optLHS.(type) {
case *types.Err, *types.Unknown:
return optLHS
@@ -741,18 +739,24 @@ func (opt *evalOptionalOr) Eval(ctx interpreter.Activation) ref.Val {
if val.HasValue() {
return optLHS
}
- return opt.rhs.Eval(ctx)
+ return opt.rhs.Exec(frame)
default:
return types.NoSuchOverloadErr()
}
}
+// Eval evaluates the left-hand side optional to determine whether it contains a value, else
+// proceeds with the right-hand side evaluation.
+func (opt *evalOptionalOr) Eval(ctx interpreter.Activation) ref.Val {
+ return opt.Exec(interpreter.AsFrame(ctx))
+}
+
// evalOptionalOrValue selects between an optional or a concrete value. If the optional has a value,
// its value is returned, otherwise the alternative value expression is evaluated and returned.
type evalOptionalOrValue struct {
id int64
- lhs interpreter.Interpretable
- rhs interpreter.Interpretable
+ lhs interpreter.InterpretableV2
+ rhs interpreter.InterpretableV2
}
// ID implements the Interpretable interface method.
@@ -760,11 +764,9 @@ func (opt *evalOptionalOrValue) ID() int64 {
return opt.id
}
-// Eval evaluates the left-hand side optional to determine whether it contains a value, else
-// proceeds with the right-hand side evaluation.
-func (opt *evalOptionalOrValue) Eval(ctx interpreter.Activation) ref.Val {
+func (opt *evalOptionalOrValue) Exec(frame *interpreter.ExecutionFrame) ref.Val {
// short-circuit lhs.
- optLHS := opt.lhs.Eval(ctx)
+ optLHS := opt.lhs.Exec(frame)
switch val := optLHS.(type) {
case *types.Err, *types.Unknown:
@@ -773,12 +775,18 @@ func (opt *evalOptionalOrValue) Eval(ctx interpreter.Activation) ref.Val {
if val.HasValue() {
return val.GetValue()
}
- return opt.rhs.Eval(ctx)
+ return opt.rhs.Exec(frame)
default:
return types.NoSuchOverloadErr()
}
}
+// Eval evaluates the left-hand side optional to determine whether it contains a value, else
+// proceeds with the right-hand side evaluation.
+func (opt *evalOptionalOrValue) Eval(ctx interpreter.Activation) ref.Val {
+ return opt.Exec(interpreter.AsFrame(ctx))
+}
+
type timeLegacyLibrary struct{}
func (timeLegacyLibrary) CompileOptions() []EnvOption {
diff --git a/vendor/github.com/google/cel-go/cel/options.go b/vendor/github.com/google/cel-go/cel/options.go
index d7d2ab0340..86a98d35db 100644
--- a/vendor/github.com/google/cel-go/cel/options.go
+++ b/vendor/github.com/google/cel-go/cel/options.go
@@ -456,6 +456,14 @@ func CustomDecorator(dec interpreter.InterpretableDecorator) ProgramOption {
}
}
+// CustomDecoratorV2 appends an InterpreterDecoratorV2 to the program.
+func CustomDecoratorV2(dec interpreter.InterpretableDecoratorV2) ProgramOption {
+ return func(p *prog) (*prog, error) {
+ p.plannerOptions = append(p.plannerOptions, interpreter.CustomDecoratorV2(dec))
+ return p, nil
+ }
+}
+
// Functions adds function overloads that extend or override the set of CEL built-ins.
//
// Deprecated: use Function() instead to declare the function, its overload signatures,
diff --git a/vendor/github.com/google/cel-go/cel/program.go b/vendor/github.com/google/cel-go/cel/program.go
index c8b7250454..1fb6587774 100644
--- a/vendor/github.com/google/cel-go/cel/program.go
+++ b/vendor/github.com/google/cel-go/cel/program.go
@@ -18,7 +18,6 @@ import (
"context"
"errors"
"fmt"
- "sync"
"github.com/google/cel-go/common/ast"
"github.com/google/cel-go/common/functions"
@@ -160,7 +159,7 @@ type prog struct {
regexOptimizations []*interpreter.RegexOptimization
// Interpretable configured from an Ast and aggregate decorator set based on program options.
- interpretable interpreter.Interpretable
+ interpretable interpreter.InterpretableV2
observable *interpreter.ObservableInterpretable
callCostEstimator interpreter.ActualCostEstimator
costOptions []interpreter.CostTrackerOption
@@ -262,8 +261,16 @@ func newProgram(e *Env, a *ast.AST, opts []ProgramOption) (Program, error) {
if p.costLimit != nil {
costOpts = append(costOpts, interpreter.CostTrackerLimit(*p.costLimit))
}
+ // Creating a new cost tracker for each evaluation causes significant work that
+ // needs to be repeated for each evaluation even though the cost tracker is
+ // mostly read-only once constructed. Therefore it gets constructed
+ // once now and later a cheap clone is used for each evaluation.
+ tracker, err := interpreter.NewCostTracker(p.callCostEstimator, costOpts...)
+ if err != nil {
+ return nil, fmt.Errorf("construct cost tracker: %w", err)
+ }
trackerFactory := func() (*interpreter.CostTracker, error) {
- return interpreter.NewCostTracker(p.callCostEstimator, costOpts...)
+ return tracker.Clone()
}
var observers []interpreter.PlannerOption
if p.evalOpts&(OptExhaustiveEval|OptTrackState) != 0 {
@@ -313,22 +320,19 @@ func (p *prog) Eval(input any) (out ref.Val, det *EvalDetails, err error) {
}
}()
// Build a hierarchical activation if there are default vars set.
- var vars Activation
- switch v := input.(type) {
- case Activation:
- vars = v
- case map[string]any:
- vars = activationPool.Setup(v)
- defer activationPool.Put(vars)
- default:
- return nil, nil, fmt.Errorf("invalid input, wanted Activation or map[string]any, got: (%T)%v", input, input)
- }
- if p.defaultVars != nil {
- vars = interpreter.NewHierarchicalActivation(p.defaultVars, vars)
+ var frame *interpreter.ExecutionFrame
+ if f, ok := input.(*interpreter.ExecutionFrame); ok {
+ frame = f
+ } else {
+ frame, err = p.newExecutionFrame(input)
+ if err != nil {
+ return nil, nil, err
+ }
+ defer frame.Close()
}
if p.observable != nil {
det = &EvalDetails{}
- out = p.observable.ObserveEval(vars, func(observed any) {
+ out = p.observable.ObserveExec(frame, func(observed any) {
switch o := observed.(type) {
case interpreter.EvalState:
det.state = o
@@ -337,7 +341,7 @@ func (p *prog) Eval(input any) (out ref.Val, det *EvalDetails, err error) {
}
})
} else {
- out = p.interpretable.Eval(vars)
+ out = p.interpretable.Exec(frame)
}
// The output of an internal Eval may have a value (`v`) that is a types.Err. This step
// translates the CEL value to a Go error response. This interface does not quite match the
@@ -353,164 +357,29 @@ func (p *prog) ContextEval(ctx context.Context, input any) (ref.Val, *EvalDetail
if ctx == nil {
return nil, nil, fmt.Errorf("context can not be nil")
}
- // Configure the input, making sure to wrap Activation inputs in the special ctxActivation which
- // exposes the #interrupted variable and manages rate-limited checks of the ctx.Done() state.
- var vars Activation
- switch v := input.(type) {
- case Activation:
- vars = ctxActivationPool.Setup(v, ctx.Done(), p.interruptCheckFrequency)
- defer ctxActivationPool.Put(vars)
- case map[string]any:
- rawVars := activationPool.Setup(v)
- defer activationPool.Put(rawVars)
- vars = ctxActivationPool.Setup(rawVars, ctx.Done(), p.interruptCheckFrequency)
- defer ctxActivationPool.Put(vars)
- default:
- return nil, nil, fmt.Errorf("invalid input, wanted Activation or map[string]any, got: (%T)%v", input, input)
- }
- out, det, err := p.Eval(vars)
- if err != nil && errors.Is(err, interpreter.InterruptError{}) {
- return out, det, fmt.Errorf("%w: %w", err, context.Cause(ctx))
- }
- return out, det, err
-}
-
-type ctxEvalActivation struct {
- parent Activation
- interrupt <-chan struct{}
- interruptCheckCount uint
- interruptCheckFrequency uint
-}
-
-// ResolveName implements the Activation interface method, but adds a special #interrupted variable
-// which is capable of testing whether a 'done' signal is provided from a context.Context channel.
-func (a *ctxEvalActivation) ResolveName(name string) (any, bool) {
- if name == "#interrupted" {
- a.interruptCheckCount++
- if a.interruptCheckCount%a.interruptCheckFrequency == 0 {
- select {
- case <-a.interrupt:
- return true, true
- default:
- return nil, false
- }
- }
- return nil, false
+ frame, err := p.newExecutionFrame(input)
+ if err != nil {
+ return nil, nil, err
}
- return a.parent.ResolveName(name)
-}
-
-func (a *ctxEvalActivation) Parent() Activation {
- return a.parent
-}
-
-func (a *ctxEvalActivation) AsPartialActivation() (interpreter.PartialActivation, bool) {
- pa, ok := a.parent.(interpreter.PartialActivation)
- return pa, ok
-}
-
-func newCtxEvalActivationPool() *ctxEvalActivationPool {
- return &ctxEvalActivationPool{
- Pool: sync.Pool{
- New: func() any {
- return &ctxEvalActivation{}
- },
- },
+ defer frame.Close()
+ frame.SetContext(ctx, p.interruptCheckFrequency)
+ out, det, errEval := p.Eval(frame)
+ if errEval != nil && errors.Is(errEval, interpreter.InterruptError{}) {
+ return out, det, fmt.Errorf("%w: %w", errEval, context.Cause(ctx))
}
+ return out, det, errEval
}
-type ctxEvalActivationPool struct {
- sync.Pool
-}
-
-// Setup initializes a pooled Activation with the ability check for context.Context cancellation
-func (p *ctxEvalActivationPool) Setup(vars Activation, done <-chan struct{}, interruptCheckRate uint) *ctxEvalActivation {
- a := p.Pool.Get().(*ctxEvalActivation)
- a.parent = vars
- a.interrupt = done
- a.interruptCheckCount = 0
- a.interruptCheckFrequency = interruptCheckRate
- return a
-}
-
-type evalActivation struct {
- vars map[string]any
- lazyVars map[string]any
-}
-
-// ResolveName looks up the value of the input variable name, if found.
-//
-// Lazy bindings may be supplied within the map-based input in either of the following forms:
-// - func() any
-// - func() ref.Val
-//
-// The lazy binding will only be invoked once per evaluation.
-//
-// Values which are not represented as ref.Val types on input may be adapted to a ref.Val using
-// the types.Adapter configured in the environment.
-func (a *evalActivation) ResolveName(name string) (any, bool) {
- v, found := a.vars[name]
- if !found {
- return nil, false
- }
- switch obj := v.(type) {
- case func() ref.Val:
- if resolved, found := a.lazyVars[name]; found {
- return resolved, true
- }
- lazy := obj()
- a.lazyVars[name] = lazy
- return lazy, true
- case func() any:
- if resolved, found := a.lazyVars[name]; found {
- return resolved, true
- }
- lazy := obj()
- a.lazyVars[name] = lazy
- return lazy, true
- default:
- return obj, true
+// newExecutionFrame creates an ExecutionFrame for the given input without a timeout context.
+func (p *prog) newExecutionFrame(input any) (*interpreter.ExecutionFrame, error) {
+ frame, err := interpreter.NewExecutionFrame(input)
+ if err != nil {
+ return nil, err
}
-}
-
-// Parent implements the Activation interface
-func (a *evalActivation) Parent() Activation {
- return nil
-}
-
-func newEvalActivationPool() *evalActivationPool {
- return &evalActivationPool{
- Pool: sync.Pool{
- New: func() any {
- return &evalActivation{lazyVars: make(map[string]any)}
- },
- },
+ if p.defaultVars != nil {
+ // Update the frame's activation in place.
+ frame.Activation = interpreter.NewHierarchicalActivation(p.defaultVars, frame.Activation)
}
-}
-
-type evalActivationPool struct {
- sync.Pool
-}
-
-// Setup initializes a pooled Activation object with the map input.
-func (p *evalActivationPool) Setup(vars map[string]any) *evalActivation {
- a := p.Pool.Get().(*evalActivation)
- a.vars = vars
- return a
-}
-func (p *evalActivationPool) Put(value any) {
- a := value.(*evalActivation)
- for k := range a.lazyVars {
- delete(a.lazyVars, k)
- }
- p.Pool.Put(a)
+ return frame, nil
}
-
-var (
- // activationPool is an internally managed pool of Activation values that wrap map[string]any inputs
- activationPool = newEvalActivationPool()
-
- // ctxActivationPool is an internally managed pool of Activation values that expose a special #interrupted variable
- ctxActivationPool = newCtxEvalActivationPool()
-)
diff --git a/vendor/github.com/google/cel-go/cel/prompt.go b/vendor/github.com/google/cel-go/cel/prompt.go
index 1529680fd9..ca25e7ad7c 100644
--- a/vendor/github.com/google/cel-go/cel/prompt.go
+++ b/vendor/github.com/google/cel-go/cel/prompt.go
@@ -1,226 +1,233 @@
-// Copyright 2025 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
-//
-// https://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 cel
-
-import (
- _ "embed"
- "sort"
- "strings"
- "text/template"
-
- "github.com/google/cel-go/common"
- "github.com/google/cel-go/common/operators"
- "github.com/google/cel-go/common/overloads"
- "github.com/google/cel-go/common/types"
-)
-
-//go:embed templates/authoring.tmpl
-var authoringPrompt string
-
-// splitImpl splits a string into a list of strings.
-//
-// Normalizes extracted comments (trim common prefix whitespace and extra trailing newlines).
-func splitImpl(str string) []string {
- str = strings.TrimRight(str, " \n\t\r")
- out := strings.Split(str, "\n")
- if len(out) == 0 {
- return nil
- }
- negative := strings.TrimLeft(out[0], " \t")
- lenNegative := len(negative)
- lenOut := len(out[0])
- if lenNegative == lenOut {
- return out
- }
- prefix := out[0][:lenOut-lenNegative]
- trimmed := make([]string, len(out))
- for i, line := range out {
- if line == "" {
- trimmed[i] = ""
- continue
- }
- if !strings.HasPrefix(line, prefix) {
- return out
- }
- trimmed[i] = strings.TrimPrefix(line, prefix)
- }
-
- return trimmed
-}
-
-// AuthoringPrompt creates a prompt template from a CEL environment for the purpose of AI-assisted authoring.
-func AuthoringPrompt(env *Env) (*Prompt, error) {
- funcMap := template.FuncMap{
- "split": splitImpl,
- "newlineToSpace": func(str string) string { return strings.ReplaceAll(str, "\n", " ") },
- }
- tmpl := template.New("cel").Funcs(funcMap)
- tmpl, err := tmpl.Parse(authoringPrompt)
- if err != nil {
- return nil, err
- }
- return &Prompt{
- Persona: defaultPersona,
- FormatRules: defaultFormatRules,
- GeneralUsage: defaultGeneralUsage,
- tmpl: tmpl,
- env: env,
- }, nil
-}
-
-// AuthoringPromptWithFieldPaths creates a prompt template from a CEL environment for the purpose of AI-assisted authoring.
-// Includes documentation for all of the reachable field paths in the environment.
-func AuthoringPromptWithFieldPaths(env *Env) (*Prompt, error) {
- p, err := AuthoringPrompt(env)
- if err != nil {
- return nil, err
- }
- p.fieldPaths = true
- return p, nil
-}
-
-// Prompt represents the core components of an LLM prompt based on a CEL environment.
-//
-// All fields of the prompt may be overwritten / modified with support for rendering the
-// prompt to a human-readable string.
-type Prompt struct {
- // Persona indicates something about the kind of user making the request
- Persona string
-
- // FormatRules indicate how the LLM should generate its output
- FormatRules string
-
- // GeneralUsage specifies additional context on how CEL should be used.
- GeneralUsage string
-
- // tmpl is the text template base-configuration for rendering text.
- tmpl *template.Template
-
- // fieldPaths is a flag to enable including reachable field paths in the prompt.
- fieldPaths bool
-
- // env reference used to collect variables, functions, and macros available to the prompt.
- env *Env
-}
-
-type promptVariable struct {
- *common.Doc
- FieldPaths []*common.Doc
-}
-
-type promptInst struct {
- *Prompt
-
- Variables []*promptVariable
- Macros []*common.Doc
- Functions []*common.Doc
- UserPrompt string
-}
-
-// Render renders the user prompt with the associated context from the prompt template
-// for use with LLM generators.
-func (p *Prompt) Render(userPrompt string) string {
- var buffer strings.Builder
- vars := make([]*promptVariable, len(p.env.Variables()))
- for i, v := range p.env.Variables() {
- vars[i] = &promptVariable{Doc: v.Documentation()}
- if p.fieldPaths && v.Type().Kind() == types.StructKind {
- var fieldPaths []*common.Doc
-
- paths := fieldPathsForType(p.env.CELTypeProvider(), v.Name(), v.Type())
- if len(paths) < 2 {
- paths = nil
- } else {
- // First path is the variable which is already documented.
- paths = paths[1:]
- }
- for _, path := range paths {
- fieldPaths = append(fieldPaths, path.Documentation())
- }
-
- sort.SliceStable(fieldPaths, func(i, j int) bool {
- return fieldPaths[i].Name < fieldPaths[j].Name
- })
- vars[i].FieldPaths = fieldPaths
- }
- }
- sort.SliceStable(vars, func(i, j int) bool {
- return vars[i].Name < vars[j].Name
- })
- macs := make([]*common.Doc, len(p.env.Macros()))
- for i, m := range p.env.Macros() {
- macs[i] = m.(common.Documentor).Documentation()
- }
- funcs := make([]*common.Doc, 0, len(p.env.Functions()))
- for _, f := range p.env.Functions() {
- if _, hidden := hiddenFunctions[f.Name()]; hidden {
- continue
- }
- funcs = append(funcs, f.Documentation())
- }
- sort.SliceStable(funcs, func(i, j int) bool {
- return funcs[i].Name < funcs[j].Name
- })
- inst := &promptInst{
- Prompt: p,
- Variables: vars,
- Macros: macs,
- Functions: funcs,
- UserPrompt: userPrompt}
- p.tmpl.Execute(&buffer, inst)
- return buffer.String()
-}
-
-const (
- defaultPersona = `You are a software engineer with expertise in networking and application security
-authoring boolean Common Expression Language (CEL) expressions to ensure firewall,
-networking, authentication, and data access is only permitted when all conditions
-are satisfied.`
-
- defaultFormatRules = `Output your response as a CEL expression.
-
-Write the expression with the comment on the first line and the expression on the
-subsequent lines. Format the expression using 80-character line limits commonly
-found in C++ or Java code.`
-
- defaultGeneralUsage = `CEL supports Protocol Buffer and JSON types, as well as simple types and aggregate types.
-
-Simple types include bool, bytes, double, int, string, and uint:
-
-* double literals must always include a decimal point: 1.0, 3.5, -2.2
-* uint literals must be positive values suffixed with a 'u': 42u
-* byte literals are strings prefixed with a 'b': b'1235'
-* string literals can use either single quotes or double quotes: 'hello', "world"
-* string literals can also be treated as raw strings that do not require any
- escaping within the string by using the 'R' prefix: R"""quote: "hi" """
-
-Aggregate types include list and map:
-
-* list literals consist of zero or more values between brackets: "['a', 'b', 'c']"
-* map literal consist of colon-separated key-value pairs within braces: "{'key1': 1, 'key2': 2}"
-* Only int, uint, string, and bool types are valid map keys.
-* Maps containing HTTP headers must always use lower-cased string keys.
-
-Comments start with two-forward slashes followed by text and a newline.`
-)
-
-var (
- hiddenFunctions = map[string]bool{
- overloads.DeprecatedIn: true,
- operators.OldIn: true,
- operators.OldNotStrictlyFalse: true,
- operators.NotStrictlyFalse: true,
- }
-)
+// Copyright 2025 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
+//
+// https://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 cel
+
+import (
+ _ "embed"
+ "sort"
+ "strings"
+ "text/template"
+
+ "github.com/google/cel-go/common"
+ "github.com/google/cel-go/common/operators"
+ "github.com/google/cel-go/common/overloads"
+ "github.com/google/cel-go/common/types"
+)
+
+//go:embed templates/authoring.tmpl
+var authoringPrompt string
+
+// splitImpl splits a string into a list of strings.
+//
+// Normalizes extracted comments (trim common prefix whitespace and extra trailing newlines).
+func splitImpl(str string) []string {
+ str = strings.TrimRight(str, " \n\t\r")
+ out := strings.Split(str, "\n")
+ if len(out) == 0 {
+ return nil
+ }
+ negative := strings.TrimLeft(out[0], " \t")
+ lenNegative := len(negative)
+ lenOut := len(out[0])
+ if lenNegative == lenOut {
+ return out
+ }
+ prefix := out[0][:lenOut-lenNegative]
+ trimmed := make([]string, len(out))
+ for i, line := range out {
+ if line == "" {
+ trimmed[i] = ""
+ continue
+ }
+ if !strings.HasPrefix(line, prefix) {
+ return out
+ }
+ trimmed[i] = strings.TrimPrefix(line, prefix)
+ }
+
+ return trimmed
+}
+
+// AuthoringPrompt creates a prompt template from a CEL environment for the purpose of AI-assisted authoring.
+func AuthoringPrompt(env *Env) (*Prompt, error) {
+ funcMap := template.FuncMap{
+ "split": splitImpl,
+ "newlineToSpace": func(str string) string { return strings.ReplaceAll(str, "\n", " ") },
+ }
+ tmpl := template.New("cel").Funcs(funcMap)
+ tmpl, err := tmpl.Parse(authoringPrompt)
+ if err != nil {
+ return nil, err
+ }
+ return &Prompt{
+ Persona: defaultPersona,
+ FormatRules: defaultFormatRules,
+ GeneralUsage: defaultGeneralUsage,
+ tmpl: tmpl,
+ env: env,
+ }, nil
+}
+
+// AuthoringPromptWithFieldPaths creates a prompt template from a CEL environment for the purpose of AI-assisted authoring.
+// Includes documentation for all of the reachable field paths in the environment.
+func AuthoringPromptWithFieldPaths(env *Env) (*Prompt, error) {
+ p, err := AuthoringPrompt(env)
+ if err != nil {
+ return nil, err
+ }
+ p.fieldPaths = true
+ return p, nil
+}
+
+// Prompt represents the core components of an LLM prompt based on a CEL environment.
+//
+// All fields of the prompt may be overwritten / modified with support for rendering the
+// prompt to a human-readable string.
+type Prompt struct {
+ // Persona indicates something about the kind of user making the request
+ Persona string
+
+ // FormatRules indicate how the LLM should generate its output
+ FormatRules string
+
+ // GeneralUsage specifies additional context on how CEL should be used.
+ GeneralUsage string
+
+ // tmpl is the text template base-configuration for rendering text.
+ tmpl *template.Template
+
+ // fieldPaths is a flag to include reachable field paths in the prompt.
+ fieldPaths bool
+
+ // env reference used to collect variables, functions, and macros available to the prompt.
+ env *Env
+}
+
+type promptVariable struct {
+ *common.Doc
+ FieldPaths []*common.Doc
+}
+
+type promptInst struct {
+ *Prompt
+
+ Variables []*promptVariable
+ Macros []*common.Doc
+ Functions []*common.Doc
+ UserPrompt string
+}
+
+// Render renders the user prompt with the associated context from the prompt template
+// for use with LLM generators.
+//
+// User-supplied input is passed as template data via the UserPrompt field, which
+// Go's text/template renders as a literal string value. Template action delimiters
+// such as {{.Persona}} in the user prompt are never evaluated as template directives
+// because text/template only executes directives present in the template definition
+// itself, not in data values interpolated at render time.
+func (p *Prompt) Render(userPrompt string) string {
+ var buffer strings.Builder
+ vars := make([]*promptVariable, len(p.env.Variables()))
+ for i, v := range p.env.Variables() {
+ vars[i] = &promptVariable{Doc: v.Documentation()}
+ if p.fieldPaths && v.Type().Kind() == types.StructKind {
+ var fieldPaths []*common.Doc
+
+ paths := fieldPathsForType(p.env.CELTypeProvider(), v.Name(), v.Type())
+ if len(paths) < 2 {
+ paths = nil
+ } else {
+ // First path is the variable which is already documented.
+ paths = paths[1:]
+ }
+ for _, path := range paths {
+ fieldPaths = append(fieldPaths, path.Documentation())
+ }
+
+ sort.SliceStable(fieldPaths, func(i, j int) bool {
+ return fieldPaths[i].Name < fieldPaths[j].Name
+ })
+ vars[i].FieldPaths = fieldPaths
+ }
+ }
+ sort.SliceStable(vars, func(i, j int) bool {
+ return vars[i].Name < vars[j].Name
+ })
+ macs := make([]*common.Doc, len(p.env.Macros()))
+ for i, m := range p.env.Macros() {
+ macs[i] = m.(common.Documentor).Documentation()
+ }
+ funcs := make([]*common.Doc, 0, len(p.env.Functions()))
+ for _, f := range p.env.Functions() {
+ if _, hidden := hiddenFunctions[f.Name()]; hidden {
+ continue
+ }
+ funcs = append(funcs, f.Documentation())
+ }
+ sort.SliceStable(funcs, func(i, j int) bool {
+ return funcs[i].Name < funcs[j].Name
+ })
+ inst := &promptInst{
+ Prompt: p,
+ Variables: vars,
+ Macros: macs,
+ Functions: funcs,
+ UserPrompt: userPrompt,
+ }
+ p.tmpl.Execute(&buffer, inst)
+ return buffer.String()
+}
+
+const (
+ defaultPersona = `You are a software engineer with expertise in networking and application security
+authoring boolean Common Expression Language (CEL) expressions to ensure firewall,
+networking, authentication, and data access is only permitted when all conditions
+are satisfied.`
+
+ defaultFormatRules = `Output your response as a CEL expression.
+
+Write the expression with the comment on the first line and the expression on the
+subsequent lines. Format the expression using 80-character line limits commonly
+found in C++ or Java code.`
+
+ defaultGeneralUsage = `CEL supports Protocol Buffer and JSON types, as well as simple types and aggregate types.
+
+Simple types include bool, bytes, double, int, string, and uint:
+
+* double literals must always include a decimal point: 1.0, 3.5, -2.2
+* uint literals must be positive values suffixed with a 'u': 42u
+* byte literals are strings prefixed with a 'b': b'1235'
+* string literals can use either single quotes or double quotes: 'hello', "world"
+* string literals can also be treated as raw strings that do not require any
+ escaping within the string by using the 'R' prefix: R"""quote: "hi" """
+
+Aggregate types include list and map:
+
+* list literals consist of zero or more values between brackets: "['a', 'b', 'c']"
+* map literal consist of colon-separated key-value pairs within braces: "{'key1': 1, 'key2': 2}"
+* Only int, uint, string, and bool types are valid map keys.
+* Maps containing HTTP headers must always use lower-cased string keys.
+
+Comments start with two-forward slashes followed by text and a newline.`
+)
+
+var (
+ hiddenFunctions = map[string]bool{
+ overloads.DeprecatedIn: true,
+ operators.OldIn: true,
+ operators.OldNotStrictlyFalse: true,
+ operators.NotStrictlyFalse: true,
+ }
+)
\ No newline at end of file
diff --git a/vendor/github.com/google/cel-go/checker/cost.go b/vendor/github.com/google/cel-go/checker/cost.go
index 086dc40fa1..3d7dd7ec4a 100644
--- a/vendor/github.com/google/cel-go/checker/cost.go
+++ b/vendor/github.com/google/cel-go/checker/cost.go
@@ -791,18 +791,26 @@ func (c *coster) functionCost(e ast.Expr, function, overloadID string, target *A
return CallEstimate{CostEstimate: c.sizeOrUnknown(args[1]).MultiplyByCostFactor(1).Add(argCostSum())}
}
// O(nm) functions
- case overloads.MatchesString:
+ case overloads.Matches, overloads.MatchesString:
// https://swtch.com/~rsc/regexp/regexp1.html applies to RE2 implementation supported by CEL
- if target != nil && len(args) == 1 {
+ var strNode, regexNode AstNode
+ if overloadID == overloads.MatchesString && target != nil && len(args) == 1 {
+ strNode = *target
+ regexNode = args[0]
+ } else if overloadID == overloads.Matches && target == nil && len(args) == 2 {
+ strNode = args[0]
+ regexNode = args[1]
+ }
+ if strNode != nil && regexNode != nil {
// 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 := c.sizeOrUnknown(*target).Add(SizeEstimate{Min: 1, Max: 1}).MultiplyByCostFactor(common.StringTraversalCostFactor)
+ strCost := c.sizeOrUnknown(strNode).Add(SizeEstimate{Min: 1, Max: 1}).MultiplyByCostFactor(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 := c.sizeOrUnknown(args[0]).MultiplyByCostFactor(common.RegexStringLengthCostFactor)
+ regexCost := c.sizeOrUnknown(regexNode).MultiplyByCostFactor(common.RegexStringLengthCostFactor)
return CallEstimate{CostEstimate: strCost.Multiply(regexCost).Add(argCostSum())}
}
case overloads.ContainsString:
diff --git a/vendor/github.com/google/cel-go/common/containers/container.go b/vendor/github.com/google/cel-go/common/containers/container.go
index fc146b6fc4..fcfcdfc3f4 100644
--- a/vendor/github.com/google/cel-go/common/containers/container.go
+++ b/vendor/github.com/google/cel-go/common/containers/container.go
@@ -227,7 +227,7 @@ func Abbrevs(qualifiedNames ...string) ContainerOption {
}
alias := qn[ind+1:]
var err error
- c, err = aliasAs("abbreviation", qn, alias)(c)
+ c, err = aliasAs("abbreviation", qn, alias, true)(c)
if err != nil {
return nil, err
}
@@ -236,31 +236,32 @@ func Abbrevs(qualifiedNames ...string) ContainerOption {
}
}
-// Alias associates a fully-qualified name with a user-defined alias.
+// Alias associates a name with a user-defined alias.
//
// In general, Abbrevs is preferred to Alias since the names generated from the Abbrevs option
// are more easily traced back to source code. The Alias option is useful for propagating alias
// configuration from one Container instance to another, and may also be useful for remapping
// poorly chosen protobuf message / package names.
-//
-// Note: all of the rules that apply to Abbrevs also apply to Alias.
func Alias(qualifiedName, alias string) ContainerOption {
- return aliasAs("alias", qualifiedName, alias)
+ return aliasAs("alias", qualifiedName, alias, false)
}
-func aliasAs(kind, qualifiedName, alias string) ContainerOption {
+func aliasAs(kind, qualifiedName, alias string, requireQualified bool) ContainerOption {
return func(c *Container) (*Container, error) {
if len(alias) == 0 || strings.Contains(alias, ".") {
return nil, fmt.Errorf(
"%s must be non-empty and simple (not qualified): %s=%s", kind, kind, alias)
}
+ if len(qualifiedName) == 0 {
+ return nil, fmt.Errorf("%s must refer to a valid name: %s", kind, qualifiedName)
+ }
if qualifiedName[0:1] == "." {
return nil, fmt.Errorf("qualified name must not begin with a leading '.': %s",
qualifiedName)
}
ind := strings.LastIndex(qualifiedName, ".")
- if ind <= 0 || ind == len(qualifiedName)-1 {
+ if ind == len(qualifiedName)-1 || (requireQualified && ind <= 0) {
return nil, fmt.Errorf("%s must refer to a valid qualified name: %s",
kind, qualifiedName)
}
diff --git a/vendor/github.com/google/cel-go/common/functions/functions.go b/vendor/github.com/google/cel-go/common/functions/functions.go
index 67f4a5944e..0c00781d9c 100644
--- a/vendor/github.com/google/cel-go/common/functions/functions.go
+++ b/vendor/github.com/google/cel-go/common/functions/functions.go
@@ -15,7 +15,11 @@
// Package functions defines the standard builtin functions supported by the interpreter
package functions
-import "github.com/google/cel-go/common/types/ref"
+import (
+ "context"
+
+ "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
@@ -41,21 +45,37 @@ type Overload struct {
// 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 defines the overload with a FunctionOp implementation. May be nil.
Function FunctionOp
+ // Async defines the overload with an AsyncOp implementation. May be nil.
+ Async AsyncOp
+
// 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
+type UnaryOp func(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
+type BinaryOp func(ref.Val, ref.Val) ref.Val
// FunctionOp is a function with accepts zero or more arguments and produces
// a value or error as a result.
-type FunctionOp func(values ...ref.Val) ref.Val
+type FunctionOp func(...ref.Val) ref.Val
+
+// AsyncOp is a function that accepts zero or more arguments and produces
+// a value or error asynchronously via a channel.
+//
+// AsyncOp is an internal interface intended for use by CEL to manage goroutines and
+// channels associated with async calls. For public API usage, use BlockingAsyncOp.
+// Implementers should listen for context cancellation on the provided context for
+// resource cleanup.
+type AsyncOp func(context.Context, ...ref.Val) <-chan ref.Val
+
+// BlockingAsyncOp is a function that accepts zero or more arguments and blocks until
+// the result is available. When used with AsyncBinding, the framework runs the function
+// in its own goroutine and manages channel lifecycle internally.
+type BlockingAsyncOp func(context.Context, ...ref.Val) ref.Val
diff --git a/vendor/github.com/google/cel-go/common/runes/buffer.go b/vendor/github.com/google/cel-go/common/runes/buffer.go
index 021198224d..58dd33e13e 100644
--- a/vendor/github.com/google/cel-go/common/runes/buffer.go
+++ b/vendor/github.com/google/cel-go/common/runes/buffer.go
@@ -16,6 +16,7 @@
package runes
import (
+ "fmt"
"strings"
"unicode/utf8"
)
@@ -113,45 +114,64 @@ var _ Buffer = &supplementalBuffer{}
var nilBuffer = &emptyBuffer{}
+// SizeLimitError indicates that the input exceeded the configured code point limit.
+type SizeLimitError struct {
+ Size int
+ Limit int
+}
+
+func (e *SizeLimitError) Error() string {
+ return fmt.Sprintf("expression code point size exceeds limit: size: %d, limit %d", e.Size, e.Limit)
+}
+
// NewBuffer returns an efficient implementation of Buffer for the given text based on the ranges of
// the encoded code points contained within.
-//
-// Code points are represented as an array of byte, uint16, or rune. This approach ensures that
-// each index represents a code point by itself without needing to use an array of rune. At first
-// we assume all code points are less than or equal to '\u007f'. If this holds true, the
-// underlying storage is a byte array containing only ASCII characters. If we encountered a code
-// point above this range but less than or equal to '\uffff' we allocate a uint16 array, copy the
-// elements of previous byte array to the uint16 array, and continue. If this holds true, the
-// underlying storage is a uint16 array containing only Unicode characters in the Basic Multilingual
-// Plane. If we encounter a code point above '\uffff' we allocate an rune array, copy the previous
-// elements of the byte or uint16 array, and continue. The underlying storage is an rune array
-// containing any Unicode character.
func NewBuffer(data string) Buffer {
- buf, _ := newBuffer(data, false)
+ buf, _, _ := newBufferWithLimit(data, false, -1)
return buf
}
// NewBufferAndLineOffsets returns an efficient implementation of Buffer for the given text based on
// the ranges of the encoded code points contained within, as well as returning the line offsets.
-//
-// Code points are represented as an array of byte, uint16, or rune. This approach ensures that
-// each index represents a code point by itself without needing to use an array of rune. At first
-// we assume all code points are less than or equal to '\u007f'. If this holds true, the
-// underlying storage is a byte array containing only ASCII characters. If we encountered a code
-// point above this range but less than or equal to '\uffff' we allocate a uint16 array, copy the
-// elements of previous byte array to the uint16 array, and continue. If this holds true, the
-// underlying storage is a uint16 array containing only Unicode characters in the Basic Multilingual
-// Plane. If we encounter a code point above '\uffff' we allocate an rune array, copy the previous
-// elements of the byte or uint16 array, and continue. The underlying storage is an rune array
-// containing any Unicode character.
func NewBufferAndLineOffsets(data string) (Buffer, []int32) {
- return newBuffer(data, true)
+ buf, offs, _ := newBufferWithLimit(data, true, -1)
+ return buf, offs
+}
+
+// NewBufferAndLineOffsetsWithLimit returns an efficient implementation of Buffer for the given text
+// and enforces a code point limit while constructing the buffer.
+func NewBufferAndLineOffsetsWithLimit(data string, limit int) (Buffer, []int32, error) {
+ if limit < 0 || len(data) <= limit {
+ return newBufferWithLimit(data, true, -1)
+ }
+ return newBufferWithLimit(data, true, limit)
+}
+
+func countRemainingCodePoints(data string, idx int, count int) int {
+ for idx < len(data) {
+ _, s := utf8.DecodeRuneInString(data[idx:])
+ idx += s
+ count++
+ }
+ return count
}
-func newBuffer(data string, lines bool) (Buffer, []int32) {
+func newBufferWithLimit(data string, lines bool, limit int) (Buffer, []int32, error) {
if len(data) == 0 {
- return nilBuffer, []int32{0}
+ return nilBuffer, []int32{0}, nil
+ }
+ if limit >= 0 && len(data) > limit {
+ size := countRemainingCodePoints(data, 0, 0)
+ if size > limit {
+ return nil, nil, &SizeLimitError{
+ Size: size,
+ Limit: limit,
+ }
+ }
}
+
+ // The resulting buffers store one element per code point, so the worst case
+ // element count never exceeds len(data).
var (
idx = 0
off int32 = 0
@@ -195,7 +215,8 @@ func newBuffer(data string, lines bool) (Buffer, []int32) {
}
return &asciiBuffer{
arr: buf8,
- }, offs
+ }, offs, nil
+
copy16:
for idx < len(data) {
r, s := utf8.DecodeRuneInString(data[idx:])
@@ -222,7 +243,8 @@ copy16:
}
return &basicBuffer{
arr: buf16,
- }, offs
+ }, offs, nil
+
copy32:
for idx < len(data) {
r, s := utf8.DecodeRuneInString(data[idx:])
@@ -238,5 +260,5 @@ copy32:
}
return &supplementalBuffer{
arr: buf32,
- }, offs
+ }, offs, nil
}
diff --git a/vendor/github.com/google/cel-go/common/source.go b/vendor/github.com/google/cel-go/common/source.go
index ec79cb5454..9187e9b5c3 100644
--- a/vendor/github.com/google/cel-go/common/source.go
+++ b/vendor/github.com/google/cel-go/common/source.go
@@ -74,6 +74,12 @@ func NewTextSource(text string) Source {
return NewStringSource(text, "")
}
+// NewTextSourceWithLimit creates a new Source from the input text string while
+// enforcing a maximum code point count when needed.
+func NewTextSourceWithLimit(text string, limit int) (Source, error) {
+ return NewStringSourceWithLimit(text, "", limit)
+}
+
// NewStringSource creates a new Source from the given contents and description.
func NewStringSource(contents string, description string) Source {
// Compute line offsets up front as they are referred to frequently.
@@ -85,6 +91,23 @@ func NewStringSource(contents string, description string) Source {
}
}
+// NewStringSourceWithLimit creates a new Source from the given contents and
+// description while enforcing a maximum code point count when needed.
+func NewStringSourceWithLimit(contents string, description string, limit int) (Source, error) {
+ if limit < 0 || len(contents) <= limit {
+ return NewStringSource(contents, description), nil
+ }
+ buf, offs, err := runes.NewBufferAndLineOffsetsWithLimit(contents, limit)
+ if err != nil {
+ return nil, err
+ }
+ return &sourceImpl{
+ Buffer: buf,
+ description: description,
+ lineOffsets: offs,
+ }, nil
+}
+
// NewInfoSource creates a new Source from a SourceInfo.
func NewInfoSource(info *exprpb.SourceInfo) Source {
return &sourceImpl{
diff --git a/vendor/github.com/google/cel-go/common/types/timestamp.go b/vendor/github.com/google/cel-go/common/types/timestamp.go
index 060caf6bbe..c4484291be 100644
--- a/vendor/github.com/google/cel-go/common/types/timestamp.go
+++ b/vendor/github.com/google/cel-go/common/types/timestamp.go
@@ -302,6 +302,9 @@ func timeZone(tz ref.Val, visitor timestampVisitor) timestampVisitor {
if err != nil {
return WrapErr(err)
}
+ if min < 0 || min > 59 {
+ return WrapErr(fmt.Errorf("timezone offset minutes out of range [0, 59]: %s", val))
+ }
var offset int
if string(val[0]) == "-" {
offset = hr*60 - min
diff --git a/vendor/github.com/google/cel-go/common/types/unknown.go b/vendor/github.com/google/cel-go/common/types/unknown.go
index 9dd2b25794..24d86518b4 100644
--- a/vendor/github.com/google/cel-go/common/types/unknown.go
+++ b/vendor/github.com/google/cel-go/common/types/unknown.go
@@ -181,6 +181,20 @@ func (u *Unknown) GetAttributeTrails(id int64) ([]*AttributeTrail, bool) {
return trails, found
}
+// HasUnknownFunction returns whether any of the attribute trails contained within the unknown
+// are unspecified. Unspecified attributes typically indicate an unresolved function call
+// or operation, rather than a missing variable.
+func (u *Unknown) HasUnknownFunction() bool {
+ for _, trails := range u.attributeTrails {
+ for _, t := range trails {
+ if t.variable == "" {
+ return true
+ }
+ }
+ }
+ return false
+}
+
// Contains returns true if the input unknown is a subset of the current unknown.
func (u *Unknown) Contains(other *Unknown) bool {
for id, otherTrails := range other.attributeTrails {
diff --git a/vendor/github.com/google/cel-go/ext/BUILD.bazel b/vendor/github.com/google/cel-go/ext/BUILD.bazel
index b993d39cbb..f362fd97b8 100644
--- a/vendor/github.com/google/cel-go/ext/BUILD.bazel
+++ b/vendor/github.com/google/cel-go/ext/BUILD.bazel
@@ -18,6 +18,7 @@ go_library(
"lists.go",
"math.go",
"native.go",
+ "network.go",
"protos.go",
"regex.go",
"sets.go",
@@ -40,6 +41,7 @@ go_library(
"//common/types/traits:go_default_library",
"//interpreter:go_default_library",
"//parser:go_default_library",
+ "@org_golang_google_protobuf//encoding/protojson:go_default_library",
"@org_golang_google_protobuf//proto:go_default_library",
"@org_golang_google_protobuf//reflect/protoreflect:go_default_library",
"@org_golang_google_protobuf//types/known/structpb",
@@ -61,6 +63,7 @@ go_test(
"lists_test.go",
"math_test.go",
"native_test.go",
+ "network_test.go",
"protos_test.go",
"regex_test.go",
"sets_test.go",
diff --git a/vendor/github.com/google/cel-go/ext/README.md b/vendor/github.com/google/cel-go/ext/README.md
index 6a7163de00..523d908564 100644
--- a/vendor/github.com/google/cel-go/ext/README.md
+++ b/vendor/github.com/google/cel-go/ext/README.md
@@ -55,6 +55,20 @@ Example:
base64.encode(b'hello') // return 'aGVsbG8='
+### JSON.Encode
+
+Introduced at version: 1
+
+Encodes a CEL value to a JSON string.
+
+ json.encode() ->
+
+Examples:
+
+ json.encode('hello') // return '"hello"'
+ json.encode([1, 'two', true]) // return '[1,"two",true]'
+ json.encode({'items': [1, 'two', false]}) // return '{"items":[1,"two",false]}'
+
## Math
Math helper macros and functions.
diff --git a/vendor/github.com/google/cel-go/ext/bindings.go b/vendor/github.com/google/cel-go/ext/bindings.go
index bef29ae270..89766d60aa 100644
--- a/vendor/github.com/google/cel-go/ext/bindings.go
+++ b/vendor/github.com/google/cel-go/ext/bindings.go
@@ -108,7 +108,7 @@ func (lib *celBindings) CompileOptions() []cel.EnvOption {
func (lib *celBindings) ProgramOptions() []cel.ProgramOption {
if lib.version >= 1 {
- celBlockPlan := func(i interpreter.Interpretable) (interpreter.Interpretable, error) {
+ celBlockPlan := func(i interpreter.InterpretableV2) (interpreter.InterpretableV2, error) {
call, ok := i.(interpreter.InterpretableCall)
if !ok {
return i, nil
@@ -140,7 +140,7 @@ func (lib *celBindings) ProgramOptions() []cel.ProgramOption {
return i, nil
}
}
- return []cel.ProgramOption{cel.CustomDecorator(celBlockPlan)}
+ return []cel.ProgramOption{cel.CustomDecoratorV2(celBlockPlan)}
}
return []cel.ProgramOption{}
}
@@ -190,7 +190,7 @@ func celBind(mef cel.MacroExprFactory, target ast.Expr, args []ast.Expr) (ast.Ex
), nil
}
-func newDynamicBlock(slotExprs []interpreter.Interpretable, expr interpreter.Interpretable) interpreter.Interpretable {
+func newDynamicBlock(slotExprs []interpreter.InterpretableV2, expr interpreter.InterpretableV2) interpreter.InterpretableV2 {
bs := &dynamicBlock{
slotExprs: slotExprs,
expr: expr,
@@ -213,8 +213,8 @@ func newDynamicBlock(slotExprs []interpreter.Interpretable, expr interpreter.Int
}
type dynamicBlock struct {
- slotExprs []interpreter.Interpretable
- expr interpreter.Interpretable
+ slotExprs []interpreter.InterpretableV2
+ expr interpreter.InterpretableV2
slotActivationPool *sync.Pool
}
@@ -223,12 +223,23 @@ func (b *dynamicBlock) ID() int64 {
return b.expr.ID()
}
-// Eval implements the Interpretable interface method.
-func (b *dynamicBlock) Eval(activation cel.Activation) ref.Val {
+// Exec implements the Interpretable interface method and pushes a new frame onto the
+// execution frame for the duration of the block execution.
+func (b *dynamicBlock) Exec(frame *interpreter.ExecutionFrame) ref.Val {
sa := b.slotActivationPool.Get().(*dynamicSlotActivation)
- sa.Activation = activation
+ sa.frame = frame.Push(sa)
+ // Ensure the 'unwrapped' Activation points to the original one from the frame,
+ // and not the hierarchical activation which composes the original and the slot
+ // activation.
+ sa.Activation = frame.Activation
+ defer sa.frame.Pop()
defer b.clearSlots(sa)
- return b.expr.Eval(sa)
+ return b.expr.Exec(sa.frame)
+}
+
+// Eval implements the Interpretable interface method.
+func (b *dynamicBlock) Eval(activation cel.Activation) ref.Val {
+ return b.Exec(interpreter.AsFrame(activation))
}
func (b *dynamicBlock) clearSlots(sa *dynamicSlotActivation) {
@@ -243,7 +254,8 @@ type slotVal struct {
type dynamicSlotActivation struct {
cel.Activation
- slotExprs []interpreter.Interpretable
+ frame *interpreter.ExecutionFrame
+ slotExprs []interpreter.InterpretableV2
slotCount int
slotVals []*slotVal
}
@@ -267,7 +279,7 @@ func (sa *dynamicSlotActivation) ResolveName(name string) (any, bool) {
return *v.value, true
}
v.visited = true
- val := sa.slotExprs[idx].Eval(sa)
+ val := sa.slotExprs[idx].Exec(sa.frame)
v.value = &val
return val, true
}
@@ -276,13 +288,14 @@ func (sa *dynamicSlotActivation) ResolveName(name string) (any, bool) {
func (sa *dynamicSlotActivation) reset() {
sa.Activation = nil
+ sa.frame = nil
for _, sv := range sa.slotVals {
sv.visited = false
sv.value = nil
}
}
-func newConstantBlock(slots traits.Lister, expr interpreter.Interpretable) interpreter.Interpretable {
+func newConstantBlock(slots traits.Lister, expr interpreter.InterpretableV2) interpreter.InterpretableV2 {
count := slots.Size().(types.Int)
return &constantBlock{slots: slots, slotCount: int(count), expr: expr}
}
@@ -290,7 +303,7 @@ func newConstantBlock(slots traits.Lister, expr interpreter.Interpretable) inter
type constantBlock struct {
slots traits.Lister
slotCount int
- expr interpreter.Interpretable
+ expr interpreter.InterpretableV2
}
// ID implements the interpreter.Interpretable interface method.
@@ -298,15 +311,24 @@ func (b *constantBlock) ID() int64 {
return b.expr.ID()
}
+// Exec implements the Interpretable interface method and pushes a new frame onto the
+// stack for the duration of the block execution.
+func (b *constantBlock) Exec(frame *interpreter.ExecutionFrame) ref.Val {
+ sa := constantSlotActivation{Activation: frame.Activation, slots: b.slots, slotCount: b.slotCount}
+ sa.frame = frame.Push(sa)
+ defer sa.frame.Pop()
+ return b.expr.Exec(sa.frame)
+}
+
// Eval implements the interpreter.Interpretable interface method, and will proxy @index prefixed variable
// lookups into a set of constant slots determined from the plan step.
func (b *constantBlock) Eval(activation cel.Activation) ref.Val {
- vars := constantSlotActivation{Activation: activation, slots: b.slots, slotCount: b.slotCount}
- return b.expr.Eval(vars)
+ return b.Exec(interpreter.AsFrame(activation))
}
type constantSlotActivation struct {
cel.Activation
+ frame *interpreter.ExecutionFrame
slots traits.Lister
slotCount int
}
diff --git a/vendor/github.com/google/cel-go/ext/encoders.go b/vendor/github.com/google/cel-go/ext/encoders.go
index 731c3d095d..10beea4f13 100644
--- a/vendor/github.com/google/cel-go/ext/encoders.go
+++ b/vendor/github.com/google/cel-go/ext/encoders.go
@@ -16,11 +16,14 @@ package ext
import (
"encoding/base64"
+ "fmt"
"math"
"github.com/google/cel-go/cel"
"github.com/google/cel-go/common/types"
"github.com/google/cel-go/common/types/ref"
+ "google.golang.org/protobuf/encoding/protojson"
+ "google.golang.org/protobuf/types/known/structpb"
)
// Encoders returns a cel.EnvOption to configure extended functions for string, byte, and object
@@ -48,6 +51,18 @@ import (
// Examples:
//
// base64.encode(b'hello') // return b'aGVsbG8='
+//
+// # JSON.Encode
+//
+// Introduced at version: 1
+//
+// Encodes a CEL value to a JSON string.
+//
+// json.encode() ->
+//
+// Examples:
+//
+// json.encode({'hello': 'world'}) // return '{"hello":"world"}'
func Encoders(options ...EncodersOption) cel.EnvOption {
l := &encoderLib{version: math.MaxUint32}
for _, o := range options {
@@ -75,8 +90,8 @@ func (*encoderLib) LibraryName() string {
return "cel.lib.ext.encoders"
}
-func (*encoderLib) CompileOptions() []cel.EnvOption {
- return []cel.EnvOption{
+func (lib *encoderLib) CompileOptions() []cel.EnvOption {
+ opts := []cel.EnvOption{
cel.Function("base64.decode",
cel.Overload("base64_decode_string", []*cel.Type{cel.StringType}, cel.BytesType,
cel.UnaryBinding(func(str ref.Val) ref.Val {
@@ -90,6 +105,16 @@ func (*encoderLib) CompileOptions() []cel.EnvOption {
return stringOrError(base64EncodeBytes([]byte(b)))
}))),
}
+ if lib.version >= 1 {
+ opts = append(opts,
+ cel.Function("json.encode",
+ cel.Overload("json_encode_dyn", []*cel.Type{cel.DynType}, cel.StringType,
+ cel.UnaryBinding(func(val ref.Val) ref.Val {
+ return stringOrError(jsonEncodeValue(val))
+ }))),
+ )
+ }
+ return opts
}
func (*encoderLib) ProgramOptions() []cel.ProgramOption {
@@ -110,3 +135,19 @@ func base64DecodeString(str string) ([]byte, error) {
func base64EncodeBytes(bytes []byte) (string, error) {
return base64.StdEncoding.EncodeToString(bytes), nil
}
+
+func jsonEncodeValue(val ref.Val) (string, error) {
+ native, err := val.ConvertToNative(types.JSONValueType)
+ if err != nil {
+ return "", err
+ }
+ jsonValue, ok := native.(*structpb.Value)
+ if !ok {
+ return "", fmt.Errorf("cannot convert %T to JSON value", native)
+ }
+ jsonBytes, err := protojson.Marshal(jsonValue)
+ if err != nil {
+ return "", err
+ }
+ return string(jsonBytes), nil
+}
diff --git a/vendor/github.com/google/cel-go/ext/lists.go b/vendor/github.com/google/cel-go/ext/lists.go
index 7a7a33c622..37ae2d3d6a 100644
--- a/vendor/github.com/google/cel-go/ext/lists.go
+++ b/vendor/github.com/google/cel-go/ext/lists.go
@@ -153,15 +153,18 @@ var comparableTypes = []*cel.Type{
// ].sortBy(e, e.score).map(e, e.name)
// == ["bar", "foo", "baz"]
func Lists(options ...ListsOption) cel.EnvOption {
- l := &listsLib{version: math.MaxUint32}
+ l := &listsLib{version: math.MaxUint32, maxRangeSize: defaultMaxRangeSize}
for _, o := range options {
l = o(l)
}
return cel.Lib(l)
}
+const defaultMaxRangeSize = 1_000_000
+
type listsLib struct {
- version uint32
+ version uint32
+ maxRangeSize int64
}
// LibraryName implements the SingletonLibrary interface method.
@@ -188,6 +191,16 @@ func ListsVersion(version uint32) ListsOption {
}
}
+// ListsMaxRangeSize sets the maximum number of elements lists.range() will
+// allocate. If not set, the default is 10,000,000. Setting this to zero
+// disables the limit (not recommended).
+func ListsMaxRangeSize(size int64) ListsOption {
+ return func(lib *listsLib) *listsLib {
+ lib.maxRangeSize = size
+ return lib
+ }
+}
+
// CompileOptions implements the Library interface method.
func (lib listsLib) CompileOptions() []cel.EnvOption {
listType := cel.ListType(cel.TypeParamType("T"))
@@ -309,11 +322,12 @@ func (lib listsLib) CompileOptions() []cel.EnvOption {
)...,
))
+ maxRange := lib.maxRangeSize
opts = append(opts, cel.Function("lists.range",
cel.Overload("lists_range",
[]*cel.Type{cel.IntType}, cel.ListType(cel.IntType),
cel.UnaryBinding(func(n ref.Val) ref.Val {
- result, err := genRange(n.(types.Int))
+ result, err := genRange(n.(types.Int), maxRange)
if err != nil {
return types.WrapErr(err)
}
@@ -403,8 +417,14 @@ func (lib *listsLib) ProgramOptions() []cel.ProgramOption {
return opts
}
-func genRange(n types.Int) (ref.Val, error) {
- var newList []ref.Val
+func genRange(n types.Int, maxSize int64) (ref.Val, error) {
+ if n < 0 {
+ return nil, fmt.Errorf("lists.range: size must be non-negative, got %d", n)
+ }
+ if maxSize > 0 && int64(n) > maxSize {
+ return nil, fmt.Errorf("lists.range: size %d exceeds maximum allowed (%d)", n, maxSize)
+ }
+ newList := make([]ref.Val, 0, n)
for i := types.Int(0); i < n; i++ {
newList = append(newList, i)
}
diff --git a/vendor/github.com/google/cel-go/ext/network.go b/vendor/github.com/google/cel-go/ext/network.go
new file mode 100644
index 0000000000..affe59e2d3
--- /dev/null
+++ b/vendor/github.com/google/cel-go/ext/network.go
@@ -0,0 +1,619 @@
+// Copyright 2025 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 ext
+
+import (
+ "fmt"
+ "net/netip"
+ "reflect"
+
+ "github.com/google/cel-go/cel"
+ "github.com/google/cel-go/common/ast"
+ "github.com/google/cel-go/common/types"
+ "github.com/google/cel-go/common/types/ref"
+)
+
+const (
+ // Version1 is the initial version of the Network library, providing
+ // parity with Kubernetes v1.30+ CEL network functions.
+ Version1 uint32 = 1
+)
+
+// Network returns a cel.EnvOption to configure extended functions for network
+// address parsing, inspection, and CIDR range manipulation.
+//
+// Note: This library defines global functions `ip`, `cidr`, `isIP`, `isCIDR`
+// and `ip.isCanonical`. If you are currently using variables named `ip` or
+// `cidr`, these functions will likely work as intended, however there is a
+// chance for collision.
+//
+// The library closely mirrors the behavior of the Kubernetes CEL network
+// libraries, treating IP addresses and CIDR ranges as opaque types. It parses
+// IPs strictly: IPv4-mapped IPv6 addresses and IP zones are not allowed.
+//
+// This library includes a TypeAdapter that allows `netip.Addr` and
+// `netip.Prefix` Go types to be passed directly into the CEL environment.
+//
+// # IP Addresses
+//
+// The `ip` function converts a string to an IP address (IPv4 or IPv6). If the
+// string is not a valid IP, an error is returned. The `isIP` function checks
+// if a string is a valid IP address without throwing an error.
+//
+// ip(string) -> ip
+// isIP(string) -> bool
+//
+// Examples:
+//
+// ip('127.0.0.1')
+// ip('::1')
+// isIP('1.2.3.4') // true
+// isIP('invalid') // false
+//
+// # CIDR Ranges
+//
+// The `cidr` function converts a string to a Classless Inter-Domain Routing
+// (CIDR) range. If the string is not valid, an error is returned.
+//
+// The `isCIDR` function checks if a string is a valid CIDR notation. Note that
+// `isCIDR` allows CIDR values with or without host bits (e.g., '10.0.0.1/8'
+// or '10.0.0.0/8').
+//
+// cidr(string) -> cidr
+// isCIDR(string) -> bool
+//
+// Examples:
+//
+// cidr('192.168.0.0/24')
+// cidr('::1/128')
+// isCIDR('10.0.0.0/8') // true
+// isCIDR('10.0.0.1/8') // true
+//
+// # IP Inspection and Canonicalization
+//
+// IP objects support various inspection methods.
+//
+// .family() -> int
+// .isLoopback() -> bool
+// .isGlobalUnicast() -> bool
+// .isLinkLocalMulticast() -> bool
+// .isLinkLocalUnicast() -> bool
+// .isUnspecified() -> bool
+//
+// The `ip.isCanonical` function takes a string and returns true if it matches
+// the RFC 5952 canonical string representation of that address.
+//
+// ip.isCanonical(string) -> bool
+//
+// Examples:
+//
+// ip('127.0.0.1').family() == 4
+// ip('::1').family() == 6
+// ip('127.0.0.1').isLoopback() == true
+// ip.isCanonical('2001:db8::1') == true // RFC 5952 format
+// ip.isCanonical('2001:DB8::1') == false // Uppercase is not canonical
+// ip.isCanonical('2001:db8:0:0:0:0:0:1') == false // Expanded is not canonical
+//
+// # CIDR Member Functions
+//
+// CIDR objects support containment checks and property extraction.
+//
+// .containsIP(ip|string) -> bool
+// .containsCIDR(cidr|string) -> bool
+// .ip() -> ip
+// .isMask() -> bool
+// .masked() -> cidr
+// .prefixLength() -> int
+//
+// Examples:
+//
+// cidr('10.0.0.0/8').containsIP(ip('10.0.0.1')) == true
+// cidr('10.0.0.0/8').containsIP('10.0.0.1') == true
+// cidr('10.0.0.0/8').containsCIDR('10.1.0.0/16') == true
+// cidr('192.168.1.5/24').ip() == ip('192.168.1.5')
+// cidr('192.168.1.0/24').isMask() == true
+// cidr('192.168.1.5/24').isMask() == false
+// cidr('192.168.1.5/24').masked() == cidr('192.168.1.0/24')
+// cidr('192.168.1.0/24').prefixLength() == 24
+func Network(opts ...NetworkOption) cel.EnvOption {
+ lib := &networkLib{version: Version1}
+ for _, o := range opts {
+ lib = o(lib)
+ }
+ return func(e *cel.Env) (*cel.Env, error) {
+ // Install the library (Types and Functions)
+ e, err := cel.Lib(lib)(e)
+ if err != nil {
+ return nil, err
+ }
+
+ // Install the Adapter (Wrapping the existing one)
+ adapter := &networkAdapter{Adapter: e.CELTypeAdapter()}
+ return cel.CustomTypeAdapter(adapter)(e)
+ }
+}
+
+// NetworkOption declares a functional operator for configuring the Network library behavior.
+type NetworkOption func(*networkLib) *networkLib
+
+// NetworkVersion sets the version of the network library to an explicit version.
+func NetworkVersion(version uint32) NetworkOption {
+ return func(lib *networkLib) *networkLib {
+ lib.version = version
+ return lib
+ }
+}
+
+const (
+ // Function names matching the original Kubernetes implementation of this networking library.
+ // isStrictCIDR and isInterfaceAddress are added to enable strict isCIDR parsing without breaking
+ // functionality for existing users. Ctx: https://github.com/kubernetes/kubernetes/issues/134224
+ cidrFunc = "cidr"
+ cidrToString = "string"
+ containsCIDRFunc = "containsCIDR"
+ containsIPFunc = "containsIP"
+ familyFunc = "family"
+ ipFunc = "ip"
+ ipToString = "string"
+ isCanonicalFunc = "ip.isCanonical"
+ isCIDRFunc = "isCIDR"
+ isGlobalUnicastFunc = "isGlobalUnicast"
+ isIPFunc = "isIP"
+ isLinkLocalMcastFunc = "isLinkLocalMulticast"
+ isLinkLocalUcastFunc = "isLinkLocalUnicast"
+ isLoopbackFunc = "isLoopback"
+ isMaskFunc = "isMask"
+ isUnspecifiedFunc = "isUnspecified"
+ maskedFunc = "masked"
+ prefixLengthFunc = "prefixLength"
+)
+
+var (
+ // Definitions for the Opaque Types
+ IPType = types.NewOpaqueType("net.IP")
+ CIDRType = types.NewOpaqueType("net.CIDR")
+)
+
+type networkLib struct {
+ version uint32
+}
+
+func (*networkLib) LibraryName() string {
+ return "cel.lib.ext.network"
+}
+
+func (*networkLib) CompileOptions() []cel.EnvOption {
+ return []cel.EnvOption{
+ // 1. Register Types
+ cel.Types(
+ IPType,
+ CIDRType,
+ ),
+
+ // 2. Register Functions
+ cel.Function(cidrFunc,
+ // K8s Parity: Following the pattern, this is "string_to_cidr"
+ cel.Overload("string_to_cidr", []*cel.Type{cel.StringType}, CIDRType,
+ cel.UnaryBinding(netCIDRString)),
+ ),
+ cel.Function(cidrToString,
+ cel.Overload("cidr_to_string", []*cel.Type{CIDRType}, cel.StringType,
+ cel.UnaryBinding(netCIDRToString)),
+ ),
+ cel.Function(containsCIDRFunc,
+ cel.MemberOverload("cidr_contains_cidr", []*cel.Type{CIDRType, CIDRType}, cel.BoolType,
+ cel.BinaryBinding(netCIDRContainsCIDR)),
+ cel.MemberOverload("cidr_contains_cidr_string", []*cel.Type{CIDRType, cel.StringType}, cel.BoolType,
+ cel.BinaryBinding(netCIDRContainsCIDRString)),
+ ),
+ cel.Function(containsIPFunc,
+ cel.MemberOverload("cidr_contains_ip_ip", []*cel.Type{CIDRType, IPType}, cel.BoolType,
+ cel.BinaryBinding(netCIDRContainsIP)),
+ cel.MemberOverload("cidr_contains_ip_string", []*cel.Type{CIDRType, cel.StringType}, cel.BoolType,
+ cel.BinaryBinding(netCIDRContainsIPString)),
+ ),
+ cel.Function(familyFunc,
+ cel.MemberOverload("ip_family", []*cel.Type{IPType}, cel.IntType,
+ cel.UnaryBinding(netIPFamily)),
+ ),
+ cel.Function(ipFunc,
+ // K8s Parity: The global overload is named "string_to_ip"
+ cel.Overload("string_to_ip", []*cel.Type{cel.StringType}, IPType,
+ cel.UnaryBinding(netIPString)),
+ // K8s Parity: The member overload is named "cidr_ip"
+ cel.MemberOverload("cidr_ip", []*cel.Type{CIDRType}, IPType,
+ cel.UnaryBinding(netCIDRIP)),
+ ),
+ cel.Function(ipToString,
+ cel.Overload("ip_to_string", []*cel.Type{IPType}, cel.StringType,
+ cel.UnaryBinding(netIPToString)),
+ ),
+ cel.Function(isCanonicalFunc,
+ cel.Overload("ip_is_canonical", []*cel.Type{cel.StringType}, cel.BoolType,
+ cel.UnaryBinding(netIPIsCanonical)),
+ ),
+ cel.Function(isCIDRFunc,
+ cel.Overload("is_cidr", []*cel.Type{cel.StringType}, cel.BoolType,
+ cel.UnaryBinding(netIsCIDR)),
+ ),
+ cel.Function(isGlobalUnicastFunc,
+ cel.MemberOverload("ip_is_global_unicast", []*cel.Type{IPType}, cel.BoolType,
+ cel.UnaryBinding(netIPIsGlobalUnicast)),
+ ),
+ cel.Function(isIPFunc,
+ cel.Overload("is_ip", []*cel.Type{cel.StringType}, cel.BoolType,
+ cel.UnaryBinding(netIsIP)),
+ ),
+ cel.Function(isLinkLocalMcastFunc,
+ cel.MemberOverload("ip_is_link_local_multicast", []*cel.Type{IPType}, cel.BoolType,
+ cel.UnaryBinding(netIPIsLinkLocalMulticast)),
+ ),
+ cel.Function(isLinkLocalUcastFunc,
+ cel.MemberOverload("ip_is_link_local_unicast", []*cel.Type{IPType}, cel.BoolType,
+ cel.UnaryBinding(netIPIsLinkLocalUnicast)),
+ ),
+ cel.Function(isLoopbackFunc,
+ cel.MemberOverload("ip_is_loopback", []*cel.Type{IPType}, cel.BoolType,
+ cel.UnaryBinding(netIPIsLoopback)),
+ ),
+ cel.Function(isMaskFunc,
+ cel.MemberOverload("cidr_is_mask", []*cel.Type{CIDRType}, cel.BoolType,
+ cel.UnaryBinding(netCIDRIsMask)),
+ ),
+ cel.Function(isUnspecifiedFunc,
+ cel.MemberOverload("ip_is_unspecified", []*cel.Type{IPType}, cel.BoolType,
+ cel.UnaryBinding(netIPIsUnspecified)),
+ ),
+ cel.Function(maskedFunc,
+ cel.MemberOverload("cidr_masked", []*cel.Type{CIDRType}, CIDRType,
+ cel.UnaryBinding(netCIDRMasked)),
+ ),
+ cel.Function(prefixLengthFunc,
+ cel.MemberOverload("cidr_prefix_length", []*cel.Type{CIDRType}, cel.IntType,
+ cel.UnaryBinding(netCIDRPrefixLength)),
+ ),
+ cel.ASTValidators(
+ networkFormatValidator{funcName: ipFunc, argNum: 0, check: checkIP},
+ networkFormatValidator{funcName: cidrFunc, argNum: 0, check: checkCIDR},
+ ),
+ }
+}
+
+func (*networkLib) ProgramOptions() []cel.ProgramOption {
+ return []cel.ProgramOption{}
+}
+
+// networkAdapter adapts netip types while preserving existing adapters.
+type networkAdapter struct {
+ types.Adapter
+}
+
+func (a *networkAdapter) NativeToValue(value any) ref.Val {
+ switch v := value.(type) {
+ case netip.Addr:
+ return IP{Addr: v}
+ case netip.Prefix:
+ return CIDR{Prefix: v}
+ }
+ // Delegate to the wrapped adapter (e.g., Protobuf adapter)
+ return a.Adapter.NativeToValue(value)
+}
+
+// --- Implementation Logic ---
+
+func netCIDRContainsCIDR(lhs, rhs ref.Val) ref.Val {
+ parent := lhs.(CIDR)
+ child := rhs.(CIDR)
+ return types.Bool(parent.Prefix.Overlaps(child.Prefix) && parent.Prefix.Bits() <= child.Prefix.Bits())
+}
+
+func netCIDRContainsCIDRString(lhs, rhs ref.Val) ref.Val {
+ parent := lhs.(CIDR)
+ s := rhs.(types.String)
+ childPrefix, err := parseCIDR(string(s))
+ if err != nil {
+ return types.WrapErr(err)
+ }
+ return types.Bool(parent.Prefix.Overlaps(childPrefix) && parent.Prefix.Bits() <= childPrefix.Bits())
+}
+
+func netCIDRContainsIP(lhs, rhs ref.Val) ref.Val {
+ cidr := lhs.(CIDR)
+ ip := rhs.(IP)
+ return types.Bool(cidr.Prefix.Contains(ip.Addr))
+}
+
+func netCIDRContainsIPString(lhs, rhs ref.Val) ref.Val {
+ cidr := lhs.(CIDR)
+ s := rhs.(types.String)
+ addr, err := parseIPAddr(string(s))
+ if err != nil {
+ return types.WrapErr(err)
+ }
+ return types.Bool(cidr.Prefix.Contains(addr))
+}
+
+func netCIDRIP(val ref.Val) ref.Val {
+ cidr := val.(CIDR)
+ return IP{Addr: cidr.Prefix.Addr()}
+}
+
+func netCIDRMasked(val ref.Val) ref.Val {
+ cidr := val.(CIDR)
+ return CIDR{Prefix: cidr.Prefix.Masked()}
+}
+
+func netCIDRPrefixLength(val ref.Val) ref.Val {
+ cidr := val.(CIDR)
+ return types.Int(cidr.Prefix.Bits())
+}
+
+func netCIDRString(val ref.Val) ref.Val {
+ s := val.(types.String)
+ str := string(s)
+ prefix, err := parseCIDR(str)
+ if err != nil {
+ return types.WrapErr(err)
+ }
+ return CIDR{Prefix: prefix}
+}
+
+func netCIDRToString(val ref.Val) ref.Val {
+ cidr := val.(CIDR)
+ return types.String(cidr.Prefix.String())
+}
+
+func netIPFamily(val ref.Val) ref.Val {
+ ip := val.(IP)
+ if ip.Addr.Is4() {
+ return types.Int(4)
+ }
+ return types.Int(6)
+}
+
+func netIPIsCanonical(val ref.Val) ref.Val {
+ s := val.(types.String)
+ str := string(s)
+ addr, err := parseIPAddr(str)
+ if err != nil {
+ return types.WrapErr(err)
+ }
+ return types.Bool(addr.String() == str)
+}
+
+func netIPIsGlobalUnicast(val ref.Val) ref.Val {
+ ip := val.(IP)
+ return types.Bool(ip.Addr.IsGlobalUnicast())
+}
+
+func netIPIsLinkLocalMulticast(val ref.Val) ref.Val {
+ ip := val.(IP)
+ return types.Bool(ip.Addr.IsLinkLocalMulticast())
+}
+
+func netIPIsLinkLocalUnicast(val ref.Val) ref.Val {
+ ip := val.(IP)
+ return types.Bool(ip.Addr.IsLinkLocalUnicast())
+}
+
+func netIPIsLoopback(val ref.Val) ref.Val {
+ ip := val.(IP)
+ return types.Bool(ip.Addr.IsLoopback())
+}
+
+func netIPIsUnspecified(val ref.Val) ref.Val {
+ ip := val.(IP)
+ return types.Bool(ip.Addr.IsUnspecified())
+}
+
+func netIPString(val ref.Val) ref.Val {
+ s := val.(types.String)
+ str := string(s)
+ addr, err := parseIPAddr(str)
+ if err != nil {
+ return types.WrapErr(err)
+ }
+ return IP{Addr: addr}
+}
+
+func netIPToString(val ref.Val) ref.Val {
+ ip := val.(IP)
+ return types.String(ip.Addr.String())
+}
+
+func netIsCIDR(val ref.Val) ref.Val {
+ s := val.(types.String)
+ _, err := parseCIDR(string(s))
+ return types.Bool(err == nil)
+}
+
+func netIsIP(val ref.Val) ref.Val {
+ s := val.(types.String)
+ _, err := parseIPAddr(string(s))
+ return types.Bool(err == nil)
+}
+
+func netCIDRIsMask(val ref.Val) ref.Val {
+ cidr := val.(CIDR)
+ return types.Bool(cidr.Prefix.Addr() == cidr.Prefix.Masked().Addr())
+}
+
+func parseCIDR(raw string) (netip.Prefix, error) {
+ prefix, err := netip.ParsePrefix(raw)
+ if err != nil {
+ return netip.Prefix{}, fmt.Errorf("CIDR %q parse error during conversion from string: %v", raw, err)
+ }
+ if prefix.Addr().Zone() != "" {
+ return netip.Prefix{}, fmt.Errorf("CIDR %q with zone value is not allowed", raw)
+ }
+ if prefix.Addr().Is4In6() {
+ return netip.Prefix{}, fmt.Errorf("IPv4-mapped IPv6 address %q is not allowed", raw)
+ }
+ return prefix, nil
+}
+
+func parseIPAddr(raw string) (netip.Addr, error) {
+ addr, err := netip.ParseAddr(raw)
+ if err != nil {
+ return netip.Addr{}, fmt.Errorf("IP Address %q parse error during conversion from string: %v", raw, err)
+ }
+ if addr.Zone() != "" {
+ return netip.Addr{}, fmt.Errorf("IP address %q with zone value is not allowed", raw)
+ }
+ if addr.Is4In6() {
+ return netip.Addr{}, fmt.Errorf("IPv4-mapped IPv6 address %q is not allowed", raw)
+ }
+ return addr, nil
+}
+
+// --- Opaque Type Wrappers ---
+
+type IP struct {
+ netip.Addr
+}
+
+// ConvertToNative converts the IP value to a native Go type.
+func (i IP) ConvertToNative(typeDesc reflect.Type) (any, error) {
+ if typeDesc == reflect.TypeFor[netip.Addr]() {
+ return i.Addr, nil
+ }
+ if typeDesc.Kind() == reflect.String {
+ return i.Addr.String(), nil
+ }
+ return nil, fmt.Errorf("unsupported type conversion to '%v'", typeDesc)
+}
+
+// ConvertToType converts the IP value to a CEL type.
+func (i IP) ConvertToType(typeValue ref.Type) ref.Val {
+ switch typeValue {
+ case types.StringType:
+ return types.String(i.Addr.String())
+ case IPType:
+ return i
+ case types.TypeType:
+ return IPType
+ }
+ return types.NewErr("type conversion error from '%s' to '%s'", IPType, typeValue)
+}
+
+// Equal returns true if this IP is equal to the other ref.Val.
+func (i IP) Equal(other ref.Val) ref.Val {
+ o, ok := other.(IP)
+ if !ok {
+ return types.False
+ }
+ return types.Bool(i.Addr == o.Addr)
+}
+
+// Type returns the CEL type of the IP.
+func (i IP) Type() ref.Type {
+ return IPType
+}
+
+// Value returns the raw Go value (netip.Addr) of the IP.
+func (i IP) Value() any {
+ return i.Addr
+}
+
+type CIDR struct {
+ netip.Prefix
+}
+
+// ConvertToNative converts the CIDR value to a native Go type.
+func (c CIDR) ConvertToNative(typeDesc reflect.Type) (any, error) {
+ if typeDesc == reflect.TypeFor[netip.Prefix]() {
+ return c.Prefix, nil
+ }
+ if typeDesc.Kind() == reflect.String {
+ return c.Prefix.String(), nil
+ }
+ return nil, fmt.Errorf("unsupported type conversion to '%v'", typeDesc)
+}
+
+// ConvertToType converts the CIDR value to a CEL type.
+func (c CIDR) ConvertToType(typeValue ref.Type) ref.Val {
+ switch typeValue {
+ case types.StringType:
+ return types.String(c.Prefix.String())
+ case CIDRType:
+ return c
+ case types.TypeType:
+ return CIDRType
+ }
+ return types.NewErr("type conversion error from '%s' to '%s'", CIDRType, typeValue)
+}
+
+// Equal returns true if this CIDR is equal to the other ref.Val.
+func (c CIDR) Equal(other ref.Val) ref.Val {
+ o, ok := other.(CIDR)
+ if !ok {
+ return types.False
+ }
+ return types.Bool(c.Prefix == o.Prefix)
+}
+
+// Type returns the CEL type of the CIDR.
+func (c CIDR) Type() ref.Type {
+ return CIDRType
+}
+
+// Value returns the raw Go value (netip.Prefix) of the CIDR.
+func (c CIDR) Value() any {
+ return c.Prefix
+}
+
+// --- Static Validators ---
+
+type argChecker func(e *cel.Env, call, arg ast.Expr) error
+
+type networkFormatValidator struct {
+ funcName string
+ argNum int
+ check argChecker
+}
+
+func (v networkFormatValidator) Name() string {
+ return fmt.Sprintf("cel.validator.network.%s", v.funcName)
+}
+
+func (v networkFormatValidator) Validate(e *cel.Env, _ cel.ValidatorConfig, a *ast.AST, iss *cel.Issues) {
+ root := ast.NavigateAST(a)
+ funcCalls := ast.MatchDescendants(root, ast.FunctionMatcher(v.funcName))
+ for _, call := range funcCalls {
+ callArgs := call.AsCall().Args()
+ if len(callArgs) <= v.argNum {
+ continue
+ }
+ litArg := callArgs[v.argNum]
+ if litArg.Kind() != ast.LiteralKind {
+ continue
+ }
+ if err := v.check(e, call, litArg); err != nil {
+ iss.ReportErrorAtID(litArg.ID(), "invalid %s argument: %v", v.funcName, err)
+ }
+ }
+}
+
+func checkIP(e *cel.Env, call, arg ast.Expr) error {
+ pattern := arg.AsLiteral().Value().(string)
+ _, err := parseIPAddr(pattern)
+ return err
+}
+
+func checkCIDR(e *cel.Env, call, arg ast.Expr) error {
+ pattern := arg.AsLiteral().Value().(string)
+ _, err := parseCIDR(pattern)
+ return err
+}
diff --git a/vendor/github.com/google/cel-go/ext/strings.go b/vendor/github.com/google/cel-go/ext/strings.go
index 0731a08174..1f7732f2fb 100644
--- a/vendor/github.com/google/cel-go/ext/strings.go
+++ b/vendor/github.com/google/cel-go/ext/strings.go
@@ -663,15 +663,19 @@ func indexOf(str, substr string) (int64, error) {
}
func indexOfOffset(str, substr string, offset int64) (int64, error) {
- if substr == "" {
- return offset, nil
- }
off := int(offset)
- runes := []rune(str)
- subrunes := []rune(substr)
if off < 0 {
return -1, fmt.Errorf("index out of range: %d", off)
}
+ runes := []rune(str)
+ if substr == "" {
+ // The empty string matches at the search offset, clamped to the end of the string.
+ if off > len(runes) {
+ return int64(len(runes)), nil
+ }
+ return offset, nil
+ }
+ subrunes := []rune(substr)
// If the offset exceeds the length, return -1 rather than error.
if off >= len(runes) {
return -1, nil
@@ -704,15 +708,19 @@ func lastIndexOf(str, substr string) (int64, error) {
}
func lastIndexOfOffset(str, substr string, offset int64) (int64, error) {
- if substr == "" {
- return offset, nil
- }
off := int(offset)
- runes := []rune(str)
- subrunes := []rune(substr)
if off < 0 {
return -1, fmt.Errorf("index out of range: %d", off)
}
+ runes := []rune(str)
+ if substr == "" {
+ // The empty string matches at the search offset, clamped to the end of the string.
+ if off > len(runes) {
+ return int64(len(runes)), nil
+ }
+ return offset, nil
+ }
+ subrunes := []rune(substr)
// If the offset is far greater than the length return -1
if off >= len(runes) {
return -1, nil
diff --git a/vendor/github.com/google/cel-go/interpreter/BUILD.bazel b/vendor/github.com/google/cel-go/interpreter/BUILD.bazel
index 220e23d475..1274a6f12d 100644
--- a/vendor/github.com/google/cel-go/interpreter/BUILD.bazel
+++ b/vendor/github.com/google/cel-go/interpreter/BUILD.bazel
@@ -14,6 +14,7 @@ go_library(
"decorators.go",
"dispatcher.go",
"evalstate.go",
+ "frame.go",
"interpretable.go",
"interpreter.go",
"optimizations.go",
@@ -47,6 +48,7 @@ go_test(
"activation_test.go",
"attribute_patterns_test.go",
"attributes_test.go",
+ "frame_test.go",
"interpreter_test.go",
"prune_test.go",
"runtimecost_test.go",
diff --git a/vendor/github.com/google/cel-go/interpreter/activation.go b/vendor/github.com/google/cel-go/interpreter/activation.go
index dd40619ee1..023efbb807 100644
--- a/vendor/github.com/google/cel-go/interpreter/activation.go
+++ b/vendor/github.com/google/cel-go/interpreter/activation.go
@@ -110,8 +110,9 @@ func (a *mapActivation) ResolveName(name string) (any, bool) {
// hierarchicalActivation which implements Activation and contains a parent and
// child activation.
type hierarchicalActivation struct {
- parent Activation
- child Activation
+ parent Activation
+ child Activation
+ poolAllocated bool
}
// Parent implements the Activation interface method.
@@ -127,10 +128,28 @@ func (a *hierarchicalActivation) ResolveName(name string) (any, bool) {
return a.parent.ResolveName(name)
}
+// Unwrap returns the parent activation, stripping the local child scope.
+// This allows global disambiguation to skip past locally introduced variables.
+func (a *hierarchicalActivation) Unwrap() Activation {
+ return a.parent
+}
+
+// AsPartialActivation checks the child first via direct type assertion (to
+// avoid recursion through the folder → frame → hierarchicalActivation cycle),
+// then walks the parent hierarchy via the free function.
+func (a *hierarchicalActivation) AsPartialActivation() (PartialActivation, bool) {
+ if pv, ok := a.child.(partialActivationConverter); ok {
+ if p, ok := pv.AsPartialActivation(); ok {
+ return p, true
+ }
+ }
+ return AsPartialActivation(a.parent)
+}
+
// 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}
+ return &hierarchicalActivation{parent: parent, child: child, poolAllocated: false}
}
// NewPartialActivation returns an Activation which contains a list of AttributePattern values
diff --git a/vendor/github.com/google/cel-go/interpreter/attributes.go b/vendor/github.com/google/cel-go/interpreter/attributes.go
index 6b8b5c1b6d..26d8eb0f3e 100644
--- a/vendor/github.com/google/cel-go/interpreter/attributes.go
+++ b/vendor/github.com/google/cel-go/interpreter/attributes.go
@@ -190,7 +190,7 @@ func (r *attrFactory) AbsoluteAttribute(id int64, names ...string) NamespacedAtt
func (r *attrFactory) ConditionalAttribute(id int64, expr Interpretable, t, f Attribute) Attribute {
return &conditionalAttribute{
id: id,
- expr: expr,
+ expr: adaptToV2(expr),
truthy: t,
falsy: f,
adapter: r.adapter,
@@ -225,7 +225,7 @@ func (r *attrFactory) MaybeAttribute(id int64, name string) Attribute {
func (r *attrFactory) RelativeAttribute(id int64, operand Interpretable) Attribute {
return &relativeAttribute{
id: id,
- operand: operand,
+ operand: adaptToV2(operand),
qualifiers: []Qualifier{},
adapter: r.adapter,
fac: r,
@@ -384,7 +384,7 @@ func (a *absoluteAttribute) Resolve(vars Activation) (any, error) {
type conditionalAttribute struct {
id int64
- expr Interpretable
+ expr InterpretableV2
truthy Attribute
falsy Attribute
adapter types.Adapter
@@ -571,7 +571,7 @@ func (a *maybeAttribute) String() string {
type relativeAttribute struct {
id int64
- operand Interpretable
+ operand InterpretableV2
qualifiers []Qualifier
adapter types.Adapter
fac AttributeFactory
@@ -964,9 +964,11 @@ func (q *intQualifier) qualifyInternal(vars Activation, obj any, presenceTest, p
}
case map[int32]any:
isMap = true
- obj, isKey := o[int32(i)]
- if isKey {
- return obj, true, nil
+ if i32 := int32(i); int64(i32) == i {
+ obj, isKey := o[i32]
+ if isKey {
+ return obj, true, nil
+ }
}
case map[int64]any:
isMap = true
@@ -1089,9 +1091,11 @@ func (q *uintQualifier) qualifyInternal(vars Activation, obj any, presenceTest,
return obj, true, nil
}
case map[uint32]any:
- obj, isKey := o[uint32(u)]
- if isKey {
- return obj, true, nil
+ if u32 := uint32(u); uint64(u32) == u {
+ obj, isKey := o[u32]
+ if isKey {
+ return obj, true, nil
+ }
}
case map[uint64]any:
obj, isKey := o[u]
@@ -1301,7 +1305,7 @@ func applyQualifiers(vars Activation, obj any, qualifiers []Qualifier) (any, boo
if !optObj.HasValue() {
return optObj, false, nil
}
- obj = optObj.GetValue().Value()
+ obj = optObj.GetValue()
}
var err error
diff --git a/vendor/github.com/google/cel-go/interpreter/decorators.go b/vendor/github.com/google/cel-go/interpreter/decorators.go
index 502db35fc0..9c973664ac 100644
--- a/vendor/github.com/google/cel-go/interpreter/decorators.go
+++ b/vendor/github.com/google/cel-go/interpreter/decorators.go
@@ -25,9 +25,13 @@ import (
// Interpretable expression nodes at construction time.
type InterpretableDecorator func(Interpretable) (Interpretable, error)
+// InterpretableDecoratorV2 is a functional interface for decorating or replacing
+// InterpretableV2 expression nodes at construction time.
+type InterpretableDecoratorV2 func(InterpretableV2) (InterpretableV2, error)
+
// decObserveEval records evaluation state into an EvalState object.
-func decObserveEval(observer EvalObserver) InterpretableDecorator {
- return func(i Interpretable) (Interpretable, error) {
+func decObserveEval(observer EvalObserver) InterpretableDecoratorV2 {
+ return func(i InterpretableV2) (InterpretableV2, error) {
switch inst := i.(type) {
case *evalWatch, *evalWatchAttr, *evalWatchConst, *evalWatchConstructor:
// these instruction are already watching, return straight-away.
@@ -49,8 +53,8 @@ func decObserveEval(observer EvalObserver) InterpretableDecorator {
}, nil
default:
return &evalWatch{
- Interpretable: i,
- observer: observer,
+ InterpretableV2: i,
+ observer: observer,
}, nil
}
}
@@ -58,8 +62,8 @@ func decObserveEval(observer EvalObserver) InterpretableDecorator {
// 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) {
+func decInterruptFolds() InterpretableDecoratorV2 {
+ return func(i InterpretableV2) (InterpretableV2, error) {
fold, ok := i.(*evalFold)
if !ok {
return i, nil
@@ -70,8 +74,8 @@ func decInterruptFolds() InterpretableDecorator {
}
// decDisableShortcircuits ensures that all branches of an expression will be evaluated, no short-circuiting.
-func decDisableShortcircuits() InterpretableDecorator {
- return func(i Interpretable) (Interpretable, error) {
+func decDisableShortcircuits() InterpretableDecoratorV2 {
+ return func(i InterpretableV2) (InterpretableV2, error) {
switch expr := i.(type) {
case *evalOr:
return &evalExhaustiveOr{
@@ -104,8 +108,8 @@ func decDisableShortcircuits() InterpretableDecorator {
// 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) {
+func decOptimize() InterpretableDecoratorV2 {
+ return func(i InterpretableV2) (InterpretableV2, error) {
switch inst := i.(type) {
case *evalList:
return maybeBuildListLiteral(i, inst)
@@ -124,7 +128,7 @@ func decOptimize() InterpretableDecorator {
}
// decRegexOptimizer compiles regex pattern string constants.
-func decRegexOptimizer(regexOptimizations ...*RegexOptimization) InterpretableDecorator {
+func decRegexOptimizer(regexOptimizations ...*RegexOptimization) InterpretableDecoratorV2 {
functionMatchMap := make(map[string]*RegexOptimization)
overloadMatchMap := make(map[string]*RegexOptimization)
for _, m := range regexOptimizations {
@@ -134,7 +138,7 @@ func decRegexOptimizer(regexOptimizations ...*RegexOptimization) InterpretableDe
}
}
- return func(i Interpretable) (Interpretable, error) {
+ return func(i InterpretableV2) (InterpretableV2, error) {
call, ok := i.(InterpretableCall)
if !ok {
return i, nil
@@ -165,7 +169,7 @@ func decRegexOptimizer(regexOptimizations ...*RegexOptimization) InterpretableDe
}
}
-func maybeOptimizeConstUnary(i Interpretable, call InterpretableCall) (Interpretable, error) {
+func maybeOptimizeConstUnary(i InterpretableV2, call InterpretableCall) (InterpretableV2, error) {
args := call.Args()
if len(args) != 1 {
return i, nil
@@ -181,7 +185,7 @@ func maybeOptimizeConstUnary(i Interpretable, call InterpretableCall) (Interpret
return NewConstValue(call.ID(), val), nil
}
-func maybeBuildListLiteral(i Interpretable, l *evalList) (Interpretable, error) {
+func maybeBuildListLiteral(i InterpretableV2, l *evalList) (InterpretableV2, error) {
for _, elem := range l.elems {
_, isConst := elem.(InterpretableConst)
if !isConst {
@@ -191,7 +195,7 @@ func maybeBuildListLiteral(i Interpretable, l *evalList) (Interpretable, error)
return NewConstValue(l.ID(), l.Eval(EmptyActivation())), nil
}
-func maybeBuildMapLiteral(i Interpretable, mp *evalMap) (Interpretable, error) {
+func maybeBuildMapLiteral(i InterpretableV2, mp *evalMap) (InterpretableV2, error) {
for idx, key := range mp.keys {
_, isConst := key.(InterpretableConst)
if !isConst {
@@ -209,7 +213,7 @@ func maybeBuildMapLiteral(i Interpretable, mp *evalMap) (Interpretable, error) {
// 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) {
+func maybeOptimizeSetMembership(i InterpretableV2, inlist InterpretableCall) (InterpretableV2, error) {
args := inlist.Args()
lhs := args[0]
rhs := args[1]
diff --git a/vendor/github.com/google/cel-go/interpreter/frame.go b/vendor/github.com/google/cel-go/interpreter/frame.go
new file mode 100644
index 0000000000..3cf2d6b6b5
--- /dev/null
+++ b/vendor/github.com/google/cel-go/interpreter/frame.go
@@ -0,0 +1,327 @@
+// Copyright 2026 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 (
+ "context"
+ "errors"
+ "fmt"
+ "sync"
+ "sync/atomic"
+
+ "github.com/google/cel-go/common/types/ref"
+)
+
+// evalContext contains the stateful information needed for a single evaluation.
+//
+// This state is shared across all frames within a single evaluation, including
+// child frames created for comprehension blocks.
+type evalContext struct {
+ // interrupt exposes a callback channel for cancellation.
+ interrupt <-chan struct{}
+
+ // interruptCheckCount is the number of times the interrupt channel has been checked.
+ interruptCheckCount atomic.Uint64
+
+ // interruptCheckFrequency is the frequency at which the interrupt channel is checked.
+ interruptCheckFrequency uint
+
+ // interrupted indicates whether the evaluation has been interrupted.
+ interrupted atomic.Bool
+
+ // state provides the context for tracking the evaluation state.
+ state EvalState
+
+ // costs provides the context for tracking the evaluation costs.
+ costs *CostTracker
+
+ // ctx is the context for async call implementations to use.
+ ctx context.Context
+
+ // cancel cancels the context when the evaluation is finished.
+ cancel context.CancelFunc
+}
+
+// ExecutionFrame provides the context for a single evaluation of an expression.
+//
+// The execution frame must not be stored in any fashion as its lifecycle is completely
+// controlled by the CEL evaluation process.
+type ExecutionFrame struct {
+ // Activation provides the context for resolving variables by name.
+ Activation
+
+ // parent provides the context for parent scopes (used for comprehension iterators).
+ parent *ExecutionFrame
+
+ // ctx provides the shared evaluation state across frames.
+ ctx *evalContext
+}
+
+// NewExecutionFrame creates a new execution frame from the pool.
+func NewExecutionFrame(input any) (*ExecutionFrame, error) {
+ f := frameStack.Get().(*ExecutionFrame)
+ switch v := input.(type) {
+ case Activation:
+ f.Activation = v
+ case map[string]any:
+ f.Activation = activationInput.create(v)
+ default:
+ return nil, fmt.Errorf("invalid input, wanted Activation or map[string]any, got: (%T)%v", input, input)
+ }
+ return f, nil
+}
+
+// SetContext sets the context for the execution frame.
+func (f *ExecutionFrame) SetContext(ctx context.Context, interruptCheckFrequency uint) error {
+ if f.parent != nil {
+ return errors.New("SetContext() called on child frame")
+ }
+ if f.ctx != nil {
+ return errors.New("SetContext() called more than once")
+ }
+ f.ctx = evalContextPool.Get().(*evalContext)
+ f.ctx.ctx, f.ctx.cancel = context.WithCancel(ctx)
+ f.ctx.interrupt = ctx.Done()
+ f.ctx.interruptCheckFrequency = interruptCheckFrequency
+ f.ctx.interruptCheckCount.Store(0)
+ f.ctx.interrupted.Store(false)
+ return nil
+}
+
+// Close releases the resources held by the execution frame and returns it to the pool.
+func (f *ExecutionFrame) Close() {
+ if f.parent == nil && f.ctx != nil {
+ if f.ctx.cancel != nil {
+ f.ctx.cancel()
+ f.ctx.cancel = nil
+ }
+ f.ctx.ctx = nil
+ f.ctx.interrupt = nil
+ f.ctx.state = nil
+ f.ctx.costs = nil
+ f.ctx.interrupted.Store(false)
+ f.ctx.interruptCheckCount.Store(0)
+ f.ctx.interruptCheckFrequency = 0
+ evalContextPool.Put(f.ctx)
+ }
+ f.ctx = nil
+ f.parent = nil
+ switch a := f.Activation.(type) {
+ case *hierarchicalActivation:
+ if child, ok := a.child.(*inputActivation); ok {
+ activationInput.release(child)
+ }
+ activationStack.release(a)
+ case *inputActivation:
+ activationInput.release(a)
+ }
+ f.Activation = nil
+ frameStack.Put(f)
+}
+
+// Push pushes the given activation onto the activation stack and returns the new frame.
+//
+// This operation is internal to the interpreter and is used to handle comprehension
+// scoping. The child frame inherits the shared evalContext from the parent.
+func (f *ExecutionFrame) Push(activation Activation) *ExecutionFrame {
+ child := frameStack.Get().(*ExecutionFrame)
+ child.parent = f
+ child.ctx = f.ctx
+ child.Activation = activationStack.create(f.Activation, activation)
+ return child
+}
+
+// Pop returns the parent frame, releasing the current frame back to the pool.
+func (f *ExecutionFrame) Pop() *ExecutionFrame {
+ if f.parent == nil {
+ return f
+ }
+ parent := f.parent
+ activationStack.release(f.Activation)
+ f.Activation = nil
+ f.parent = nil
+ f.ctx = nil
+ frameStack.Put(f)
+ return parent
+}
+
+// ResolveName implements the Activation interface by proxying to the internal activation.
+func (f *ExecutionFrame) ResolveName(name string) (any, bool) {
+ return f.Activation.ResolveName(name)
+}
+
+// Parent implements the Activation interface by proxying to the internal activation.
+func (f *ExecutionFrame) Parent() Activation {
+ return f.Activation.Parent()
+}
+
+// AsPartialActivation implements the PartialActivation interface by proxying to the internal activation.
+func (f *ExecutionFrame) AsPartialActivation() (PartialActivation, bool) {
+ return AsPartialActivation(f.Activation)
+}
+
+// Unwrap returns the internal activation.
+func (f *ExecutionFrame) Unwrap() Activation {
+ return f.Activation
+}
+
+// CheckInterrupt returns whether the evaluation has been interrupted.
+func (f *ExecutionFrame) CheckInterrupt() bool {
+ if f.ctx == nil {
+ return false
+ }
+ if f.ctx.interrupted.Load() {
+ return true
+ }
+ count := f.ctx.interruptCheckCount.Add(1)
+ if f.ctx.interruptCheckFrequency > 0 && count%uint64(f.ctx.interruptCheckFrequency) == 0 {
+ select {
+ case <-f.ctx.interrupt:
+ f.ctx.interrupted.Store(true)
+ return true
+ default:
+ return false
+ }
+ }
+ return false
+}
+
+// frameStack provides a synchronized pool of ExecutionFrames.
+var frameStack = &sync.Pool{
+ New: func() any {
+ return &ExecutionFrame{}
+ },
+}
+
+// evalContextPool provides a synchronized pool of evalContexts.
+var evalContextPool = &sync.Pool{
+ New: func() any {
+ return &evalContext{}
+ },
+}
+
+type activationStackPool struct {
+ sync.Pool
+}
+
+func (pool *activationStackPool) create(parent, child Activation) Activation {
+ h := pool.Get().(*hierarchicalActivation)
+ h.child = child
+ h.parent = parent
+ h.poolAllocated = true
+ return h
+}
+
+func (pool *activationStackPool) release(activation Activation) {
+ h, ok := activation.(*hierarchicalActivation)
+ if !ok || !h.poolAllocated {
+ return
+ }
+ h.parent = nil
+ h.child = nil
+ pool.Pool.Put(h)
+}
+
+func newActivationStackPool() *activationStackPool {
+ return &activationStackPool{
+ Pool: sync.Pool{
+ New: func() any {
+ return &hierarchicalActivation{}
+ },
+ },
+ }
+}
+
+type inputActivation struct {
+ vars map[string]any
+ lazyVars map[string]any
+}
+
+// ResolveName looks up the value of the input variable name, if found.
+//
+// Lazy bindings may be supplied within the map-based input in either of the following forms:
+// - func() any
+// - func() ref.Val
+//
+// The lazy binding will only be invoked once per evaluation.
+//
+// Values which are not represented as ref.Val types on input may be adapted to a ref.Val using
+// the types.Adapter configured in the environment.
+func (a *inputActivation) ResolveName(name string) (any, bool) {
+ v, found := a.vars[name]
+ if !found {
+ return nil, false
+ }
+ switch obj := v.(type) {
+ case func() ref.Val:
+ if resolved, found := a.lazyVars[name]; found {
+ return resolved, true
+ }
+ lazy := obj()
+ a.lazyVars[name] = lazy
+ return lazy, true
+ case func() any:
+ if resolved, found := a.lazyVars[name]; found {
+ return resolved, true
+ }
+ lazy := obj()
+ a.lazyVars[name] = lazy
+ return lazy, true
+ default:
+ return obj, true
+ }
+}
+
+// Parent implements the Activation interface
+func (a *inputActivation) Parent() Activation {
+ return nil
+}
+
+func newActivationInputPool() *activationInputPool {
+ return &activationInputPool{
+ Pool: sync.Pool{
+ New: func() any {
+ return &inputActivation{
+ lazyVars: make(map[string]any),
+ }
+ },
+ },
+ }
+}
+
+type activationInputPool struct {
+ sync.Pool
+}
+
+// create initializes a pooled Activation object with the map input.
+func (p *activationInputPool) create(vars map[string]any) *inputActivation {
+ a := p.Pool.Get().(*inputActivation)
+ a.vars = vars
+ return a
+}
+
+func (p *activationInputPool) release(value any) {
+ a := value.(*inputActivation)
+ for k := range a.lazyVars {
+ delete(a.lazyVars, k)
+ }
+ a.vars = nil
+ p.Pool.Put(a)
+}
+
+var (
+ activationStack = newActivationStackPool()
+ activationInput = newActivationInputPool()
+)
diff --git a/vendor/github.com/google/cel-go/interpreter/interpretable.go b/vendor/github.com/google/cel-go/interpreter/interpretable.go
index 50e66d6373..5b94e23e6c 100644
--- a/vendor/github.com/google/cel-go/interpreter/interpretable.go
+++ b/vendor/github.com/google/cel-go/interpreter/interpretable.go
@@ -26,20 +26,53 @@ import (
"github.com/google/cel-go/common/types/traits"
)
-// Interpretable can accept a given Activation and produce a value along with
-// an accompanying EvalState which can be used to inspect whether additional
-// data might be necessary to complete the evaluation.
+// Interpretable evaluates an Activation and produces a value.
type Interpretable interface {
// ID value corresponding to the expression node.
ID() int64
- // Eval an Activation to produce an output.
+ // Eval evaluates an Activation and produces an output.
Eval(activation Activation) ref.Val
}
+// InterpretableV2 evaluates an ExecutionFrame and produces a value.
+//
+// The ExecutionFrame should not be stored and should always be passed as the first
+// argument to any function as it behaves like Golang's context.Context.
+type InterpretableV2 interface {
+ Interpretable
+
+ // Exec evaluates the expression within the given ExecutionFrame.
+ Exec(frame *ExecutionFrame) ref.Val
+}
+
+// adaptToV2 adapts a V1 Interpretable implementation to the V2 interface.
+//
+// This adapter is used to bridge the legacy Interpretable interface to the
+// modern InterpretableV2 interface, providing a shim that allows the use of
+// both interfaces in the same system.
+func adaptToV2(i Interpretable) InterpretableV2 {
+ switch v := i.(type) {
+ case InterpretableV2:
+ return v
+ default:
+ return &v1Adapter{Interpretable: v}
+ }
+}
+
+// v1Adapter handles bridging a V1 Interpretable implementation to the V2 interface.
+type v1Adapter struct {
+ Interpretable
+}
+
+// Exec implements the InterpretableV2 interface method.
+func (a *v1Adapter) Exec(f *ExecutionFrame) ref.Val {
+ return a.Eval(f)
+}
+
// InterpretableConst interface for tracking whether the Interpretable is a constant value.
type InterpretableConst interface {
- Interpretable
+ InterpretableV2
// Value returns the constant value of the instruction.
Value() ref.Val
@@ -47,7 +80,7 @@ type InterpretableConst interface {
// InterpretableAttribute interface for tracking whether the Interpretable is an attribute.
type InterpretableAttribute interface {
- Interpretable
+ InterpretableV2
// Attr returns the Attribute value.
Attr() Attribute
@@ -81,7 +114,7 @@ type InterpretableAttribute interface {
// InterpretableCall interface for inspecting Interpretable instructions related to function calls.
type InterpretableCall interface {
- Interpretable
+ InterpretableV2
// Function returns the function name as it appears in text or mangled operator name as it
// appears in the operators.go file.
@@ -94,16 +127,16 @@ type InterpretableCall interface {
// Args returns the normalized arguments to the function overload.
// For receiver-style functions, the receiver target is arg 0.
- Args() []Interpretable
+ Args() []InterpretableV2
}
// InterpretableConstructor interface for inspecting Interpretable instructions that initialize a list, map
// or struct.
type InterpretableConstructor interface {
- Interpretable
+ InterpretableV2
// InitVals returns all the list elements, map key and values or struct field values.
- InitVals() []Interpretable
+ InitVals() []InterpretableV2
// Type returns the type constructed.
Type() ref.Type
@@ -112,18 +145,23 @@ type InterpretableConstructor interface {
// ObservableInterpretable is an Interpretable which supports stateful observation, such as tracing
// or cost-tracking.
type ObservableInterpretable struct {
- Interpretable
+ InterpretableV2
observers []StatefulObserver
}
// ID implements the Interpretable method to get the expression id associated with the step.
func (oi *ObservableInterpretable) ID() int64 {
- return oi.Interpretable.ID()
+ return oi.InterpretableV2.ID()
+}
+
+// Exec implements the InterpretableV2 interface method.
+func (oi *ObservableInterpretable) Exec(frame *ExecutionFrame) ref.Val {
+ return oi.ObserveExec(frame, func(any) {})
}
// Eval proxies to the ObserveEval method while invoking a no-op callback to report the observations.
func (oi *ObservableInterpretable) Eval(vars Activation) ref.Val {
- return oi.ObserveEval(vars, func(any) {})
+ return oi.ObserveExec(AsFrame(vars), func(any) {})
}
// ObserveEval evaluates an interpretable and performs per-evaluation state-tracking.
@@ -131,25 +169,65 @@ func (oi *ObservableInterpretable) Eval(vars Activation) ref.Val {
// This method is concurrency safe and the expectation is that the observer function will use
// a switch statement to determine the type of the state which has been reported back from the call.
func (oi *ObservableInterpretable) ObserveEval(vars Activation, observer func(any)) ref.Val {
- var err error
+ return oi.ObserveExec(AsFrame(vars), observer)
+}
+
+// ObserveExec evaluates an interpretable and performs per-evaluation state-tracking.
+//
+// This method is concurrency safe and the expectation is that the observer function will use
+// a switch statement to determine the type of the state which has been reported back from the call.
+func (oi *ObservableInterpretable) ObserveExec(frame *ExecutionFrame, observer func(any)) ref.Val {
// Initialize the state needed for the observers to function.
for _, obs := range oi.observers {
- vars, err = obs.InitState(vars)
+ state, err := obs.InitState(frame)
if err != nil {
return types.WrapErr(err)
}
// Provide an initial reference to the state to ensure state is available
// even in cases of interrupting errors generated during evaluation.
- observer(obs.GetState(vars))
+ observer(state)
}
- result := oi.Interpretable.Eval(vars)
+ result := oi.InterpretableV2.Exec(frame)
// Get the state which needs to be reported back as having been observed.
for _, obs := range oi.observers {
- observer(obs.GetState(vars))
+ observer(obs.GetState(frame))
}
return result
}
+// AsFrame promotes an Activation to an ExecutionFrame.
+func AsFrame(a Activation) *ExecutionFrame {
+ if f, ok := a.(*ExecutionFrame); ok {
+ return f
+ }
+ frame := &ExecutionFrame{Activation: a}
+ // Walk the activation hierarchy to find a parent ExecutionFrame and inherit
+ // its shared context.
+ if parent := findFrame(a); parent != nil {
+ frame.ctx = parent.ctx
+ }
+ return frame
+}
+
+// findFrame walks the activation hierarchy via Unwrap and Parent to locate an
+// existing ExecutionFrame, if one exists.
+func findFrame(a Activation) *ExecutionFrame {
+ if wrapper, ok := a.(activationWrapper); ok {
+ unwrapped := wrapper.Unwrap()
+ if f, ok := unwrapped.(*ExecutionFrame); ok {
+ return f
+ }
+ return findFrame(unwrapped)
+ }
+ if p := a.Parent(); p != nil {
+ if f, ok := p.(*ExecutionFrame); ok {
+ return f
+ }
+ return findFrame(p)
+ }
+ return nil
+}
+
// Core Interpretable implementations used during the program planning phase.
type evalTestOnly struct {
@@ -162,9 +240,9 @@ func (test *evalTestOnly) ID() int64 {
return test.id
}
-// Eval implements the Interpretable interface method.
-func (test *evalTestOnly) Eval(ctx Activation) ref.Val {
- val, err := test.Resolve(ctx)
+// Exec implements the InterpretableV2 interface method.
+func (test *evalTestOnly) Exec(frame *ExecutionFrame) ref.Val {
+ val, err := test.Resolve(frame)
// Return an error if the resolve step fails
if err != nil {
return types.LabelErrNode(test.id, types.WrapErr(err))
@@ -175,6 +253,11 @@ func (test *evalTestOnly) Eval(ctx Activation) ref.Val {
return test.Adapter().NativeToValue(val)
}
+// Eval implements the Interpretable interface method.
+func (test *evalTestOnly) Eval(ctx Activation) ref.Val {
+ return test.Exec(AsFrame(ctx))
+}
+
// AddQualifier appends a qualifier that will always and only perform a presence test.
func (test *evalTestOnly) AddQualifier(q Qualifier) (Attribute, error) {
cq, ok := q.(ConstantQualifier)
@@ -194,7 +277,7 @@ func (q *testOnlyQualifier) Qualify(vars Activation, obj any) (any, error) {
if err != nil {
return nil, err
}
- if unk, isUnk := out.(types.Unknown); isUnk {
+ if unk, isUnk := out.(*types.Unknown); isUnk {
return unk, nil
}
return present, nil
@@ -230,6 +313,11 @@ func (cons *evalConst) ID() int64 {
return cons.id
}
+// Exec implements the InterpretableV2 interface method.
+func (cons *evalConst) Exec(frame *ExecutionFrame) ref.Val {
+ return cons.val
+}
+
// Eval implements the Interpretable interface method.
func (cons *evalConst) Eval(ctx Activation) ref.Val {
return cons.val
@@ -242,7 +330,7 @@ func (cons *evalConst) Value() ref.Val {
type evalOr struct {
id int64
- terms []Interpretable
+ terms []InterpretableV2
}
// ID implements the Interpretable interface method.
@@ -250,12 +338,12 @@ func (or *evalOr) ID() int64 {
return or.id
}
-// Eval implements the Interpretable interface method.
-func (or *evalOr) Eval(ctx Activation) ref.Val {
+// Exec implements the InterpretableV2 interface method.
+func (or *evalOr) Exec(frame *ExecutionFrame) ref.Val {
var err ref.Val = nil
var unk *types.Unknown
for _, term := range or.terms {
- val := term.Eval(ctx)
+ val := term.Exec(frame)
boolVal, ok := val.(types.Bool)
// short-circuit on true.
if ok && boolVal == types.True {
@@ -283,9 +371,14 @@ func (or *evalOr) Eval(ctx Activation) ref.Val {
return types.False
}
+// Eval implements the Interpretable interface method.
+func (or *evalOr) Eval(ctx Activation) ref.Val {
+ return or.Exec(AsFrame(ctx))
+}
+
type evalAnd struct {
id int64
- terms []Interpretable
+ terms []InterpretableV2
}
// ID implements the Interpretable interface method.
@@ -293,12 +386,12 @@ func (and *evalAnd) ID() int64 {
return and.id
}
-// Eval implements the Interpretable interface method.
-func (and *evalAnd) Eval(ctx Activation) ref.Val {
+// Exec implements the InterpretableV2 interface method.
+func (and *evalAnd) Exec(frame *ExecutionFrame) ref.Val {
var err ref.Val = nil
var unk *types.Unknown
for _, term := range and.terms {
- val := term.Eval(ctx)
+ val := term.Exec(frame)
boolVal, ok := val.(types.Bool)
// short-circuit on false.
if ok && boolVal == types.False {
@@ -326,10 +419,15 @@ func (and *evalAnd) Eval(ctx Activation) ref.Val {
return types.True
}
+// Eval implements the Interpretable interface method.
+func (and *evalAnd) Eval(ctx Activation) ref.Val {
+ return and.Exec(AsFrame(ctx))
+}
+
type evalEq struct {
id int64
- lhs Interpretable
- rhs Interpretable
+ lhs InterpretableV2
+ rhs InterpretableV2
}
// ID implements the Interpretable interface method.
@@ -337,10 +435,10 @@ func (eq *evalEq) ID() int64 {
return eq.id
}
-// Eval implements the Interpretable interface method.
-func (eq *evalEq) Eval(ctx Activation) ref.Val {
- lVal := eq.lhs.Eval(ctx)
- rVal := eq.rhs.Eval(ctx)
+// Exec implements the InterpretableV2 interface method.
+func (eq *evalEq) Exec(frame *ExecutionFrame) ref.Val {
+ lVal := eq.lhs.Exec(frame)
+ rVal := eq.rhs.Exec(frame)
if types.IsUnknownOrError(lVal) {
return lVal
}
@@ -350,6 +448,11 @@ func (eq *evalEq) Eval(ctx Activation) ref.Val {
return types.Equal(lVal, rVal)
}
+// Eval implements the Interpretable interface method.
+func (eq *evalEq) Eval(ctx Activation) ref.Val {
+ return eq.Exec(AsFrame(ctx))
+}
+
// Function implements the InterpretableCall interface method.
func (*evalEq) Function() string {
return operators.Equals
@@ -361,14 +464,14 @@ func (*evalEq) OverloadID() string {
}
// Args implements the InterpretableCall interface method.
-func (eq *evalEq) Args() []Interpretable {
- return []Interpretable{eq.lhs, eq.rhs}
+func (eq *evalEq) Args() []InterpretableV2 {
+ return []InterpretableV2{eq.lhs, eq.rhs}
}
type evalNe struct {
id int64
- lhs Interpretable
- rhs Interpretable
+ lhs InterpretableV2
+ rhs InterpretableV2
}
// ID implements the Interpretable interface method.
@@ -376,10 +479,10 @@ func (ne *evalNe) ID() int64 {
return ne.id
}
-// Eval implements the Interpretable interface method.
-func (ne *evalNe) Eval(ctx Activation) ref.Val {
- lVal := ne.lhs.Eval(ctx)
- rVal := ne.rhs.Eval(ctx)
+// Exec implements the InterpretableV2 interface method.
+func (ne *evalNe) Exec(frame *ExecutionFrame) ref.Val {
+ lVal := ne.lhs.Exec(frame)
+ rVal := ne.rhs.Exec(frame)
if types.IsUnknownOrError(lVal) {
return lVal
}
@@ -389,6 +492,11 @@ func (ne *evalNe) Eval(ctx Activation) ref.Val {
return types.Bool(types.Equal(lVal, rVal) != types.True)
}
+// Eval implements the Interpretable interface method.
+func (ne *evalNe) Eval(ctx Activation) ref.Val {
+ return ne.Exec(AsFrame(ctx))
+}
+
// Function implements the InterpretableCall interface method.
func (*evalNe) Function() string {
return operators.NotEquals
@@ -400,8 +508,8 @@ func (*evalNe) OverloadID() string {
}
// Args implements the InterpretableCall interface method.
-func (ne *evalNe) Args() []Interpretable {
- return []Interpretable{ne.lhs, ne.rhs}
+func (ne *evalNe) Args() []InterpretableV2 {
+ return []InterpretableV2{ne.lhs, ne.rhs}
}
type evalZeroArity struct {
@@ -416,9 +524,14 @@ func (zero *evalZeroArity) ID() int64 {
return zero.id
}
+// Exec implements the InterpretableV2 interface method.
+func (zero *evalZeroArity) Exec(frame *ExecutionFrame) ref.Val {
+ return types.LabelErrNode(zero.id, zero.impl())
+}
+
// Eval implements the Interpretable interface method.
func (zero *evalZeroArity) Eval(ctx Activation) ref.Val {
- return types.LabelErrNode(zero.id, zero.impl())
+ return zero.Exec(AsFrame(ctx))
}
// Function implements the InterpretableCall interface method.
@@ -432,15 +545,15 @@ func (zero *evalZeroArity) OverloadID() string {
}
// Args returns the argument to the unary function.
-func (zero *evalZeroArity) Args() []Interpretable {
- return []Interpretable{}
+func (zero *evalZeroArity) Args() []InterpretableV2 {
+ return []InterpretableV2{}
}
type evalUnary struct {
id int64
function string
overload string
- arg Interpretable
+ arg InterpretableV2
trait int
impl functions.UnaryOp
nonStrict bool
@@ -451,9 +564,9 @@ func (un *evalUnary) ID() int64 {
return un.id
}
-// Eval implements the Interpretable interface method.
-func (un *evalUnary) Eval(ctx Activation) ref.Val {
- argVal := un.arg.Eval(ctx)
+// Exec implements the InterpretableV2 interface method.
+func (un *evalUnary) Exec(frame *ExecutionFrame) ref.Val {
+ argVal := un.arg.Exec(frame)
// Early return if the argument to the function is unknown or error.
strict := !un.nonStrict
if strict && types.IsUnknownOrError(argVal) {
@@ -472,6 +585,11 @@ func (un *evalUnary) Eval(ctx Activation) ref.Val {
return types.NewErrWithNodeID(un.id, "no such overload: %s", un.function)
}
+// Eval implements the Interpretable interface method.
+func (un *evalUnary) Eval(ctx Activation) ref.Val {
+ return un.Exec(AsFrame(ctx))
+}
+
// Function implements the InterpretableCall interface method.
func (un *evalUnary) Function() string {
return un.function
@@ -483,16 +601,16 @@ func (un *evalUnary) OverloadID() string {
}
// Args returns the argument to the unary function.
-func (un *evalUnary) Args() []Interpretable {
- return []Interpretable{un.arg}
+func (un *evalUnary) Args() []InterpretableV2 {
+ return []InterpretableV2{un.arg}
}
type evalBinary struct {
id int64
function string
overload string
- lhs Interpretable
- rhs Interpretable
+ lhs InterpretableV2
+ rhs InterpretableV2
trait int
impl functions.BinaryOp
nonStrict bool
@@ -503,10 +621,10 @@ func (bin *evalBinary) ID() int64 {
return bin.id
}
-// Eval implements the Interpretable interface method.
-func (bin *evalBinary) Eval(ctx Activation) ref.Val {
- lVal := bin.lhs.Eval(ctx)
- rVal := bin.rhs.Eval(ctx)
+// Exec implements the InterpretableV2 interface method.
+func (bin *evalBinary) Exec(frame *ExecutionFrame) ref.Val {
+ lVal := bin.lhs.Exec(frame)
+ rVal := bin.rhs.Exec(frame)
// Early return if any argument to the function is unknown or error.
strict := !bin.nonStrict
if strict {
@@ -530,6 +648,11 @@ func (bin *evalBinary) Eval(ctx Activation) ref.Val {
return types.NewErrWithNodeID(bin.id, "no such overload: %s", bin.function)
}
+// Eval implements the Interpretable interface method.
+func (bin *evalBinary) Eval(ctx Activation) ref.Val {
+ return bin.Exec(AsFrame(ctx))
+}
+
// Function implements the InterpretableCall interface method.
func (bin *evalBinary) Function() string {
return bin.function
@@ -541,22 +664,22 @@ func (bin *evalBinary) OverloadID() string {
}
// Args returns the argument to the unary function.
-func (bin *evalBinary) Args() []Interpretable {
- return []Interpretable{bin.lhs, bin.rhs}
+func (bin *evalBinary) Args() []InterpretableV2 {
+ return []InterpretableV2{bin.lhs, bin.rhs}
}
type evalVarArgs struct {
id int64
function string
overload string
- args []Interpretable
+ args []InterpretableV2
trait int
impl functions.FunctionOp
nonStrict bool
}
// NewCall creates a new call Interpretable.
-func NewCall(id int64, function, overload string, args []Interpretable, impl functions.FunctionOp) InterpretableCall {
+func NewCall(id int64, function, overload string, args []InterpretableV2, impl functions.FunctionOp) InterpretableCall {
return &evalVarArgs{
id: id,
function: function,
@@ -571,13 +694,13 @@ func (fn *evalVarArgs) ID() int64 {
return fn.id
}
-// Eval implements the Interpretable interface method.
-func (fn *evalVarArgs) Eval(ctx Activation) ref.Val {
+// Exec implements the InterpretableV2 interface method.
+func (fn *evalVarArgs) Exec(frame *ExecutionFrame) ref.Val {
argVals := make([]ref.Val, len(fn.args))
// Early return if any argument to the function is unknown or error.
strict := !fn.nonStrict
for i, arg := range fn.args {
- argVals[i] = arg.Eval(ctx)
+ argVals[i] = arg.Exec(frame)
if strict && types.IsUnknownOrError(argVals[i]) {
return argVals[i]
}
@@ -596,6 +719,11 @@ func (fn *evalVarArgs) Eval(ctx Activation) ref.Val {
return types.NewErrWithNodeID(fn.id, "no such overload: %s %d", fn.function, fn.id)
}
+// Eval implements the Interpretable interface method.
+func (fn *evalVarArgs) Eval(ctx Activation) ref.Val {
+ return fn.Exec(AsFrame(ctx))
+}
+
// Function implements the InterpretableCall interface method.
func (fn *evalVarArgs) Function() string {
return fn.function
@@ -607,13 +735,13 @@ func (fn *evalVarArgs) OverloadID() string {
}
// Args returns the argument to the unary function.
-func (fn *evalVarArgs) Args() []Interpretable {
+func (fn *evalVarArgs) Args() []InterpretableV2 {
return fn.args
}
type evalList struct {
id int64
- elems []Interpretable
+ elems []InterpretableV2
optionals []bool
hasOptionals bool
adapter types.Adapter
@@ -624,12 +752,12 @@ func (l *evalList) ID() int64 {
return l.id
}
-// Eval implements the Interpretable interface method.
-func (l *evalList) Eval(ctx Activation) ref.Val {
+// Exec implements the InterpretableV2 interface method.
+func (l *evalList) Exec(frame *ExecutionFrame) ref.Val {
elemVals := make([]ref.Val, 0, len(l.elems))
// If any argument is unknown or error early terminate.
for i, elem := range l.elems {
- elemVal := elem.Eval(ctx)
+ elemVal := elem.Exec(frame)
if types.IsUnknownOrError(elemVal) {
return elemVal
}
@@ -645,10 +773,15 @@ func (l *evalList) Eval(ctx Activation) ref.Val {
}
elemVals = append(elemVals, elemVal)
}
- return l.adapter.NativeToValue(elemVals)
+ return types.NewRefValList(l.adapter, elemVals)
}
-func (l *evalList) InitVals() []Interpretable {
+// Eval implements the Interpretable interface method.
+func (l *evalList) Eval(ctx Activation) ref.Val {
+ return l.Exec(AsFrame(ctx))
+}
+
+func (l *evalList) InitVals() []InterpretableV2 {
return l.elems
}
@@ -658,8 +791,8 @@ func (l *evalList) Type() ref.Type {
type evalMap struct {
id int64
- keys []Interpretable
- vals []Interpretable
+ keys []InterpretableV2
+ vals []InterpretableV2
optionals []bool
hasOptionals bool
adapter types.Adapter
@@ -670,16 +803,16 @@ func (m *evalMap) ID() int64 {
return m.id
}
-// Eval implements the Interpretable interface method.
-func (m *evalMap) Eval(ctx Activation) ref.Val {
- entries := make(map[ref.Val]ref.Val)
+// Exec implements the InterpretableV2 interface method.
+func (m *evalMap) Exec(frame *ExecutionFrame) ref.Val {
+ entries := make(map[ref.Val]ref.Val, len(m.keys))
// If any argument is unknown or error early terminate.
for i, key := range m.keys {
- keyVal := key.Eval(ctx)
+ keyVal := key.Exec(frame)
if types.IsUnknownOrError(keyVal) {
return keyVal
}
- valVal := m.vals[i].Eval(ctx)
+ valVal := m.vals[i].Exec(frame)
if types.IsUnknownOrError(valVal) {
return valVal
}
@@ -696,14 +829,19 @@ func (m *evalMap) Eval(ctx Activation) ref.Val {
}
entries[keyVal] = valVal
}
- return m.adapter.NativeToValue(entries)
+ return types.NewRefValMap(m.adapter, entries)
}
-func (m *evalMap) InitVals() []Interpretable {
+// Eval implements the Interpretable interface method.
+func (m *evalMap) Eval(ctx Activation) ref.Val {
+ return m.Exec(AsFrame(ctx))
+}
+
+func (m *evalMap) InitVals() []InterpretableV2 {
if len(m.keys) != len(m.vals) {
return nil
}
- result := make([]Interpretable, len(m.keys)+len(m.vals))
+ result := make([]InterpretableV2, len(m.keys)+len(m.vals))
idx := 0
for i, k := range m.keys {
v := m.vals[i]
@@ -723,7 +861,7 @@ type evalObj struct {
id int64
typeName string
fields []string
- vals []Interpretable
+ vals []InterpretableV2
optionals []bool
hasOptionals bool
provider types.Provider
@@ -734,12 +872,12 @@ func (o *evalObj) ID() int64 {
return o.id
}
-// Eval implements the Interpretable interface method.
-func (o *evalObj) Eval(ctx Activation) ref.Val {
- fieldVals := make(map[string]ref.Val)
+// Exec implements the InterpretableV2 interface method.
+func (o *evalObj) Exec(frame *ExecutionFrame) ref.Val {
+ fieldVals := make(map[string]ref.Val, len(o.fields))
// If any argument is unknown or error early terminate.
for i, field := range o.fields {
- val := o.vals[i].Eval(ctx)
+ val := o.vals[i].Exec(frame)
if types.IsUnknownOrError(val) {
return val
}
@@ -759,8 +897,13 @@ func (o *evalObj) Eval(ctx Activation) ref.Val {
return types.LabelErrNode(o.id, o.provider.NewValue(o.typeName, fieldVals))
}
+// Eval implements the Interpretable interface method.
+func (o *evalObj) Eval(ctx Activation) ref.Val {
+ return o.Exec(AsFrame(ctx))
+}
+
// InitVals implements the InterpretableConstructor interface method.
-func (o *evalObj) InitVals() []Interpretable {
+func (o *evalObj) InitVals() []InterpretableV2 {
return o.vals
}
@@ -774,11 +917,11 @@ type evalFold struct {
accuVar string
iterVar string
iterVar2 string
- iterRange Interpretable
- accu Interpretable
- cond Interpretable
- step Interpretable
- result Interpretable
+ iterRange InterpretableV2
+ accu InterpretableV2
+ cond InterpretableV2
+ step InterpretableV2
+ result InterpretableV2
adapter types.Adapter
// note an exhaustive fold will ensure that all branches are evaluated
@@ -793,13 +936,13 @@ func (fold *evalFold) ID() int64 {
return fold.id
}
-// Eval implements the Interpretable interface method.
-func (fold *evalFold) Eval(ctx Activation) ref.Val {
+// Exec implements the InterpretableV2 interface method.
+func (fold *evalFold) Exec(frame *ExecutionFrame) ref.Val {
// Initialize the folder interface
- f := newFolder(fold, ctx)
+ f := newFolder(fold, frame)
defer releaseFolder(f)
- foldRange := fold.iterRange.Eval(ctx)
+ foldRange := fold.iterRange.Exec(frame)
if types.IsUnknownOrError(foldRange) {
return foldRange
}
@@ -824,14 +967,19 @@ func (fold *evalFold) Eval(ctx Activation) ref.Val {
return f.foldIterable(iterable)
}
+// Eval implements the Interpretable interface method.
+func (fold *evalFold) Eval(ctx Activation) ref.Val {
+ return fold.Exec(AsFrame(ctx))
+}
+
// Optional Interpretable implementations that specialize, subsume, or extend the core evaluation
// plan via decorators.
// evalSetMembership is an Interpretable implementation which tests whether an input value
// exists within the set of map keys used to model a set.
type evalSetMembership struct {
- inst Interpretable
- arg Interpretable
+ inst InterpretableV2
+ arg InterpretableV2
valueSet map[ref.Val]ref.Val
}
@@ -840,9 +988,9 @@ func (e *evalSetMembership) ID() int64 {
return e.inst.ID()
}
-// Eval implements the Interpretable interface method.
-func (e *evalSetMembership) Eval(ctx Activation) ref.Val {
- val := e.arg.Eval(ctx)
+// Exec implements the InterpretableV2 interface method.
+func (e *evalSetMembership) Exec(frame *ExecutionFrame) ref.Val {
+ val := e.arg.Exec(frame)
if types.IsUnknownOrError(val) {
return val
}
@@ -852,18 +1000,28 @@ func (e *evalSetMembership) Eval(ctx Activation) ref.Val {
return types.False
}
+// Eval implements the Interpretable interface method.
+func (e *evalSetMembership) Eval(ctx Activation) ref.Val {
+ return e.Exec(AsFrame(ctx))
+}
+
// evalWatch is an Interpretable implementation that wraps the execution of a given
// expression so that it may observe the computed value and send it to an observer.
type evalWatch struct {
- Interpretable
+ InterpretableV2
observer EvalObserver
}
+// Exec implements the InterpretableV2 interface method.
+func (e *evalWatch) Exec(frame *ExecutionFrame) ref.Val {
+ val := e.InterpretableV2.Exec(frame)
+ e.observer(frame, e.ID(), e.InterpretableV2, val)
+ return val
+}
+
// Eval implements the Interpretable interface method.
func (e *evalWatch) Eval(vars Activation) ref.Val {
- val := e.Interpretable.Eval(vars)
- e.observer(vars, e.ID(), e.Interpretable, val)
- return val
+ return e.Exec(AsFrame(vars))
}
// evalWatchAttr describes a watcher of an InterpretableAttribute Interpretable.
@@ -918,11 +1076,16 @@ func (e *evalWatchAttr) AddQualifier(q Qualifier) (Attribute, error) {
return e, err
}
+// Exec implements the InterpretableV2 interface method.
+func (e *evalWatchAttr) Exec(frame *ExecutionFrame) ref.Val {
+ val := e.InterpretableAttribute.Exec(frame)
+ e.observer(frame, e.ID(), e.InterpretableAttribute, val)
+ return val
+}
+
// Eval implements the Interpretable interface method.
func (e *evalWatchAttr) Eval(vars Activation) ref.Val {
- val := e.InterpretableAttribute.Eval(vars)
- e.observer(vars, e.ID(), e.InterpretableAttribute, val)
- return val
+ return e.Exec(AsFrame(vars))
}
// evalWatchConstQual observes the qualification of an object using a constant boolean, int,
@@ -1049,17 +1212,22 @@ type evalWatchConst struct {
observer EvalObserver
}
-// Eval implements the Interpretable interface method.
-func (e *evalWatchConst) Eval(vars Activation) ref.Val {
+// Exec implements the InterpretableV2 interface method.
+func (e *evalWatchConst) Exec(frame *ExecutionFrame) ref.Val {
val := e.Value()
- e.observer(vars, e.ID(), e.InterpretableConst, val)
+ e.observer(frame, e.ID(), e.InterpretableConst, val)
return val
}
+// Eval implements the Interpretable interface method.
+func (e *evalWatchConst) Eval(vars Activation) ref.Val {
+ return e.Exec(AsFrame(vars))
+}
+
// evalExhaustiveOr is just like evalOr, but does not short-circuit argument evaluation.
type evalExhaustiveOr struct {
id int64
- terms []Interpretable
+ terms []InterpretableV2
}
// ID implements the Interpretable interface method.
@@ -1067,13 +1235,13 @@ func (or *evalExhaustiveOr) ID() int64 {
return or.id
}
-// Eval implements the Interpretable interface method.
-func (or *evalExhaustiveOr) Eval(ctx Activation) ref.Val {
+// Exec implements the InterpretableV2 interface method.
+func (or *evalExhaustiveOr) Exec(frame *ExecutionFrame) ref.Val {
var err ref.Val = nil
var unk *types.Unknown
isTrue := false
for _, term := range or.terms {
- val := term.Eval(ctx)
+ val := term.Exec(frame)
boolVal, ok := val.(types.Bool)
// flag the result as true
if ok && boolVal == types.True {
@@ -1103,10 +1271,15 @@ func (or *evalExhaustiveOr) Eval(ctx Activation) ref.Val {
return types.False
}
+// Eval implements the Interpretable interface method.
+func (or *evalExhaustiveOr) Eval(ctx Activation) ref.Val {
+ return or.Exec(AsFrame(ctx))
+}
+
// evalExhaustiveAnd is just like evalAnd, but does not short-circuit argument evaluation.
type evalExhaustiveAnd struct {
id int64
- terms []Interpretable
+ terms []InterpretableV2
}
// ID implements the Interpretable interface method.
@@ -1114,13 +1287,13 @@ func (and *evalExhaustiveAnd) ID() int64 {
return and.id
}
-// Eval implements the Interpretable interface method.
-func (and *evalExhaustiveAnd) Eval(ctx Activation) ref.Val {
+// Exec implements the InterpretableV2 interface method.
+func (and *evalExhaustiveAnd) Exec(frame *ExecutionFrame) ref.Val {
var err ref.Val = nil
var unk *types.Unknown
isFalse := false
for _, term := range and.terms {
- val := term.Eval(ctx)
+ val := term.Exec(frame)
boolVal, ok := val.(types.Bool)
// short-circuit on false.
if ok && boolVal == types.False {
@@ -1150,6 +1323,11 @@ func (and *evalExhaustiveAnd) Eval(ctx Activation) ref.Val {
return types.True
}
+// Eval implements the Interpretable interface method.
+func (and *evalExhaustiveAnd) Eval(ctx Activation) ref.Val {
+ return and.Exec(AsFrame(ctx))
+}
+
// evalExhaustiveConditional is like evalConditional, but does not short-circuit argument
// evaluation.
type evalExhaustiveConditional struct {
@@ -1163,11 +1341,11 @@ func (cond *evalExhaustiveConditional) ID() int64 {
return cond.id
}
-// Eval implements the Interpretable interface method.
-func (cond *evalExhaustiveConditional) Eval(ctx Activation) ref.Val {
- cVal := cond.attr.expr.Eval(ctx)
- tVal, tErr := cond.attr.truthy.Resolve(ctx)
- fVal, fErr := cond.attr.falsy.Resolve(ctx)
+// Exec implements the InterpretableV2 interface method.
+func (cond *evalExhaustiveConditional) Exec(frame *ExecutionFrame) ref.Val {
+ cVal := cond.attr.expr.Exec(frame)
+ tVal, tErr := cond.attr.truthy.Resolve(frame)
+ fVal, fErr := cond.attr.falsy.Resolve(frame)
cBool, ok := cVal.(types.Bool)
if !ok {
return types.ValOrErr(cVal, "no such overload")
@@ -1184,6 +1362,11 @@ func (cond *evalExhaustiveConditional) Eval(ctx Activation) ref.Val {
return cond.adapter.NativeToValue(fVal)
}
+// Eval implements the Interpretable interface method.
+func (cond *evalExhaustiveConditional) Eval(ctx Activation) ref.Val {
+ return cond.Exec(AsFrame(ctx))
+}
+
// evalAttr evaluates an Attribute value.
type evalAttr struct {
adapter types.Adapter
@@ -1215,15 +1398,20 @@ func (a *evalAttr) Adapter() types.Adapter {
return a.adapter
}
-// Eval implements the Interpretable interface method.
-func (a *evalAttr) Eval(ctx Activation) ref.Val {
- v, err := a.attr.Resolve(ctx)
+// Exec implements the InterpretableV2 interface method.
+func (a *evalAttr) Exec(frame *ExecutionFrame) ref.Val {
+ v, err := a.attr.Resolve(frame)
if err != nil {
return types.LabelErrNode(a.ID(), types.WrapErr(err))
}
return a.adapter.NativeToValue(v)
}
+// Eval implements the Interpretable interface method.
+func (a *evalAttr) Eval(ctx Activation) ref.Val {
+ return a.Exec(AsFrame(ctx))
+}
+
// Qualify proxies to the Attribute's Qualify method.
func (a *evalAttr) Qualify(vars Activation, obj any) (any, error) {
return a.attr.Qualify(vars, obj)
@@ -1249,7 +1437,7 @@ type evalWatchConstructor struct {
}
// InitVals implements the InterpretableConstructor InitVals function.
-func (c *evalWatchConstructor) InitVals() []Interpretable {
+func (c *evalWatchConstructor) InitVals() []InterpretableV2 {
return c.constructor.InitVals()
}
@@ -1263,11 +1451,16 @@ func (c *evalWatchConstructor) ID() int64 {
return c.constructor.ID()
}
+// Exec implements the InterpretableV2 interface method.
+func (c *evalWatchConstructor) Exec(frame *ExecutionFrame) ref.Val {
+ val := c.constructor.Exec(frame)
+ c.observer(frame, c.ID(), c.constructor, val)
+ return val
+}
+
// Eval implements the Interpretable Eval function.
func (c *evalWatchConstructor) Eval(vars Activation) ref.Val {
- val := c.constructor.Eval(vars)
- c.observer(vars, c.ID(), c.constructor, val)
- return val
+ return c.Exec(AsFrame(vars))
}
func invalidOptionalEntryInit(field any, value ref.Val) ref.Val {
@@ -1279,10 +1472,10 @@ func invalidOptionalElementInit(value ref.Val) ref.Val {
}
// newFolder creates or initializes a pooled folder instance.
-func newFolder(eval *evalFold, ctx Activation) *folder {
+func newFolder(eval *evalFold, frame *ExecutionFrame) *folder {
f := folderPool.Get().(*folder)
f.evalFold = eval
- f.activation = ctx
+ f.frame = frame.Push(f)
return f
}
@@ -1303,7 +1496,7 @@ func releaseFolder(f *folder) {
// cel.bind or cel.@block.
type folder struct {
*evalFold
- activation Activation
+ frame *ExecutionFrame
// fold state objects.
accuVal ref.Val
@@ -1322,16 +1515,16 @@ func (f *folder) foldIterable(iterable traits.Iterable) ref.Val {
for it.HasNext() == types.True {
f.iterVar1Val = it.Next()
- cond := f.cond.Eval(f)
+ cond := f.cond.Exec(f.frame)
condBool, ok := cond.(types.Bool)
if f.interrupted || (!f.exhaustive && ok && condBool != types.True) {
return f.evalResult()
}
// Update the accumulation value and check for eval interuption.
- f.accuVal = f.step.Eval(f)
+ f.accuVal = f.step.Exec(f.frame)
f.initialized = true
- if f.interruptable && checkInterrupt(f.activation) {
+ if f.interruptable && f.frame.CheckInterrupt() {
f.interrupted = true
return f.evalResult()
}
@@ -1348,16 +1541,16 @@ func (f *folder) FoldEntry(key, val any) bool {
// Terminate evaluation if evaluation is interrupted or the condition is not true and exhaustive
// eval is not enabled.
- cond := f.cond.Eval(f)
+ cond := f.cond.Exec(f.frame)
condBool, ok := cond.(types.Bool)
if f.interrupted || (!f.exhaustive && ok && condBool != types.True) {
return false
}
// Update the accumulation value and check for eval interuption.
- f.accuVal = f.step.Eval(f)
+ f.accuVal = f.step.Exec(f.frame)
f.initialized = true
- if f.interruptable && checkInterrupt(f.activation) {
+ if f.interruptable && f.frame.CheckInterrupt() {
f.interrupted = true
return false
}
@@ -1371,7 +1564,7 @@ func (f *folder) ResolveName(name string) (any, bool) {
if name == f.accuVar {
if !f.initialized {
f.initialized = true
- initVal := f.accu.Eval(f.activation)
+ initVal := f.accu.Exec(f.frame.parent)
if !f.exhaustive {
if l, isList := initVal.(traits.Lister); isList && l.Size() == types.IntZero {
initVal = types.NewMutableList(f.adapter)
@@ -1396,23 +1589,23 @@ func (f *folder) ResolveName(name string) (any, bool) {
return f.iterVar2Val, true
}
}
- return f.activation.ResolveName(name)
+ return f.frame.parent.ResolveName(name)
}
// Parent returns the activation embedded into the folder.
func (f *folder) Parent() Activation {
- return f.activation
+ return f.frame.parent
}
// Unwrap returns the parent activation, thus omitting access to local state
func (f *folder) Unwrap() Activation {
- return f.activation
+ return f.frame.parent
}
// UnknownAttributePatterns implements the PartialActivation interface returning the unknown patterns
// if they were provided to the input activation, or an empty set if the proxied activation is not partial.
func (f *folder) UnknownAttributePatterns() []*AttributePattern {
- if pv, ok := f.activation.(partialActivationConverter); ok {
+ if pv, ok := f.frame.parent.Activation.(partialActivationConverter); ok {
if partial, isPartial := pv.AsPartialActivation(); isPartial {
return partial.UnknownAttributePatterns()
}
@@ -1421,7 +1614,7 @@ func (f *folder) UnknownAttributePatterns() []*AttributePattern {
}
func (f *folder) AsPartialActivation() (PartialActivation, bool) {
- if pv, ok := f.activation.(partialActivationConverter); ok {
+ if pv, ok := f.frame.parent.Activation.(partialActivationConverter); ok {
if _, isPartial := pv.AsPartialActivation(); isPartial {
return f, true
}
@@ -1435,7 +1628,7 @@ func (f *folder) evalResult() ref.Val {
if f.interrupted {
return types.WrapErr(InterruptError{})
}
- res := f.result.Eval(f)
+ res := f.result.Exec(f.frame)
// Convert a mutable list or map to an immutable one if the comprehension has generated a list or
// map as a result.
if !types.IsUnknownOrError(res) && f.mutableValue {
@@ -1452,7 +1645,8 @@ func (f *folder) evalResult() ref.Val {
// reset clears any state associated with folder evaluation.
func (f *folder) reset() {
f.evalFold = nil
- f.activation = nil
+ f.frame.Pop()
+ f.frame = nil
f.accuVal = nil
f.iterVar1Val = nil
f.iterVar2Val = nil
@@ -1463,11 +1657,6 @@ func (f *folder) reset() {
f.computeResult = false
}
-func checkInterrupt(a Activation) bool {
- stop, found := a.ResolveName("#interrupted")
- return found && stop == true
-}
-
// InterruptError is a specialized error type used to signal that program evaluation should check
// whether a context cancellation is responsible for the error.
type InterruptError struct{}
diff --git a/vendor/github.com/google/cel-go/interpreter/interpreter.go b/vendor/github.com/google/cel-go/interpreter/interpreter.go
index d81ef1280f..38174aff69 100644
--- a/vendor/github.com/google/cel-go/interpreter/interpreter.go
+++ b/vendor/github.com/google/cel-go/interpreter/interpreter.go
@@ -29,11 +29,11 @@ import (
// PlannerOption configures the program plan options during interpretable setup.
type PlannerOption func(*planner) (*planner, error)
-// Interpreter generates a new Interpretable from a checked or unchecked expression.
+// Interpreter generates a new InterpretableV2 from a checked or unchecked expression.
type Interpreter interface {
- // NewInterpretable creates an Interpretable from a checked expression and an
+ // NewInterpretable creates an InterpretableV2 from a checked expression and an
// optional list of PlannerOption values.
- NewInterpretable(exprAST *ast.AST, opts ...PlannerOption) (Interpretable, error)
+ NewInterpretable(exprAST *ast.AST, opts ...PlannerOption) (InterpretableV2, error)
}
// EvalObserver is a functional interface that accepts an expression id and an observed value.
@@ -43,16 +43,16 @@ type EvalObserver func(vars Activation, id int64, programStep any, value ref.Val
// StatefulObserver observes evaluation while tracking or utilizing stateful behavior.
type StatefulObserver interface {
- // InitState configures stateful metadata on the activation.
- InitState(Activation) (Activation, error)
+ // InitState configures stateful metadata on the execution frame.
+ InitState(*ExecutionFrame) (any, error)
- // GetState retrieves the stateful metadata from the activation.
- GetState(Activation) any
+ // GetState retrieves the stateful metadata from the execution frame.
+ GetState(*ExecutionFrame) any
// Observe passes the activation and relevant evaluation metadata to the observer.
- // The observe method is expected to do the equivalent of GetState(vars) in order
+ // The observe method is expected to do the equivalent of GetState(AsFrame(activation))
// to find the metadata that needs to be updated upon invocation.
- Observe(vars Activation, id int64, programStep any, value ref.Val)
+ Observe(Activation, int64, any, ref.Val)
}
// EvalCancelledError represents a cancelled program evaluation operation.
@@ -106,37 +106,6 @@ func EvalStateObserver(opts ...evalStateOption) PlannerOption {
}
}
-// evalStateConverter identifies an object which is convertible to an EvalState instance.
-type evalStateConverter interface {
- asEvalState() EvalState
-}
-
-// evalStateActivation hides state in the Activation in a manner not accessible to expressions.
-type evalStateActivation struct {
- vars Activation
- state EvalState
-}
-
-// ResolveName proxies variable lookups to the backing activation.
-func (esa evalStateActivation) ResolveName(name string) (any, bool) {
- return esa.vars.ResolveName(name)
-}
-
-// Parent proxies parent lookups to the backing activation.
-func (esa evalStateActivation) Parent() Activation {
- return esa.vars
-}
-
-// AsPartialActivation supports conversion to a partial activation in order to detect unknown attributes.
-func (esa evalStateActivation) AsPartialActivation() (PartialActivation, bool) {
- return AsPartialActivation(esa.vars)
-}
-
-// asEvalState implements the evalStateConverter method.
-func (esa evalStateActivation) asEvalState() EvalState {
- return esa.state
-}
-
// activationWrapper identifies an object carrying local variables which should not be exposed to the user
// Activations used for such purposes can be unwrapped to return the activation which omits local state.
type activationWrapper interface {
@@ -144,57 +113,56 @@ type activationWrapper interface {
Unwrap() Activation
}
-// asEvalState walks the Activation hierarchy and returns the first EvalState found, if present.
-func asEvalState(vars Activation) (EvalState, bool) {
- if conv, ok := vars.(evalStateConverter); ok {
- return conv.asEvalState(), true
- }
- // Check if the current activation wraps another activation. This is used to support
- // wrappers such as the @block() activation which may be composed of a dynamicSlotActivation or a
- // constantSlotActivation. In this case, the underlying activation is the portion which interacts
- // with the EvalState.
- if wrapper, ok := vars.(activationWrapper); ok {
- unwrapped := wrapper.Unwrap()
- // Recursively call asEvalState on the unwrapped activation. This will check the unwrapped value and its parents.
- return asEvalState(unwrapped)
- }
- if vars.Parent() != nil {
- return asEvalState(vars.Parent())
- }
- return nil, false
-}
-
// evalStateFactory holds a reference to a factory function that produces an EvalState instance.
type evalStateFactory struct {
factory func() EvalState
}
-// InitState produces an EvalState instance and bundles it into the Activation in a way which is
+// InitState produces an EvalState instance and bundles it into the ExecutionFrame in a way which is
// not visible to expression evaluation.
-func (et *evalStateFactory) InitState(vars Activation) (Activation, error) {
+func (et *evalStateFactory) InitState(frame *ExecutionFrame) (any, error) {
state := et.factory()
- return evalStateActivation{vars: vars, state: state}, nil
+ if frame.ctx == nil {
+ frame.ctx = evalContextPool.Get().(*evalContext)
+ }
+ frame.ctx.state = state
+ return state, nil
}
// GetState extracts the EvalState from the Activation.
-func (et *evalStateFactory) GetState(vars Activation) any {
- if state, found := asEvalState(vars); found {
- return state
+func (et *evalStateFactory) GetState(frame *ExecutionFrame) any {
+ if frame.ctx == nil {
+ return nil
}
- return nil
+ return frame.ctx.state
}
// Observe records the evaluation state for a given expression node and program step.
func (et *evalStateFactory) Observe(vars Activation, id int64, programStep any, val ref.Val) {
- state, found := asEvalState(vars)
- if !found {
+ frame := AsFrame(vars)
+ if frame.ctx == nil || frame.ctx.state == nil {
return
}
- state.SetValue(id, val)
+ frame.ctx.state.SetValue(id, val)
}
// CustomDecorator configures a custom interpretable decorator for the program.
func CustomDecorator(dec InterpretableDecorator) PlannerOption {
+ return func(p *planner) (*planner, error) {
+ dec2 := func(i InterpretableV2) (InterpretableV2, error) {
+ legacy, err := dec(i)
+ if err != nil {
+ return nil, err
+ }
+ return adaptToV2(legacy), nil
+ }
+ p.decorators = append(p.decorators, dec2)
+ return p, nil
+ }
+}
+
+// CustomDecoratorV2 configures a custom V2 interpretable decorator for the program.
+func CustomDecoratorV2(dec InterpretableDecoratorV2) PlannerOption {
return func(p *planner) (*planner, error) {
p.decorators = append(p.decorators, dec)
return p, nil
@@ -207,7 +175,7 @@ func CustomDecorator(dec InterpretableDecorator) PlannerOption {
// provided to the decorator. This decorator is not thread-safe, and the EvalState
// must be reset between Eval() calls.
func ExhaustiveEval() PlannerOption {
- return CustomDecorator(decDisableShortcircuits())
+ return CustomDecoratorV2(decDisableShortcircuits())
}
// InterruptableEval annotates comprehension loops with information that indicates they
@@ -216,13 +184,13 @@ func ExhaustiveEval() PlannerOption {
// 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() PlannerOption {
- return CustomDecorator(decInterruptFolds())
+ return CustomDecoratorV2(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() PlannerOption {
- return CustomDecorator(decOptimize())
+ return CustomDecoratorV2(decOptimize())
}
// RegexOptimization provides a way to replace an InterpretableCall for a regex function when the
@@ -247,7 +215,7 @@ type RegexOptimization struct {
// CompileRegexConstants compiles regex pattern string constants at program creation time and reports any regex pattern
// compile errors.
func CompileRegexConstants(regexOptimizations ...*RegexOptimization) PlannerOption {
- return CustomDecorator(decRegexOptimizer(regexOptimizations...))
+ return CustomDecoratorV2(decRegexOptimizer(regexOptimizations...))
}
type exprInterpreter struct {
@@ -273,10 +241,10 @@ func NewInterpreter(dispatcher Dispatcher,
attrFactory: attrFactory}
}
-// NewIntepretable implements the Interpreter interface method.
+// NewInterpretable implements the Interpreter interface method.
func (i *exprInterpreter) NewInterpretable(
checked *ast.AST,
- opts ...PlannerOption) (Interpretable, error) {
+ opts ...PlannerOption) (InterpretableV2, error) {
p := newPlanner(i.dispatcher, i.provider, i.adapter, i.attrFactory, i.container, checked)
var err error
for _, o := range opts {
diff --git a/vendor/github.com/google/cel-go/interpreter/planner.go b/vendor/github.com/google/cel-go/interpreter/planner.go
index 0bc38449ce..95fd34551c 100644
--- a/vendor/github.com/google/cel-go/interpreter/planner.go
+++ b/vendor/github.com/google/cel-go/interpreter/planner.go
@@ -43,7 +43,7 @@ func newPlanner(disp Dispatcher,
container: cont,
refMap: exprAST.ReferenceMap(),
typeMap: exprAST.TypeMap(),
- decorators: make([]InterpretableDecorator, 0),
+ decorators: make([]InterpretableDecoratorV2, 0),
observers: make([]StatefulObserver, 0),
}
}
@@ -57,7 +57,7 @@ type planner struct {
container *containers.Container
refMap map[int64]*ast.ReferenceInfo
typeMap map[int64]*types.Type
- decorators []InterpretableDecorator
+ decorators []InterpretableDecoratorV2
observers []StatefulObserver
}
@@ -72,7 +72,7 @@ type planBuilder struct {
// 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 ast.Expr) (Interpretable, error) {
+func (p *planner) Plan(expr ast.Expr) (InterpretableV2, error) {
pb := &planBuilder{planner: p, localVars: make(map[string]int)}
i, err := pb.plan(expr)
if err != nil {
@@ -81,10 +81,10 @@ func (p *planner) Plan(expr ast.Expr) (Interpretable, error) {
if len(p.observers) == 0 {
return i, nil
}
- return &ObservableInterpretable{Interpretable: i, observers: p.observers}, nil
+ return &ObservableInterpretable{InterpretableV2: i, observers: p.observers}, nil
}
-func (p *planBuilder) plan(expr ast.Expr) (Interpretable, error) {
+func (p *planBuilder) plan(expr ast.Expr) (InterpretableV2, error) {
switch expr.Kind() {
case ast.CallKind:
return p.decorate(p.planCall(expr))
@@ -109,7 +109,7 @@ func (p *planBuilder) plan(expr ast.Expr) (Interpretable, error) {
// 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 *planBuilder) decorate(i Interpretable, err error) (Interpretable, error) {
+func (p *planBuilder) decorate(i InterpretableV2, err error) (InterpretableV2, error) {
if err != nil {
return nil, err
}
@@ -123,7 +123,7 @@ func (p *planBuilder) decorate(i Interpretable, err error) (Interpretable, error
}
// planIdent creates an Interpretable that resolves an identifier from an Activation.
-func (p *planBuilder) planIdent(expr ast.Expr) (Interpretable, error) {
+func (p *planBuilder) planIdent(expr ast.Expr) (InterpretableV2, error) {
// Establish whether the identifier is in the reference map.
if identRef, found := p.refMap[expr.ID()]; found {
return p.planCheckedIdent(expr.ID(), identRef)
@@ -142,7 +142,7 @@ func (p *planBuilder) planIdent(expr ast.Expr) (Interpretable, error) {
}, nil
}
-func (p *planBuilder) planCheckedIdent(id int64, identRef *ast.ReferenceInfo) (Interpretable, error) {
+func (p *planBuilder) planCheckedIdent(id int64, identRef *ast.ReferenceInfo) (InterpretableV2, error) {
// Plan a constant reference if this is the case for this simple identifier.
if identRef.Value != nil {
return NewConstValue(id, identRef.Value), nil
@@ -171,7 +171,7 @@ func (p *planBuilder) planCheckedIdent(id int64, identRef *ast.ReferenceInfo) (I
// 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 *planBuilder) planSelect(expr ast.Expr) (Interpretable, error) {
+func (p *planBuilder) planSelect(expr ast.Expr) (InterpretableV2, 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.ID()]; found {
@@ -227,7 +227,7 @@ func (p *planBuilder) planSelect(expr ast.Expr) (Interpretable, error) {
// 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 *planBuilder) planCall(expr ast.Expr) (Interpretable, error) {
+func (p *planBuilder) planCall(expr ast.Expr) (InterpretableV2, error) {
call := expr.AsCall()
target, fnName, oName := p.resolveFunction(expr)
argCount := len(call.Args())
@@ -237,7 +237,7 @@ func (p *planBuilder) planCall(expr ast.Expr) (Interpretable, error) {
offset++
}
- args := make([]Interpretable, argCount)
+ args := make([]InterpretableV2, argCount)
if target != nil {
arg, err := p.plan(target)
if err != nil {
@@ -307,7 +307,7 @@ func (p *planBuilder) planCall(expr ast.Expr) (Interpretable, error) {
func (p *planBuilder) planCallZero(expr ast.Expr,
function string,
overload string,
- impl *functions.Overload) (Interpretable, error) {
+ impl *functions.Overload) (InterpretableV2, error) {
if impl == nil || impl.Function == nil {
return nil, fmt.Errorf("no such overload: %s()", function)
}
@@ -324,7 +324,7 @@ func (p *planBuilder) planCallUnary(expr ast.Expr,
function string,
overload string,
impl *functions.Overload,
- args []Interpretable) (Interpretable, error) {
+ args []InterpretableV2) (InterpretableV2, error) {
var fn functions.UnaryOp
var trait int
var nonStrict bool
@@ -352,7 +352,7 @@ func (p *planBuilder) planCallBinary(expr ast.Expr,
function string,
overload string,
impl *functions.Overload,
- args []Interpretable) (Interpretable, error) {
+ args []InterpretableV2) (InterpretableV2, error) {
var fn functions.BinaryOp
var trait int
var nonStrict bool
@@ -381,7 +381,7 @@ func (p *planBuilder) planCallVarArgs(expr ast.Expr,
function string,
overload string,
impl *functions.Overload,
- args []Interpretable) (Interpretable, error) {
+ args []InterpretableV2) (InterpretableV2, error) {
var fn functions.FunctionOp
var trait int
var nonStrict bool
@@ -405,7 +405,7 @@ func (p *planBuilder) planCallVarArgs(expr ast.Expr,
}
// planCallEqual generates an equals (==) Interpretable.
-func (p *planBuilder) planCallEqual(expr ast.Expr, args []Interpretable) (Interpretable, error) {
+func (p *planBuilder) planCallEqual(expr ast.Expr, args []InterpretableV2) (InterpretableV2, error) {
return &evalEq{
id: expr.ID(),
lhs: args[0],
@@ -414,7 +414,7 @@ func (p *planBuilder) planCallEqual(expr ast.Expr, args []Interpretable) (Interp
}
// planCallNotEqual generates a not equals (!=) Interpretable.
-func (p *planBuilder) planCallNotEqual(expr ast.Expr, args []Interpretable) (Interpretable, error) {
+func (p *planBuilder) planCallNotEqual(expr ast.Expr, args []InterpretableV2) (InterpretableV2, error) {
return &evalNe{
id: expr.ID(),
lhs: args[0],
@@ -423,7 +423,7 @@ func (p *planBuilder) planCallNotEqual(expr ast.Expr, args []Interpretable) (Int
}
// planCallLogicalAnd generates a logical and (&&) Interpretable.
-func (p *planBuilder) planCallLogicalAnd(expr ast.Expr, args []Interpretable) (Interpretable, error) {
+func (p *planBuilder) planCallLogicalAnd(expr ast.Expr, args []InterpretableV2) (InterpretableV2, error) {
return &evalAnd{
id: expr.ID(),
terms: args,
@@ -431,7 +431,7 @@ func (p *planBuilder) planCallLogicalAnd(expr ast.Expr, args []Interpretable) (I
}
// planCallLogicalOr generates a logical or (||) Interpretable.
-func (p *planBuilder) planCallLogicalOr(expr ast.Expr, args []Interpretable) (Interpretable, error) {
+func (p *planBuilder) planCallLogicalOr(expr ast.Expr, args []InterpretableV2) (InterpretableV2, error) {
return &evalOr{
id: expr.ID(),
terms: args,
@@ -439,7 +439,7 @@ func (p *planBuilder) planCallLogicalOr(expr ast.Expr, args []Interpretable) (In
}
// planCallConditional generates a conditional / ternary (c ? t : f) Interpretable.
-func (p *planBuilder) planCallConditional(expr ast.Expr, args []Interpretable) (Interpretable, error) {
+func (p *planBuilder) planCallConditional(expr ast.Expr, args []InterpretableV2) (InterpretableV2, error) {
cond := args[0]
t := args[1]
var tAttr Attribute
@@ -467,7 +467,7 @@ func (p *planBuilder) planCallConditional(expr ast.Expr, args []Interpretable) (
// 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 *planBuilder) planCallIndex(expr ast.Expr, args []Interpretable, optional bool) (Interpretable, error) {
+func (p *planBuilder) planCallIndex(expr ast.Expr, args []InterpretableV2, optional bool) (InterpretableV2, error) {
op := args[0]
ind := args[1]
opType := p.typeMap[op.ID()]
@@ -502,7 +502,7 @@ func (p *planBuilder) planCallIndex(expr ast.Expr, args []Interpretable, optiona
}
// planCreateList generates a list construction Interpretable.
-func (p *planBuilder) planCreateList(expr ast.Expr) (Interpretable, error) {
+func (p *planBuilder) planCreateList(expr ast.Expr) (InterpretableV2, error) {
list := expr.AsList()
optionalIndices := list.OptionalIndices()
elements := list.Elements()
@@ -513,7 +513,7 @@ func (p *planBuilder) planCreateList(expr ast.Expr) (Interpretable, error) {
}
optionals[index] = true
}
- elems := make([]Interpretable, len(elements))
+ elems := make([]InterpretableV2, len(elements))
for i, elem := range elements {
elemVal, err := p.plan(elem)
if err != nil {
@@ -531,12 +531,12 @@ func (p *planBuilder) planCreateList(expr ast.Expr) (Interpretable, error) {
}
// planCreateStruct generates a map or object construction Interpretable.
-func (p *planBuilder) planCreateMap(expr ast.Expr) (Interpretable, error) {
+func (p *planBuilder) planCreateMap(expr ast.Expr) (InterpretableV2, error) {
m := expr.AsMap()
entries := m.Entries()
optionals := make([]bool, len(entries))
- keys := make([]Interpretable, len(entries))
- vals := make([]Interpretable, len(entries))
+ keys := make([]InterpretableV2, len(entries))
+ vals := make([]InterpretableV2, len(entries))
hasOptionals := false
for i, e := range entries {
entry := e.AsMapEntry()
@@ -565,7 +565,7 @@ func (p *planBuilder) planCreateMap(expr ast.Expr) (Interpretable, error) {
}
// planCreateObj generates an object construction Interpretable.
-func (p *planBuilder) planCreateStruct(expr ast.Expr) (Interpretable, error) {
+func (p *planBuilder) planCreateStruct(expr ast.Expr) (InterpretableV2, error) {
obj := expr.AsStruct()
typeName, defined := p.resolveTypeName(obj.TypeName())
if !defined {
@@ -574,7 +574,7 @@ func (p *planBuilder) planCreateStruct(expr ast.Expr) (Interpretable, error) {
objFields := obj.Fields()
optionals := make([]bool, len(objFields))
fields := make([]string, len(objFields))
- vals := make([]Interpretable, len(objFields))
+ vals := make([]InterpretableV2, len(objFields))
hasOptionals := false
for i, f := range objFields {
field := f.AsStructField()
@@ -599,7 +599,7 @@ func (p *planBuilder) planCreateStruct(expr ast.Expr) (Interpretable, error) {
}
// planComprehension generates an Interpretable fold operation.
-func (p *planBuilder) planComprehension(expr ast.Expr) (Interpretable, error) {
+func (p *planBuilder) planComprehension(expr ast.Expr) (InterpretableV2, error) {
fold := expr.AsComprehension()
accu, err := p.plan(fold.AccuInit())
if err != nil {
@@ -639,7 +639,7 @@ func (p *planBuilder) planComprehension(expr ast.Expr) (Interpretable, error) {
}
// planConst generates a constant valued Interpretable.
-func (p *planBuilder) planConst(expr ast.Expr) (Interpretable, error) {
+func (p *planBuilder) planConst(expr ast.Expr) (InterpretableV2, error) {
return NewConstValue(expr.ID(), expr.AsLiteral()), nil
}
@@ -726,7 +726,7 @@ func (p *planBuilder) resolveFunction(expr ast.Expr) (ast.Expr, string, string)
// relativeAttr indicates that the attribute in this case acts as a qualifier and as such needs to
// be observed to ensure that it's evaluation value is properly recorded for state tracking.
-func (p *planBuilder) relativeAttr(id int64, eval Interpretable, opt bool) (InterpretableAttribute, error) {
+func (p *planBuilder) relativeAttr(id int64, eval InterpretableV2, opt bool) (InterpretableAttribute, error) {
eAttr, ok := eval.(InterpretableAttribute)
if !ok {
eAttr = &evalAttr{
diff --git a/vendor/github.com/google/cel-go/interpreter/runtimecost.go b/vendor/github.com/google/cel-go/interpreter/runtimecost.go
index 6c44cd7986..68e4310106 100644
--- a/vendor/github.com/google/cel-go/interpreter/runtimecost.go
+++ b/vendor/github.com/google/cel-go/interpreter/runtimecost.go
@@ -62,48 +62,6 @@ func CostObserver(opts ...costTrackPlanOption) PlannerOption {
}
}
-// costTrackerConverter identifies an object which is convertible to a CostTracker instance.
-type costTrackerConverter interface {
- asCostTracker() *CostTracker
-}
-
-// costTrackActivation hides state in the Activation in a manner not accessible to expressions.
-type costTrackActivation struct {
- vars Activation
- costTracker *CostTracker
-}
-
-// ResolveName proxies variable lookups to the backing activation.
-func (cta costTrackActivation) ResolveName(name string) (any, bool) {
- return cta.vars.ResolveName(name)
-}
-
-// Parent proxies parent lookups to the backing activation.
-func (cta costTrackActivation) Parent() Activation {
- return cta.vars
-}
-
-// AsPartialActivation supports conversion to a partial activation in order to detect unknown attributes.
-func (cta costTrackActivation) AsPartialActivation() (PartialActivation, bool) {
- return AsPartialActivation(cta.vars)
-}
-
-// asCostTracker implements the costTrackerConverter method.
-func (cta costTrackActivation) asCostTracker() *CostTracker {
- return cta.costTracker
-}
-
-// asCostTracker walks the Activation hierarchy and returns the first cost tracker found, if present.
-func asCostTracker(vars Activation) (*CostTracker, bool) {
- if conv, ok := vars.(costTrackerConverter); ok {
- return conv.asCostTracker(), true
- }
- if vars.Parent() != nil {
- return asCostTracker(vars.Parent())
- }
- return nil, false
-}
-
// costTrackerFactory holds a factory for producing new CostTracker instances on each Eval call.
type costTrackerFactory struct {
factory func() (*CostTracker, error)
@@ -111,27 +69,37 @@ type costTrackerFactory struct {
// InitState produces a CostTracker and bundles it into an Activation in a way which is not visible
// to expression evaluation.
-func (ct *costTrackerFactory) InitState(vars Activation) (Activation, error) {
+func (ct *costTrackerFactory) InitState(frame *ExecutionFrame) (any, error) {
tracker, err := ct.factory()
if err != nil {
return nil, err
}
- return costTrackActivation{vars: vars, costTracker: tracker}, nil
+ if frame.ctx == nil {
+ frame.ctx = evalContextPool.Get().(*evalContext)
+ }
+ frame.ctx.costs = tracker
+ return tracker, nil
}
// GetState extracts the CostTracker from the Activation.
-func (ct *costTrackerFactory) GetState(vars Activation) any {
- if tracker, found := asCostTracker(vars); found {
- return tracker
+func (ct *costTrackerFactory) GetState(frame *ExecutionFrame) any {
+ if frame == nil || frame.ctx == nil {
+ return nil
}
- return nil
+ return frame.ctx.costs
}
// Observe computes the incremental cost of each step and records it into the CostTracker associated
// with the evaluation.
func (ct *costTrackerFactory) Observe(vars Activation, id int64, programStep any, val ref.Val) {
- tracker, found := asCostTracker(vars)
- if !found {
+ frame := AsFrame(vars)
+ state := ct.GetState(frame)
+ if state == nil {
+ return
+ }
+ tracker, ok := state.(*CostTracker)
+ if !ok {
+ // The state is configured with CostTrackFactory so this shouldn't happen.
return
}
switch t := programStep.(type) {
@@ -265,6 +233,19 @@ type CostTracker struct {
stack refValStack
}
+// Clone makes a shallow copy of the tracker.
+// The different clones can be used independently from
+// each other.
+func (c *CostTracker) Clone() (*CostTracker, error) {
+ tracker := &CostTracker{
+ Estimator: c.Estimator,
+ overloadTrackers: c.overloadTrackers,
+ Limit: c.Limit,
+ presenceTestHasCost: c.presenceTestHasCost,
+ }
+ return tracker, nil
+}
+
// ActualCost returns the runtime cost
func (c *CostTracker) ActualCost() uint64 {
return c.cost
@@ -292,7 +273,9 @@ func (c *CostTracker) costCall(call InterpretableCall, args []ref.Val, result re
// 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, overloads.ExtQuoteString, overloads.ExtFormatString:
+ case overloads.StartsWithString, overloads.EndsWithString:
+ cost += uint64(math.Ceil(float64(actualSize(args[1])) * common.StringTraversalCostFactor))
+ case overloads.StringToBytes, overloads.BytesToString, overloads.ExtQuoteString, overloads.ExtFormatString:
cost += uint64(math.Ceil(float64(actualSize(args[0])) * common.StringTraversalCostFactor))
case overloads.InList:
// If a list is composed entirely of constant values this is O(1), but we don't account for that here.
@@ -317,7 +300,7 @@ func (c *CostTracker) costCall(call InterpretableCall, args []ref.Val, result re
// In the worst case scenario, we would need to reallocate a new backing store and copy both operands over.
cost += uint64(math.Ceil(float64(actualSize(args[0])+actualSize(args[1])) * common.StringTraversalCostFactor))
// O(nm) functions
- case overloads.MatchesString:
+ case overloads.Matches, 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.
@@ -397,7 +380,7 @@ func (s *refValStack) drop(ids ...int64) {
// 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) {
+func (s *refValStack) dropArgs(args []InterpretableV2) ([]ref.Val, bool) {
result := make([]ref.Val, len(args))
argloop:
for nIdx := len(args) - 1; nIdx >= 0; nIdx-- {
diff --git a/vendor/github.com/google/cel-go/parser/unparser.go b/vendor/github.com/google/cel-go/parser/unparser.go
index ffd5b18e47..d503a450e9 100644
--- a/vendor/github.com/google/cel-go/parser/unparser.go
+++ b/vendor/github.com/google/cel-go/parser/unparser.go
@@ -297,7 +297,7 @@ func (un *unparser) visitConstVal(val ref.Val) error {
// represent the float using the minimum required digits
d := strconv.FormatFloat(float64(val), 'g', -1, 64)
un.str.WriteString(d)
- if !strings.Contains(d, ".") {
+ if !strings.ContainsAny(d, ".eE") {
un.str.WriteString(".0")
}
case types.Int:
diff --git a/vendor/modules.txt b/vendor/modules.txt
index a28b7638cd..5bfef08bc4 100644
--- a/vendor/modules.txt
+++ b/vendor/modules.txt
@@ -289,7 +289,7 @@ github.com/golang/snappy
# github.com/google/btree v1.1.3
## explicit; go 1.18
github.com/google/btree
-# github.com/google/cel-go v0.28.1
+# github.com/google/cel-go v0.29.0
## explicit; go 1.23.0
github.com/google/cel-go/cel
github.com/google/cel-go/checker