Skip to content

Commit 3f12eca

Browse files
Support for cel.@block during policy composition (#1056)
* Runtime support for cel.@block * Additional checks to prevent bad index specification * Support for constant lists and extended validations * Support for cel.@block during policy composition
1 parent f9db1d6 commit 3f12eca

File tree

11 files changed

+757
-96
lines changed

11 files changed

+757
-96
lines changed

cel/env.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -459,14 +459,20 @@ func (e *Env) ParseSource(src Source) (*Ast, *Issues) {
459459

460460
// Program generates an evaluable instance of the Ast within the environment (Env).
461461
func (e *Env) Program(ast *Ast, opts ...ProgramOption) (Program, error) {
462+
return e.PlanProgram(ast.NativeRep(), opts...)
463+
}
464+
465+
// PlanProgram generates an evaluable instance of the AST in the go-native representation within
466+
// the environment (Env).
467+
func (e *Env) PlanProgram(a *celast.AST, opts ...ProgramOption) (Program, error) {
462468
optSet := e.progOpts
463469
if len(opts) != 0 {
464470
mergedOpts := []ProgramOption{}
465471
mergedOpts = append(mergedOpts, e.progOpts...)
466472
mergedOpts = append(mergedOpts, opts...)
467473
optSet = mergedOpts
468474
}
469-
return newProgram(e, ast, optSet)
475+
return newProgram(e, a, optSet)
470476
}
471477

472478
// CELTypeAdapter returns the `types.Adapter` configured for the environment.

cel/optimizer.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,16 @@ type OptimizerContext struct {
211211
*Issues
212212
}
213213

214+
// ExtendEnv auguments the context's environment with the additional options.
215+
func (opt *OptimizerContext) ExtendEnv(opts ...EnvOption) error {
216+
e, err := opt.Env.Extend(opts...)
217+
if err != nil {
218+
return err
219+
}
220+
opt.Env = e
221+
return nil
222+
}
223+
214224
// ASTOptimizer applies an optimization over an AST and returns the optimized result.
215225
type ASTOptimizer interface {
216226
// Optimize optimizes a type-checked AST within an Environment and accumulates any issues.

cel/program.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import (
1919
"fmt"
2020
"sync"
2121

22+
"github.com/google/cel-go/common/ast"
2223
"github.com/google/cel-go/common/types"
2324
"github.com/google/cel-go/common/types/ref"
2425
"github.com/google/cel-go/interpreter"
@@ -151,7 +152,7 @@ func (p *prog) clone() *prog {
151152
// ProgramOption values.
152153
//
153154
// If the program cannot be configured the prog will be nil, with a non-nil error response.
154-
func newProgram(e *Env, a *Ast, opts []ProgramOption) (Program, error) {
155+
func newProgram(e *Env, a *ast.AST, opts []ProgramOption) (Program, error) {
155156
// Build the dispatcher, interpreter, and default program value.
156157
disp := interpreter.NewDispatcher()
157158

@@ -255,9 +256,9 @@ func newProgram(e *Env, a *Ast, opts []ProgramOption) (Program, error) {
255256
return p.initInterpretable(a, decorators)
256257
}
257258

258-
func (p *prog) initInterpretable(a *Ast, decs []interpreter.InterpretableDecorator) (*prog, error) {
259+
func (p *prog) initInterpretable(a *ast.AST, decs []interpreter.InterpretableDecorator) (*prog, error) {
259260
// When the AST has been exprAST it contains metadata that can be used to speed up program execution.
260-
interpretable, err := p.interpreter.NewInterpretable(a.impl, decs...)
261+
interpretable, err := p.interpreter.NewInterpretable(a, decs...)
261262
if err != nil {
262263
return nil, err
263264
}

conformance/BUILD.bazel

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ _ALL_TESTS = [
3232
"@dev_cel_expr//tests/simple:testdata/timestamps.textproto",
3333
"@dev_cel_expr//tests/simple:testdata/unknowns.textproto",
3434
"@dev_cel_expr//tests/simple:testdata/wrappers.textproto",
35+
"@dev_cel_expr//tests/simple:testdata/block_ext.textproto",
3536
]
3637

3738
_TESTS_TO_SKIP = [
@@ -68,6 +69,7 @@ go_test(
6869
deps = [
6970
"//cel:go_default_library",
7071
"//common:go_default_library",
72+
"//common/ast:go_default_library",
7173
"//common/types:go_default_library",
7274
"//common/types/ref:go_default_library",
7375
"//ext:go_default_library",

conformance/conformance_test.go

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313

1414
"github.com/google/cel-go/cel"
1515
"github.com/google/cel-go/common"
16+
"github.com/google/cel-go/common/ast"
1617
"github.com/google/cel-go/common/types"
1718
"github.com/google/cel-go/common/types/ref"
1819
"github.com/google/cel-go/ext"
@@ -89,6 +90,7 @@ func init() {
8990
ext.Math(),
9091
ext.Protos(),
9192
ext.Strings(),
93+
cel.Lib(celBlockLib{}),
9294
}
9395

9496
var err error
@@ -279,3 +281,89 @@ func TestConformance(t *testing.T) {
279281
}
280282
}
281283
}
284+
285+
type celBlockLib struct{}
286+
287+
func (celBlockLib) LibraryName() string {
288+
return "cel.lib.ext.cel.block.conformance"
289+
}
290+
291+
func (celBlockLib) CompileOptions() []cel.EnvOption {
292+
// Simulate indexed arguments which would normally have strong types associated
293+
// with the values as part of a static optimization pass
294+
maxIndices := 30
295+
indexOpts := make([]cel.EnvOption, maxIndices)
296+
for i := 0; i < maxIndices; i++ {
297+
indexOpts[i] = cel.Variable(fmt.Sprintf("@index%d", i), cel.DynType)
298+
}
299+
return append([]cel.EnvOption{
300+
cel.Macros(
301+
// cel.block([args], expr)
302+
cel.ReceiverMacro("block", 2, celBlock),
303+
// cel.index(int)
304+
cel.ReceiverMacro("index", 1, celIndex),
305+
// cel.iterVar(int, int)
306+
cel.ReceiverMacro("iterVar", 2, celCompreVar("cel.iterVar", "@it")),
307+
// cel.accuVar(int, int)
308+
cel.ReceiverMacro("accuVar", 2, celCompreVar("cel.accuVar", "@ac")),
309+
),
310+
}, indexOpts...)
311+
}
312+
313+
func (celBlockLib) ProgramOptions() []cel.ProgramOption {
314+
return []cel.ProgramOption{}
315+
}
316+
317+
func celBlock(mef cel.MacroExprFactory, target ast.Expr, args []ast.Expr) (ast.Expr, *cel.Error) {
318+
if !isCELNamespace(target) {
319+
return nil, nil
320+
}
321+
bindings := args[0]
322+
if bindings.Kind() != ast.ListKind {
323+
return bindings, mef.NewError(bindings.ID(), "cel.block requires the first arg to be a list literal")
324+
}
325+
return mef.NewCall("cel.@block", args...), nil
326+
}
327+
328+
func celIndex(mef cel.MacroExprFactory, target ast.Expr, args []ast.Expr) (ast.Expr, *cel.Error) {
329+
if !isCELNamespace(target) {
330+
return nil, nil
331+
}
332+
index := args[0]
333+
if !isNonNegativeInt(index) {
334+
return index, mef.NewError(index.ID(), "cel.index requires a single non-negative int constant arg")
335+
}
336+
indexVal := index.AsLiteral().(types.Int)
337+
return mef.NewIdent(fmt.Sprintf("@index%d", indexVal)), nil
338+
}
339+
340+
func celCompreVar(funcName, varPrefix string) cel.MacroFactory {
341+
return func(mef cel.MacroExprFactory, target ast.Expr, args []ast.Expr) (ast.Expr, *cel.Error) {
342+
if !isCELNamespace(target) {
343+
return nil, nil
344+
}
345+
depth := args[0]
346+
if !isNonNegativeInt(depth) {
347+
return depth, mef.NewError(depth.ID(), fmt.Sprintf("%s requires two non-negative int constant args", funcName))
348+
}
349+
unique := args[1]
350+
if !isNonNegativeInt(unique) {
351+
return unique, mef.NewError(unique.ID(), fmt.Sprintf("%s requires two non-negative int constant args", funcName))
352+
}
353+
depthVal := depth.AsLiteral().(types.Int)
354+
uniqueVal := unique.AsLiteral().(types.Int)
355+
return mef.NewIdent(fmt.Sprintf("%s:%d:%d", varPrefix, depthVal, uniqueVal)), nil
356+
}
357+
}
358+
359+
func isCELNamespace(target ast.Expr) bool {
360+
return target.Kind() == ast.IdentKind && target.AsIdent() == "cel"
361+
}
362+
363+
func isNonNegativeInt(expr ast.Expr) bool {
364+
if expr.Kind() != ast.LiteralKind {
365+
return false
366+
}
367+
val := expr.AsLiteral()
368+
return val.Type() == cel.IntType && val.(types.Int) >= 0
369+
}

0 commit comments

Comments
 (0)