diff --git a/policy/src/main/java/dev/cel/policy/CelPolicyCompilerImpl.java b/policy/src/main/java/dev/cel/policy/CelPolicyCompilerImpl.java index d57ad3260..f43ed57c1 100644 --- a/policy/src/main/java/dev/cel/policy/CelPolicyCompilerImpl.java +++ b/policy/src/main/java/dev/cel/policy/CelPolicyCompilerImpl.java @@ -38,6 +38,7 @@ import dev.cel.optimizer.CelOptimizer; import dev.cel.optimizer.CelOptimizerFactory; import dev.cel.optimizer.optimizers.ConstantFoldingOptimizer; +import dev.cel.optimizer.optimizers.ConstantFoldingOptimizer.ConstantFoldingOptions; import dev.cel.optimizer.optimizers.SubexpressionOptimizer; import dev.cel.optimizer.optimizers.SubexpressionOptimizer.SubexpressionOptimizerOptions; import dev.cel.policy.CelCompiledRule.CelCompiledMatch; @@ -419,9 +420,15 @@ static Builder newBuilder(Cel cel) { .setIterationLimit(DEFAULT_ITERATION_LIMIT) .setOptimizers( ImmutableList.of( - ConstantFoldingOptimizer.getInstance(), + ConstantFoldingOptimizer.newInstance( + ConstantFoldingOptions.newBuilder() + .maxIterationLimit(DEFAULT_ITERATION_LIMIT) + .build()), SubexpressionOptimizer.newInstance( - SubexpressionOptimizerOptions.newBuilder().populateMacroCalls(true).build()))); + SubexpressionOptimizerOptions.newBuilder() + .iterationLimit(DEFAULT_ITERATION_LIMIT) + .populateMacroCalls(true) + .build()))); } private CelPolicyCompilerImpl( diff --git a/policy/src/main/java/dev/cel/policy/tools/BUILD.bazel b/policy/src/main/java/dev/cel/policy/tools/BUILD.bazel index 98ecb32a1..140c44b2c 100644 --- a/policy/src/main/java/dev/cel/policy/tools/BUILD.bazel +++ b/policy/src/main/java/dev/cel/policy/tools/BUILD.bazel @@ -25,6 +25,7 @@ java_library( "//common:proto_v1alpha1_ast", "//extensions", "//extensions:optional_library", + "//optimizer:ast_optimizer", "//optimizer/optimizers:common_subexpression_elimination", "//optimizer/optimizers:constant_folding", "//optimizer/optimizers:select_optimizer", diff --git a/policy/src/main/java/dev/cel/policy/tools/CelPolicyCompilerTool.java b/policy/src/main/java/dev/cel/policy/tools/CelPolicyCompilerTool.java index e1e16ba6b..4c8bf8395 100644 --- a/policy/src/main/java/dev/cel/policy/tools/CelPolicyCompilerTool.java +++ b/policy/src/main/java/dev/cel/policy/tools/CelPolicyCompilerTool.java @@ -36,7 +36,9 @@ import dev.cel.common.CelProtoV1Alpha1AbstractSyntaxTree; import dev.cel.extensions.CelExtensions; import dev.cel.extensions.CelOptionalLibrary; +import dev.cel.optimizer.CelAstOptimizer; import dev.cel.optimizer.optimizers.ConstantFoldingOptimizer; +import dev.cel.optimizer.optimizers.ConstantFoldingOptimizer.ConstantFoldingOptions; import dev.cel.optimizer.optimizers.SelectOptimizer; import dev.cel.optimizer.optimizers.SelectOptimizer.SelectOptimizerOptions; import dev.cel.optimizer.optimizers.SubexpressionOptimizer; @@ -125,6 +127,12 @@ public final class CelPolicyCompilerTool implements Callable { description = "Enable inline variable definitions (e.g., '- var_name: expr') in the policy") private boolean simpleVariables = false; + @Option( + names = {"--iteration_limit"}, + defaultValue = "1000", + description = "Maximum iteration limit for composing and optimizing the policy") + private int iterationLimit = 1000; + private static final CelOptions CEL_OPTIONS = CelOptions.current() .populateMacroCalls(true) @@ -214,19 +222,29 @@ public Integer call() { } try { - CelPolicyCompilerBuilder policyCompilerBuilder = - CelPolicyCompilerFactory.newPolicyCompiler(cel); - + ImmutableList.Builder optimizersBuilder = + ImmutableList.builder() + .add( + ConstantFoldingOptimizer.newInstance( + ConstantFoldingOptions.newBuilder() + .maxIterationLimit(iterationLimit) + .build()), + SubexpressionOptimizer.newInstance( + SubexpressionOptimizerOptions.newBuilder() + .iterationLimit(iterationLimit) + .populateMacroCalls(true) + .build())); if (optimizeFieldSelection) { - policyCompilerBuilder.setOptimizers( - ImmutableList.of( - ConstantFoldingOptimizer.getInstance(), - SubexpressionOptimizer.newInstance( - SubexpressionOptimizerOptions.newBuilder().populateMacroCalls(true).build()), - SelectOptimizer.newInstance( - SelectOptimizerOptions.newBuilder().build(), transitiveFileDescriptors))); + optimizersBuilder.add( + SelectOptimizer.newInstance( + SelectOptimizerOptions.newBuilder().build(), transitiveFileDescriptors)); } + CelPolicyCompilerBuilder policyCompilerBuilder = + CelPolicyCompilerFactory.newPolicyCompiler(cel) + .setIterationLimit(iterationLimit) + .setOptimizers(optimizersBuilder.build()); + CelPolicyCompiler policyCompiler = policyCompilerBuilder.build(); CelAbstractSyntaxTree ast = policyCompiler.compile(policy); diff --git a/policy/src/test/java/dev/cel/policy/tools/CelPolicyCompilerToolTest.java b/policy/src/test/java/dev/cel/policy/tools/CelPolicyCompilerToolTest.java index a14e72402..274dad626 100644 --- a/policy/src/test/java/dev/cel/policy/tools/CelPolicyCompilerToolTest.java +++ b/policy/src/test/java/dev/cel/policy/tools/CelPolicyCompilerToolTest.java @@ -299,6 +299,34 @@ public void compile_withSimpleVariables_success() throws Exception { assertThat(celRuntime.createProgram(ast).eval()).isEqualTo(Optional.of(true)); } + @Test + public void compile_withIterationLimitReached_returnsError() throws Exception { + String configPath = createFile("config.yaml", "name: test-env\n"); + String policyPath = + createFile( + "policy.yaml", + "name: p\n" + + "rule:\n" + + " variables:\n" + + " - a: 1 + 2\n" + + " - b: variables.a + 3\n" + + " match:\n" + + " - condition: variables.b == 6\n" + + " output: 'true'\n"); + + String stdErr = + executeExpectingError( + "--policy", + policyPath, + "--config", + configPath, + "--simple_variables", + "--iteration_limit", + "1"); + + assertThat(stdErr).contains("Reason: Unexpected error while composing rules."); + } + @Test public void compile_withOptimizeFieldSelection_rewritesSelectAndEvaluates() throws Exception { String configRlocation = diff --git a/policy/tools/compile_cel_policy.bzl b/policy/tools/compile_cel_policy.bzl index 97214059f..7574c251f 100644 --- a/policy/tools/compile_cel_policy.bzl +++ b/policy/tools/compile_cel_policy.bzl @@ -27,6 +27,7 @@ def compile_cel_policy( output_version = "canonical", optimize_field_selection = False, simple_variables = False, + iteration_limit = None, visibility = None): """Compiles a CEL policy into a CheckedExpr binarypb or textpb with optional select optimization. @@ -45,12 +46,14 @@ def compile_cel_policy( output_format: (optional) str either "binarypb", "textpb", or "textproto" (default "binarypb") output_version: (optional) str either "canonical" or "v1alpha1" (default "canonical") optimize_field_selection: (optional) bool whether to enable AST select optimization (default False). - When True, embeds field numbers, wire types, and default values directly into the compiled + When True, embeds field numbers, field types, and default values directly into the compiled `CheckedExpr` AST. This enables version-skew and field-rename resilience (evaluating unknown fields from raw wire bytes on older clients) and faster field selections, at the cost of a larger serialized AST payload. simple_variables: (optional) bool whether to enable inline variable definitions (e.g., `- var_name: expr` instead of `- name: var_name` / `expression: expr`) in the policy (default False) + iteration_limit: (optional) int maximum iteration limit for composing and optimizing the policy + (default None, which uses the compiler default of 1000) visibility: (optional) visibility to use on the genrule macro (default None) """ if output_format not in ("binarypb", "textpb", "textproto"): @@ -97,6 +100,9 @@ def compile_cel_policy( if simple_variables: args.append("--simple_variables") + if iteration_limit != None: + args.append("--iteration_limit=%d" % iteration_limit) + cmd = ( "$(location //policy/tools:cel_policy_compiler_tool) " + " ".join(args)