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