Skip to content

Commit c0dbe7e

Browse files
l46kokcopybara-github
authored andcommitted
Generally handle function strictness in the runtime, Add NotStrictlyFalse function to standard lib
PiperOrigin-RevId: 827685375
1 parent 93c9c1e commit c0dbe7e

18 files changed

+311
-40
lines changed

runtime/BUILD.bazel

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,3 +232,15 @@ java_library(
232232
visibility = ["//:internal"],
233233
exports = ["//runtime/src/main/java/dev/cel/runtime:resolved_overload_internal"],
234234
)
235+
236+
java_library(
237+
name = "internal_function_binder",
238+
visibility = ["//:internal"],
239+
exports = ["//runtime/src/main/java/dev/cel/runtime:internal_function_binder"],
240+
)
241+
242+
cel_android_library(
243+
name = "internal_function_binder_android",
244+
visibility = ["//:internal"],
245+
exports = ["//runtime/src/main/java/dev/cel/runtime:internal_function_binder_andriod"],
246+
)

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

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -644,6 +644,7 @@ java_library(
644644
"//runtime/standard:multiply",
645645
"//runtime/standard:negate",
646646
"//runtime/standard:not_equals",
647+
"//runtime/standard:not_strictly_false",
647648
"//runtime/standard:size",
648649
"//runtime/standard:standard_function",
649650
"//runtime/standard:standard_overload",
@@ -700,6 +701,7 @@ cel_android_library(
700701
"//runtime/standard:multiply_android",
701702
"//runtime/standard:negate_android",
702703
"//runtime/standard:not_equals_android",
704+
"//runtime/standard:not_strictly_false_android",
703705
"//runtime/standard:size_android",
704706
"//runtime/standard:standard_function_android",
705707
"//runtime/standard:standard_overload_android",
@@ -794,6 +796,7 @@ java_library(
794796
],
795797
deps = [
796798
":function_overload",
799+
":unknown_attributes",
797800
"@maven//:com_google_code_findbugs_annotations",
798801
"@maven//:com_google_errorprone_error_prone_annotations",
799802
"@maven//:com_google_protobuf_protobuf_java",
@@ -806,6 +809,7 @@ cel_android_library(
806809
visibility = ["//visibility:private"],
807810
deps = [
808811
":function_overload_android",
812+
":unknown_attributes_android",
809813
"@maven//:com_google_code_findbugs_annotations",
810814
"@maven//:com_google_errorprone_error_prone_annotations",
811815
"@maven_android//:com_google_protobuf_protobuf_javalite",
@@ -1190,3 +1194,29 @@ cel_android_library(
11901194
"@maven_android//:com_google_guava_guava",
11911195
],
11921196
)
1197+
1198+
java_library(
1199+
name = "internal_function_binder",
1200+
srcs = ["InternalFunctionBinder.java"],
1201+
tags = [
1202+
],
1203+
deps = [
1204+
":function_overload",
1205+
"//common/annotations",
1206+
"//runtime:function_binding",
1207+
"@maven//:com_google_guava_guava",
1208+
],
1209+
)
1210+
1211+
cel_android_library(
1212+
name = "internal_function_binder_andriod",
1213+
srcs = ["InternalFunctionBinder.java"],
1214+
tags = [
1215+
],
1216+
deps = [
1217+
":function_overload_android",
1218+
"//common/annotations",
1219+
"//runtime:function_binding_android",
1220+
"@maven_android//:com_google_guava_guava",
1221+
],
1222+
)

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,6 @@ public interface CelFunctionResolver {
3535
* @return an optional value of the resolved overload.
3636
* @throws CelEvaluationException if the overload resolution is ambiguous,
3737
*/
38-
Optional<ResolvedOverload> findOverload(
38+
Optional<ResolvedOverload> findOverloadMatchingArgs(
3939
String functionName, List<String> overloadIds, Object[] args) throws CelEvaluationException;
4040
}

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,9 @@ private CelLateFunctionBindings(ImmutableMap<String, ResolvedOverload> functions
3636
}
3737

3838
@Override
39-
public Optional<ResolvedOverload> findOverload(
39+
public Optional<ResolvedOverload> findOverloadMatchingArgs(
4040
String functionName, List<String> overloadIds, Object[] args) throws CelEvaluationException {
41-
return DefaultDispatcher.findOverload(functionName, overloadIds, functions, args);
41+
return DefaultDispatcher.findOverloadMatchingArgs(functionName, overloadIds, functions, args);
4242
}
4343

4444
public static CelLateFunctionBindings from(CelFunctionBinding... functions) {

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

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,8 @@
100100
import dev.cel.runtime.standard.NegateOperator.NegateOverload;
101101
import dev.cel.runtime.standard.NotEqualsOperator;
102102
import dev.cel.runtime.standard.NotEqualsOperator.NotEqualsOverload;
103+
import dev.cel.runtime.standard.NotStrictlyFalseFunction;
104+
import dev.cel.runtime.standard.NotStrictlyFalseFunction.NotStrictlyFalseOverload;
103105
import dev.cel.runtime.standard.SizeFunction;
104106
import dev.cel.runtime.standard.SizeFunction.SizeOverload;
105107
import dev.cel.runtime.standard.StartsWithFunction;
@@ -159,7 +161,8 @@ public final class CelStandardFunctions {
159161
StringFunction.create(),
160162
SubtractOperator.create(),
161163
TimestampFunction.create(),
162-
UintFunction.create());
164+
UintFunction.create(),
165+
NotStrictlyFalseFunction.create());
163166

164167
/**
165168
* Enumeration of Standard Function bindings.
@@ -170,6 +173,7 @@ public final class CelStandardFunctions {
170173
public enum StandardFunction {
171174
LOGICAL_NOT(BooleanOperator.LOGICAL_NOT),
172175
IN(InternalOperator.IN_LIST, InternalOperator.IN_MAP),
176+
NOT_STRICTLY_FALSE(InternalOperator.NOT_STRICTLY_FALSE),
173177
EQUALS(Relation.EQUALS),
174178
NOT_EQUALS(Relation.NOT_EQUALS),
175179
BOOL(Conversions.BOOL_TO_BOOL, Conversions.STRING_TO_BOOL),
@@ -331,10 +335,14 @@ public enum StandardFunction {
331335
/** Container class for CEL standard function overloads. */
332336
public static final class Overload {
333337

334-
/** Overloads for internal functions that may have been rewritten by macros (ex: @in) */
338+
/**
339+
* Overloads for internal functions that may have been rewritten by macros
340+
* (ex: @in, @not_strictly_false)
341+
*/
335342
public enum InternalOperator implements CelStandardOverload {
336343
IN_LIST(InOverload.IN_LIST),
337-
IN_MAP(InOverload.IN_MAP);
344+
IN_MAP(InOverload.IN_MAP),
345+
NOT_STRICTLY_FALSE(NotStrictlyFalseOverload.NOT_STRICTLY_FALSE);
338346

339347
private final CelStandardOverload standardOverload;
340348

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

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,13 +34,13 @@ final class DefaultDispatcher implements CelFunctionResolver {
3434
private final ImmutableMap<String, ResolvedOverload> overloads;
3535

3636
@Override
37-
public Optional<ResolvedOverload> findOverload(
37+
public Optional<ResolvedOverload> findOverloadMatchingArgs(
3838
String functionName, List<String> overloadIds, Object[] args) throws CelEvaluationException {
39-
return findOverload(functionName, overloadIds, overloads, args);
39+
return findOverloadMatchingArgs(functionName, overloadIds, overloads, args);
4040
}
4141

4242
/** Finds the overload that matches the given function name, overload IDs, and arguments. */
43-
public static Optional<ResolvedOverload> findOverload(
43+
static Optional<ResolvedOverload> findOverloadMatchingArgs(
4444
String functionName,
4545
List<String> overloadIds,
4646
Map<String, ? extends ResolvedOverload> overloads,
@@ -75,6 +75,32 @@ public static Optional<ResolvedOverload> findOverload(
7575
return Optional.ofNullable(match);
7676
}
7777

78+
/**
79+
* Finds the single registered overload iff it's marked as a non-strict function.
80+
*
81+
* <p>The intent behind this function is to provide an at-parity behavior with existing
82+
* DefaultInterpreter, where it historically special-cased locating a single overload for certain
83+
* non-strict functions, such as not_strictly_false. This method should not be used outside of
84+
* this specific context.
85+
*
86+
* @throws IllegalStateException if there are multiple overloads that are marked non-strict.
87+
*/
88+
Optional<ResolvedOverload> findSingleNonStrictOverload(List<String> overloadIds) {
89+
for (String overloadId : overloadIds) {
90+
ResolvedOverload overload = overloads.get(overloadId);
91+
if (overload != null && !overload.isStrict()) {
92+
if (overloadIds.size() > 1) {
93+
throw new IllegalStateException(
94+
String.format(
95+
"%d overloads found for a non-strict function. Expected 1.", overloadIds.size()));
96+
}
97+
return Optional.of(overload);
98+
}
99+
}
100+
101+
return Optional.empty();
102+
}
103+
78104
static Builder newBuilder() {
79105
return new AutoBuilder_DefaultDispatcher_Builder();
80106
}

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

Lines changed: 26 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -421,8 +421,6 @@ private IntermediateResult evalCall(ExecutionFrame frame, CelExpr expr, CelCall
421421
return evalLogicalAnd(frame, callExpr);
422422
case "logical_or":
423423
return evalLogicalOr(frame, callExpr);
424-
case "not_strictly_false":
425-
return evalNotStrictlyFalse(frame, callExpr);
426424
case "type":
427425
return evalType(frame, callExpr);
428426
case "optional_or_optional":
@@ -441,18 +439,35 @@ private IntermediateResult evalCall(ExecutionFrame frame, CelExpr expr, CelCall
441439
break;
442440
}
443441

444-
// Delegate handling of call to dispatcher.
442+
boolean isNonStrictCall =
443+
dispatcher.findSingleNonStrictOverload(reference.overloadIds()).isPresent();
444+
return dispatchCall(frame, expr, callExpr, reference, isNonStrictCall);
445+
}
445446

447+
private IntermediateResult dispatchCall(
448+
ExecutionFrame frame,
449+
CelExpr expr,
450+
CelCall callExpr,
451+
CelReference reference,
452+
boolean isNonStrict)
453+
throws CelEvaluationException {
446454
List<CelExpr> callArgs = new ArrayList<>();
447455
callExpr.target().ifPresent(callArgs::add);
448456

449457
callArgs.addAll(callExpr.args());
450458
IntermediateResult[] argResults = new IntermediateResult[callArgs.size()];
451459

452460
for (int i = 0; i < argResults.length; i++) {
453-
// Default evaluation is strict so errors will propagate (via thrown Java exception) before
454-
// unknowns.
455-
argResults[i] = evalInternal(frame, callArgs.get(i));
461+
IntermediateResult result;
462+
try {
463+
result = evalInternal(frame, callArgs.get(i));
464+
} catch (Exception e) {
465+
if (!isNonStrict) {
466+
throw e;
467+
}
468+
result = IntermediateResult.create(e);
469+
}
470+
argResults[i] = result;
456471
}
457472

458473
Optional<CelAttribute> indexAttr =
@@ -469,7 +484,7 @@ private IntermediateResult evalCall(ExecutionFrame frame, CelExpr expr, CelCall
469484
indexAttr.isPresent()
470485
? CallArgumentChecker.createAcceptingPartial(frame.getResolver())
471486
: CallArgumentChecker.create(frame.getResolver());
472-
for (DefaultInterpreter.IntermediateResult element : argResults) {
487+
for (IntermediateResult element : argResults) {
473488
argChecker.checkArg(element);
474489
}
475490
Optional<Object> unknowns = argChecker.maybeUnknowns();
@@ -511,7 +526,7 @@ private ResolvedOverload findOverloadOrThrow(
511526
throws CelEvaluationException {
512527
try {
513528
Optional<ResolvedOverload> funcImpl =
514-
dispatcher.findOverload(functionName, overloadIds, args);
529+
dispatcher.findOverloadMatchingArgs(functionName, overloadIds, args);
515530
if (funcImpl.isPresent()) {
516531
return funcImpl.get();
517532
}
@@ -684,20 +699,6 @@ private IntermediateResult evalLogicalAnd(ExecutionFrame frame, CelCall callExpr
684699
return mergeBooleanUnknowns(left, right);
685700
}
686701

687-
// Returns true unless the expression evaluates to false, in which case it returns false.
688-
// True is also returned if evaluation yields an error or an unknown set.
689-
private IntermediateResult evalNotStrictlyFalse(ExecutionFrame frame, CelCall callExpr) {
690-
try {
691-
IntermediateResult value = evalBooleanStrict(frame, callExpr.args().get(0));
692-
if (value.value() instanceof Boolean) {
693-
return value;
694-
}
695-
} catch (Exception e) {
696-
/*nothing to do*/
697-
}
698-
return IntermediateResult.create(true);
699-
}
700-
701702
private IntermediateResult evalType(ExecutionFrame frame, CelCall callExpr)
702703
throws CelEvaluationException {
703704
CelExpr typeExprArg = callExpr.args().get(0);
@@ -1134,7 +1135,9 @@ private RuntimeUnknownResolver getResolver() {
11341135
private Optional<ResolvedOverload> findOverload(
11351136
String function, List<String> overloadIds, Object[] args) throws CelEvaluationException {
11361137
if (lateBoundFunctionResolver.isPresent()) {
1137-
return lateBoundFunctionResolver.get().findOverload(function, overloadIds, args);
1138+
return lateBoundFunctionResolver
1139+
.get()
1140+
.findOverloadMatchingArgs(function, overloadIds, args);
11381141
}
11391142
return Optional.empty();
11401143
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
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 at
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.common.collect.ImmutableList;
18+
import dev.cel.common.annotations.Internal;
19+
20+
/**
21+
* A helper to create CelFunctionBinding instances with sensitive controls, such as to toggle the
22+
* strictness of the function.
23+
*
24+
* <p>CEL Library Internals. Do Not Use.
25+
*/
26+
@Internal
27+
public final class InternalFunctionBinder {
28+
29+
/**
30+
* Create a unary function binding from the {@code overloadId}, {@code arg}, {@code impl}, and
31+
* {@code} isStrict.
32+
*/
33+
@SuppressWarnings("unchecked")
34+
public static <T> CelFunctionBinding from(
35+
String overloadId, Class<T> arg, CelFunctionOverload.Unary<T> impl, boolean isStrict) {
36+
return from(overloadId, ImmutableList.of(arg), (args) -> impl.apply((T) args[0]), isStrict);
37+
}
38+
39+
/**
40+
* Create a function binding from the {@code overloadId}, {@code argTypes}, {@code impl} and
41+
* {@code isStrict}.
42+
*/
43+
public static CelFunctionBinding from(
44+
String overloadId, Iterable<Class<?>> argTypes, CelFunctionOverload impl, boolean isStrict) {
45+
return new FunctionBindingImpl(overloadId, ImmutableList.copyOf(argTypes), impl, isStrict);
46+
}
47+
48+
private InternalFunctionBinder() {}
49+
}

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,14 @@ default boolean canHandle(Object[] arguments) {
7070
}
7171
continue;
7272
}
73+
74+
if (arg instanceof Exception || arg instanceof CelUnknownSet) {
75+
if (!isStrict()) {
76+
// Only non-strict functions can accept errors/unknowns as arguments to a function
77+
return true;
78+
}
79+
}
80+
7381
if (!paramType.isAssignableFrom(arg.getClass())) {
7482
return false;
7583
}

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

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1419,6 +1419,38 @@ cel_android_library(
14191419
],
14201420
)
14211421

1422+
java_library(
1423+
name = "not_strictly_false",
1424+
srcs = ["NotStrictlyFalseFunction.java"],
1425+
tags = [
1426+
],
1427+
deps = [
1428+
":standard_overload",
1429+
"//common:options",
1430+
"//runtime:function_binding",
1431+
"//runtime:internal_function_binder",
1432+
"//runtime:runtime_equality",
1433+
"//runtime/standard:standard_function",
1434+
"@maven//:com_google_guava_guava",
1435+
],
1436+
)
1437+
1438+
cel_android_library(
1439+
name = "not_strictly_false_android",
1440+
srcs = ["NotStrictlyFalseFunction.java"],
1441+
tags = [
1442+
],
1443+
deps = [
1444+
":standard_function_android",
1445+
":standard_overload_android",
1446+
"//common:options",
1447+
"//runtime:function_binding_android",
1448+
"//runtime:internal_function_binder_android",
1449+
"//runtime:runtime_equality_android",
1450+
"@maven_android//:com_google_guava_guava",
1451+
],
1452+
)
1453+
14221454
java_library(
14231455
name = "standard_overload",
14241456
srcs = ["CelStandardOverload.java"],

0 commit comments

Comments
 (0)