Note: This is not to be confused with the notion of CEL Runtime. Use {@code
- * CelEvaluationException} instead to signify an evaluation error.
- *
- *
TODO: Make this class abstract and define specific exception classes that
- * corresponds to the CelErrorCode.
+ * CelEvaluationException} instead to signify an evaluation error. corresponds to the CelErrorCode.
*/
@Internal
public class CelRuntimeException extends RuntimeException {
private final CelErrorCode errorCode;
+ public CelRuntimeException(String errorMessage, CelErrorCode errorCode) {
+ super(errorMessage);
+ this.errorCode = errorCode;
+ }
+
public CelRuntimeException(Throwable cause, CelErrorCode errorCode) {
super(cause);
this.errorCode = errorCode;
diff --git a/common/src/main/java/dev/cel/common/exceptions/BUILD.bazel b/common/src/main/java/dev/cel/common/exceptions/BUILD.bazel
new file mode 100644
index 000000000..6bd1ad9ca
--- /dev/null
+++ b/common/src/main/java/dev/cel/common/exceptions/BUILD.bazel
@@ -0,0 +1,87 @@
+load("@rules_java//java:defs.bzl", "java_library")
+
+package(
+ default_applicable_licenses = ["//:license"],
+ default_visibility = [
+ "//common/exceptions:__pkg__",
+ "//publish:__pkg__",
+ ],
+)
+
+java_library(
+ name = "attribute_not_found",
+ srcs = ["CelAttributeNotFoundException.java"],
+ # used_by_android
+ tags = [
+ ],
+ deps = [
+ "//common:error_codes",
+ "//common:runtime_exception",
+ "//common/annotations",
+ ],
+)
+
+java_library(
+ name = "divide_by_zero",
+ srcs = ["CelDivideByZeroException.java"],
+ # used_by_android
+ tags = [
+ ],
+ deps = [
+ "//common:error_codes",
+ "//common:runtime_exception",
+ "//common/annotations",
+ ],
+)
+
+java_library(
+ name = "index_out_of_bounds",
+ srcs = ["CelIndexOutOfBoundsException.java"],
+ # used_by_android
+ tags = [
+ ],
+ deps = [
+ "//common:error_codes",
+ "//common:runtime_exception",
+ "//common/annotations",
+ ],
+)
+
+java_library(
+ name = "bad_format",
+ srcs = ["CelBadFormatException.java"],
+ # used_by_android
+ tags = [
+ ],
+ deps = [
+ "//common:error_codes",
+ "//common:runtime_exception",
+ "//common/annotations",
+ ],
+)
+
+java_library(
+ name = "numeric_overflow",
+ srcs = ["CelNumericOverflowException.java"],
+ # used_by_android
+ tags = [
+ ],
+ deps = [
+ "//common:error_codes",
+ "//common:runtime_exception",
+ "//common/annotations",
+ ],
+)
+
+java_library(
+ name = "invalid_argument",
+ srcs = ["CelInvalidArgumentException.java"],
+ # used_by_android
+ tags = [
+ ],
+ deps = [
+ "//common:error_codes",
+ "//common:runtime_exception",
+ "//common/annotations",
+ ],
+)
diff --git a/common/src/main/java/dev/cel/common/exceptions/CelAttributeNotFoundException.java b/common/src/main/java/dev/cel/common/exceptions/CelAttributeNotFoundException.java
new file mode 100644
index 000000000..4aa43693d
--- /dev/null
+++ b/common/src/main/java/dev/cel/common/exceptions/CelAttributeNotFoundException.java
@@ -0,0 +1,57 @@
+// 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.common.exceptions;
+
+import dev.cel.common.CelErrorCode;
+import dev.cel.common.CelRuntimeException;
+import dev.cel.common.annotations.Internal;
+import java.util.Arrays;
+import java.util.Collection;
+
+/** Indicates an attempt to access a map or object using an invalid attribute or key. */
+@Internal
+public final class CelAttributeNotFoundException extends CelRuntimeException {
+
+ public static CelAttributeNotFoundException of(String message) {
+ return new CelAttributeNotFoundException(message);
+ }
+
+ public static CelAttributeNotFoundException forMissingMapKey(String key) {
+ return new CelAttributeNotFoundException(String.format("key '%s' is not present in map.", key));
+ }
+
+ public static CelAttributeNotFoundException forFieldResolution(String... fields) {
+ return forFieldResolution(Arrays.asList(fields));
+ }
+
+ public static CelAttributeNotFoundException forFieldResolution(Collection fields) {
+ return new CelAttributeNotFoundException(formatErrorMessage(fields));
+ }
+
+ private static String formatErrorMessage(Collection fields) {
+ String maybePlural = "";
+ if (fields.size() > 1) {
+ maybePlural = "s";
+ }
+
+ return String.format(
+ "Error resolving field%s '%s'. Field selections must be performed on messages or maps.",
+ maybePlural, String.join(", ", fields));
+ }
+
+ private CelAttributeNotFoundException(String message) {
+ super(message, CelErrorCode.ATTRIBUTE_NOT_FOUND);
+ }
+}
diff --git a/common/src/main/java/dev/cel/common/exceptions/CelBadFormatException.java b/common/src/main/java/dev/cel/common/exceptions/CelBadFormatException.java
new file mode 100644
index 000000000..92dde0101
--- /dev/null
+++ b/common/src/main/java/dev/cel/common/exceptions/CelBadFormatException.java
@@ -0,0 +1,32 @@
+// 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.common.exceptions;
+
+import dev.cel.common.CelErrorCode;
+import dev.cel.common.CelRuntimeException;
+import dev.cel.common.annotations.Internal;
+
+/** Indicates that a data conversion failed due to a mismatch in the format specification. */
+@Internal
+public final class CelBadFormatException extends CelRuntimeException {
+
+ public CelBadFormatException(Throwable cause) {
+ super(cause, CelErrorCode.BAD_FORMAT);
+ }
+
+ public CelBadFormatException(String errorMessage) {
+ super(errorMessage, CelErrorCode.BAD_FORMAT);
+ }
+}
diff --git a/common/src/main/java/dev/cel/common/exceptions/CelDivideByZeroException.java b/common/src/main/java/dev/cel/common/exceptions/CelDivideByZeroException.java
new file mode 100644
index 000000000..d433d804c
--- /dev/null
+++ b/common/src/main/java/dev/cel/common/exceptions/CelDivideByZeroException.java
@@ -0,0 +1,32 @@
+// 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.common.exceptions;
+
+import dev.cel.common.CelErrorCode;
+import dev.cel.common.CelRuntimeException;
+import dev.cel.common.annotations.Internal;
+
+/** Indicates that a division by zero occurred. */
+@Internal
+public final class CelDivideByZeroException extends CelRuntimeException {
+
+ public CelDivideByZeroException() {
+ super("/ by zero", CelErrorCode.DIVIDE_BY_ZERO);
+ }
+
+ public CelDivideByZeroException(Throwable cause) {
+ super(cause, CelErrorCode.DIVIDE_BY_ZERO);
+ }
+}
diff --git a/common/src/main/java/dev/cel/common/exceptions/CelIndexOutOfBoundsException.java b/common/src/main/java/dev/cel/common/exceptions/CelIndexOutOfBoundsException.java
new file mode 100644
index 000000000..3c2d0d03a
--- /dev/null
+++ b/common/src/main/java/dev/cel/common/exceptions/CelIndexOutOfBoundsException.java
@@ -0,0 +1,28 @@
+// 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.common.exceptions;
+
+import dev.cel.common.CelErrorCode;
+import dev.cel.common.CelRuntimeException;
+import dev.cel.common.annotations.Internal;
+
+/** Indicates that a list index access was attempted using an index that is out of bounds. */
+@Internal
+public final class CelIndexOutOfBoundsException extends CelRuntimeException {
+
+ public CelIndexOutOfBoundsException(Object index) {
+ super("Index out of bounds: " + index, CelErrorCode.INDEX_OUT_OF_BOUNDS);
+ }
+}
diff --git a/common/src/main/java/dev/cel/common/exceptions/CelInvalidArgumentException.java b/common/src/main/java/dev/cel/common/exceptions/CelInvalidArgumentException.java
new file mode 100644
index 000000000..5bbaf6cab
--- /dev/null
+++ b/common/src/main/java/dev/cel/common/exceptions/CelInvalidArgumentException.java
@@ -0,0 +1,28 @@
+// 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.common.exceptions;
+
+import dev.cel.common.CelErrorCode;
+import dev.cel.common.CelRuntimeException;
+import dev.cel.common.annotations.Internal;
+
+/** Indicates that an invalid argument was supplied to a function. */
+@Internal
+public final class CelInvalidArgumentException extends CelRuntimeException {
+
+ public CelInvalidArgumentException(Throwable cause) {
+ super(cause, CelErrorCode.INVALID_ARGUMENT);
+ }
+}
diff --git a/common/src/main/java/dev/cel/common/exceptions/CelNumericOverflowException.java b/common/src/main/java/dev/cel/common/exceptions/CelNumericOverflowException.java
new file mode 100644
index 000000000..439d1348f
--- /dev/null
+++ b/common/src/main/java/dev/cel/common/exceptions/CelNumericOverflowException.java
@@ -0,0 +1,35 @@
+// 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.common.exceptions;
+
+import dev.cel.common.CelErrorCode;
+import dev.cel.common.CelRuntimeException;
+import dev.cel.common.annotations.Internal;
+
+/**
+ * Indicates that a numeric overflow occurred due to arithmetic operations or conversions resulting
+ * in a value outside the representable range.
+ */
+@Internal
+public final class CelNumericOverflowException extends CelRuntimeException {
+
+ public CelNumericOverflowException(String message) {
+ super(message, CelErrorCode.NUMERIC_OVERFLOW);
+ }
+
+ public CelNumericOverflowException(Throwable cause) {
+ super(cause, CelErrorCode.NUMERIC_OVERFLOW);
+ }
+}
diff --git a/common/src/main/java/dev/cel/common/values/CelValueConverter.java b/common/src/main/java/dev/cel/common/values/CelValueConverter.java
index 3ab68c80e..b58790eaf 100644
--- a/common/src/main/java/dev/cel/common/values/CelValueConverter.java
+++ b/common/src/main/java/dev/cel/common/values/CelValueConverter.java
@@ -72,8 +72,6 @@ public Object toRuntimeValue(Object value) {
.map(this::toRuntimeValue)
.map(OptionalValue::create)
.orElse(OptionalValue.EMPTY);
- } else if (value instanceof Exception) {
- return ErrorValue.create((Exception) value);
}
return normalizePrimitive(value);
diff --git a/common/src/main/java/dev/cel/common/values/ErrorValue.java b/common/src/main/java/dev/cel/common/values/ErrorValue.java
index 818f86850..6bc04cda4 100644
--- a/common/src/main/java/dev/cel/common/values/ErrorValue.java
+++ b/common/src/main/java/dev/cel/common/values/ErrorValue.java
@@ -33,6 +33,8 @@
"Immutable") // Exception is technically not immutable as the stacktrace is malleable.
public abstract class ErrorValue extends CelValue {
+ public abstract long exprId();
+
@Override
public abstract Exception value();
@@ -46,7 +48,7 @@ public CelType celType() {
return SimpleType.ERROR;
}
- public static ErrorValue create(Exception value) {
- return new AutoValue_ErrorValue(value);
+ public static ErrorValue create(long exprId, Exception value) {
+ return new AutoValue_ErrorValue(exprId, value);
}
}
diff --git a/common/src/test/java/dev/cel/common/values/CelValueConverterTest.java b/common/src/test/java/dev/cel/common/values/CelValueConverterTest.java
index e4d767ef4..308d7b510 100644
--- a/common/src/test/java/dev/cel/common/values/CelValueConverterTest.java
+++ b/common/src/test/java/dev/cel/common/values/CelValueConverterTest.java
@@ -34,15 +34,6 @@ public void toRuntimeValue_optionalValue() {
assertThat(optionalValue).isEqualTo(OptionalValue.create("test"));
}
- @Test
- public void toRuntimeValue_errorValue() {
- IllegalArgumentException e = new IllegalArgumentException("error");
-
- ErrorValue errorValue = (ErrorValue) CEL_VALUE_CONVERTER.toRuntimeValue(e);
-
- assertThat(errorValue.value()).isEqualTo(e);
- }
-
@Test
@SuppressWarnings("unchecked") // Test only
public void unwrap_optionalValue() {
diff --git a/common/src/test/java/dev/cel/common/values/ErrorValueTest.java b/common/src/test/java/dev/cel/common/values/ErrorValueTest.java
index 0db711f72..a6a4edb66 100644
--- a/common/src/test/java/dev/cel/common/values/ErrorValueTest.java
+++ b/common/src/test/java/dev/cel/common/values/ErrorValueTest.java
@@ -27,7 +27,7 @@ public class ErrorValueTest {
@Test
public void errorValue_construct() {
IllegalArgumentException exception = new IllegalArgumentException("test");
- ErrorValue opaqueValue = ErrorValue.create(exception);
+ ErrorValue opaqueValue = ErrorValue.create(0L, exception);
assertThat(opaqueValue.value()).isEqualTo(exception);
assertThat(opaqueValue.isZeroValue()).isFalse();
@@ -35,12 +35,12 @@ public void errorValue_construct() {
@Test
public void create_nullValue_throws() {
- assertThrows(NullPointerException.class, () -> ErrorValue.create(null));
+ assertThrows(NullPointerException.class, () -> ErrorValue.create(0L, null));
}
@Test
public void celTypeTest() {
- ErrorValue value = ErrorValue.create(new IllegalArgumentException("test"));
+ ErrorValue value = ErrorValue.create(0L, new IllegalArgumentException("test"));
assertThat(value.celType()).isEqualTo(SimpleType.ERROR);
}
diff --git a/runtime/BUILD.bazel b/runtime/BUILD.bazel
index f3b60d4d7..7760d96b8 100644
--- a/runtime/BUILD.bazel
+++ b/runtime/BUILD.bazel
@@ -11,10 +11,10 @@ java_library(
exports = [
":evaluation_exception",
":late_function_binding",
+ ":metadata",
"//runtime/src/main/java/dev/cel/runtime",
"//runtime/src/main/java/dev/cel/runtime:descriptor_message_provider",
"//runtime/src/main/java/dev/cel/runtime:function_overload",
- "//runtime/src/main/java/dev/cel/runtime:metadata",
"//runtime/src/main/java/dev/cel/runtime:runtime_type_provider",
],
)
@@ -249,3 +249,9 @@ cel_android_library(
name = "program_android",
exports = ["//runtime/src/main/java/dev/cel/runtime:program_android"],
)
+
+java_library(
+ name = "metadata",
+ visibility = ["//:internal"],
+ exports = ["//runtime/src/main/java/dev/cel/runtime:metadata"],
+)
diff --git a/runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel b/runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel
index 3ad0c0173..e7d7b8bdd 100644
--- a/runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel
+++ b/runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel
@@ -14,6 +14,7 @@ java_library(
],
deps = [
":attribute",
+ ":error_metadata",
":eval_and",
":eval_attribute",
":eval_conditional",
@@ -25,6 +26,7 @@ java_library(
":eval_unary",
":eval_var_args_call",
":eval_zero_arity",
+ ":planned_interpretable",
":planned_program",
"//:auto_value",
"//common:cel_ast",
@@ -51,6 +53,9 @@ java_library(
name = "planned_program",
srcs = ["PlannedProgram.java"],
deps = [
+ ":error_metadata",
+ ":planned_interpretable",
+ ":strict_error_exception",
"//:auto_value",
"//common:runtime_exception",
"//common/values",
@@ -68,6 +73,7 @@ java_library(
name = "eval_const",
srcs = ["EvalConstant.java"],
deps = [
+ ":planned_interpretable",
"//common/values",
"//common/values:cel_byte_string",
"//runtime:evaluation_listener",
@@ -99,6 +105,7 @@ java_library(
srcs = ["EvalAttribute.java"],
deps = [
":attribute",
+ ":planned_interpretable",
"//runtime:evaluation_listener",
"//runtime:function_resolver",
"//runtime:interpretable",
@@ -111,6 +118,7 @@ java_library(
name = "eval_zero_arity",
srcs = ["EvalZeroArity.java"],
deps = [
+ ":planned_interpretable",
"//runtime:evaluation_exception",
"//runtime:evaluation_listener",
"//runtime:function_resolver",
@@ -123,6 +131,8 @@ java_library(
name = "eval_unary",
srcs = ["EvalUnary.java"],
deps = [
+ ":eval_helpers",
+ ":planned_interpretable",
"//runtime:evaluation_exception",
"//runtime:evaluation_listener",
"//runtime:function_resolver",
@@ -135,6 +145,8 @@ java_library(
name = "eval_var_args_call",
srcs = ["EvalVarArgsCall.java"],
deps = [
+ ":eval_helpers",
+ ":planned_interpretable",
"//runtime:evaluation_exception",
"//runtime:evaluation_listener",
"//runtime:function_resolver",
@@ -148,6 +160,7 @@ java_library(
srcs = ["EvalOr.java"],
deps = [
":eval_helpers",
+ ":planned_interpretable",
"//common/values",
"//runtime:evaluation_listener",
"//runtime:function_resolver",
@@ -161,6 +174,7 @@ java_library(
srcs = ["EvalAnd.java"],
deps = [
":eval_helpers",
+ ":planned_interpretable",
"//common/values",
"//runtime:evaluation_listener",
"//runtime:function_resolver",
@@ -173,6 +187,7 @@ java_library(
name = "eval_conditional",
srcs = ["EvalConditional.java"],
deps = [
+ ":planned_interpretable",
"//runtime:evaluation_exception",
"//runtime:evaluation_listener",
"//runtime:function_resolver",
@@ -185,6 +200,7 @@ java_library(
name = "eval_create_struct",
srcs = ["EvalCreateStruct.java"],
deps = [
+ ":planned_interpretable",
"//common/types",
"//common/values",
"//common/values:cel_value_provider",
@@ -201,6 +217,7 @@ java_library(
name = "eval_create_list",
srcs = ["EvalCreateList.java"],
deps = [
+ ":planned_interpretable",
"//runtime:evaluation_exception",
"//runtime:evaluation_listener",
"//runtime:function_resolver",
@@ -214,6 +231,7 @@ java_library(
name = "eval_create_map",
srcs = ["EvalCreateMap.java"],
deps = [
+ ":planned_interpretable",
"//runtime:evaluation_exception",
"//runtime:evaluation_listener",
"//runtime:function_resolver",
@@ -227,7 +245,39 @@ java_library(
name = "eval_helpers",
srcs = ["EvalHelpers.java"],
deps = [
+ ":planned_interpretable",
+ ":strict_error_exception",
+ "//common:error_codes",
+ "//common:runtime_exception",
"//common/values",
"//runtime:interpretable",
],
)
+
+java_library(
+ name = "strict_error_exception",
+ srcs = ["StrictErrorException.java"],
+ deps = [
+ "//common:error_codes",
+ "//common:runtime_exception",
+ ],
+)
+
+java_library(
+ name = "error_metadata",
+ srcs = ["ErrorMetadata.java"],
+ deps = [
+ "//runtime:metadata",
+ "@maven//:com_google_errorprone_error_prone_annotations",
+ "@maven//:com_google_guava_guava",
+ ],
+)
+
+java_library(
+ name = "planned_interpretable",
+ srcs = ["PlannedInterpretable.java"],
+ deps = [
+ "//runtime:interpretable",
+ "@maven//:com_google_errorprone_error_prone_annotations",
+ ],
+)
diff --git a/runtime/src/main/java/dev/cel/runtime/planner/ErrorMetadata.java b/runtime/src/main/java/dev/cel/runtime/planner/ErrorMetadata.java
new file mode 100644
index 000000000..2358bd0f9
--- /dev/null
+++ b/runtime/src/main/java/dev/cel/runtime/planner/ErrorMetadata.java
@@ -0,0 +1,52 @@
+// 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.planner;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.errorprone.annotations.Immutable;
+import dev.cel.runtime.Metadata;
+
+@Immutable
+final class ErrorMetadata implements Metadata {
+
+ private final ImmutableMap exprIdToPositionMap;
+ private final String location;
+
+ @Override
+ public String getLocation() {
+ return location;
+ }
+
+ @Override
+ public int getPosition(long exprId) {
+ return exprIdToPositionMap.getOrDefault(exprId, 0);
+ }
+
+ @Override
+ public boolean hasPosition(long exprId) {
+ return exprIdToPositionMap.containsKey(exprId);
+ }
+
+ static ErrorMetadata create(ImmutableMap exprIdToPositionMap, String location) {
+ return new ErrorMetadata(exprIdToPositionMap, location);
+ }
+
+ private ErrorMetadata(ImmutableMap exprIdToPositionMap, String location) {
+ this.exprIdToPositionMap = checkNotNull(exprIdToPositionMap);
+ this.location = checkNotNull(location);
+ }
+}
diff --git a/runtime/src/main/java/dev/cel/runtime/planner/EvalAnd.java b/runtime/src/main/java/dev/cel/runtime/planner/EvalAnd.java
index 4bf9af517..a3a39ce8a 100644
--- a/runtime/src/main/java/dev/cel/runtime/planner/EvalAnd.java
+++ b/runtime/src/main/java/dev/cel/runtime/planner/EvalAnd.java
@@ -21,17 +21,16 @@
import dev.cel.runtime.CelEvaluationListener;
import dev.cel.runtime.CelFunctionResolver;
import dev.cel.runtime.GlobalResolver;
-import dev.cel.runtime.Interpretable;
-final class EvalAnd implements Interpretable {
+final class EvalAnd extends PlannedInterpretable {
@SuppressWarnings("Immutable")
- private final Interpretable[] args;
+ private final PlannedInterpretable[] args;
@Override
public Object eval(GlobalResolver resolver) {
ErrorValue errorValue = null;
- for (Interpretable arg : args) {
+ for (PlannedInterpretable arg : args) {
Object argVal = evalNonstrictly(arg, resolver);
if (argVal instanceof Boolean) {
// Short-circuit on false
@@ -75,11 +74,12 @@ public Object eval(
throw new UnsupportedOperationException("Not yet supported");
}
- static EvalAnd create(Interpretable[] args) {
- return new EvalAnd(args);
+ static EvalAnd create(long exprId, PlannedInterpretable[] args) {
+ return new EvalAnd(exprId, args);
}
- private EvalAnd(Interpretable[] args) {
+ private EvalAnd(long exprId, PlannedInterpretable[] args) {
+ super(exprId);
Preconditions.checkArgument(args.length == 2);
this.args = args;
}
diff --git a/runtime/src/main/java/dev/cel/runtime/planner/EvalAttribute.java b/runtime/src/main/java/dev/cel/runtime/planner/EvalAttribute.java
index 23b7fa63c..6e20d4f59 100644
--- a/runtime/src/main/java/dev/cel/runtime/planner/EvalAttribute.java
+++ b/runtime/src/main/java/dev/cel/runtime/planner/EvalAttribute.java
@@ -18,10 +18,9 @@
import dev.cel.runtime.CelEvaluationListener;
import dev.cel.runtime.CelFunctionResolver;
import dev.cel.runtime.GlobalResolver;
-import dev.cel.runtime.Interpretable;
@Immutable
-final class EvalAttribute implements Interpretable {
+final class EvalAttribute extends PlannedInterpretable {
private final Attribute attr;
@@ -47,15 +46,15 @@ public Object eval(
GlobalResolver resolver,
CelFunctionResolver lateBoundFunctionResolver,
CelEvaluationListener listener) {
- // TODO: Implement support
throw new UnsupportedOperationException("Not yet supported");
}
- static EvalAttribute create(Attribute attr) {
- return new EvalAttribute(attr);
+ static EvalAttribute create(long exprId, Attribute attr) {
+ return new EvalAttribute(exprId, attr);
}
- private EvalAttribute(Attribute attr) {
+ private EvalAttribute(long exprId, Attribute attr) {
+ super(exprId);
this.attr = attr;
}
}
diff --git a/runtime/src/main/java/dev/cel/runtime/planner/EvalConditional.java b/runtime/src/main/java/dev/cel/runtime/planner/EvalConditional.java
index ca32b405c..4445d3e71 100644
--- a/runtime/src/main/java/dev/cel/runtime/planner/EvalConditional.java
+++ b/runtime/src/main/java/dev/cel/runtime/planner/EvalConditional.java
@@ -21,7 +21,7 @@
import dev.cel.runtime.GlobalResolver;
import dev.cel.runtime.Interpretable;
-final class EvalConditional implements Interpretable {
+final class EvalConditional extends PlannedInterpretable {
@SuppressWarnings("Immutable")
private final Interpretable[] args;
@@ -67,11 +67,12 @@ public Object eval(
throw new UnsupportedOperationException("Not yet supported");
}
- static EvalConditional create(Interpretable[] args) {
- return new EvalConditional(args);
+ static EvalConditional create(long exprId, Interpretable[] args) {
+ return new EvalConditional(exprId, args);
}
- private EvalConditional(Interpretable[] args) {
+ private EvalConditional(long exprId, Interpretable[] args) {
+ super(exprId);
Preconditions.checkArgument(args.length == 3);
this.args = args;
}
diff --git a/runtime/src/main/java/dev/cel/runtime/planner/EvalConstant.java b/runtime/src/main/java/dev/cel/runtime/planner/EvalConstant.java
index a1a2dc998..408d04046 100644
--- a/runtime/src/main/java/dev/cel/runtime/planner/EvalConstant.java
+++ b/runtime/src/main/java/dev/cel/runtime/planner/EvalConstant.java
@@ -21,10 +21,9 @@
import dev.cel.runtime.CelEvaluationListener;
import dev.cel.runtime.CelFunctionResolver;
import dev.cel.runtime.GlobalResolver;
-import dev.cel.runtime.Interpretable;
@Immutable
-final class EvalConstant implements Interpretable {
+final class EvalConstant extends PlannedInterpretable {
// Pre-allocation of common constants
private static final EvalConstant NULL_VALUE = new EvalConstant(NullValue.NULL_VALUE);
@@ -115,6 +114,7 @@ static EvalConstant create(Object value) {
}
private EvalConstant(Object constant) {
+ super(/* exprId= */ -1); // It's not possible to throw while evaluating a constant
this.constant = constant;
}
}
diff --git a/runtime/src/main/java/dev/cel/runtime/planner/EvalCreateList.java b/runtime/src/main/java/dev/cel/runtime/planner/EvalCreateList.java
index 6a1917475..4ec275eef 100644
--- a/runtime/src/main/java/dev/cel/runtime/planner/EvalCreateList.java
+++ b/runtime/src/main/java/dev/cel/runtime/planner/EvalCreateList.java
@@ -23,7 +23,7 @@
import dev.cel.runtime.Interpretable;
@Immutable
-final class EvalCreateList implements Interpretable {
+final class EvalCreateList extends PlannedInterpretable {
// Array contents are not mutated
@SuppressWarnings("Immutable")
@@ -51,17 +51,20 @@ public Object eval(GlobalResolver resolver, CelFunctionResolver lateBoundFunctio
}
@Override
- public Object eval(GlobalResolver resolver, CelFunctionResolver lateBoundFunctionResolver,
+ public Object eval(
+ GlobalResolver resolver,
+ CelFunctionResolver lateBoundFunctionResolver,
CelEvaluationListener listener) {
// TODO: Implement support
throw new UnsupportedOperationException("Not yet supported");
}
- static EvalCreateList create(Interpretable[] values) {
- return new EvalCreateList(values);
+ static EvalCreateList create(long exprId, Interpretable[] values) {
+ return new EvalCreateList(exprId, values);
}
- private EvalCreateList(Interpretable[] values) {
+ private EvalCreateList(long exprId, Interpretable[] values) {
+ super(exprId);
this.values = values;
}
}
diff --git a/runtime/src/main/java/dev/cel/runtime/planner/EvalCreateMap.java b/runtime/src/main/java/dev/cel/runtime/planner/EvalCreateMap.java
index a3fb2cb85..38d690303 100644
--- a/runtime/src/main/java/dev/cel/runtime/planner/EvalCreateMap.java
+++ b/runtime/src/main/java/dev/cel/runtime/planner/EvalCreateMap.java
@@ -24,7 +24,7 @@
import dev.cel.runtime.Interpretable;
@Immutable
-final class EvalCreateMap implements Interpretable {
+final class EvalCreateMap extends PlannedInterpretable {
// Array contents are not mutated
@SuppressWarnings("Immutable")
@@ -58,18 +58,20 @@ public Object eval(GlobalResolver resolver, CelFunctionResolver lateBoundFunctio
}
@Override
- public Object eval(GlobalResolver resolver, CelFunctionResolver lateBoundFunctionResolver,
+ public Object eval(
+ GlobalResolver resolver,
+ CelFunctionResolver lateBoundFunctionResolver,
CelEvaluationListener listener) {
// TODO: Implement support
throw new UnsupportedOperationException("Not yet supported");
}
-
- static EvalCreateMap create(Interpretable[] keys, Interpretable[] values) {
- return new EvalCreateMap(keys, values);
+ static EvalCreateMap create(long exprId, Interpretable[] keys, Interpretable[] values) {
+ return new EvalCreateMap(exprId, keys, values);
}
- private EvalCreateMap(Interpretable[] keys, Interpretable[] values) {
+ private EvalCreateMap(long exprId, Interpretable[] keys, Interpretable[] values) {
+ super(exprId);
Preconditions.checkArgument(keys.length == values.length);
this.keys = keys;
this.values = values;
diff --git a/runtime/src/main/java/dev/cel/runtime/planner/EvalCreateStruct.java b/runtime/src/main/java/dev/cel/runtime/planner/EvalCreateStruct.java
index 5c1f1d77b..7553add80 100644
--- a/runtime/src/main/java/dev/cel/runtime/planner/EvalCreateStruct.java
+++ b/runtime/src/main/java/dev/cel/runtime/planner/EvalCreateStruct.java
@@ -28,7 +28,7 @@
import java.util.Map;
@Immutable
-final class EvalCreateStruct implements Interpretable {
+final class EvalCreateStruct extends PlannedInterpretable {
private final CelValueProvider valueProvider;
private final StructType structType;
@@ -84,18 +84,21 @@ public Object eval(
}
static EvalCreateStruct create(
+ long exprId,
CelValueProvider valueProvider,
StructType structType,
String[] keys,
Interpretable[] values) {
- return new EvalCreateStruct(valueProvider, structType, keys, values);
+ return new EvalCreateStruct(exprId, valueProvider, structType, keys, values);
}
private EvalCreateStruct(
+ long exprId,
CelValueProvider valueProvider,
StructType structType,
String[] keys,
Interpretable[] values) {
+ super(exprId);
this.valueProvider = valueProvider;
this.structType = structType;
this.keys = keys;
diff --git a/runtime/src/main/java/dev/cel/runtime/planner/EvalHelpers.java b/runtime/src/main/java/dev/cel/runtime/planner/EvalHelpers.java
index 82bd6124a..3b5bda1bc 100644
--- a/runtime/src/main/java/dev/cel/runtime/planner/EvalHelpers.java
+++ b/runtime/src/main/java/dev/cel/runtime/planner/EvalHelpers.java
@@ -14,17 +14,33 @@
package dev.cel.runtime.planner;
+import dev.cel.common.CelErrorCode;
+import dev.cel.common.CelRuntimeException;
import dev.cel.common.values.ErrorValue;
import dev.cel.runtime.GlobalResolver;
-import dev.cel.runtime.Interpretable;
final class EvalHelpers {
- static Object evalNonstrictly(Interpretable interpretable, GlobalResolver resolver) {
+ static Object evalNonstrictly(PlannedInterpretable interpretable, GlobalResolver resolver) {
try {
return interpretable.eval(resolver);
+ } catch (StrictErrorException e) {
+ // Intercept the strict exception to get a more localized expr ID for error reporting purposes
+ // Example: foo [1] && strict_err [2] -> ID 2 is propagated.
+ return ErrorValue.create(e.exprId(), e);
} catch (Exception e) {
- return ErrorValue.create(e);
+ return ErrorValue.create(interpretable.exprId(), e);
+ }
+ }
+
+ static Object evalStrictly(PlannedInterpretable interpretable, GlobalResolver resolver) {
+ try {
+ return interpretable.eval(resolver);
+ } catch (CelRuntimeException e) {
+ throw new StrictErrorException(e, interpretable.exprId());
+ } catch (Exception e) {
+ throw new StrictErrorException(
+ e.getCause(), CelErrorCode.INTERNAL_ERROR, interpretable.exprId());
}
}
diff --git a/runtime/src/main/java/dev/cel/runtime/planner/EvalOr.java b/runtime/src/main/java/dev/cel/runtime/planner/EvalOr.java
index afa02dfb8..f287bdd59 100644
--- a/runtime/src/main/java/dev/cel/runtime/planner/EvalOr.java
+++ b/runtime/src/main/java/dev/cel/runtime/planner/EvalOr.java
@@ -21,17 +21,16 @@
import dev.cel.runtime.CelEvaluationListener;
import dev.cel.runtime.CelFunctionResolver;
import dev.cel.runtime.GlobalResolver;
-import dev.cel.runtime.Interpretable;
-final class EvalOr implements Interpretable {
+final class EvalOr extends PlannedInterpretable {
@SuppressWarnings("Immutable")
- private final Interpretable[] args;
+ private final PlannedInterpretable[] args;
@Override
public Object eval(GlobalResolver resolver) {
ErrorValue errorValue = null;
- for (Interpretable arg : args) {
+ for (PlannedInterpretable arg : args) {
Object argVal = evalNonstrictly(arg, resolver);
if (argVal instanceof Boolean) {
// Short-circuit on true
@@ -75,11 +74,12 @@ public Object eval(
throw new UnsupportedOperationException("Not yet supported");
}
- static EvalOr create(Interpretable[] args) {
- return new EvalOr(args);
+ static EvalOr create(long exprId, PlannedInterpretable[] args) {
+ return new EvalOr(exprId, args);
}
- private EvalOr(Interpretable[] args) {
+ private EvalOr(long exprId, PlannedInterpretable[] args) {
+ super(exprId);
Preconditions.checkArgument(args.length == 2);
this.args = args;
}
diff --git a/runtime/src/main/java/dev/cel/runtime/planner/EvalUnary.java b/runtime/src/main/java/dev/cel/runtime/planner/EvalUnary.java
index c6daff4b2..13b59d11e 100644
--- a/runtime/src/main/java/dev/cel/runtime/planner/EvalUnary.java
+++ b/runtime/src/main/java/dev/cel/runtime/planner/EvalUnary.java
@@ -14,21 +14,24 @@
package dev.cel.runtime.planner;
+import static dev.cel.runtime.planner.EvalHelpers.evalNonstrictly;
+import static dev.cel.runtime.planner.EvalHelpers.evalStrictly;
+
import dev.cel.runtime.CelEvaluationException;
import dev.cel.runtime.CelEvaluationListener;
import dev.cel.runtime.CelFunctionResolver;
import dev.cel.runtime.CelResolvedOverload;
import dev.cel.runtime.GlobalResolver;
-import dev.cel.runtime.Interpretable;
-final class EvalUnary implements Interpretable {
+final class EvalUnary extends PlannedInterpretable {
private final CelResolvedOverload resolvedOverload;
- private final Interpretable arg;
+ private final PlannedInterpretable arg;
@Override
public Object eval(GlobalResolver resolver) throws CelEvaluationException {
- Object argVal = arg.eval(resolver);
+ Object argVal =
+ resolvedOverload.isStrict() ? evalStrictly(arg, resolver) : evalNonstrictly(arg, resolver);
Object[] arguments = new Object[] {argVal};
return resolvedOverload.getDefinition().apply(arguments);
@@ -55,11 +58,13 @@ public Object eval(
throw new UnsupportedOperationException("Not yet supported");
}
- static EvalUnary create(CelResolvedOverload resolvedOverload, Interpretable arg) {
- return new EvalUnary(resolvedOverload, arg);
+ static EvalUnary create(
+ long exprId, CelResolvedOverload resolvedOverload, PlannedInterpretable arg) {
+ return new EvalUnary(exprId, resolvedOverload, arg);
}
- private EvalUnary(CelResolvedOverload resolvedOverload, Interpretable arg) {
+ private EvalUnary(long exprId, CelResolvedOverload resolvedOverload, PlannedInterpretable arg) {
+ super(exprId);
this.resolvedOverload = resolvedOverload;
this.arg = arg;
}
diff --git a/runtime/src/main/java/dev/cel/runtime/planner/EvalVarArgsCall.java b/runtime/src/main/java/dev/cel/runtime/planner/EvalVarArgsCall.java
index 48fc7ba04..a2a4c0acc 100644
--- a/runtime/src/main/java/dev/cel/runtime/planner/EvalVarArgsCall.java
+++ b/runtime/src/main/java/dev/cel/runtime/planner/EvalVarArgsCall.java
@@ -14,25 +14,30 @@
package dev.cel.runtime.planner;
+import static dev.cel.runtime.planner.EvalHelpers.evalNonstrictly;
+import static dev.cel.runtime.planner.EvalHelpers.evalStrictly;
+
import dev.cel.runtime.CelEvaluationException;
import dev.cel.runtime.CelEvaluationListener;
import dev.cel.runtime.CelFunctionResolver;
import dev.cel.runtime.CelResolvedOverload;
import dev.cel.runtime.GlobalResolver;
-import dev.cel.runtime.Interpretable;
@SuppressWarnings("Immutable")
-final class EvalVarArgsCall implements Interpretable {
+final class EvalVarArgsCall extends PlannedInterpretable {
private final CelResolvedOverload resolvedOverload;
- private final Interpretable[] args;
+ private final PlannedInterpretable[] args;
@Override
public Object eval(GlobalResolver resolver) throws CelEvaluationException {
Object[] argVals = new Object[args.length];
for (int i = 0; i < args.length; i++) {
- Interpretable arg = args[i];
- argVals[i] = arg.eval(resolver);
+ PlannedInterpretable arg = args[i];
+ argVals[i] =
+ resolvedOverload.isStrict()
+ ? evalStrictly(arg, resolver)
+ : evalNonstrictly(arg, resolver);
}
return resolvedOverload.getDefinition().apply(argVals);
@@ -59,11 +64,14 @@ public Object eval(
throw new UnsupportedOperationException("Not yet supported");
}
- static EvalVarArgsCall create(CelResolvedOverload resolvedOverload, Interpretable[] args) {
- return new EvalVarArgsCall(resolvedOverload, args);
+ static EvalVarArgsCall create(
+ long exprId, CelResolvedOverload resolvedOverload, PlannedInterpretable[] args) {
+ return new EvalVarArgsCall(exprId, resolvedOverload, args);
}
- private EvalVarArgsCall(CelResolvedOverload resolvedOverload, Interpretable[] args) {
+ private EvalVarArgsCall(
+ long exprId, CelResolvedOverload resolvedOverload, PlannedInterpretable[] args) {
+ super(exprId);
this.resolvedOverload = resolvedOverload;
this.args = args;
}
diff --git a/runtime/src/main/java/dev/cel/runtime/planner/EvalZeroArity.java b/runtime/src/main/java/dev/cel/runtime/planner/EvalZeroArity.java
index 813b84629..628e4a70f 100644
--- a/runtime/src/main/java/dev/cel/runtime/planner/EvalZeroArity.java
+++ b/runtime/src/main/java/dev/cel/runtime/planner/EvalZeroArity.java
@@ -19,10 +19,8 @@
import dev.cel.runtime.CelFunctionResolver;
import dev.cel.runtime.CelResolvedOverload;
import dev.cel.runtime.GlobalResolver;
-import dev.cel.runtime.Interpretable;
-
-final class EvalZeroArity implements Interpretable {
+final class EvalZeroArity extends PlannedInterpretable {
private static final Object[] EMPTY_ARRAY = new Object[0];
private final CelResolvedOverload resolvedOverload;
@@ -53,11 +51,12 @@ public Object eval(
throw new UnsupportedOperationException("Not yet supported");
}
- static EvalZeroArity create(CelResolvedOverload resolvedOverload) {
- return new EvalZeroArity(resolvedOverload);
+ static EvalZeroArity create(long exprId, CelResolvedOverload resolvedOverload) {
+ return new EvalZeroArity(exprId, resolvedOverload);
}
- private EvalZeroArity(CelResolvedOverload resolvedOverload) {
+ private EvalZeroArity(long exprId, CelResolvedOverload resolvedOverload) {
+ super(exprId);
this.resolvedOverload = resolvedOverload;
}
}
diff --git a/runtime/src/main/java/dev/cel/runtime/planner/PlannedInterpretable.java b/runtime/src/main/java/dev/cel/runtime/planner/PlannedInterpretable.java
new file mode 100644
index 000000000..87a1a7dc4
--- /dev/null
+++ b/runtime/src/main/java/dev/cel/runtime/planner/PlannedInterpretable.java
@@ -0,0 +1,31 @@
+// 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.planner;
+
+import com.google.errorprone.annotations.Immutable;
+import dev.cel.runtime.Interpretable;
+
+@Immutable
+abstract class PlannedInterpretable implements Interpretable {
+ private final long exprId;
+
+ long exprId() {
+ return exprId;
+ }
+
+ PlannedInterpretable(long exprId) {
+ this.exprId = exprId;
+ }
+}
diff --git a/runtime/src/main/java/dev/cel/runtime/planner/PlannedProgram.java b/runtime/src/main/java/dev/cel/runtime/planner/PlannedProgram.java
index 2c0d402c2..d1214fab0 100644
--- a/runtime/src/main/java/dev/cel/runtime/planner/PlannedProgram.java
+++ b/runtime/src/main/java/dev/cel/runtime/planner/PlannedProgram.java
@@ -23,14 +23,15 @@
import dev.cel.runtime.CelEvaluationExceptionBuilder;
import dev.cel.runtime.CelFunctionResolver;
import dev.cel.runtime.GlobalResolver;
-import dev.cel.runtime.Interpretable;
import dev.cel.runtime.Program;
import java.util.Map;
@Immutable
@AutoValue
abstract class PlannedProgram implements Program {
- abstract Interpretable interpretable();
+ abstract PlannedInterpretable interpretable();
+
+ abstract ErrorMetadata metadata();
@Override
public Object eval() throws CelEvaluationException {
@@ -48,34 +49,36 @@ public Object eval(Map mapValue, CelFunctionResolver lateBoundFunctio
throw new UnsupportedOperationException("Late bound functions not supported yet");
}
- private Object evalOrThrow(Interpretable interpretable, GlobalResolver resolver)
+ private Object evalOrThrow(PlannedInterpretable interpretable, GlobalResolver resolver)
throws CelEvaluationException {
try {
Object evalResult = interpretable.eval(resolver);
if (evalResult instanceof ErrorValue) {
ErrorValue errorValue = (ErrorValue) evalResult;
- throw newCelEvaluationException(errorValue.value());
+ throw newCelEvaluationException(errorValue.exprId(), errorValue.value());
}
return evalResult;
} catch (RuntimeException e) {
- throw newCelEvaluationException(e);
+ throw newCelEvaluationException(interpretable.exprId(), e);
}
}
- private static CelEvaluationException newCelEvaluationException(Exception e) {
+ private CelEvaluationException newCelEvaluationException(long exprId, Exception e) {
CelEvaluationExceptionBuilder builder;
- if (e instanceof CelRuntimeException) {
+ if (e instanceof StrictErrorException) {
// Preserve detailed error, including error codes if one exists.
+ builder = CelEvaluationExceptionBuilder.newBuilder((CelRuntimeException) e.getCause());
+ } else if (e instanceof CelRuntimeException) {
builder = CelEvaluationExceptionBuilder.newBuilder((CelRuntimeException) e);
} else {
builder = CelEvaluationExceptionBuilder.newBuilder(e.getMessage()).setCause(e);
}
- return builder.build();
+ return builder.setMetadata(metadata(), exprId).build();
}
- static Program create(Interpretable interpretable) {
- return new AutoValue_PlannedProgram(interpretable);
+ static Program create(PlannedInterpretable interpretable, ErrorMetadata metadata) {
+ return new AutoValue_PlannedProgram(interpretable, metadata);
}
}
diff --git a/runtime/src/main/java/dev/cel/runtime/planner/ProgramPlanner.java b/runtime/src/main/java/dev/cel/runtime/planner/ProgramPlanner.java
index bf7729c0f..252695ed7 100644
--- a/runtime/src/main/java/dev/cel/runtime/planner/ProgramPlanner.java
+++ b/runtime/src/main/java/dev/cel/runtime/planner/ProgramPlanner.java
@@ -66,17 +66,19 @@ public final class ProgramPlanner {
* CelAbstractSyntaxTree}.
*/
public Program plan(CelAbstractSyntaxTree ast) throws CelEvaluationException {
- Interpretable plannedInterpretable;
+ PlannedInterpretable plannedInterpretable;
try {
plannedInterpretable = plan(ast.getExpr(), PlannerContext.create(ast));
} catch (RuntimeException e) {
throw CelEvaluationExceptionBuilder.newBuilder(e.getMessage()).setCause(e).build();
}
- return PlannedProgram.create(plannedInterpretable);
+ ErrorMetadata errorMetadata =
+ ErrorMetadata.create(ast.getSource().getPositionsMap(), ast.getSource().getDescription());
+ return PlannedProgram.create(plannedInterpretable, errorMetadata);
}
- private Interpretable plan(CelExpr celExpr, PlannerContext ctx) {
+ private PlannedInterpretable plan(CelExpr celExpr, PlannerContext ctx) {
switch (celExpr.getKind()) {
case CONSTANT:
return planConstant(celExpr.constant());
@@ -97,7 +99,7 @@ private Interpretable plan(CelExpr celExpr, PlannerContext ctx) {
}
}
- private Interpretable planConstant(CelConstant celConstant) {
+ private PlannedInterpretable planConstant(CelConstant celConstant) {
switch (celConstant.getKind()) {
case NULL_VALUE:
return EvalConstant.create(celConstant.nullValue());
@@ -118,16 +120,17 @@ private Interpretable planConstant(CelConstant celConstant) {
}
}
- private Interpretable planIdent(CelExpr celExpr, PlannerContext ctx) {
+ private PlannedInterpretable planIdent(CelExpr celExpr, PlannerContext ctx) {
CelReference ref = ctx.referenceMap().get(celExpr.id());
if (ref != null) {
return planCheckedIdent(celExpr.id(), ref, ctx.typeMap());
}
- return EvalAttribute.create(attributeFactory.newMaybeAttribute(celExpr.ident().name()));
+ return EvalAttribute.create(
+ celExpr.id(), attributeFactory.newMaybeAttribute(celExpr.ident().name()));
}
- private Interpretable planCheckedIdent(
+ private PlannedInterpretable planCheckedIdent(
long id, CelReference identRef, ImmutableMap typeMap) {
if (identRef.value().isPresent()) {
return planConstant(identRef.value().get());
@@ -146,10 +149,10 @@ private Interpretable planCheckedIdent(
return EvalConstant.create(identType);
}
- return EvalAttribute.create(attributeFactory.newAbsoluteAttribute(identRef.name()));
+ return EvalAttribute.create(id, attributeFactory.newAbsoluteAttribute(identRef.name()));
}
- private Interpretable planCall(CelExpr expr, PlannerContext ctx) {
+ private PlannedInterpretable planCall(CelExpr expr, PlannerContext ctx) {
ResolvedFunction resolvedFunction = resolveFunction(expr, ctx.referenceMap());
CelExpr target = resolvedFunction.target().orElse(null);
int argCount = expr.call().args().size();
@@ -157,7 +160,7 @@ private Interpretable planCall(CelExpr expr, PlannerContext ctx) {
argCount++;
}
- Interpretable[] evaluatedArgs = new Interpretable[argCount];
+ PlannedInterpretable[] evaluatedArgs = new PlannedInterpretable[argCount];
int offset = 0;
if (target != null) {
@@ -175,11 +178,11 @@ private Interpretable planCall(CelExpr expr, PlannerContext ctx) {
if (operator != null) {
switch (operator) {
case LOGICAL_OR:
- return EvalOr.create(evaluatedArgs);
+ return EvalOr.create(expr.id(), evaluatedArgs);
case LOGICAL_AND:
- return EvalAnd.create(evaluatedArgs);
+ return EvalAnd.create(expr.id(), evaluatedArgs);
case CONDITIONAL:
- return EvalConditional.create(evaluatedArgs);
+ return EvalConditional.create(expr.id(), evaluatedArgs);
default:
// fall-through
}
@@ -200,15 +203,15 @@ private Interpretable planCall(CelExpr expr, PlannerContext ctx) {
switch (argCount) {
case 0:
- return EvalZeroArity.create(resolvedOverload);
+ return EvalZeroArity.create(expr.id(), resolvedOverload);
case 1:
- return EvalUnary.create(resolvedOverload, evaluatedArgs[0]);
+ return EvalUnary.create(expr.id(), resolvedOverload, evaluatedArgs[0]);
default:
- return EvalVarArgsCall.create(resolvedOverload, evaluatedArgs);
+ return EvalVarArgsCall.create(expr.id(), resolvedOverload, evaluatedArgs);
}
}
- private Interpretable planCreateStruct(CelExpr celExpr, PlannerContext ctx) {
+ private PlannedInterpretable planCreateStruct(CelExpr celExpr, PlannerContext ctx) {
CelStruct struct = celExpr.struct();
StructType structType = resolveStructType(struct);
@@ -222,28 +225,28 @@ private Interpretable planCreateStruct(CelExpr celExpr, PlannerContext ctx) {
values[i] = plan(entry.value(), ctx);
}
- return EvalCreateStruct.create(valueProvider, structType, keys, values);
+ return EvalCreateStruct.create(celExpr.id(), valueProvider, structType, keys, values);
}
- private Interpretable planCreateList(CelExpr celExpr, PlannerContext ctx) {
+ private PlannedInterpretable planCreateList(CelExpr celExpr, PlannerContext ctx) {
CelList list = celExpr.list();
ImmutableList elements = list.elements();
- Interpretable[] values = new Interpretable[elements.size()];
+ PlannedInterpretable[] values = new PlannedInterpretable[elements.size()];
for (int i = 0; i < elements.size(); i++) {
values[i] = plan(elements.get(i), ctx);
}
- return EvalCreateList.create(values);
+ return EvalCreateList.create(celExpr.id(), values);
}
- private Interpretable planCreateMap(CelExpr celExpr, PlannerContext ctx) {
+ private PlannedInterpretable planCreateMap(CelExpr celExpr, PlannerContext ctx) {
CelMap map = celExpr.map();
ImmutableList entries = map.entries();
- Interpretable[] keys = new Interpretable[entries.size()];
- Interpretable[] values = new Interpretable[entries.size()];
+ PlannedInterpretable[] keys = new PlannedInterpretable[entries.size()];
+ PlannedInterpretable[] values = new PlannedInterpretable[entries.size()];
for (int i = 0; i < entries.size(); i++) {
CelMap.Entry entry = entries.get(i);
@@ -251,7 +254,7 @@ private Interpretable planCreateMap(CelExpr celExpr, PlannerContext ctx) {
values[i] = plan(entry.value(), ctx);
}
- return EvalCreateMap.create(keys, values);
+ return EvalCreateMap.create(celExpr.id(), keys, values);
}
/**
diff --git a/runtime/src/main/java/dev/cel/runtime/planner/StrictErrorException.java b/runtime/src/main/java/dev/cel/runtime/planner/StrictErrorException.java
new file mode 100644
index 000000000..3acd6ff27
--- /dev/null
+++ b/runtime/src/main/java/dev/cel/runtime/planner/StrictErrorException.java
@@ -0,0 +1,42 @@
+// 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.planner;
+
+import dev.cel.common.CelErrorCode;
+import dev.cel.common.CelRuntimeException;
+
+/**
+ * An exception that's raised when a strict call failed to invoke, which includes the source of
+ * expression ID, along with canonical CelErrorCode.
+ *
+ *
Note that StrictErrorException should not be surfaced directly back to the user.
+ */
+final class StrictErrorException extends CelRuntimeException {
+
+ private final long exprId;
+
+ long exprId() {
+ return exprId;
+ }
+
+ StrictErrorException(CelRuntimeException cause, long exprId) {
+ this(cause, cause.getErrorCode(), exprId);
+ }
+
+ StrictErrorException(Throwable cause, CelErrorCode errorCode, long exprId) {
+ super(cause, errorCode);
+ this.exprId = exprId;
+ }
+}
diff --git a/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java b/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java
index 205e2ef8b..bb580cbb3 100644
--- a/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java
+++ b/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java
@@ -421,7 +421,7 @@ public void plan_call_throws() throws Exception {
Program program = PLANNER.plan(ast);
CelEvaluationException e = assertThrows(CelEvaluationException.class, program::eval);
- assertThat(e).hasMessageThat().contains("evaluation error: Intentional error");
+ assertThat(e).hasMessageThat().contains("evaluation error at :5: Intentional error");
assertThat(e).hasCauseThat().isInstanceOf(IllegalArgumentException.class);
}
@@ -514,8 +514,8 @@ public void plan_call_logicalOr_throws(String expression) throws Exception {
Program program = PLANNER.plan(ast);
CelEvaluationException e = assertThrows(CelEvaluationException.class, program::eval);
- // TODO: Tag metadata (source loc)
- assertThat(e).hasMessageThat().isEqualTo("evaluation error: / by zero");
+ assertThat(e).hasMessageThat().startsWith("evaluation error at :");
+ assertThat(e).hasMessageThat().endsWith("/ by zero");
assertThat(e).hasCauseThat().isInstanceOf(ArithmeticException.class);
assertThat(e.getErrorCode()).isEqualTo(CelErrorCode.DIVIDE_BY_ZERO);
}
@@ -546,8 +546,8 @@ public void plan_call_logicalAnd_throws(String expression) throws Exception {
Program program = PLANNER.plan(ast);
CelEvaluationException e = assertThrows(CelEvaluationException.class, program::eval);
- // TODO: Tag metadata (source loc)
- assertThat(e).hasMessageThat().isEqualTo("evaluation error: / by zero");
+ assertThat(e).hasMessageThat().startsWith("evaluation error at :");
+ assertThat(e).hasMessageThat().endsWith("/ by zero");
assertThat(e).hasCauseThat().isInstanceOf(ArithmeticException.class);
assertThat(e.getErrorCode()).isEqualTo(CelErrorCode.DIVIDE_BY_ZERO);
}
@@ -576,7 +576,8 @@ public void plan_call_conditional_throws(String expression) throws Exception {
Program program = PLANNER.plan(ast);
CelEvaluationException e = assertThrows(CelEvaluationException.class, program::eval);
- assertThat(e).hasMessageThat().isEqualTo("evaluation error: / by zero");
+ assertThat(e).hasMessageThat().startsWith("evaluation error at :");
+ assertThat(e).hasMessageThat().endsWith("/ by zero");
assertThat(e).hasCauseThat().isInstanceOf(ArithmeticException.class);
assertThat(e.getErrorCode()).isEqualTo(CelErrorCode.DIVIDE_BY_ZERO);
}