5757import java .util .List ;
5858import java .util .Map ;
5959import 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