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
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@
import dev.cel.runtime.CelEvaluationException;
import dev.cel.runtime.CelFunctionBinding;
import dev.cel.runtime.CelRuntime;
import dev.cel.runtime.InterpreterUtil;
import dev.cel.runtime.CelUnknownSet;
import dev.cel.runtime.PartialVars;
import java.time.Duration;
import java.time.Instant;
Expand Down Expand Up @@ -937,7 +937,7 @@ public void optionalIndex_onMapWithUnknownInput_returnsUnknownResult(String sour
cel.createProgram(ast)
.eval(PartialVars.of(CelAttributePattern.fromQualifiedIdentifier("x")));

assertThat(InterpreterUtil.isUnknown(result)).isTrue();
assertThat(result).isInstanceOf(CelUnknownSet.class);
}

@Test
Expand Down Expand Up @@ -1029,7 +1029,7 @@ public void optionalIndex_onListWithUnknownInput_returnsUnknownResult() throws E
cel.createProgram(ast)
.eval(PartialVars.of(CelAttributePattern.fromQualifiedIdentifier("x")));

assertThat(InterpreterUtil.isUnknown(result)).isTrue();
assertThat(result).isInstanceOf(CelUnknownSet.class);
}

@Test
Expand Down Expand Up @@ -1066,7 +1066,7 @@ public void optionalFieldSelect_fieldMarkedUnknown_returnsUnknownSet() throws Ex
ImmutableMap.of("msg", TestAllTypes.newBuilder().setSingleInt32(42).build()),
CelAttributePattern.fromQualifiedIdentifier("msg.single_int32")));

assertThat(InterpreterUtil.isUnknown(result)).isTrue();
assertThat(result).isInstanceOf(CelUnknownSet.class);
}

