Skip to content

Commit 8e7b4b7

Browse files
l46kokcopybara-github
authored andcommitted
[Draft] Accumulated unknowns (#749)
PiperOrigin-RevId: 783626490
1 parent a98f8ad commit 8e7b4b7

11 files changed

+205
-80
lines changed
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
// Copyright 2025 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License aj
6+
//
7+
// https://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package dev.cel.runtime;
16+
17+
import com.google.errorprone.annotations.CanIgnoreReturnValue;
18+
import java.util.ArrayList;
19+
import java.util.Arrays;
20+
import java.util.Collection;
21+
import java.util.List;
22+
23+
/**
24+
* An internal representation used for fast accumulation of unknown expr IDs and attributes. For
25+
* safety, this object should never be returned as an evaluated result and instead be adapted into
26+
* an immutable CelUnknownSet.
27+
*/
28+
final class AccumulatedUnknowns {
29+
30+
private final List<Long> exprIds;
31+
private final List<CelAttribute> attributes;
32+
33+
List<Long> exprIds() {
34+
return exprIds;
35+
}
36+
37+
List<CelAttribute> attributes() {
38+
return attributes;
39+
}
40+
41+
@CanIgnoreReturnValue
42+
AccumulatedUnknowns merge(AccumulatedUnknowns arg) {
43+
this.exprIds.addAll(arg.exprIds);
44+
this.attributes.addAll(arg.attributes);
45+
return this;
46+
}
47+
48+
static AccumulatedUnknowns create(Long... ids) {
49+
return create(Arrays.asList(ids));
50+
}
51+
52+
static AccumulatedUnknowns create(Collection<Long> ids) {
53+
return create(ids, new ArrayList<>());
54+
}
55+
56+
static AccumulatedUnknowns create(Collection<Long> exprIds, Collection<CelAttribute> attributes) {
57+
return new AccumulatedUnknowns(new ArrayList<>(exprIds), new ArrayList<>(attributes));
58+
}
59+
60+
private AccumulatedUnknowns(List<Long> exprIds, List<CelAttribute> attributes) {
61+
this.exprIds = exprIds;
62+
this.attributes = attributes;
63+
}
64+
}

runtime/src/main/java/dev/cel/runtime/BUILD.bazel

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,7 @@ java_library(
271271
],
272272
exports = [":base"],
273273
deps = [
274+
":accumulated_unknowns",
274275
":base",
275276
":concatenated_list_view",
276277
":dispatcher",
@@ -306,6 +307,7 @@ cel_android_library(
306307
srcs = INTERPRETER_SOURCES,
307308
visibility = ["//visibility:private"],
308309
deps = [
310+
":accumulated_unknowns_android",
309311
":base_android",
310312
":concatenated_list_view",
311313
":dispatcher_android",
@@ -849,15 +851,13 @@ java_library(
849851
":cel_value_runtime_type_provider",
850852
":dispatcher",
851853
":evaluation_exception",
852-
":evaluation_listener",
853854
":function_binding",
854855
":function_resolver",
855856
":interpretable",
856857
":interpreter",
857858
":lite_runtime",
858859
":runtime_equality",
859860
":runtime_helpers",
860-
":standard_functions",
861861
":type_resolver",
862862
"//:auto_value",
863863
"//common:cel_ast",
@@ -880,15 +880,13 @@ cel_android_library(
880880
":cel_value_runtime_type_provider_android",
881881
":dispatcher_android",
882882
":evaluation_exception",
883-
":evaluation_listener_android",
884883
":function_binding_android",
885884
":function_resolver",
886885
":interpretable_android",
887886
":interpreter_android",
888887
":lite_runtime_android",
889888
":runtime_equality_android",
890889
":runtime_helpers_android",
891-
":standard_functions_android",
892890
":type_resolver_android",
893891
"//:auto_value",
894892
"//common:cel_ast_android",
@@ -1034,6 +1032,7 @@ java_library(
10341032
tags = [
10351033
],
10361034
deps = [
1035+
":accumulated_unknowns",
10371036
":evaluation_exception",
10381037
":unknown_attributes",
10391038
"//common/annotations",
@@ -1047,6 +1046,7 @@ cel_android_library(
10471046
srcs = ["InterpreterUtil.java"],
10481047
visibility = ["//visibility:private"],
10491048
deps = [
1049+
":accumulated_unknowns_android",
10501050
":evaluation_exception",
10511051
":unknown_attributes_android",
10521052
"//common/annotations",
@@ -1105,3 +1105,23 @@ java_library(
11051105
# used_by_android
11061106
visibility = ["//visibility:private"],
11071107
)
1108+
1109+
java_library(
1110+
name = "accumulated_unknowns",
1111+
srcs = ["AccumulatedUnknowns.java"],
1112+
visibility = ["//visibility:private"],
1113+
deps = [
1114+
":unknown_attributes",
1115+
"@maven//:com_google_errorprone_error_prone_annotations",
1116+
],
1117+
)
1118+
1119+
cel_android_library(
1120+
name = "accumulated_unknowns_android",
1121+
srcs = ["AccumulatedUnknowns.java"],
1122+
visibility = ["//visibility:private"],
1123+
deps = [
1124+
":unknown_attributes_android",
1125+
"@maven//:com_google_errorprone_error_prone_annotations",
1126+
],
1127+
)

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

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ class CallArgumentChecker {
3232
private final ArrayList<Long> exprIds;
3333
private final RuntimeUnknownResolver resolver;
3434
private final boolean acceptPartial;
35-
private Optional<CelUnknownSet> unknowns;
35+
private Optional<AccumulatedUnknowns> unknowns;
3636

3737
private CallArgumentChecker(RuntimeUnknownResolver resolver, boolean acceptPartial) {
3838
this.exprIds = new ArrayList<>();
@@ -61,29 +61,30 @@ static CallArgumentChecker createAcceptingPartial(RuntimeUnknownResolver resolve
6161
return new CallArgumentChecker(resolver, true);
6262
}
6363

64-
private static Optional<CelUnknownSet> mergeOptionalUnknowns(
65-
Optional<CelUnknownSet> lhs, Optional<CelUnknownSet> rhs) {
64+
private static Optional<AccumulatedUnknowns> mergeOptionalUnknowns(
65+
Optional<AccumulatedUnknowns> lhs, Optional<AccumulatedUnknowns> rhs) {
6666
return lhs.isPresent() ? rhs.isPresent() ? Optional.of(lhs.get().merge(rhs.get())) : lhs : rhs;
6767
}
6868

6969
/** Determine if the call argument is unknown and accumulate if so. */
7070
void checkArg(DefaultInterpreter.IntermediateResult arg) {
7171
// Handle attribute tracked unknowns.
72-
Optional<CelUnknownSet> argUnknowns = maybeUnknownFromArg(arg);
72+
Optional<AccumulatedUnknowns> argUnknowns = maybeUnknownFromArg(arg);
7373
unknowns = mergeOptionalUnknowns(unknowns, argUnknowns);
7474

7575
// support for ExprValue unknowns.
76-
if (InterpreterUtil.isUnknown(arg.value())) {
77-
CelUnknownSet unknownSet = (CelUnknownSet) arg.value();
78-
exprIds.addAll(unknownSet.unknownExprIds());
76+
if (InterpreterUtil.isAccumulatedUnknowns(arg.value())) {
77+
AccumulatedUnknowns unknownSet = (AccumulatedUnknowns) arg.value();
78+
exprIds.addAll(unknownSet.exprIds());
7979
}
8080
}
8181

82-
private Optional<CelUnknownSet> maybeUnknownFromArg(DefaultInterpreter.IntermediateResult arg) {
83-
if (arg.value() instanceof CelUnknownSet) {
84-
CelUnknownSet celUnknownSet = (CelUnknownSet) arg.value();
82+
private Optional<AccumulatedUnknowns> maybeUnknownFromArg(
83+
DefaultInterpreter.IntermediateResult arg) {
84+
if (arg.value() instanceof AccumulatedUnknowns) {
85+
AccumulatedUnknowns celUnknownSet = (AccumulatedUnknowns) arg.value();
8586
if (!celUnknownSet.attributes().isEmpty()) {
86-
return Optional.of((CelUnknownSet) arg.value());
87+
return Optional.of((AccumulatedUnknowns) arg.value());
8788
}
8889
}
8990
if (!acceptPartial) {
@@ -99,7 +100,7 @@ Optional<Object> maybeUnknowns() {
99100
}
100101

101102
if (!exprIds.isEmpty()) {
102-
return Optional.of(CelUnknownSet.create(exprIds));
103+
return Optional.of(AccumulatedUnknowns.create(exprIds));
103104
}
104105

105106
return Optional.empty();

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

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,4 @@ public interface CelEvaluationListener {
3333
* @param evaluatedResult Evaluated result.
3434
*/
3535
void callback(CelExpr expr, Object evaluatedResult);
36-
37-
/** Construct a listener that does nothing. */
38-
static CelEvaluationListener noOpListener() {
39-
return (arg1, arg2) -> {};
40-
}
4136
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ static CelUnknownSet create(Iterable<Long> unknownExprIds) {
5959
return create(ImmutableSet.of(), ImmutableSet.copyOf(unknownExprIds));
6060
}
6161

62-
private static CelUnknownSet create(
62+
static CelUnknownSet create(
6363
ImmutableSet<CelAttribute> attributes, ImmutableSet<Long> unknownExprIds) {
6464
return new AutoValue_CelUnknownSet(attributes, unknownExprIds);
6565
}

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

Lines changed: 42 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import com.google.common.base.Joiner;
2222
import com.google.common.base.Preconditions;
2323
import com.google.common.collect.ImmutableList;
24+
import com.google.common.collect.ImmutableSet;
2425
import com.google.errorprone.annotations.CanIgnoreReturnValue;
2526
import com.google.errorprone.annotations.Immutable;
2627
import javax.annotation.concurrent.ThreadSafe;
@@ -137,20 +138,24 @@ static final class DefaultInterpretable implements Interpretable, UnknownTrackin
137138
@Override
138139
public Object eval(GlobalResolver resolver) throws CelEvaluationException {
139140
// Result is already unwrapped from IntermediateResult.
140-
return eval(resolver, CelEvaluationListener.noOpListener());
141+
return evalTrackingUnknowns(
142+
RuntimeUnknownResolver.fromResolver(resolver), Optional.empty(), Optional.empty());
141143
}
142144

143145
@Override
144146
public Object eval(GlobalResolver resolver, CelEvaluationListener listener)
145147
throws CelEvaluationException {
146148
return evalTrackingUnknowns(
147-
RuntimeUnknownResolver.fromResolver(resolver), Optional.empty(), listener);
149+
RuntimeUnknownResolver.fromResolver(resolver), Optional.empty(), Optional.of(listener));
148150
}
149151

150152
@Override
151153
public Object eval(GlobalResolver resolver, FunctionResolver lateBoundFunctionResolver)
152154
throws CelEvaluationException {
153-
return eval(resolver, lateBoundFunctionResolver, CelEvaluationListener.noOpListener());
155+
return evalTrackingUnknowns(
156+
RuntimeUnknownResolver.fromResolver(resolver),
157+
Optional.of(lateBoundFunctionResolver),
158+
Optional.empty());
154159
}
155160

156161
@Override
@@ -162,19 +167,32 @@ public Object eval(
162167
return evalTrackingUnknowns(
163168
RuntimeUnknownResolver.fromResolver(resolver),
164169
Optional.of(lateBoundFunctionResolver),
165-
listener);
170+
Optional.of(listener));
166171
}
167172

168173
@Override
169174
public Object evalTrackingUnknowns(
170175
RuntimeUnknownResolver resolver,
171176
Optional<? extends FunctionResolver> functionResolver,
172-
CelEvaluationListener listener)
177+
Optional<CelEvaluationListener> listener)
173178
throws CelEvaluationException {
174179
ExecutionFrame frame = newExecutionFrame(resolver, functionResolver, listener);
175180
IntermediateResult internalResult = evalInternal(frame, ast.getExpr());
176181

177-
return internalResult.value();
182+
Object underlyingValue = internalResult.value();
183+
184+
return maybeAdaptToCelUnknownSet(underlyingValue);
185+
}
186+
187+
private static Object maybeAdaptToCelUnknownSet(Object val) {
188+
if (!(val instanceof AccumulatedUnknowns)) {
189+
return val;
190+
}
191+
192+
AccumulatedUnknowns unknowns = (AccumulatedUnknowns) val;
193+
194+
return CelUnknownSet.create(
195+
ImmutableSet.copyOf(unknowns.attributes()), ImmutableSet.copyOf(unknowns.exprIds()));
178196
}
179197

180198
/**
@@ -196,15 +214,13 @@ ExecutionFrame populateExecutionFrame(ExecutionFrame frame) throws CelEvaluation
196214
@VisibleForTesting
197215
ExecutionFrame newTestExecutionFrame(GlobalResolver resolver) {
198216
return newExecutionFrame(
199-
RuntimeUnknownResolver.fromResolver(resolver),
200-
Optional.empty(),
201-
CelEvaluationListener.noOpListener());
217+
RuntimeUnknownResolver.fromResolver(resolver), Optional.empty(), Optional.empty());
202218
}
203219

204220
private ExecutionFrame newExecutionFrame(
205221
RuntimeUnknownResolver resolver,
206222
Optional<? extends FunctionResolver> functionResolver,
207-
CelEvaluationListener listener) {
223+
Optional<CelEvaluationListener> listener) {
208224
int comprehensionMaxIterations =
209225
celOptions.enableComprehension() ? celOptions.comprehensionMaxIterations() : 0;
210226
return new ExecutionFrame(listener, resolver, functionResolver, comprehensionMaxIterations);
@@ -244,7 +260,11 @@ private IntermediateResult evalInternal(ExecutionFrame frame, CelExpr expr)
244260
throw new IllegalStateException(
245261
"unexpected expression kind: " + expr.exprKind().getKind());
246262
}
247-
frame.getEvaluationListener().callback(expr, result.value());
263+
264+
frame
265+
.getEvaluationListener()
266+
.ifPresent(
267+
listener -> listener.callback(expr, maybeAdaptToCelUnknownSet(result.value())));
248268
return result;
249269
} catch (CelRuntimeException e) {
250270
throw CelEvaluationExceptionBuilder.newBuilder(e).setMetadata(metadata, expr.id()).build();
@@ -257,7 +277,7 @@ private IntermediateResult evalInternal(ExecutionFrame frame, CelExpr expr)
257277
}
258278

259279
private static boolean isUnknownValue(Object value) {
260-
return value instanceof CelUnknownSet || InterpreterUtil.isUnknown(value);
280+
return InterpreterUtil.isAccumulatedUnknowns(value);
261281
}
262282

263283
private static boolean isUnknownOrError(Object value) {
@@ -552,18 +572,20 @@ private IntermediateResult mergeBooleanUnknowns(IntermediateResult lhs, Intermed
552572
throws CelEvaluationException {
553573
// TODO: migrate clients to a common type that reports both expr-id unknowns
554574
// and attribute sets.
555-
if (lhs.value() instanceof CelUnknownSet && rhs.value() instanceof CelUnknownSet) {
575+
Object lhsVal = lhs.value();
576+
Object rhsVal = rhs.value();
577+
if (lhsVal instanceof AccumulatedUnknowns && rhsVal instanceof AccumulatedUnknowns) {
556578
return IntermediateResult.create(
557-
((CelUnknownSet) lhs.value()).merge((CelUnknownSet) rhs.value()));
558-
} else if (lhs.value() instanceof CelUnknownSet) {
579+
((AccumulatedUnknowns) lhsVal).merge((AccumulatedUnknowns) rhsVal));
580+
} else if (lhsVal instanceof AccumulatedUnknowns) {
559581
return lhs;
560-
} else if (rhs.value() instanceof CelUnknownSet) {
582+
} else if (rhsVal instanceof AccumulatedUnknowns) {
561583
return rhs;
562584
}
563585

564586
// Otherwise fallback to normal impl
565587
return IntermediateResult.create(
566-
InterpreterUtil.shortcircuitUnknownOrThrowable(lhs.value(), rhs.value()));
588+
InterpreterUtil.shortcircuitUnknownOrThrowable(lhsVal, rhsVal));
567589
}
568590

569591
private enum ShortCircuitableOperators {
@@ -1050,7 +1072,7 @@ private LazyExpression(CelExpr celExpr) {
10501072

10511073
/** This class tracks the state meaningful to a single evaluation pass. */
10521074
static class ExecutionFrame {
1053-
private final CelEvaluationListener evaluationListener;
1075+
private final Optional<CelEvaluationListener> evaluationListener;
10541076
private final int maxIterations;
10551077
private final ArrayDeque<RuntimeUnknownResolver> resolvers;
10561078
private final Optional<? extends FunctionResolver> lateBoundFunctionResolver;
@@ -1059,7 +1081,7 @@ static class ExecutionFrame {
10591081
@VisibleForTesting int scopeLevel;
10601082

10611083
private ExecutionFrame(
1062-
CelEvaluationListener evaluationListener,
1084+
Optional<CelEvaluationListener> evaluationListener,
10631085
RuntimeUnknownResolver resolver,
10641086
Optional<? extends FunctionResolver> lateBoundFunctionResolver,
10651087
int maxIterations) {
@@ -1071,7 +1093,7 @@ private ExecutionFrame(
10711093
this.maxIterations = maxIterations;
10721094
}
10731095

1074-
private CelEvaluationListener getEvaluationListener() {
1096+
private Optional<CelEvaluationListener> getEvaluationListener() {
10751097
return evaluationListener;
10761098
}
10771099

0 commit comments

Comments
 (0)