| // 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() |
| } |