Skip to content

Commit d240eb9

Browse files
l46kokcopybara-github
authored andcommitted
Reject block indices that leads to cycles
PiperOrigin-RevId: 845021315
1 parent c26e26d commit d240eb9

File tree

2 files changed

+66
-2
lines changed

2 files changed

+66
-2
lines changed

optimizer/src/test/java/dev/cel/optimizer/optimizers/SubexpressionOptimizerTest.java

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
import dev.cel.parser.CelStandardMacro;
5353
import dev.cel.parser.CelUnparser;
5454
import dev.cel.parser.CelUnparserFactory;
55+
import dev.cel.runtime.CelEvaluationException;
5556
import dev.cel.runtime.CelFunctionBinding;
5657
import dev.cel.runtime.CelRuntime;
5758
import dev.cel.runtime.CelRuntimeFactory;
@@ -579,6 +580,28 @@ public void verifyOptimizedAstCorrectness_indexIsNotForwardReferencing_throws(St
579580
.contains("Illegal block index found. The index value must be less than");
580581
}
581582

583+
@Test
584+
public void block_containsCycle_throws() throws Exception {
585+
CelAbstractSyntaxTree ast = compileUsingInternalFunctions("cel.block([index1,index0],index0)");
586+
587+
CelEvaluationException e =
588+
assertThrows(CelEvaluationException.class, () -> CEL.createProgram(ast).eval());
589+
assertThat(e).hasMessageThat().contains("Cycle detected: @index0");
590+
}
591+
592+
@Test
593+
public void block_lazyEvaluationContainsError_cleansUpCycleState() throws Exception {
594+
CelAbstractSyntaxTree ast =
595+
compileUsingInternalFunctions(
596+
"cel.block([1/0 > 0], (index0 && false) || (index0 && true))");
597+
598+
CelEvaluationException e =
599+
assertThrows(CelEvaluationException.class, () -> CEL.createProgram(ast).eval());
600+
601+
assertThat(e).hasMessageThat().contains("/ by zero");
602+
assertThat(e).hasMessageThat().doesNotContain("Cycle detected");
603+
}
604+
582605
/**
583606
* Converts AST containing cel.block related test functions to internal functions (e.g: cel.block
584607
* -> cel.@block)

runtime/src/main/java/dev/cel/runtime/DefaultInterpreter.java

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@
5757
import java.util.List;
5858
import java.util.Map;
5959
import java.util.Optional;
60+
import java.util.Set;
6061

6162
/** Default implementation of the CEL interpreter. */
6263
@ThreadSafe
@@ -343,7 +344,13 @@ private IntermediateResult resolveIdent(ExecutionFrame frame, CelExpr expr, Stri
343344
Object value = rawResult.value();
344345
boolean isLazyExpression = value instanceof LazyExpression;
345346
if (isLazyExpression) {
346-
value = evalInternal(frame, ((LazyExpression) value).celExpr).value();
347+
frame.markLazyEvaluationOrThrow(name);
348+
349+
try {
350+
value = evalInternal(frame, ((LazyExpression) value).celExpr).value();
351+
} finally {
352+
frame.endLazyEvaluation(name);
353+
}
347354
}
348355

349356
// Value resolved from Binding, it could be Message, PartialMessage or unbound(null)
@@ -1063,9 +1070,14 @@ private IntermediateResult evalCelBlock(
10631070
indexKey,
10641071
IntermediateResult.create(new LazyExpression(exprList.elements().get(index))));
10651072
}
1073+
frame.setRequireCycleCheck(true);
10661074
frame.pushLazyScope(Collections.unmodifiableMap(blockList));
10671075

1068-
return evalInternal(frame, blockCall.args().get(1));
1076+
try {
1077+
return evalInternal(frame, blockCall.args().get(1));
1078+
} finally {
1079+
frame.popScope();
1080+
}
10691081
}
10701082

10711083
private CelType getCheckedTypeOrThrow(CelExpr expr) throws CelEvaluationException {
@@ -1115,8 +1127,10 @@ static class ExecutionFrame {
11151127
private final int maxIterations;
11161128
private final ArrayDeque<RuntimeUnknownResolver> resolvers;
11171129
private final Optional<? extends CelFunctionResolver> lateBoundFunctionResolver;
1130+
private final Set<String> activeLazyAttributes = new HashSet<>();
11181131
private RuntimeUnknownResolver currentResolver;
11191132
private int iterations;
1133+
private boolean requireCycleCheck;
11201134
@VisibleForTesting int scopeLevel;
11211135

11221136
private ExecutionFrame(
@@ -1132,6 +1146,25 @@ private ExecutionFrame(
11321146
this.maxIterations = maxIterations;
11331147
}
11341148

1149+
private void markLazyEvaluationOrThrow(String name) {
1150+
if (!requireCycleCheck) {
1151+
return;
1152+
}
1153+
1154+
boolean added = activeLazyAttributes.add(name);
1155+
if (!added) {
1156+
throw new IllegalStateException(String.format("Cycle detected: %s", name));
1157+
}
1158+
}
1159+
1160+
private void endLazyEvaluation(String name) {
1161+
if (!requireCycleCheck) {
1162+
return;
1163+
}
1164+
1165+
activeLazyAttributes.remove(name);
1166+
}
1167+
11351168
private Optional<CelEvaluationListener> getEvaluationListener() {
11361169
return evaluationListener;
11371170
}
@@ -1175,6 +1208,14 @@ private void cacheLazilyEvaluatedResult(
11751208
currentResolver.cacheLazilyEvaluatedResult(name, result);
11761209
}
11771210

1211+
/**
1212+
* If set, interpreter will check for potential cycles for lazily evaluable attributes. This
1213+
* only applies for cel.@block indices.
1214+
*/
1215+
private void setRequireCycleCheck(boolean requireCycleCheck) {
1216+
this.requireCycleCheck = requireCycleCheck;
1217+
}
1218+
11781219
private void pushLazyScope(Map<String, IntermediateResult> scope) {
11791220
pushScope(scope);
11801221
for (String lazyAttribute : scope.keySet()) {

0 commit comments

Comments
 (0)