Bump CEL to 0.11.2

This commit is contained in:
Joe Betz
2022-03-24 11:34:14 -04:00
parent e7845861a5
commit 4c90653d19
139 changed files with 2688 additions and 1637 deletions

View File

@@ -35,13 +35,17 @@ type Activation interface {
Parent() Activation
}
// EmptyActivation returns a variable free activation.
// EmptyActivation returns a variable-free activation.
func EmptyActivation() Activation {
// This call cannot fail.
a, _ := NewActivation(map[string]interface{}{})
return a
return emptyActivation{}
}
// emptyActivation is a variable-free activation.
type emptyActivation struct{}
func (emptyActivation) ResolveName(string) (interface{}, bool) { return nil, false }
func (emptyActivation) Parent() Activation { return nil }
// NewActivation returns an activation based on a map-based binding where the map keys are
// expected to be qualified names used with ResolveName calls.
//

View File

@@ -105,7 +105,7 @@ func (apat *AttributePattern) QualifierPatterns() []*AttributeQualifierPattern {
return apat.qualifierPatterns
}
// AttributeQualifierPattern holds a wilcard or valued qualifier pattern.
// AttributeQualifierPattern holds a wildcard or valued qualifier pattern.
type AttributeQualifierPattern struct {
wildcard bool
value interface{}
@@ -125,7 +125,7 @@ func (qpat *AttributeQualifierPattern) Matches(q Qualifier) bool {
// type, is equal to the value held in the Qualifier. This interface is used by the
// AttributeQualifierPattern to determine pattern matches for non-wildcard qualifier patterns.
//
// Note: Attribute values are also Qualifier values; however, Attriutes are resolved before
// Note: Attribute values are also Qualifier values; however, Attributes are resolved before
// qualification happens. This is an implementation detail, but one relevant to why the Attribute
// types do not surface in the list of implementations.
//
@@ -206,7 +206,7 @@ func (fac *partialAttributeFactory) AbsoluteAttribute(id int64, names ...string)
}
// MaybeAttribute implementation of the AttributeFactory interface which ensure that the set of
// 'maybe' NamespacedAttribute values are produced using the PartialAttributeFactory rather than
// 'maybe' NamespacedAttribute values are produced using the partialAttributeFactory rather than
// the base AttributeFactory implementation.
func (fac *partialAttributeFactory) MaybeAttribute(id int64, name string) Attribute {
return &maybeAttribute{

View File

@@ -587,7 +587,7 @@ func (a *relativeAttribute) Resolve(vars Activation) (interface{}, error) {
if types.IsUnknown(v) {
return v, nil
}
// Next, qualify it. Qualification handles unkonwns as well, so there's no need to recheck.
// Next, qualify it. Qualification handles unknowns as well, so there's no need to recheck.
var err error
var obj interface{} = v
for _, qual := range a.qualifiers {
@@ -637,6 +637,8 @@ func newQualifier(adapter ref.TypeAdapter, id int64, v interface{}) (Qualifier,
qual = &uintQualifier{id: id, value: uint64(val), celValue: val, adapter: adapter}
case types.Bool:
qual = &boolQualifier{id: id, value: bool(val), celValue: val, adapter: adapter}
case types.Double:
qual = &doubleQualifier{id: id, value: float64(val), celValue: val, adapter: adapter}
default:
return nil, fmt.Errorf("invalid qualifier type: %T", v)
}

View File

@@ -98,7 +98,7 @@ func decDisableShortcircuits() InterpretableDecorator {
}
// decOptimize optimizes the program plan by looking for common evaluation patterns and
// conditionally precomputating the result.
// conditionally precomputing the result.
// - build list and map values with constant elements.
// - convert 'in' operations to set membership tests if possible.
func decOptimize() InterpretableDecorator {

View File

@@ -677,7 +677,19 @@ func (m *evalMap) Eval(ctx Activation) ref.Val {
}
func (m *evalMap) InitVals() []Interpretable {
return append(m.keys, m.vals...)
if len(m.keys) != len(m.vals) {
return nil
}
result := make([]Interpretable, len(m.keys)+len(m.vals))
idx := 0
for i, k := range m.keys {
v := m.vals[i]
result[idx] = k
idx++
result[idx] = v
idx++
}
return result
}
func (m *evalMap) Type() ref.Type {
@@ -852,7 +864,7 @@ func (fold *evalFold) Cost() (min, max int64) {
iMax + aMax + cMax*rangeCnt + sMax*rangeCnt + rMax
}
// Optional Intepretable implementations that specialize, subsume, or extend the core evaluation
// 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
@@ -969,7 +981,7 @@ func (e *evalWatchConstQual) Qualify(vars Activation, obj interface{}) (interfac
return out, err
}
// QualifierValueEquals tests whether the incoming value is equal to the qualificying constant.
// QualifierValueEquals tests whether the incoming value is equal to the qualifying constant.
func (e *evalWatchConstQual) QualifierValueEquals(value interface{}) bool {
qve, ok := e.ConstantQualifier.(qualifierValueEquator)
return ok && qve.QualifierValueEquals(value)

View File

@@ -162,7 +162,7 @@ type exprInterpreter struct {
}
// NewInterpreter builds an Interpreter from a Dispatcher and TypeProvider which will be used
// throughout the Eval of all Interpretable instances gerenated from it.
// throughout the Eval of all Interpretable instances generated from it.
func NewInterpreter(dispatcher Dispatcher,
container *containers.Container,
provider ref.TypeProvider,

View File

@@ -77,7 +77,7 @@ func newUncheckedPlanner(disp Dispatcher,
}
}
// planner is an implementatio of the interpretablePlanner interface.
// planner is an implementation of the interpretablePlanner interface.
type planner struct {
disp Dispatcher
provider ref.TypeProvider

View File

@@ -228,7 +228,7 @@ func (p *astPruner) prune(node *exprpb.Expr) (*exprpb.Expr, bool) {
}
}
// We have either an unknown/error value, or something we dont want to
// We have either an unknown/error value, or something we don't want to
// transform, or expression was not evaluated. If possible, drill down
// more.

View File

@@ -41,26 +41,42 @@ func CostObserver(tracker *CostTracker) EvalObserver {
case ConstantQualifier:
// TODO: Push identifiers on to the stack before observing constant qualifiers that apply to them
// and enable the below pop. Once enabled this can case can be collapsed into the Qualifier case.
//tracker.stack.pop(1)
tracker.cost++
case InterpretableConst:
// zero cost
case InterpretableAttribute:
// Ternary has no direct cost. All cost is from the conditional and the true/false branch expressions.
_, isConditional := t.Attr().(*conditionalAttribute)
if !isConditional {
switch a := t.Attr().(type) {
case *conditionalAttribute:
// Ternary has no direct cost. All cost is from the conditional and the true/false branch expressions.
tracker.stack.drop(a.falsy.ID(), a.truthy.ID(), a.expr.ID())
default:
tracker.stack.drop(t.Attr().ID())
tracker.cost += common.SelectAndIdentCost
}
case *evalExhaustiveConditional, *evalOr, *evalAnd, *evalExhaustiveOr, *evalExhaustiveAnd:
case *evalExhaustiveConditional:
// Ternary has no direct cost. All cost is from the conditional and the true/false branch expressions.
tracker.stack.drop(t.attr.falsy.ID(), t.attr.truthy.ID(), t.attr.expr.ID())
// While the field names are identical, the boolean operation eval structs do not share an interface and so
// must be handled individually.
case *evalOr:
tracker.stack.drop(t.rhs.ID(), t.lhs.ID())
case *evalAnd:
tracker.stack.drop(t.rhs.ID(), t.lhs.ID())
case *evalExhaustiveOr:
tracker.stack.drop(t.rhs.ID(), t.lhs.ID())
case *evalExhaustiveAnd:
tracker.stack.drop(t.rhs.ID(), t.lhs.ID())
case *evalFold:
tracker.stack.drop(t.iterRange.ID())
case Qualifier:
tracker.stack.pop(1)
tracker.cost++
case InterpretableCall:
if argVals, ok := tracker.stack.pop(len(t.Args())); ok {
if argVals, ok := tracker.stack.dropArgs(t.Args()); ok {
tracker.cost += tracker.costCall(t, argVals, val)
}
case InterpretableConstructor:
tracker.stack.dropArgs(t.InitVals())
switch t.Type() {
case types.ListType:
tracker.cost += common.ListCreateBaseCost
@@ -70,7 +86,7 @@ func CostObserver(tracker *CostTracker) EvalObserver {
tracker.cost += common.StructCreateBaseCost
}
}
tracker.stack.push(val)
tracker.stack.push(val, id)
if tracker.Limit != nil && tracker.cost > *tracker.Limit {
panic(EvalCancelledError{Cause: CostLimitExceeded, Message: "operation cancelled: actual cost limit exceeded"})
@@ -170,19 +186,56 @@ func (c CostTracker) actualSize(value ref.Val) uint64 {
return 1
}
// refValStack keeps track of values of the stack for cost calculation purposes
type refValStack []ref.Val
type stackVal struct {
Val ref.Val
ID int64
}
func (s *refValStack) push(value ref.Val) {
// refValStack keeps track of values of the stack for cost calculation purposes
type refValStack []stackVal
func (s *refValStack) push(val ref.Val, id int64) {
value := stackVal{Val: val, ID: id}
*s = append(*s, value)
}
func (s *refValStack) pop(count int) ([]ref.Val, bool) {
if len(*s) < count {
// TODO: Allowing drop and dropArgs to remove stack items above the IDs they are provided is a workaround. drop and dropArgs
// should find and remove only the stack items matching the provided IDs once all attributes are properly pushed and popped from stack.
// drop searches the stack for each ID and removes the ID and all stack items above it.
// If none of the IDs are found, the stack is not modified.
// WARNING: It is possible for multiple expressions with the same ID to exist (due to how macros are implemented) so it's
// possible that a dropped ID will remain on the stack. They should be removed when IDs on the stack are popped.
func (s *refValStack) drop(ids ...int64) {
for _, id := range ids {
for idx := len(*s) - 1; idx >= 0; idx-- {
if (*s)[idx].ID == id {
*s = (*s)[:idx]
break
}
}
}
}
// dropArgs searches the stack for all the args by their IDs, accumulates their associated ref.Vals and drops any
// stack items above any of the arg IDs. If any of the IDs are not found the stack, false is returned.
// Args are assumed to be found in the stack in reverse order, i.e. the last arg is expected to be found highest in
// the stack.
// WARNING: It is possible for multiple expressions with the same ID to exist (due to how macros are implemented) so it's
// possible that a dropped ID will remain on the stack. They should be removed when IDs on the stack are popped.
func (s *refValStack) dropArgs(args []Interpretable) ([]ref.Val, bool) {
result := make([]ref.Val, len(args))
argloop:
for nIdx := len(args) - 1; nIdx >= 0; nIdx-- {
for idx := len(*s) - 1; idx >= 0; idx-- {
if (*s)[idx].ID == args[nIdx].ID() {
el := (*s)[idx]
*s = (*s)[:idx]
result[nIdx] = el.Val
continue argloop
}
}
return nil, false
}
idx := len(*s) - count
el := (*s)[idx:]
*s = (*s)[:idx]
return el, true
return result, true
}