@Test
Expand All @@ -1089,7 +1089,7 @@ public void optionalChainedFunctions_lhsIsUnknown_returnsUnknown(String expressi
cel.createProgram(ast)
.eval(PartialVars.of(CelAttributePattern.fromQualifiedIdentifier("optx")));

assertThat(InterpreterUtil.isUnknown(result)).isTrue();
assertThat(result).isInstanceOf(CelUnknownSet.class);
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ void checkArg(DefaultInterpreter.IntermediateResult arg) {
unknowns = mergeOptionalUnknowns(unknowns, argUnknowns);

// support for ExprValue unknowns.
if (InterpreterUtil.isAccumulatedUnknowns(arg.value())) {
if (arg.value() instanceof AccumulatedUnknowns) {
AccumulatedUnknowns unknownSet = (AccumulatedUnknowns) arg.value();
exprIds.addAll(unknownSet.exprIds());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -282,7 +282,7 @@ private IntermediateResult evalInternal(ExecutionFrame frame, CelExpr expr)
}

private static boolean isUnknownValue(Object value) {
return InterpreterUtil.isAccumulatedUnknowns(value);
return value instanceof AccumulatedUnknowns;
}

private static boolean isUnknownOrError(Object value) {
Expand Down
10 changes: 5 additions & 5 deletions runtime/src/main/java/dev/cel/runtime/InterpreterUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

import com.google.common.collect.ImmutableSet;
import com.google.errorprone.annotations.CheckReturnValue;
import com.google.errorprone.annotations.InlineMe;
import dev.cel.common.annotations.Internal;
import org.jspecify.annotations.Nullable;

Expand Down Expand Up @@ -51,15 +52,14 @@ public static Object strict(Object valueOrThrowable) throws CelEvaluationExcepti
*
* @param obj Object to check.
* @return boolean value if object is unknown.
* @deprecated Perform {@code obj instanceof CelUnknownSet} directly instead.
*/
@Deprecated
@InlineMe(replacement = "obj instanceof CelUnknownSet", imports = "dev.cel.runtime.CelUnknownSet")
public static boolean isUnknown(Object obj) {
return obj instanceof CelUnknownSet;
}

public static boolean isAccumulatedUnknowns(Object obj) {
return obj instanceof AccumulatedUnknowns;
}

/** If the argument is {@link CelUnknownSet}, adapts it into {@link AccumulatedUnknowns} */
public static Object maybeAdaptToAccumulatedUnknowns(Object val) {
if (!(val instanceof CelUnknownSet)) {
Expand Down Expand Up @@ -102,7 +102,7 @@ public static Object enforceStrictness(Object left, Object right) throws CelEval

public static Object valueOrUnknown(@Nullable Object valueOrThrowable, Long id) {
// Handle the unknown value case.
if (isAccumulatedUnknowns(valueOrThrowable)) {
if (valueOrThrowable instanceof AccumulatedUnknowns) {
return AccumulatedUnknowns.create(id);
}
// Handle the null value case.
Expand Down
6 changes: 6 additions & 0 deletions runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -342,9 +342,12 @@ java_library(
"//common:error_codes",
"//common/exceptions:runtime_exception",
"//common/values",
"//runtime:accumulated_unknowns",
"//runtime:evaluation_exception",
"//runtime:interpretable",
"//runtime:interpreter_util",
"//runtime:resolved_overload",
"//runtime:unknown_attributes",
"@maven//:com_google_guava_guava",
],
)
Expand Down Expand Up @@ -851,9 +854,12 @@ cel_android_library(
"//common:error_codes",
"//common/exceptions:runtime_exception",
"//common/values:values_android",
"//runtime:accumulated_unknowns_android",
"//runtime:evaluation_exception",
"//runtime:interpretable_android",
"//runtime:interpreter_util_android",
"//runtime:resolved_overload_android",
"//runtime:unknown_attributes_android",
"@maven_android//:com_google_guava_guava",
],
)
Expand Down
19 changes: 16 additions & 3 deletions runtime/src/main/java/dev/cel/runtime/planner/EvalHelpers.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,12 @@
import dev.cel.common.exceptions.CelRuntimeException;
import dev.cel.common.values.CelValueConverter;
import dev.cel.common.values.ErrorValue;
import dev.cel.runtime.AccumulatedUnknowns;
import dev.cel.runtime.CelEvaluationException;
import dev.cel.runtime.CelResolvedOverload;
import dev.cel.runtime.CelUnknownSet;
import dev.cel.runtime.GlobalResolver;
import dev.cel.runtime.InterpreterUtil;

final class EvalHelpers {

Expand Down Expand Up @@ -63,7 +66,7 @@ static Object dispatch(
throws CelEvaluationException {
try {
Object result = overload.invoke(args);
return valueConverter.maybeUnwrap(valueConverter.toRuntimeValue(result));
return convertAndAdaptResult(valueConverter, result);
} catch (RuntimeException e) {
throw handleDispatchException(e, overload, args);
}
Expand All @@ -77,7 +80,7 @@ static Object dispatch(
throws CelEvaluationException {
try {
Object result = overload.invoke(arg);
return valueConverter.maybeUnwrap(valueConverter.toRuntimeValue(result));
return convertAndAdaptResult(valueConverter, result);
} catch (RuntimeException e) {
throw handleDispatchException(e, overload, arg);
}
Expand All @@ -92,12 +95,22 @@ static Object dispatch(
throws CelEvaluationException {
try {
Object result = overload.invoke(arg1, arg2);
return valueConverter.maybeUnwrap(valueConverter.toRuntimeValue(result));
return convertAndAdaptResult(valueConverter, result);
} catch (RuntimeException e) {
throw handleDispatchException(e, overload, arg1, arg2);
}
}

/**
* Converts the raw invocation result into a CEL runtime value, unwraps it if necessary, and
* adapts any public {@link CelUnknownSet} instances into internal {@link AccumulatedUnknowns} for
* AST evaluation.
*/
private static Object convertAndAdaptResult(CelValueConverter valueConverter, Object result) {
return InterpreterUtil.maybeAdaptToAccumulatedUnknowns(
valueConverter.maybeUnwrap(valueConverter.toRuntimeValue(result)));
}

private static RuntimeException handleDispatchException(
RuntimeException e, CelResolvedOverload overload, Object... args) {
if (e instanceof CelRuntimeException) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,9 @@ public NamespacedAttribute addQualifier(Qualifier qualifier) {

private static Object applyQualifiers(
Object value, CelValueConverter celValueConverter, ImmutableList<Qualifier> qualifiers) {
if (value instanceof AccumulatedUnknowns) {
return value;
}
Object obj = celValueConverter.toRuntimeValue(value);

// Avoid enhanced for loop to prevent UnmodifiableIterator from being allocated
Expand Down
157 changes: 152 additions & 5 deletions runtime/src/test/java/dev/cel/runtime/CelRuntimeTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -522,7 +522,7 @@ public void trace_shortCircuitingDisabledWithUnknownsAndedToFalse_returnsFalse(S
(expr, res) -> {
if (expr.constantOrDefault().getKind().equals(CelConstant.Kind.BOOLEAN_VALUE)
|| expr.identOrDefault().name().equals("x")) {
if (InterpreterUtil.isUnknown(res)) {
if (res instanceof CelUnknownSet) {
branchResults.add("x"); // Swap unknown result with a sentinel value for testing
} else {
branchResults.add(res);
Expand Down Expand Up @@ -577,7 +577,7 @@ public void trace_shortCircuitingDisabledWithUnknownAndedToTrue_returnsUnknown(S
PartialVars partialVars = PartialVars.of(CelAttributePattern.create("x"));
Object unknownResult = cel.createProgram(ast).trace(partialVars, listener);

assertThat(InterpreterUtil.isUnknown(unknownResult)).isTrue();
assertThat(unknownResult).isInstanceOf(CelUnknownSet.class);
assertThat(branchResults.build()).containsExactly(true, true, unknownResult);
}

Expand Down Expand Up @@ -653,7 +653,7 @@ public void trace_shortCircuitingDisabledWithUnknownsOredToFalse_returnsUnknown(
PartialVars partialVars = PartialVars.of(CelAttributePattern.create("x"));
Object unknownResult = cel.createProgram(ast).trace(partialVars, listener);

assertThat(InterpreterUtil.isUnknown(unknownResult)).isTrue();
assertThat(unknownResult).isInstanceOf(CelUnknownSet.class);
assertThat(branchResults.build()).containsExactly(false, false, unknownResult);
}

Expand All @@ -668,7 +668,7 @@ public void trace_shortCircuitingDisabledWithUnknownOredToTrue_returnsTrue(Strin
(expr, res) -> {
if (expr.constantOrDefault().getKind().equals(CelConstant.Kind.BOOLEAN_VALUE)
|| expr.identOrDefault().name().equals("x")) {
if (InterpreterUtil.isUnknown(res)) {
if (res instanceof CelUnknownSet) {
branchResults.add("x"); // Swap unknown result with a sentinel value for testing
} else {
branchResults.add(res);
Expand Down Expand Up @@ -748,7 +748,7 @@ public void trace_shortCircuitingDisabled_ternaryWithUnknowns(String source) thr
PartialVars partialVars = PartialVars.of(CelAttributePattern.create("x"));
Object unknownResult = cel.createProgram(ast).trace(partialVars, listener);

assertThat(InterpreterUtil.isUnknown(unknownResult)).isTrue();
assertThat(unknownResult).isInstanceOf(CelUnknownSet.class);
assertThat(branchResults.build()).containsExactly(false, unknownResult, true);
}

Expand Down Expand Up @@ -944,4 +944,151 @@ public void trace_shortCircuitingDisabled_logicalOrPrefersFirstError() throws Ex
CelEvaluationException e = assertThrows(CelEvaluationException.class, () -> program.eval());
assertThat(e).hasCauseThat().hasMessageThat().contains("error 1");
}

@Test
public void evaluate_customFunctionReturningCelUnknownSet_propagatesUnknown(
@TestParameter({
// Field selection
"getMsg().single_int32",
"getMsg().single_nested_message.bb",
// Binary & unary operators
"getMsg().single_int32 == 100",
"getMsg().single_int32 + 5 == 10",
"-getMsg().single_int32 == -10",
// Boolean operators & ternary
"true && (getMsg().single_int32 == 100)",
"false || (getMsg().single_int32 == 100)",
"(getMsg().single_int32 == 100) ? 'match' : 'no-match'",
// Comprehensions
"[1, 2, 3].exists(x, x == getMsg().single_int32)",
"[1, 2, 3].all(x, x > 0 && getMsg().single_int32 > 0)",
"[1, 2, 3].map(x, x + getMsg().single_int32)",
"[1, 2, 3].filter(x, x == getMsg().single_int32)",
})
String expression)
throws Exception {
Cel cel =
runtimeFlavor
.builder()
.setStandardMacros(CelStandardMacro.STANDARD_MACROS)
.addMessageTypes(TestAllTypes.getDescriptor())
.addFunctionDeclarations(
CelFunctionDecl.newFunctionDeclaration(
"getMsg",
CelOverloadDecl.newGlobalOverload(
"getMsg_overload",
StructTypeReference.create(TestAllTypes.getDescriptor().getFullName()),
ImmutableList.of())))
.addFunctionBindings(
CelFunctionBinding.from(
"getMsg_overload",
ImmutableList.of(),
args -> CelUnknownSet.create(CelAttribute.create("custom_msg"))))
.build();

Object result = cel.createProgram(cel.compile(expression).getAst()).eval();

assertThat(result).isInstanceOf(CelUnknownSet.class);
}

@Test
// Short-circuited boolean operators
@TestParameters("{expression: 'false && (getMsg().single_int32 == 100)', expected: false}")
@TestParameters("{expression: 'true || (getMsg().single_int32 == 100)', expected: true}")
// Short-circuited comprehensions
@TestParameters(
"{expression: '[1, 2, 3].exists(x, x == 1 || x == getMsg().single_int32)', expected: true}")
@TestParameters(
"{expression: '[1, 2, 3].all(x, x == 0 && getMsg().single_int32 > 0)', expected: false}")
public void evaluate_customFunctionReturningCelUnknownSet_shortCircuits(
String expression, boolean expected) throws Exception {
Cel cel =
runtimeFlavor
.builder()
.setStandardMacros(CelStandardMacro.STANDARD_MACROS)
.addMessageTypes(TestAllTypes.getDescriptor())
.addFunctionDeclarations(
CelFunctionDecl.newFunctionDeclaration(
"getMsg",
CelOverloadDecl.newGlobalOverload(
"getMsg_overload",
StructTypeReference.create(TestAllTypes.getDescriptor().getFullName()),
ImmutableList.of())))
.addFunctionBindings(
CelFunctionBinding.from(
"getMsg_overload",
ImmutableList.of(),
args -> CelUnknownSet.create(CelAttribute.create("custom_msg"))))
.build();

Object result = cel.createProgram(cel.compile(expression).getAst()).eval();

assertThat(result).isEqualTo(expected);
}

@Test
public void evaluate_customFunctionReturningCelUnknownSet_differentArities() throws Exception {
Cel cel =
runtimeFlavor
.builder()
.addFunctionDeclarations(
CelFunctionDecl.newFunctionDeclaration(
"unkZero",
CelOverloadDecl.newGlobalOverload(
"unk_zero", SimpleType.INT, ImmutableList.of())),
CelFunctionDecl.newFunctionDeclaration(
"unkUnary",
CelOverloadDecl.newGlobalOverload("unk_unary", SimpleType.INT, SimpleType.INT)),
CelFunctionDecl.newFunctionDeclaration(
"unkBinary",
CelOverloadDecl.newGlobalOverload(
"unk_binary", SimpleType.INT, SimpleType.INT, SimpleType.INT)),
CelFunctionDecl.newFunctionDeclaration(
"unkMember",
CelOverloadDecl.newMemberOverload(
"unk_member", SimpleType.INT, SimpleType.STRING, SimpleType.INT)),
CelFunctionDecl.newFunctionDeclaration(
"unkVarargs",
CelOverloadDecl.newGlobalOverload(
"unk_varargs",
SimpleType.INT,
SimpleType.INT,
SimpleType.INT,
SimpleType.INT)))
.addFunctionBindings(
CelFunctionBinding.from(
"unk_zero",
ImmutableList.of(),
args -> CelUnknownSet.create(CelAttribute.create("attr_zero"))),
CelFunctionBinding.from(
"unk_unary",
Long.class,
arg -> CelUnknownSet.create(CelAttribute.create("attr_unary"))),
CelFunctionBinding.from(
"unk_binary",
Long.class,
Long.class,
(a, b) -> CelUnknownSet.create(CelAttribute.create("attr_binary"))),
CelFunctionBinding.from(
"unk_member",
String.class,
Long.class,
(target, arg) -> CelUnknownSet.create(CelAttribute.create("attr_member"))),
CelFunctionBinding.from(
"unk_varargs",
ImmutableList.of(Long.class, Long.class, Long.class),
args -> CelUnknownSet.create(CelAttribute.create("attr_varargs"))))
.build();

assertThat(cel.createProgram(cel.compile("unkZero() + 1").getAst()).eval())
.isEqualTo(CelUnknownSet.create(CelAttribute.create("attr_zero")));
assertThat(cel.createProgram(cel.compile("unkUnary(1) + 1").getAst()).eval())
.isEqualTo(CelUnknownSet.create(CelAttribute.create("attr_unary")));
assertThat(cel.createProgram(cel.compile("unkBinary(1, 2) + 1").getAst()).eval())
.isEqualTo(CelUnknownSet.create(CelAttribute.create("attr_binary")));
assertThat(cel.createProgram(cel.compile("'target'.unkMember(1) + 1").getAst()).eval())
.isEqualTo(CelUnknownSet.create(CelAttribute.create("attr_member")));
assertThat(cel.createProgram(cel.compile("unkVarargs(1, 2, 3) + 1").getAst()).eval())
.isEqualTo(CelUnknownSet.create(CelAttribute.create("attr_varargs")));
}
}
Loading
Loading