Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions runtime/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -232,3 +232,15 @@ java_library(
visibility = ["//:internal"],
exports = ["//runtime/src/main/java/dev/cel/runtime:resolved_overload_internal"],
)

java_library(
name = "internal_function_binder",
visibility = ["//:internal"],
exports = ["//runtime/src/main/java/dev/cel/runtime:internal_function_binder"],
)

cel_android_library(
name = "internal_function_binder_android",
visibility = ["//:internal"],
exports = ["//runtime/src/main/java/dev/cel/runtime:internal_function_binder_andriod"],
)
30 changes: 30 additions & 0 deletions runtime/src/main/java/dev/cel/runtime/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -644,6 +644,7 @@ java_library(
"//runtime/standard:multiply",
"//runtime/standard:negate",
"//runtime/standard:not_equals",
"//runtime/standard:not_strictly_false",
"//runtime/standard:size",
"//runtime/standard:standard_function",
"//runtime/standard:standard_overload",
Expand Down Expand Up @@ -700,6 +701,7 @@ cel_android_library(
"//runtime/standard:multiply_android",
"//runtime/standard:negate_android",
"//runtime/standard:not_equals_android",
"//runtime/standard:not_strictly_false_android",
"//runtime/standard:size_android",
"//runtime/standard:standard_function_android",
"//runtime/standard:standard_overload_android",
Expand Down Expand Up @@ -794,6 +796,7 @@ java_library(
],
deps = [
":function_overload",
":unknown_attributes",
"@maven//:com_google_code_findbugs_annotations",
"@maven//:com_google_errorprone_error_prone_annotations",
"@maven//:com_google_protobuf_protobuf_java",
Expand All @@ -806,6 +809,7 @@ cel_android_library(
visibility = ["//visibility:private"],
deps = [
":function_overload_android",
":unknown_attributes_android",
"@maven//:com_google_code_findbugs_annotations",
"@maven//:com_google_errorprone_error_prone_annotations",
"@maven_android//:com_google_protobuf_protobuf_javalite",
Expand Down Expand Up @@ -1190,3 +1194,29 @@ cel_android_library(
"@maven_android//:com_google_guava_guava",
],
)

java_library(
name = "internal_function_binder",
srcs = ["InternalFunctionBinder.java"],
tags = [
],
deps = [
":function_overload",
"//common/annotations",
"//runtime:function_binding",
"@maven//:com_google_guava_guava",
],
)

cel_android_library(
name = "internal_function_binder_andriod",
srcs = ["InternalFunctionBinder.java"],
tags = [
],
deps = [
":function_overload_android",
"//common/annotations",
"//runtime:function_binding_android",
"@maven_android//:com_google_guava_guava",
],
)
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,6 @@ public interface CelFunctionResolver {
* @return an optional value of the resolved overload.
* @throws CelEvaluationException if the overload resolution is ambiguous,
*/
Optional<ResolvedOverload> findOverload(
Optional<ResolvedOverload> findOverloadMatchingArgs(
String functionName, List<String> overloadIds, Object[] args) throws CelEvaluationException;
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,9 @@ private CelLateFunctionBindings(ImmutableMap<String, ResolvedOverload> functions
}

@Override
public Optional<ResolvedOverload> findOverload(
public Optional<ResolvedOverload> findOverloadMatchingArgs(
String functionName, List<String> overloadIds, Object[] args) throws CelEvaluationException {
return DefaultDispatcher.findOverload(functionName, overloadIds, functions, args);
return DefaultDispatcher.findOverloadMatchingArgs(functionName, overloadIds, functions, args);
}

public static CelLateFunctionBindings from(CelFunctionBinding... functions) {
Expand Down
14 changes: 11 additions & 3 deletions runtime/src/main/java/dev/cel/runtime/CelStandardFunctions.java
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,8 @@
import dev.cel.runtime.standard.NegateOperator.NegateOverload;
import dev.cel.runtime.standard.NotEqualsOperator;
import dev.cel.runtime.standard.NotEqualsOperator.NotEqualsOverload;
import dev.cel.runtime.standard.NotStrictlyFalseFunction;
import dev.cel.runtime.standard.NotStrictlyFalseFunction.NotStrictlyFalseOverload;
import dev.cel.runtime.standard.SizeFunction;
import dev.cel.runtime.standard.SizeFunction.SizeOverload;
import dev.cel.runtime.standard.StartsWithFunction;
Expand Down Expand Up @@ -159,7 +161,8 @@ public final class CelStandardFunctions {
StringFunction.create(),
SubtractOperator.create(),
TimestampFunction.create(),
UintFunction.create());
UintFunction.create(),
NotStrictlyFalseFunction.create());

/**
* Enumeration of Standard Function bindings.
Expand All @@ -170,6 +173,7 @@ public final class CelStandardFunctions {
public enum StandardFunction {
LOGICAL_NOT(BooleanOperator.LOGICAL_NOT),
IN(InternalOperator.IN_LIST, InternalOperator.IN_MAP),
NOT_STRICTLY_FALSE(InternalOperator.NOT_STRICTLY_FALSE),
EQUALS(Relation.EQUALS),
NOT_EQUALS(Relation.NOT_EQUALS),
BOOL(Conversions.BOOL_TO_BOOL, Conversions.STRING_TO_BOOL),
Expand Down Expand Up @@ -331,10 +335,14 @@ public enum StandardFunction {
/** Container class for CEL standard function overloads. */
public static final class Overload {

/** Overloads for internal functions that may have been rewritten by macros (ex: @in) */
/**
* Overloads for internal functions that may have been rewritten by macros
* (ex: @in, @not_strictly_false)
*/
public enum InternalOperator implements CelStandardOverload {
IN_LIST(InOverload.IN_LIST),
IN_MAP(InOverload.IN_MAP);
IN_MAP(InOverload.IN_MAP),
NOT_STRICTLY_FALSE(NotStrictlyFalseOverload.NOT_STRICTLY_FALSE);

private final CelStandardOverload standardOverload;

Expand Down
32 changes: 29 additions & 3 deletions runtime/src/main/java/dev/cel/runtime/DefaultDispatcher.java
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,13 @@ final class DefaultDispatcher implements CelFunctionResolver {
private final ImmutableMap<String, ResolvedOverload> overloads;

@Override
public Optional<ResolvedOverload> findOverload(
public Optional<ResolvedOverload> findOverloadMatchingArgs(
String functionName, List<String> overloadIds, Object[] args) throws CelEvaluationException {
return findOverload(functionName, overloadIds, overloads, args);
return findOverloadMatchingArgs(functionName, overloadIds, overloads, args);
}

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

/**
* Finds the single registered overload iff it's marked as a non-strict function.
*
* <p>The intent behind this function is to provide an at-parity behavior with existing
* DefaultInterpreter, where it historically special-cased locating a single overload for certain
* non-strict functions, such as not_strictly_false. This method should not be used outside of
* this specific context.
*
* @throws IllegalStateException if there are multiple overloads that are marked non-strict.
*/
Optional<ResolvedOverload> findSingleNonStrictOverload(List<String> overloadIds) {
for (String overloadId : overloadIds) {
ResolvedOverload overload = overloads.get(overloadId);
if (overload != null && !overload.isStrict()) {
if (overloadIds.size() > 1) {
throw new IllegalStateException(
String.format(
"%d overloads found for a non-strict function. Expected 1.", overloadIds.size()));
}
return Optional.of(overload);
}
}

return Optional.empty();
}

static Builder newBuilder() {
return new AutoBuilder_DefaultDispatcher_Builder();
}
Expand Down
49 changes: 26 additions & 23 deletions runtime/src/main/java/dev/cel/runtime/DefaultInterpreter.java
Original file line number Diff line number Diff line change
Expand Up @@ -421,8 +421,6 @@ private IntermediateResult evalCall(ExecutionFrame frame, CelExpr expr, CelCall
return evalLogicalAnd(frame, callExpr);
case "logical_or":
return evalLogicalOr(frame, callExpr);
case "not_strictly_false":
return evalNotStrictlyFalse(frame, callExpr);
case "type":
return evalType(frame, callExpr);
case "optional_or_optional":
Expand All @@ -441,18 +439,35 @@ private IntermediateResult evalCall(ExecutionFrame frame, CelExpr expr, CelCall
break;
}

// Delegate handling of call to dispatcher.
boolean isNonStrictCall =
dispatcher.findSingleNonStrictOverload(reference.overloadIds()).isPresent();
return dispatchCall(frame, expr, callExpr, reference, isNonStrictCall);
}

private IntermediateResult dispatchCall(
ExecutionFrame frame,
CelExpr expr,
CelCall callExpr,
CelReference reference,
boolean isNonStrict)
throws CelEvaluationException {
List<CelExpr> callArgs = new ArrayList<>();
callExpr.target().ifPresent(callArgs::add);

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

for (int i = 0; i < argResults.length; i++) {
// Default evaluation is strict so errors will propagate (via thrown Java exception) before
// unknowns.
argResults[i] = evalInternal(frame, callArgs.get(i));
IntermediateResult result;
try {
result = evalInternal(frame, callArgs.get(i));
} catch (Exception e) {
if (!isNonStrict) {
throw e;
}
result = IntermediateResult.create(e);
}
argResults[i] = result;
}

Optional<CelAttribute> indexAttr =
Expand All @@ -469,7 +484,7 @@ private IntermediateResult evalCall(ExecutionFrame frame, CelExpr expr, CelCall
indexAttr.isPresent()
? CallArgumentChecker.createAcceptingPartial(frame.getResolver())
: CallArgumentChecker.create(frame.getResolver());
for (DefaultInterpreter.IntermediateResult element : argResults) {
for (IntermediateResult element : argResults) {
argChecker.checkArg(element);
}
Optional<Object> unknowns = argChecker.maybeUnknowns();
Expand Down Expand Up @@ -511,7 +526,7 @@ private ResolvedOverload findOverloadOrThrow(
throws CelEvaluationException {
try {
Optional<ResolvedOverload> funcImpl =
dispatcher.findOverload(functionName, overloadIds, args);
dispatcher.findOverloadMatchingArgs(functionName, overloadIds, args);
if (funcImpl.isPresent()) {
return funcImpl.get();
}
Expand Down Expand Up @@ -684,20 +699,6 @@ private IntermediateResult evalLogicalAnd(ExecutionFrame frame, CelCall callExpr
return mergeBooleanUnknowns(left, right);
}

// Returns true unless the expression evaluates to false, in which case it returns false.
// True is also returned if evaluation yields an error or an unknown set.
private IntermediateResult evalNotStrictlyFalse(ExecutionFrame frame, CelCall callExpr) {
try {
IntermediateResult value = evalBooleanStrict(frame, callExpr.args().get(0));
if (value.value() instanceof Boolean) {
return value;
}
} catch (Exception e) {
/*nothing to do*/
}
return IntermediateResult.create(true);
}

private IntermediateResult evalType(ExecutionFrame frame, CelCall callExpr)
throws CelEvaluationException {
CelExpr typeExprArg = callExpr.args().get(0);
Expand Down Expand Up @@ -1134,7 +1135,9 @@ private RuntimeUnknownResolver getResolver() {
private Optional<ResolvedOverload> findOverload(
String function, List<String> overloadIds, Object[] args) throws CelEvaluationException {
if (lateBoundFunctionResolver.isPresent()) {
return lateBoundFunctionResolver.get().findOverload(function, overloadIds, args);
return lateBoundFunctionResolver
.get()
.findOverloadMatchingArgs(function, overloadIds, args);
}
return Optional.empty();
}
Expand Down
49 changes: 49 additions & 0 deletions runtime/src/main/java/dev/cel/runtime/InternalFunctionBinder.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// Copyright 2025 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package dev.cel.runtime;

import com.google.common.collect.ImmutableList;
import dev.cel.common.annotations.Internal;

/**
* A helper to create CelFunctionBinding instances with sensitive controls, such as to toggle the
* strictness of the function.
*
* <p>CEL Library Internals. Do Not Use.
*/
@Internal
public final class InternalFunctionBinder {

/**
* Create a unary function binding from the {@code overloadId}, {@code arg}, {@code impl}, and
* {@code} isStrict.
*/
@SuppressWarnings("unchecked")
public static <T> CelFunctionBinding from(
String overloadId, Class<T> arg, CelFunctionOverload.Unary<T> impl, boolean isStrict) {
return from(overloadId, ImmutableList.of(arg), (args) -> impl.apply((T) args[0]), isStrict);
}

/**
* Create a function binding from the {@code overloadId}, {@code argTypes}, {@code impl} and
* {@code isStrict}.
*/
public static CelFunctionBinding from(
String overloadId, Iterable<Class<?>> argTypes, CelFunctionOverload impl, boolean isStrict) {
return new FunctionBindingImpl(overloadId, ImmutableList.copyOf(argTypes), impl, isStrict);
}

private InternalFunctionBinder() {}
}
8 changes: 8 additions & 0 deletions runtime/src/main/java/dev/cel/runtime/ResolvedOverload.java
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,14 @@ default boolean canHandle(Object[] arguments) {
}
continue;
}

if (arg instanceof Exception || arg instanceof CelUnknownSet) {
if (!isStrict()) {
// Only non-strict functions can accept errors/unknowns as arguments to a function
return true;
}
}

if (!paramType.isAssignableFrom(arg.getClass())) {
return false;
}
Expand Down
32 changes: 32 additions & 0 deletions runtime/src/main/java/dev/cel/runtime/standard/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -1419,6 +1419,38 @@ cel_android_library(
],
)

java_library(
name = "not_strictly_false",
srcs = ["NotStrictlyFalseFunction.java"],
tags = [
],
deps = [
":standard_overload",
"//common:options",
"//runtime:function_binding",
"//runtime:internal_function_binder",
"//runtime:runtime_equality",
"//runtime/standard:standard_function",
"@maven//:com_google_guava_guava",
],
)

cel_android_library(
name = "not_strictly_false_android",
srcs = ["NotStrictlyFalseFunction.java"],
tags = [
],
deps = [
":standard_function_android",
":standard_overload_android",
"//common:options",
"//runtime:function_binding_android",
"//runtime:internal_function_binder_android",
"//runtime:runtime_equality_android",
"@maven_android//:com_google_guava_guava",
],
)

java_library(
name = "standard_overload",
srcs = ["CelStandardOverload.java"],
Expand Down
Loading
Loading