blob: 3ceaace24ca2d5967f383512293f9688b6e02709 [file] [log] [blame]
// Copyright 2019 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 cel
import (
"fmt"
"math"
"github.com/google/cel-go/common/types"
"github.com/google/cel-go/common/types/ref"
"github.com/google/cel-go/interpreter"
exprpb "google.golang.org/genproto/googleapis/api/expr/v1alpha1"
)
// Program is an evaluable view of an Ast.
type Program interface {
// Eval returns the result of an evaluation of the Ast and environment against the input vars.
//
// The vars value may either be an `interpreter.Activation` or a `map[string]interface{}`.
//
// If the `OptTrackState` or `OptExhaustiveEval` flags are used, the `details` response will
// be non-nil. Given this caveat on `details`, the return state from evaluation will be:
//
// * `val`, `details`, `nil` - Successful evaluation of a non-error result.
// * `val`, `details`, `err` - Successful evaluation to an error result.
// * `nil`, `details`, `err` - Unsuccessful evaluation.
//
// An unsuccessful evaluation is typically the result of a series of incompatible `EnvOption`
// or `ProgramOption` values used in the creation of the evaluation environment or executable
// program.
Eval(vars interface{}) (ref.Val, *EvalDetails, error)
}
// NoVars returns an empty Activation.
func NoVars() interpreter.Activation {
return interpreter.EmptyActivation()
}
// PartialVars returns a PartialActivation which contains variables and a set of AttributePattern
// values that indicate variables or parts of variables whose value are not yet known.
//
// The `vars` value may either be an interpreter.Activation or any valid input to the
// interpreter.NewActivation call.
func PartialVars(vars interface{},
unknowns ...*interpreter.AttributePattern) (interpreter.PartialActivation, error) {
return interpreter.NewPartialActivation(vars, unknowns...)
}
// AttributePattern returns an AttributePattern that matches a top-level variable. The pattern is
// mutable, and its methods support the specification of one or more qualifier patterns.
//
// For example, the AttributePattern(`a`).QualString(`b`) represents a variable access `a` with a
// string field or index qualification `b`. This pattern will match Attributes `a`, and `a.b`,
// but not `a.c`.
//
// When using a CEL expression within a container, e.g. a package or namespace, the variable name
// in the pattern must match the qualified name produced during the variable namespace resolution.
// For example, when variable `a` is declared within an expression whose container is `ns.app`, the
// fully qualified variable name may be `ns.app.a`, `ns.a`, or `a` per the CEL namespace resolution
// rules. Pick the fully qualified variable name that makes sense within the container as the
// AttributePattern `varName` argument.
//
// See the interpreter.AttributePattern and interpreter.AttributeQualifierPattern for more info
// about how to create and manipulate AttributePattern values.
func AttributePattern(varName string) *interpreter.AttributePattern {
return interpreter.NewAttributePattern(varName)
}
// EvalDetails holds additional information observed during the Eval() call.
type EvalDetails struct {
state interpreter.EvalState
}
// State of the evaluation, non-nil if the OptTrackState or OptExhaustiveEval is specified
// within EvalOptions.
func (ed *EvalDetails) State() interpreter.EvalState {
return ed.state
}
// prog is the internal implementation of the Program interface.
type prog struct {
*Env
evalOpts EvalOption
decorators []interpreter.InterpretableDecorator
defaultVars interpreter.Activation
dispatcher interpreter.Dispatcher
interpreter interpreter.Interpreter
interpretable interpreter.Interpretable
attrFactory interpreter.AttributeFactory
}
// progFactory is a helper alias for marking a program creation factory function.
type progFactory func(interpreter.EvalState) (Program, error)
// progGen holds a reference to a progFactory instance and implements the Program interface.
type progGen struct {
factory progFactory
}
// newProgram creates a program instance with an environment, an ast, and an optional list of
// ProgramOption values.
//
// If the program cannot be configured the prog will be nil, with a non-nil error response.
func newProgram(e *Env, ast *Ast, opts []ProgramOption) (Program, error) {
// Build the dispatcher, interpreter, and default program value.
disp := interpreter.NewDispatcher()
// Ensure the default attribute factory is set after the adapter and provider are
// configured.
p := &prog{
Env: e,
decorators: []interpreter.InterpretableDecorator{},
dispatcher: disp,
}
// Configure the program via the ProgramOption values.
var err error
for _, opt := range opts {
if opt == nil {
return nil, fmt.Errorf("program options should be non-nil")
}
p, err = opt(p)
if err != nil {
return nil, err
}
}
// Set the attribute factory after the options have been set.
if p.evalOpts&OptPartialEval == OptPartialEval {
p.attrFactory = interpreter.NewPartialAttributeFactory(e.Container, e.adapter, e.provider)
} else {
p.attrFactory = interpreter.NewAttributeFactory(e.Container, e.adapter, e.provider)
}
interp := interpreter.NewInterpreter(disp, e.Container, e.provider, e.adapter, p.attrFactory)
p.interpreter = interp
// Translate the EvalOption flags into InterpretableDecorator instances.
decorators := make([]interpreter.InterpretableDecorator, len(p.decorators))
copy(decorators, p.decorators)
// Enable constant folding first.
if p.evalOpts&OptOptimize == OptOptimize {
decorators = append(decorators, interpreter.Optimize())
}
// Enable exhaustive eval over state tracking since it offers a superset of features.
if p.evalOpts&OptExhaustiveEval == OptExhaustiveEval {
// State tracking requires that each Eval() call operate on an isolated EvalState
// object; hence, the presence of the factory.
factory := func(state interpreter.EvalState) (Program, error) {
decs := append(decorators, interpreter.ExhaustiveEval(state))
clone := &prog{
evalOpts: p.evalOpts,
defaultVars: p.defaultVars,
Env: e,
dispatcher: disp,
interpreter: interp}
return initInterpretable(clone, ast, decs)
}
return initProgGen(factory)
}
// Enable state tracking last since it too requires the factory approach but is less
// featured than the ExhaustiveEval decorator.
if p.evalOpts&OptTrackState == OptTrackState {
factory := func(state interpreter.EvalState) (Program, error) {
decs := append(decorators, interpreter.TrackState(state))
clone := &prog{
evalOpts: p.evalOpts,
defaultVars: p.defaultVars,
Env: e,
dispatcher: disp,
interpreter: interp}
return initInterpretable(clone, ast, decs)
}
return initProgGen(factory)
}
return initInterpretable(p, ast, decorators)
}
// initProgGen tests the factory object by calling it once and returns a factory-based Program if
// the test is successful.
func initProgGen(factory progFactory) (Program, error) {
// Test the factory to make sure that configuration errors are spotted at config
_, err := factory(interpreter.NewEvalState())
if err != nil {
return nil, err
}
return &progGen{factory: factory}, nil
}
// initIterpretable creates a checked or unchecked interpretable depending on whether the Ast
// has been run through the type-checker.
func initInterpretable(
p *prog,
ast *Ast,
decorators []interpreter.InterpretableDecorator) (Program, error) {
var err error
// Unchecked programs do not contain type and reference information and may be
// slower to execute than their checked counterparts.
if !ast.IsChecked() {
p.interpretable, err =
p.interpreter.NewUncheckedInterpretable(ast.Expr(), decorators...)
if err != nil {
return nil, err
}
return p, nil
}
// When the AST has been checked it contains metadata that can be used to speed up program
// execution.
var checked *exprpb.CheckedExpr
checked, err = AstToCheckedExpr(ast)
if err != nil {
return nil, err
}
p.interpretable, err = p.interpreter.NewInterpretable(checked, decorators...)
if err != nil {
return nil, err
}
return p, nil
}
// Eval implements the Program interface method.
func (p *prog) Eval(input interface{}) (v ref.Val, det *EvalDetails, err error) {
// Configure error recovery for unexpected panics during evaluation. Note, the use of named
// return values makes it possible to modify the error response during the recovery
// function.
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("internal error: %v", r)
}
}()
// Build a hierarchical activation if there are default vars set.
vars, err := interpreter.NewActivation(input)
if err != nil {
return
}
if p.defaultVars != nil {
vars = interpreter.NewHierarchicalActivation(p.defaultVars, vars)
}
v = p.interpretable.Eval(vars)
// 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
// RPC signature which allows for multiple errors to be returned, but should be sufficient.
if types.IsError(v) {
err = v.(*types.Err)
}
return
}
// Cost implements the Coster interface method.
func (p *prog) Cost() (min, max int64) {
return estimateCost(p.interpretable)
}
// Eval implements the Program interface method.
func (gen *progGen) Eval(input interface{}) (ref.Val, *EvalDetails, error) {
// The factory based Eval() differs from the standard evaluation model in that it generates a
// new EvalState instance for each call to ensure that unique evaluations yield unique stateful
// results.
state := interpreter.NewEvalState()
det := &EvalDetails{state: state}
// Generate a new instance of the interpretable using the factory configured during the call to
// newProgram(). It is incredibly unlikely that the factory call will generate an error given
// the factory test performed within the Program() call.
p, err := gen.factory(state)
if err != nil {
return nil, det, err
}
// Evaluate the input, returning the result and the 'state' within EvalDetails.
v, _, err := p.Eval(input)
if err != nil {
return v, det, err
}
return v, det, nil
}
// Cost implements the Coster interface method.
func (gen *progGen) Cost() (min, max int64) {
// Use an empty state value since no evaluation is performed.
p, err := gen.factory(emptyEvalState)
if err != nil {
return 0, math.MaxInt64
}
return estimateCost(p)
}
var (
emptyEvalState = interpreter.NewEvalState()
)
// EstimateCost returns the heuristic cost interval for the program.
func EstimateCost(p Program) (min, max int64) {
return estimateCost(p)
}
func estimateCost(i interface{}) (min, max int64) {
c, ok := i.(interpreter.Coster)
if !ok {
return 0, math.MaxInt64
}
return c.Cost()
}