|
17 | 17 | import static com.google.common.base.Preconditions.checkNotNull; |
18 | 18 |
|
19 | 19 | import com.google.auto.value.AutoValue; |
| 20 | +import com.google.common.annotations.VisibleForTesting; |
20 | 21 | import com.google.common.base.Joiner; |
21 | 22 | import com.google.common.base.Preconditions; |
22 | 23 | import com.google.common.collect.ImmutableList; |
| 24 | +import com.google.errorprone.annotations.CanIgnoreReturnValue; |
23 | 25 | import com.google.errorprone.annotations.Immutable; |
24 | 26 | import javax.annotation.concurrent.ThreadSafe; |
25 | 27 | import dev.cel.common.CelAbstractSyntaxTree; |
|
51 | 53 | import java.util.Map; |
52 | 54 | import java.util.Optional; |
53 | 55 |
|
54 | | -/** |
55 | | - * Default implementation of the CEL interpreter. |
56 | | - */ |
| 56 | +/** Default implementation of the CEL interpreter. */ |
57 | 57 | @ThreadSafe |
58 | 58 | final class DefaultInterpreter implements Interpreter { |
59 | 59 |
|
@@ -111,8 +111,8 @@ public Interpretable createInterpretable(CelAbstractSyntaxTree ast) { |
111 | 111 | } |
112 | 112 |
|
113 | 113 | @Immutable |
114 | | - private static final class DefaultInterpretable |
115 | | - implements Interpretable, UnknownTrackingInterpretable { |
| 114 | + @VisibleForTesting |
| 115 | + static final class DefaultInterpretable implements Interpretable, UnknownTrackingInterpretable { |
116 | 116 | private final TypeResolver typeResolver; |
117 | 117 | private final RuntimeTypeProvider typeProvider; |
118 | 118 | private final Dispatcher.ImmutableCopy dispatcher; |
@@ -165,14 +165,44 @@ public Object evalTrackingUnknowns( |
165 | 165 | Optional<? extends FunctionResolver> functionResolver, |
166 | 166 | CelEvaluationListener listener) |
167 | 167 | throws CelEvaluationException { |
168 | | - int comprehensionMaxIterations = |
169 | | - celOptions.enableComprehension() ? celOptions.comprehensionMaxIterations() : 0; |
170 | | - ExecutionFrame frame = |
171 | | - new ExecutionFrame(listener, resolver, functionResolver, comprehensionMaxIterations); |
| 168 | + ExecutionFrame frame = newExecutionFrame(resolver, functionResolver, listener); |
172 | 169 | IntermediateResult internalResult = evalInternal(frame, ast.getExpr()); |
173 | 170 | return internalResult.value(); |
174 | 171 | } |
175 | 172 |
|
| 173 | + /** |
| 174 | + * Evaluates this interpretable and returns the resulting execution frame populated with |
| 175 | + * evaluation state. This method is specifically designed for testing the interpreter's internal |
| 176 | + * invariants. |
| 177 | + * |
| 178 | + * <p><b>Do not expose to public. This method is strictly for internal testing purposes |
| 179 | + * only.</b> |
| 180 | + */ |
| 181 | + @VisibleForTesting |
| 182 | + @CanIgnoreReturnValue |
| 183 | + ExecutionFrame populateExecutionFrame(ExecutionFrame frame) throws CelEvaluationException { |
| 184 | + evalInternal(frame, ast.getExpr()); |
| 185 | + |
| 186 | + return frame; |
| 187 | + } |
| 188 | + |
| 189 | + @VisibleForTesting |
| 190 | + ExecutionFrame newTestExecutionFrame(GlobalResolver resolver) { |
| 191 | + return newExecutionFrame( |
| 192 | + RuntimeUnknownResolver.fromResolver(resolver), |
| 193 | + Optional.empty(), |
| 194 | + CelEvaluationListener.noOpListener()); |
| 195 | + } |
| 196 | + |
| 197 | + private ExecutionFrame newExecutionFrame( |
| 198 | + RuntimeUnknownResolver resolver, |
| 199 | + Optional<? extends FunctionResolver> functionResolver, |
| 200 | + CelEvaluationListener listener) { |
| 201 | + int comprehensionMaxIterations = |
| 202 | + celOptions.enableComprehension() ? celOptions.comprehensionMaxIterations() : 0; |
| 203 | + return new ExecutionFrame(listener, resolver, functionResolver, comprehensionMaxIterations); |
| 204 | + } |
| 205 | + |
176 | 206 | private IntermediateResult evalInternal(ExecutionFrame frame, CelExpr expr) |
177 | 207 | throws CelEvaluationException { |
178 | 208 | try { |
@@ -927,8 +957,12 @@ private IntermediateResult evalComprehension( |
927 | 957 | } |
928 | 958 |
|
929 | 959 | frame.pushScope(Collections.singletonMap(accuVar, accuValue)); |
930 | | - IntermediateResult result = evalInternal(frame, compre.result()); |
931 | | - frame.popScope(); |
| 960 | + IntermediateResult result; |
| 961 | + try { |
| 962 | + result = evalInternal(frame, compre.result()); |
| 963 | + } finally { |
| 964 | + frame.popScope(); |
| 965 | + } |
932 | 966 | return result; |
933 | 967 | } |
934 | 968 |
|
@@ -976,13 +1010,14 @@ private LazyExpression(CelExpr celExpr) { |
976 | 1010 | } |
977 | 1011 |
|
978 | 1012 | /** This class tracks the state meaningful to a single evaluation pass. */ |
979 | | - private static class ExecutionFrame { |
| 1013 | + static class ExecutionFrame { |
980 | 1014 | private final CelEvaluationListener evaluationListener; |
981 | 1015 | private final int maxIterations; |
982 | 1016 | private final ArrayDeque<RuntimeUnknownResolver> resolvers; |
983 | 1017 | private final Optional<? extends FunctionResolver> lateBoundFunctionResolver; |
984 | 1018 | private RuntimeUnknownResolver currentResolver; |
985 | 1019 | private int iterations; |
| 1020 | + @VisibleForTesting int scopeLevel; |
986 | 1021 |
|
987 | 1022 | private ExecutionFrame( |
988 | 1023 | CelEvaluationListener evaluationListener, |
@@ -1040,12 +1075,14 @@ private void cacheLazilyEvaluatedResult( |
1040 | 1075 |
|
1041 | 1076 | /** Note: we utilize a HashMap instead of ImmutableMap to make lookups faster on string keys. */ |
1042 | 1077 | private void pushScope(Map<String, IntermediateResult> scope) { |
| 1078 | + scopeLevel++; |
1043 | 1079 | RuntimeUnknownResolver scopedResolver = currentResolver.withScope(scope); |
1044 | 1080 | currentResolver = scopedResolver; |
1045 | 1081 | resolvers.addLast(scopedResolver); |
1046 | 1082 | } |
1047 | 1083 |
|
1048 | 1084 | private void popScope() { |
| 1085 | + scopeLevel--; |
1049 | 1086 | if (resolvers.isEmpty()) { |
1050 | 1087 | throw new IllegalStateException("Execution frame error: more scopes popped than pushed"); |
1051 | 1088 | } |
|
0 commit comments