From 4aa9177bc584191fc7dcc8658cb16fb0c1f7c47a Mon Sep 17 00:00:00 2001 From: Sokwhan Huh Date: Fri, 5 Dec 2025 18:20:52 -0800 Subject: [PATCH] Support attribute qualification, plan select, allow parsed-only ident resolution on enums PiperOrigin-RevId: 840949005 --- common/BUILD.bazel | 7 - common/exceptions/BUILD.bazel | 48 +++++ .../src/main/java/dev/cel/common/BUILD.bazel | 19 +- .../java/dev/cel/common/CelException.java | 5 - .../dev/cel/common/exceptions/BUILD.bazel | 99 +++++++++ .../CelAttributeNotFoundException.java | 56 +++++ .../exceptions/CelBadFormatException.java | 31 +++ .../exceptions/CelDivideByZeroException.java | 20 +- .../CelIndexOutOfBoundsException.java | 27 +++ .../CelInvalidArgumentException.java | 27 +++ .../CelNumericOverflowException.java | 34 +++ .../{ => exceptions}/CelRuntimeException.java | 13 +- .../java/dev/cel/common/internal/BUILD.bazel | 13 +- .../cel/common/internal/DateTimeHelpers.java | 7 +- .../dev/cel/common/internal/ProtoAdapter.java | 7 +- .../cel/common/internal/ProtoLiteAdapter.java | 11 +- .../cel/common/values/CelValueConverter.java | 4 +- .../dev/cel/common/values/ErrorValue.java | 6 +- .../common/values/CelValueConverterTest.java | 9 - .../dev/cel/common/values/ErrorValueTest.java | 6 +- .../main/java/dev/cel/extensions/BUILD.bazel | 1 + .../dev/cel/extensions/CelMathExtensions.java | 9 +- .../test/java/dev/cel/extensions/BUILD.bazel | 3 +- .../CelComprehensionsExtensionsTest.java | 8 +- runtime/BUILD.bazel | 8 +- .../src/main/java/dev/cel/runtime/BUILD.bazel | 32 ++- .../CelEvaluationExceptionBuilder.java | 15 +- .../runtime/CelValueRuntimeTypeProvider.java | 15 +- .../dev/cel/runtime/DefaultInterpreter.java | 2 +- .../runtime/DescriptorMessageProvider.java | 22 +- .../java/dev/cel/runtime/RuntimeEquality.java | 7 +- .../java/dev/cel/runtime/RuntimeHelpers.java | 41 ++-- .../dev/cel/runtime/planner/Attribute.java | 55 +---- .../cel/runtime/planner/AttributeFactory.java | 33 ++- .../java/dev/cel/runtime/planner/BUILD.bazel | 96 ++++++++- .../cel/runtime/planner/ErrorMetadata.java | 52 +++++ .../java/dev/cel/runtime/planner/EvalAnd.java | 14 +- .../cel/runtime/planner/EvalAttribute.java | 23 ++- .../cel/runtime/planner/EvalConditional.java | 9 +- .../dev/cel/runtime/planner/EvalConstant.java | 4 +- .../cel/runtime/planner/EvalCreateList.java | 13 +- .../cel/runtime/planner/EvalCreateMap.java | 14 +- .../cel/runtime/planner/EvalCreateStruct.java | 7 +- .../dev/cel/runtime/planner/EvalHelpers.java | 22 +- .../java/dev/cel/runtime/planner/EvalOr.java | 14 +- .../dev/cel/runtime/planner/EvalUnary.java | 19 +- .../cel/runtime/planner/EvalVarArgsCall.java | 24 ++- .../cel/runtime/planner/EvalZeroArity.java | 11 +- .../planner/InterpretableAttribute.java | 27 +++ .../cel/runtime/planner/MaybeAttribute.java | 81 ++++++++ .../cel/runtime/planner/MissingAttribute.java | 47 +++++ .../runtime/planner/NamespacedAttribute.java | 126 +++++++++++ .../runtime/planner/PlannedInterpretable.java | 31 +++ .../cel/runtime/planner/PlannedProgram.java | 25 ++- .../cel/runtime/planner/ProgramPlanner.java | 88 +++++--- .../dev/cel/runtime/planner/Qualifier.java | 28 +++ .../runtime/planner/RelativeAttribute.java | 70 +++++++ .../runtime/planner/StrictErrorException.java | 42 ++++ .../cel/runtime/planner/StringQualifier.java | 61 ++++++ .../dev/cel/runtime/standard/AddOperator.java | 10 +- .../java/dev/cel/runtime/standard/BUILD.bazel | 98 +++------ .../cel/runtime/standard/BoolFunction.java | 11 +- .../cel/runtime/standard/DivideOperator.java | 6 +- .../cel/runtime/standard/DoubleFunction.java | 5 +- .../runtime/standard/DurationFunction.java | 7 +- .../dev/cel/runtime/standard/IntFunction.java | 18 +- .../cel/runtime/standard/MatchesFunction.java | 7 +- .../cel/runtime/standard/ModuloOperator.java | 6 +- .../runtime/standard/MultiplyOperator.java | 10 +- .../cel/runtime/standard/NegateOperator.java | 6 +- .../cel/runtime/standard/StringFunction.java | 15 +- .../runtime/standard/SubtractOperator.java | 10 +- .../runtime/standard/TimestampFunction.java | 7 +- .../cel/runtime/standard/UintFunction.java | 26 +-- .../src/test/java/dev/cel/runtime/BUILD.bazel | 5 +- .../CelEvaluationExceptionBuilderTest.java | 8 +- .../cel/runtime/CelRuntimeLegacyImplTest.java | 3 +- .../DescriptorMessageProviderTest.java | 8 +- .../ProtoMessageRuntimeEqualityTest.java | 2 +- .../ProtoMessageRuntimeHelpersTest.java | 21 +- .../java/dev/cel/runtime/planner/BUILD.bazel | 2 + .../runtime/planner/ProgramPlannerTest.java | 195 +++++++++++++++--- 82 files changed, 1587 insertions(+), 547 deletions(-) create mode 100644 common/exceptions/BUILD.bazel create mode 100644 common/src/main/java/dev/cel/common/exceptions/BUILD.bazel create mode 100644 common/src/main/java/dev/cel/common/exceptions/CelAttributeNotFoundException.java create mode 100644 common/src/main/java/dev/cel/common/exceptions/CelBadFormatException.java rename runtime/src/main/java/dev/cel/runtime/standard/ArithmeticHelpers.java => common/src/main/java/dev/cel/common/exceptions/CelDivideByZeroException.java (59%) create mode 100644 common/src/main/java/dev/cel/common/exceptions/CelIndexOutOfBoundsException.java create mode 100644 common/src/main/java/dev/cel/common/exceptions/CelInvalidArgumentException.java create mode 100644 common/src/main/java/dev/cel/common/exceptions/CelNumericOverflowException.java rename common/src/main/java/dev/cel/common/{ => exceptions}/CelRuntimeException.java (80%) create mode 100644 runtime/src/main/java/dev/cel/runtime/planner/ErrorMetadata.java create mode 100644 runtime/src/main/java/dev/cel/runtime/planner/InterpretableAttribute.java create mode 100644 runtime/src/main/java/dev/cel/runtime/planner/MaybeAttribute.java create mode 100644 runtime/src/main/java/dev/cel/runtime/planner/MissingAttribute.java create mode 100644 runtime/src/main/java/dev/cel/runtime/planner/NamespacedAttribute.java create mode 100644 runtime/src/main/java/dev/cel/runtime/planner/PlannedInterpretable.java create mode 100644 runtime/src/main/java/dev/cel/runtime/planner/Qualifier.java create mode 100644 runtime/src/main/java/dev/cel/runtime/planner/RelativeAttribute.java create mode 100644 runtime/src/main/java/dev/cel/runtime/planner/StrictErrorException.java create mode 100644 runtime/src/main/java/dev/cel/runtime/planner/StringQualifier.java diff --git a/common/BUILD.bazel b/common/BUILD.bazel index 815d8a1da..75459597b 100644 --- a/common/BUILD.bazel +++ b/common/BUILD.bazel @@ -53,13 +53,6 @@ java_library( exports = ["//common/src/main/java/dev/cel/common:mutable_source"], ) -java_library( - name = "runtime_exception", - # used_by_android - visibility = ["//:internal"], - exports = ["//common/src/main/java/dev/cel/common:runtime_exception"], -) - java_library( name = "proto_json_adapter", exports = ["//common/src/main/java/dev/cel/common:proto_json_adapter"], diff --git a/common/exceptions/BUILD.bazel b/common/exceptions/BUILD.bazel new file mode 100644 index 000000000..7547ddc18 --- /dev/null +++ b/common/exceptions/BUILD.bazel @@ -0,0 +1,48 @@ +load("@rules_java//java:defs.bzl", "java_library") + +package( + default_applicable_licenses = ["//:license"], + default_visibility = ["//:internal"], +) + +java_library( + name = "runtime_exception", + # used_by_android + exports = ["//common/src/main/java/dev/cel/common/exceptions:runtime_exception"], +) + +java_library( + name = "attribute_not_found", + # used_by_android + exports = ["//common/src/main/java/dev/cel/common/exceptions:attribute_not_found"], +) + +java_library( + name = "divide_by_zero", + # used_by_android + exports = ["//common/src/main/java/dev/cel/common/exceptions:divide_by_zero"], +) + +java_library( + name = "index_out_of_bounds", + # used_by_android + exports = ["//common/src/main/java/dev/cel/common/exceptions:index_out_of_bounds"], +) + +java_library( + name = "bad_format", + # used_by_android + exports = ["//common/src/main/java/dev/cel/common/exceptions:bad_format"], +) + +java_library( + name = "numeric_overflow", + # used_by_android + exports = ["//common/src/main/java/dev/cel/common/exceptions:numeric_overflow"], +) + +java_library( + name = "invalid_argument", + # used_by_android + exports = ["//common/src/main/java/dev/cel/common/exceptions:invalid_argument"], +) diff --git a/common/src/main/java/dev/cel/common/BUILD.bazel b/common/src/main/java/dev/cel/common/BUILD.bazel index 7cd10f392..0050a2a7e 100644 --- a/common/src/main/java/dev/cel/common/BUILD.bazel +++ b/common/src/main/java/dev/cel/common/BUILD.bazel @@ -172,18 +172,6 @@ java_library( ], ) -java_library( - name = "runtime_exception", - srcs = ["CelRuntimeException.java"], - # used_by_android - tags = [ - ], - deps = [ - ":error_codes", - "//common/annotations", - ], -) - java_library( name = "mutable_ast", srcs = ["CelMutableAst.java"], @@ -205,7 +193,6 @@ java_library( ], deps = [ ":cel_source", - "//:auto_value", "//common/ast:mutable_expr", "@maven//:com_google_errorprone_error_prone_annotations", "@maven//:com_google_guava_guava", @@ -249,7 +236,6 @@ java_library( ":source", ":source_location", "//:auto_value", - "//common/annotations", "//common/ast", "//common/internal", "@maven//:com_google_errorprone_error_prone_annotations", @@ -360,8 +346,5 @@ java_library( srcs = ["Operator.java"], tags = [ ], - deps = [ - "//common/ast", - "@maven//:com_google_guava_guava", - ], + deps = ["@maven//:com_google_guava_guava"], ) diff --git a/common/src/main/java/dev/cel/common/CelException.java b/common/src/main/java/dev/cel/common/CelException.java index 9d80a9ba1..55c8623a4 100644 --- a/common/src/main/java/dev/cel/common/CelException.java +++ b/common/src/main/java/dev/cel/common/CelException.java @@ -27,11 +27,6 @@ public CelException(String message, Throwable cause) { super(message, cause); } - public CelException(String message, CelErrorCode errorCode) { - super(message); - this.errorCode = errorCode; - } - public CelException(String message, Throwable cause, CelErrorCode errorCode) { super(message, 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..b78034f6a --- /dev/null +++ b/common/src/main/java/dev/cel/common/exceptions/BUILD.bazel @@ -0,0 +1,99 @@ +load("@rules_java//java:defs.bzl", "java_library") + +package( + default_applicable_licenses = ["//:license"], + default_visibility = [ + "//common/exceptions:__pkg__", + "//publish:__pkg__", + ], +) + +java_library( + name = "runtime_exception", + srcs = ["CelRuntimeException.java"], + # used_by_android + tags = [ + ], + deps = [ + "//common:error_codes", + "//common/annotations", + ], +) + +java_library( + name = "attribute_not_found", + srcs = ["CelAttributeNotFoundException.java"], + # used_by_android + tags = [ + ], + deps = [ + "//common:error_codes", + "//common/annotations", + "//common/exceptions:runtime_exception", + ], +) + +java_library( + name = "divide_by_zero", + srcs = ["CelDivideByZeroException.java"], + # used_by_android + tags = [ + ], + deps = [ + "//common:error_codes", + "//common/annotations", + "//common/exceptions:runtime_exception", + ], +) + +java_library( + name = "index_out_of_bounds", + srcs = ["CelIndexOutOfBoundsException.java"], + # used_by_android + tags = [ + ], + deps = [ + "//common:error_codes", + "//common/annotations", + "//common/exceptions:runtime_exception", + ], +) + +java_library( + name = "bad_format", + srcs = ["CelBadFormatException.java"], + # used_by_android + tags = [ + ], + deps = [ + "//common:error_codes", + "//common/annotations", + "//common/exceptions:runtime_exception", + ], +) + +java_library( + name = "numeric_overflow", + srcs = ["CelNumericOverflowException.java"], + # used_by_android + tags = [ + ], + deps = [ + "//common:error_codes", + "//common/annotations", + "//common/exceptions:runtime_exception", + ], +) + +java_library( + name = "invalid_argument", + srcs = ["CelInvalidArgumentException.java"], + # used_by_android + tags = [ + ], + deps = [ + "//common:error_codes", + "//common/annotations", + "//common/exceptions:runtime_exception", + ], +) 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..6204fd2fe --- /dev/null +++ b/common/src/main/java/dev/cel/common/exceptions/CelAttributeNotFoundException.java @@ -0,0 +1,56 @@ +// 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.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..ba4db602a --- /dev/null +++ b/common/src/main/java/dev/cel/common/exceptions/CelBadFormatException.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.common.exceptions; + +import dev.cel.common.CelErrorCode; +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/runtime/src/main/java/dev/cel/runtime/standard/ArithmeticHelpers.java b/common/src/main/java/dev/cel/common/exceptions/CelDivideByZeroException.java similarity index 59% rename from runtime/src/main/java/dev/cel/runtime/standard/ArithmeticHelpers.java rename to common/src/main/java/dev/cel/common/exceptions/CelDivideByZeroException.java index 310e9401f..c507797c5 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/ArithmeticHelpers.java +++ b/common/src/main/java/dev/cel/common/exceptions/CelDivideByZeroException.java @@ -12,20 +12,20 @@ // See the License for the specific language governing permissions and // limitations under the License. -package dev.cel.runtime.standard; +package dev.cel.common.exceptions; import dev.cel.common.CelErrorCode; +import dev.cel.common.annotations.Internal; -final class ArithmeticHelpers { +/** Indicates that a division by zero occurred. */ +@Internal +public final class CelDivideByZeroException extends CelRuntimeException { - static CelErrorCode getArithmeticErrorCode(ArithmeticException e) { - String exceptionMessage = e.getMessage(); - // The two known cases for an arithmetic exception is divide by zero and overflow. - if (exceptionMessage.equals("/ by zero")) { - return CelErrorCode.DIVIDE_BY_ZERO; - } - return CelErrorCode.NUMERIC_OVERFLOW; + public CelDivideByZeroException() { + super("/ by zero", CelErrorCode.DIVIDE_BY_ZERO); } - private ArithmeticHelpers() {} + 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..72a6cd1c0 --- /dev/null +++ b/common/src/main/java/dev/cel/common/exceptions/CelIndexOutOfBoundsException.java @@ -0,0 +1,27 @@ +// 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.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..41358bb79 --- /dev/null +++ b/common/src/main/java/dev/cel/common/exceptions/CelInvalidArgumentException.java @@ -0,0 +1,27 @@ +// 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.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..78fbe807e --- /dev/null +++ b/common/src/main/java/dev/cel/common/exceptions/CelNumericOverflowException.java @@ -0,0 +1,34 @@ +// 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.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/CelRuntimeException.java b/common/src/main/java/dev/cel/common/exceptions/CelRuntimeException.java similarity index 80% rename from common/src/main/java/dev/cel/common/CelRuntimeException.java rename to common/src/main/java/dev/cel/common/exceptions/CelRuntimeException.java index 6f194c474..c87e192bd 100644 --- a/common/src/main/java/dev/cel/common/CelRuntimeException.java +++ b/common/src/main/java/dev/cel/common/exceptions/CelRuntimeException.java @@ -12,8 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. -package dev.cel.common; +package dev.cel.common.exceptions; +import dev.cel.common.CelErrorCode; import dev.cel.common.annotations.Internal; /** @@ -21,14 +22,16 @@ * *

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. */ @Internal -public class CelRuntimeException extends RuntimeException { +public abstract class CelRuntimeException extends RuntimeException { private final CelErrorCode errorCode; + 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/internal/BUILD.bazel b/common/src/main/java/dev/cel/common/internal/BUILD.bazel index 690b1cc75..c96a3dcfd 100644 --- a/common/src/main/java/dev/cel/common/internal/BUILD.bazel +++ b/common/src/main/java/dev/cel/common/internal/BUILD.bazel @@ -173,11 +173,9 @@ java_library( ":proto_lite_adapter", ":proto_message_factory", ":well_known_proto", - "//:auto_value", - "//common:error_codes", "//common:options", - "//common:runtime_exception", "//common/annotations", + "//common/exceptions:numeric_overflow", "//common/values", "//common/values:cel_byte_string", "@maven//:com_google_code_findbugs_annotations", @@ -194,11 +192,10 @@ java_library( ], deps = [ ":well_known_proto", - "//common:error_codes", "//common:options", "//common:proto_json_adapter", - "//common:runtime_exception", "//common/annotations", + "//common/exceptions:numeric_overflow", "//common/internal:proto_time_utils", "//common/values", "//common/values:cel_byte_string", @@ -440,9 +437,8 @@ java_library( tags = [ ], deps = [ - "//common:error_codes", - "//common:runtime_exception", "//common/annotations", + "//common/exceptions:bad_format", "@maven//:com_google_guava_guava", "@maven//:com_google_protobuf_protobuf_java", ], @@ -454,9 +450,8 @@ cel_android_library( tags = [ ], deps = [ - "//common:error_codes", - "//common:runtime_exception", "//common/annotations", + "//common/exceptions:bad_format", "@maven_android//:com_google_guava_guava", "@maven_android//:com_google_protobuf_protobuf_javalite", ], diff --git a/common/src/main/java/dev/cel/common/internal/DateTimeHelpers.java b/common/src/main/java/dev/cel/common/internal/DateTimeHelpers.java index 9abe39b4b..703fd2801 100644 --- a/common/src/main/java/dev/cel/common/internal/DateTimeHelpers.java +++ b/common/src/main/java/dev/cel/common/internal/DateTimeHelpers.java @@ -16,9 +16,8 @@ import com.google.common.base.Strings; import com.google.protobuf.Timestamp; -import dev.cel.common.CelErrorCode; -import dev.cel.common.CelRuntimeException; import dev.cel.common.annotations.Internal; +import dev.cel.common.exceptions.CelBadFormatException; import java.time.DateTimeException; import java.time.Duration; import java.time.Instant; @@ -184,7 +183,7 @@ private static ZoneId timeZone(String tz) { try { int ind = tz.indexOf(":"); if (ind == -1) { - throw new CelRuntimeException(e, CelErrorCode.BAD_FORMAT); + throw new CelBadFormatException(e); } int hourOffset = Integer.parseInt(tz.substring(0, ind)); @@ -199,7 +198,7 @@ private static ZoneId timeZone(String tz) { return ZoneId.of(formattedOffset); } catch (DateTimeException e2) { - throw new CelRuntimeException(e2, CelErrorCode.BAD_FORMAT); + throw new CelBadFormatException(e2); } } } diff --git a/common/src/main/java/dev/cel/common/internal/ProtoAdapter.java b/common/src/main/java/dev/cel/common/internal/ProtoAdapter.java index cd4be2f5e..3c3382ef2 100644 --- a/common/src/main/java/dev/cel/common/internal/ProtoAdapter.java +++ b/common/src/main/java/dev/cel/common/internal/ProtoAdapter.java @@ -31,10 +31,9 @@ import com.google.protobuf.MapEntry; import com.google.protobuf.Message; import com.google.protobuf.MessageOrBuilder; -import dev.cel.common.CelErrorCode; import dev.cel.common.CelOptions; -import dev.cel.common.CelRuntimeException; import dev.cel.common.annotations.Internal; +import dev.cel.common.exceptions.CelNumericOverflowException; import dev.cel.common.values.CelByteString; import dev.cel.common.values.NullValue; import java.util.ArrayList; @@ -361,7 +360,7 @@ private static int intCheckedCast(long value) { try { return Ints.checkedCast(value); } catch (IllegalArgumentException e) { - throw new CelRuntimeException(e, CelErrorCode.NUMERIC_OVERFLOW); + throw new CelNumericOverflowException(e); } } @@ -369,7 +368,7 @@ private static int unsignedIntCheckedCast(long value) { try { return UnsignedInts.checkedCast(value); } catch (IllegalArgumentException e) { - throw new CelRuntimeException(e, CelErrorCode.NUMERIC_OVERFLOW); + throw new CelNumericOverflowException(e); } } } diff --git a/common/src/main/java/dev/cel/common/internal/ProtoLiteAdapter.java b/common/src/main/java/dev/cel/common/internal/ProtoLiteAdapter.java index e05d92f68..3b13ecea1 100644 --- a/common/src/main/java/dev/cel/common/internal/ProtoLiteAdapter.java +++ b/common/src/main/java/dev/cel/common/internal/ProtoLiteAdapter.java @@ -42,11 +42,10 @@ import com.google.protobuf.UInt32Value; import com.google.protobuf.UInt64Value; import com.google.protobuf.Value; -import dev.cel.common.CelErrorCode; import dev.cel.common.CelOptions; import dev.cel.common.CelProtoJsonAdapter; -import dev.cel.common.CelRuntimeException; import dev.cel.common.annotations.Internal; +import dev.cel.common.exceptions.CelNumericOverflowException; import dev.cel.common.values.CelByteString; import java.time.Instant; import java.util.Map; @@ -284,14 +283,14 @@ private Message adaptValueToUint32(Object value) { try { return UInt32Value.of(unsignedIntCheckedCast((Long) value)); } catch (IllegalArgumentException e) { - throw new CelRuntimeException(e, CelErrorCode.NUMERIC_OVERFLOW); + throw new CelNumericOverflowException(e); } } if (value instanceof UnsignedLong) { try { return UInt32Value.of(unsignedIntCheckedCast(((UnsignedLong) value).longValue())); } catch (IllegalArgumentException e) { - throw new CelRuntimeException(e, CelErrorCode.NUMERIC_OVERFLOW); + throw new CelNumericOverflowException(e); } } @@ -316,7 +315,7 @@ private static int intCheckedCast(long value) { try { return Ints.checkedCast(value); } catch (IllegalArgumentException e) { - throw new CelRuntimeException(e, CelErrorCode.NUMERIC_OVERFLOW); + throw new CelNumericOverflowException(e); } } @@ -324,7 +323,7 @@ private static int unsignedIntCheckedCast(long value) { try { return UnsignedInts.checkedCast(value); } catch (IllegalArgumentException e) { - throw new CelRuntimeException(e, CelErrorCode.NUMERIC_OVERFLOW); + throw new CelNumericOverflowException(e); } } 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..c3f3727a1 100644 --- a/common/src/main/java/dev/cel/common/values/CelValueConverter.java +++ b/common/src/main/java/dev/cel/common/values/CelValueConverter.java @@ -33,7 +33,7 @@ @SuppressWarnings("unchecked") // Unchecked cast of generics due to type-erasure (ex: MapValue). @Internal @Immutable -abstract class CelValueConverter { +public abstract class CelValueConverter { /** Adapts a {@link CelValue} to a plain old Java Object. */ public Object unwrap(CelValue celValue) { @@ -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/extensions/src/main/java/dev/cel/extensions/BUILD.bazel b/extensions/src/main/java/dev/cel/extensions/BUILD.bazel index 9b897cf84..ed2d19d6f 100644 --- a/extensions/src/main/java/dev/cel/extensions/BUILD.bazel +++ b/extensions/src/main/java/dev/cel/extensions/BUILD.bazel @@ -123,6 +123,7 @@ java_library( "//common:compiler_common", "//common:options", "//common/ast", + "//common/exceptions:numeric_overflow", "//common/internal:comparison_functions", "//common/types", "//compiler:compiler_builder", diff --git a/extensions/src/main/java/dev/cel/extensions/CelMathExtensions.java b/extensions/src/main/java/dev/cel/extensions/CelMathExtensions.java index 1e318aedc..e74177bf1 100644 --- a/extensions/src/main/java/dev/cel/extensions/CelMathExtensions.java +++ b/extensions/src/main/java/dev/cel/extensions/CelMathExtensions.java @@ -32,6 +32,7 @@ import dev.cel.common.ast.CelConstant; import dev.cel.common.ast.CelExpr; import dev.cel.common.ast.CelExpr.ExprKind.Kind; +import dev.cel.common.exceptions.CelNumericOverflowException; import dev.cel.common.internal.ComparisonFunctions; import dev.cel.common.types.ListType; import dev.cel.common.types.SimpleType; @@ -216,8 +217,7 @@ enum Function { "math_@max_int_double", Long.class, Double.class, CelMathExtensions::maxPair), CelFunctionBinding.from( "math_@max_double_int", Double.class, Long.class, CelMathExtensions::maxPair), - CelFunctionBinding.from( - "math_@max_list_dyn", List.class, CelMathExtensions::maxList)), + CelFunctionBinding.from("math_@max_list_dyn", List.class, CelMathExtensions::maxList)), ImmutableSet.of( CelFunctionBinding.from("math_@max_uint", Long.class, x -> x), CelFunctionBinding.from( @@ -640,8 +640,7 @@ enum Function { ImmutableSet.of( CelFunctionBinding.from( "math_sqrt_double", Double.class, CelMathExtensions::sqrtDouble), - CelFunctionBinding.from( - "math_sqrt_int", Long.class, CelMathExtensions::sqrtInt), + CelFunctionBinding.from("math_sqrt_int", Long.class, CelMathExtensions::sqrtInt), CelFunctionBinding.from( "math_sqrt_uint", UnsignedLong.class, CelMathExtensions::sqrtUint))); @@ -856,7 +855,7 @@ private static Comparable minPair(Comparable x, Comparable y) { private static long absExact(long x) { if (x == Long.MIN_VALUE) { // The only case where standard Math.abs overflows silently - throw new ArithmeticException("integer overflow"); + throw new CelNumericOverflowException("integer overflow"); } return Math.abs(x); } diff --git a/extensions/src/test/java/dev/cel/extensions/BUILD.bazel b/extensions/src/test/java/dev/cel/extensions/BUILD.bazel index 45d48aeca..d5155f662 100644 --- a/extensions/src/test/java/dev/cel/extensions/BUILD.bazel +++ b/extensions/src/test/java/dev/cel/extensions/BUILD.bazel @@ -14,7 +14,8 @@ java_library( "//common:compiler_common", "//common:container", "//common:options", - "//common/internal:proto_time_utils", + "//common/exceptions:divide_by_zero", + "//common/exceptions:index_out_of_bounds", "//common/types", "//common/types:type_providers", "//common/values", diff --git a/extensions/src/test/java/dev/cel/extensions/CelComprehensionsExtensionsTest.java b/extensions/src/test/java/dev/cel/extensions/CelComprehensionsExtensionsTest.java index 5318234b9..34696b688 100644 --- a/extensions/src/test/java/dev/cel/extensions/CelComprehensionsExtensionsTest.java +++ b/extensions/src/test/java/dev/cel/extensions/CelComprehensionsExtensionsTest.java @@ -24,6 +24,8 @@ import dev.cel.common.CelFunctionDecl; import dev.cel.common.CelOptions; import dev.cel.common.CelValidationException; +import dev.cel.common.exceptions.CelDivideByZeroException; +import dev.cel.common.exceptions.CelIndexOutOfBoundsException; import dev.cel.common.types.SimpleType; import dev.cel.common.types.TypeParamType; import dev.cel.compiler.CelCompiler; @@ -347,13 +349,13 @@ public void twoVarComprehension_keyCollision_runtimeError(String expr, String er } @Test - public void twoVarComprehension_arithematicException_runtimeError() throws Exception { + public void twoVarComprehension_arithmeticException_runtimeError() throws Exception { CelAbstractSyntaxTree ast = CEL_COMPILER.compile("[0].all(i, k, i/k < k)").getAst(); CelEvaluationException e = assertThrows(CelEvaluationException.class, () -> CEL_RUNTIME.createProgram(ast).eval()); - assertThat(e).hasCauseThat().isInstanceOf(ArithmeticException.class); + assertThat(e).hasCauseThat().isInstanceOf(CelDivideByZeroException.class); assertThat(e).hasCauseThat().hasMessageThat().contains("/ by zero"); } @@ -364,7 +366,7 @@ public void twoVarComprehension_outOfBounds_runtimeError() throws Exception { CelEvaluationException e = assertThrows(CelEvaluationException.class, () -> CEL_RUNTIME.createProgram(ast).eval()); - assertThat(e).hasCauseThat().isInstanceOf(IndexOutOfBoundsException.class); + assertThat(e).hasCauseThat().isInstanceOf(CelIndexOutOfBoundsException.class); assertThat(e).hasCauseThat().hasMessageThat().contains("Index out of bounds: 1"); } } 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/BUILD.bazel b/runtime/src/main/java/dev/cel/runtime/BUILD.bazel index 847092cc3..178bb1728 100644 --- a/runtime/src/main/java/dev/cel/runtime/BUILD.bazel +++ b/runtime/src/main/java/dev/cel/runtime/BUILD.bazel @@ -98,10 +98,9 @@ java_library( ":runtime_type_provider", "//common:cel_descriptor_util", "//common:cel_descriptors", - "//common:error_codes", "//common:options", - "//common:runtime_exception", "//common/annotations", + "//common/exceptions:attribute_not_found", "//common/internal:cel_descriptor_pools", "//common/internal:default_message_factory", "//common/internal:dynamic_proto", @@ -298,9 +297,9 @@ java_library( "//common:cel_ast", "//common:error_codes", "//common:options", - "//common:runtime_exception", "//common/annotations", "//common/ast", + "//common/exceptions:runtime_exception", "//common/types", "//common/types:type_providers", "//common/values:cel_byte_string", @@ -308,7 +307,6 @@ java_library( "@maven//:com_google_errorprone_error_prone_annotations", "@maven//:com_google_guava_guava", "@maven//:com_google_protobuf_protobuf_java", - "@maven//:org_jspecify_jspecify", ], ) @@ -337,9 +335,9 @@ cel_android_library( "//common:cel_ast_android", "//common:error_codes", "//common:options", - "//common:runtime_exception", "//common/annotations", "//common/ast:ast_android", + "//common/exceptions:runtime_exception", "//common/types:type_providers_android", "//common/types:types_android", "//common/values:cel_byte_string", @@ -360,10 +358,9 @@ java_library( ], deps = [ ":runtime_helpers", - "//common:error_codes", "//common:options", - "//common:runtime_exception", "//common/annotations", + "//common/exceptions:attribute_not_found", "//common/internal:comparison_functions", "@maven//:com_google_errorprone_error_prone_annotations", "@maven//:com_google_guava_guava", @@ -378,10 +375,9 @@ cel_android_library( ], deps = [ ":runtime_helpers_android", - "//common:error_codes", "//common:options", - "//common:runtime_exception", "//common/annotations", + "//common/exceptions:attribute_not_found", "//common/internal:comparison_functions_android", "@maven//:com_google_errorprone_error_prone_annotations", "@maven_android//:com_google_guava_guava", @@ -416,10 +412,11 @@ cel_android_library( ], deps = [ ":concatenated_list_view", - "//common:error_codes", "//common:options", - "//common:runtime_exception", "//common/annotations", + "//common/exceptions:divide_by_zero", + "//common/exceptions:index_out_of_bounds", + "//common/exceptions:numeric_overflow", "//common/internal:converter", "//common/values:values_android", "@maven//:com_google_errorprone_error_prone_annotations", @@ -439,10 +436,11 @@ java_library( ], deps = [ ":concatenated_list_view", - "//common:error_codes", "//common:options", - "//common:runtime_exception", "//common/annotations", + "//common/exceptions:divide_by_zero", + "//common/exceptions:index_out_of_bounds", + "//common/exceptions:numeric_overflow", "//common/internal:converter", "//common/values", "@maven//:com_google_errorprone_error_prone_annotations", @@ -558,8 +556,8 @@ java_library( ":evaluation_exception", ":metadata", "//common:error_codes", - "//common:runtime_exception", "//common/annotations", + "//common/exceptions:runtime_exception", "//common/internal:safe_string_formatter", "@maven//:com_google_errorprone_error_prone_annotations", "@maven//:org_jspecify_jspecify", @@ -1029,9 +1027,8 @@ java_library( deps = [ ":runtime_type_provider", ":unknown_attributes", - "//common:error_codes", - "//common:runtime_exception", "//common/annotations", + "//common/exceptions:attribute_not_found", "//common/values", "//common/values:base_proto_cel_value_converter", "//common/values:base_proto_message_value_provider", @@ -1050,9 +1047,8 @@ cel_android_library( deps = [ ":runtime_type_provider_android", ":unknown_attributes_android", - "//common:error_codes", - "//common:runtime_exception", "//common/annotations", + "//common/exceptions:attribute_not_found", "//common/values:base_proto_cel_value_converter_android", "//common/values:base_proto_message_value_provider_android", "//common/values:cel_value_android", diff --git a/runtime/src/main/java/dev/cel/runtime/CelEvaluationExceptionBuilder.java b/runtime/src/main/java/dev/cel/runtime/CelEvaluationExceptionBuilder.java index 6aaed4da7..986788bac 100644 --- a/runtime/src/main/java/dev/cel/runtime/CelEvaluationExceptionBuilder.java +++ b/runtime/src/main/java/dev/cel/runtime/CelEvaluationExceptionBuilder.java @@ -16,8 +16,8 @@ import com.google.errorprone.annotations.CanIgnoreReturnValue; import dev.cel.common.CelErrorCode; -import dev.cel.common.CelRuntimeException; import dev.cel.common.annotations.Internal; +import dev.cel.common.exceptions.CelRuntimeException; import dev.cel.common.internal.SafeStringFormatter; import org.jspecify.annotations.Nullable; @@ -83,10 +83,15 @@ public static CelEvaluationExceptionBuilder newBuilder(String message, Object... */ @Internal public static CelEvaluationExceptionBuilder newBuilder(CelRuntimeException celRuntimeException) { - Throwable cause = celRuntimeException.getCause(); - return new CelEvaluationExceptionBuilder(cause.getMessage()) - .setCause(cause) - .setErrorCode(celRuntimeException.getErrorCode()); + // Intercept the cause to prevent including the cause's class name in the exception message. + String message = + celRuntimeException.getCause() == null + ? celRuntimeException.getMessage() + : celRuntimeException.getCause().getMessage(); + + return new CelEvaluationExceptionBuilder(message) + .setErrorCode(celRuntimeException.getErrorCode()) + .setCause(celRuntimeException); } private CelEvaluationExceptionBuilder(String message) { diff --git a/runtime/src/main/java/dev/cel/runtime/CelValueRuntimeTypeProvider.java b/runtime/src/main/java/dev/cel/runtime/CelValueRuntimeTypeProvider.java index 989d3b7be..e071289ca 100644 --- a/runtime/src/main/java/dev/cel/runtime/CelValueRuntimeTypeProvider.java +++ b/runtime/src/main/java/dev/cel/runtime/CelValueRuntimeTypeProvider.java @@ -18,9 +18,8 @@ import com.google.errorprone.annotations.Immutable; import com.google.protobuf.MessageLite; -import dev.cel.common.CelErrorCode; -import dev.cel.common.CelRuntimeException; import dev.cel.common.annotations.Internal; +import dev.cel.common.exceptions.CelAttributeNotFoundException; import dev.cel.common.values.BaseProtoCelValueConverter; import dev.cel.common.values.BaseProtoMessageValueProvider; import dev.cel.common.values.CelValue; @@ -82,9 +81,7 @@ public Object selectField(Object message, String fieldName) { return map.get(fieldName); } - throw new CelRuntimeException( - new IllegalArgumentException(String.format("key '%s' is not present in map.", fieldName)), - CelErrorCode.ATTRIBUTE_NOT_FOUND); + throw CelAttributeNotFoundException.forMissingMapKey(fieldName); } SelectableValue selectableValue = getSelectableValueOrThrow(message, fieldName); @@ -142,13 +139,7 @@ private Object maybeUnwrapCelValue(Object object) { } private static void throwInvalidFieldSelection(String fieldName) { - throw new CelRuntimeException( - new IllegalArgumentException( - String.format( - "Error resolving field '%s'. Field selections must be performed on messages or" - + " maps.", - fieldName)), - CelErrorCode.ATTRIBUTE_NOT_FOUND); + throw CelAttributeNotFoundException.forFieldResolution(fieldName); } private CelValueRuntimeTypeProvider( diff --git a/runtime/src/main/java/dev/cel/runtime/DefaultInterpreter.java b/runtime/src/main/java/dev/cel/runtime/DefaultInterpreter.java index c0a21cc3f..350163b14 100644 --- a/runtime/src/main/java/dev/cel/runtime/DefaultInterpreter.java +++ b/runtime/src/main/java/dev/cel/runtime/DefaultInterpreter.java @@ -31,7 +31,6 @@ import dev.cel.common.CelAbstractSyntaxTree; import dev.cel.common.CelErrorCode; import dev.cel.common.CelOptions; -import dev.cel.common.CelRuntimeException; import dev.cel.common.ast.CelConstant; import dev.cel.common.ast.CelExpr; import dev.cel.common.ast.CelExpr.CelCall; @@ -42,6 +41,7 @@ import dev.cel.common.ast.CelExpr.CelStruct; import dev.cel.common.ast.CelExpr.ExprKind; import dev.cel.common.ast.CelReference; +import dev.cel.common.exceptions.CelRuntimeException; import dev.cel.common.types.CelKind; import dev.cel.common.types.CelType; import dev.cel.common.types.TypeType; diff --git a/runtime/src/main/java/dev/cel/runtime/DescriptorMessageProvider.java b/runtime/src/main/java/dev/cel/runtime/DescriptorMessageProvider.java index ab6934e01..f2ea85b9b 100644 --- a/runtime/src/main/java/dev/cel/runtime/DescriptorMessageProvider.java +++ b/runtime/src/main/java/dev/cel/runtime/DescriptorMessageProvider.java @@ -21,10 +21,9 @@ import com.google.protobuf.Message; import com.google.protobuf.MessageOrBuilder; import com.google.protobuf.NullValue; -import dev.cel.common.CelErrorCode; import dev.cel.common.CelOptions; -import dev.cel.common.CelRuntimeException; import dev.cel.common.annotations.Internal; +import dev.cel.common.exceptions.CelAttributeNotFoundException; import dev.cel.common.internal.DynamicProto; import dev.cel.common.internal.ProtoAdapter; import dev.cel.common.internal.ProtoMessageFactory; @@ -79,10 +78,8 @@ public DescriptorMessageProvider(ProtoMessageFactory protoMessageFactory, CelOpt .newBuilder(messageName) .orElseThrow( () -> - new CelRuntimeException( - new IllegalArgumentException( - String.format("cannot resolve '%s' as a message", messageName)), - CelErrorCode.ATTRIBUTE_NOT_FOUND)); + CelAttributeNotFoundException.of( + String.format("cannot resolve '%s' as a message", messageName))); try { Descriptor descriptor = builder.getDescriptorForType(); @@ -122,10 +119,7 @@ public DescriptorMessageProvider(ProtoMessageFactory protoMessageFactory, CelOpt if (isOptionalMessage) { return Optional.empty(); } else { - throw new CelRuntimeException( - new IllegalArgumentException( - String.format("key '%s' is not present in map.", fieldName)), - CelErrorCode.ATTRIBUTE_NOT_FOUND); + throw CelAttributeNotFoundException.forMissingMapKey(fieldName); } } @@ -197,13 +191,7 @@ private FieldDescriptor findField(Descriptor descriptor, String fieldName) { private static MessageOrBuilder assertFullProtoMessage(Object candidate, String fieldName) { if (!(candidate instanceof MessageOrBuilder)) { // This can happen when the field selection is done on dyn, and it is not a message. - throw new CelRuntimeException( - new IllegalArgumentException( - String.format( - "Error resolving field '%s'. Field selections must be performed on messages or" - + " maps.", - fieldName)), - CelErrorCode.ATTRIBUTE_NOT_FOUND); + throw CelAttributeNotFoundException.forFieldResolution(fieldName); } return (MessageOrBuilder) candidate; } diff --git a/runtime/src/main/java/dev/cel/runtime/RuntimeEquality.java b/runtime/src/main/java/dev/cel/runtime/RuntimeEquality.java index d0b2fcd6b..2044a2518 100644 --- a/runtime/src/main/java/dev/cel/runtime/RuntimeEquality.java +++ b/runtime/src/main/java/dev/cel/runtime/RuntimeEquality.java @@ -17,10 +17,9 @@ import com.google.common.primitives.UnsignedLong; import com.google.errorprone.annotations.Immutable; import com.google.protobuf.MessageLiteOrBuilder; -import dev.cel.common.CelErrorCode; import dev.cel.common.CelOptions; -import dev.cel.common.CelRuntimeException; import dev.cel.common.annotations.Internal; +import dev.cel.common.exceptions.CelAttributeNotFoundException; import dev.cel.common.internal.ComparisonFunctions; import java.util.Iterator; import java.util.List; @@ -67,8 +66,8 @@ public B indexMap(Map map, A index) { if (value.isPresent()) { return (B) value.get(); } - throw new CelRuntimeException( - new IndexOutOfBoundsException(index.toString()), CelErrorCode.ATTRIBUTE_NOT_FOUND); + + throw CelAttributeNotFoundException.of(index.toString()); } /** Determine whether the {@code map} contains the given {@code key}. */ diff --git a/runtime/src/main/java/dev/cel/runtime/RuntimeHelpers.java b/runtime/src/main/java/dev/cel/runtime/RuntimeHelpers.java index 25f01ef59..0ee7824b7 100644 --- a/runtime/src/main/java/dev/cel/runtime/RuntimeHelpers.java +++ b/runtime/src/main/java/dev/cel/runtime/RuntimeHelpers.java @@ -23,10 +23,11 @@ import com.google.protobuf.Duration; import com.google.protobuf.MessageLiteOrBuilder; import com.google.re2j.Pattern; -import dev.cel.common.CelErrorCode; import dev.cel.common.CelOptions; -import dev.cel.common.CelRuntimeException; import dev.cel.common.annotations.Internal; +import dev.cel.common.exceptions.CelDivideByZeroException; +import dev.cel.common.exceptions.CelIndexOutOfBoundsException; +import dev.cel.common.exceptions.CelNumericOverflowException; import dev.cel.common.internal.Converter; import dev.cel.common.values.NullValue; import java.time.format.DateTimeParseException; @@ -116,17 +117,11 @@ public static A indexList(List list, Number index) { if (index instanceof Double) { return doubleToLongLossless(index.doubleValue()) .map(v -> indexList(list, v)) - .orElseThrow( - () -> - new CelRuntimeException( - new IndexOutOfBoundsException("Index out of bounds: " + index.doubleValue()), - CelErrorCode.INDEX_OUT_OF_BOUNDS)); + .orElseThrow(() -> new CelIndexOutOfBoundsException(index.doubleValue())); } int castIndex = Ints.checkedCast(index.longValue()); if (castIndex < 0 || castIndex >= list.size()) { - throw new CelRuntimeException( - new IndexOutOfBoundsException("Index out of bounds: " + castIndex), - CelErrorCode.INDEX_OUT_OF_BOUNDS); + throw new CelIndexOutOfBoundsException(castIndex); } return list.get(castIndex); } @@ -145,7 +140,7 @@ public static long int64Add(long x, long y, CelOptions celOptions) { public static long int64Divide(long x, long y, CelOptions celOptions) { if (celOptions.errorOnIntWrap() && x == Long.MIN_VALUE && y == -1) { - throw new ArithmeticException("most negative number wraps"); + throw new CelNumericOverflowException("most negative number wraps"); } return x / y; } @@ -186,13 +181,13 @@ public static long uint64Add(long x, long y, CelOptions celOptions) { if (celOptions.errorOnIntWrap()) { if (x < 0 && y < 0) { // Both numbers are in the upper half of the range, so it must overflow. - throw new ArithmeticException("range overflow on unsigned addition"); + throw new CelNumericOverflowException("range overflow on unsigned addition"); } long z = x + y; if ((x < 0 || y < 0) && z >= 0) { // Only one number is in the upper half of the range. It overflows if the result // is not in the upper half. - throw new ArithmeticException("range overflow on unsigned addition"); + throw new CelNumericOverflowException("range overflow on unsigned addition"); } return z; } @@ -201,7 +196,7 @@ public static long uint64Add(long x, long y, CelOptions celOptions) { public static UnsignedLong uint64Add(UnsignedLong x, UnsignedLong y) { if (x.compareTo(UnsignedLong.MAX_VALUE.minus(y)) > 0) { - throw new ArithmeticException("range overflow on unsigned addition"); + throw new CelNumericOverflowException("range overflow on unsigned addition"); } return x.plus(y); } @@ -228,7 +223,7 @@ public static long uint64Divide(long x, long y, CelOptions celOptions) { ? UnsignedLongs.divide(x, y) : UnsignedLong.valueOf(x).dividedBy(UnsignedLong.valueOf(y)).longValue(); } catch (ArithmeticException e) { - throw new CelRuntimeException(e, CelErrorCode.DIVIDE_BY_ZERO); + throw new CelDivideByZeroException(e); } } @@ -240,8 +235,7 @@ static long uint64Divide(long x, long y) { public static UnsignedLong uint64Divide(UnsignedLong x, UnsignedLong y) { if (y.equals(UnsignedLong.ZERO)) { - throw new CelRuntimeException( - new ArithmeticException("/ by zero"), CelErrorCode.DIVIDE_BY_ZERO); + throw new CelDivideByZeroException(); } return x.dividedBy(y); } @@ -252,14 +246,13 @@ public static long uint64Mod(long x, long y, CelOptions celOptions) { ? UnsignedLongs.remainder(x, y) : UnsignedLong.valueOf(x).mod(UnsignedLong.valueOf(y)).longValue(); } catch (ArithmeticException e) { - throw new CelRuntimeException(e, CelErrorCode.DIVIDE_BY_ZERO); + throw new CelDivideByZeroException(e); } } public static UnsignedLong uint64Mod(UnsignedLong x, UnsignedLong y) { if (y.equals(UnsignedLong.ZERO)) { - throw new CelRuntimeException( - new ArithmeticException("/ by zero"), CelErrorCode.DIVIDE_BY_ZERO); + throw new CelDivideByZeroException(); } return x.mod(y); } @@ -276,7 +269,7 @@ public static long uint64Multiply(long x, long y, CelOptions celOptions) { ? x * y : UnsignedLong.valueOf(x).times(UnsignedLong.valueOf(y)).longValue(); if (celOptions.errorOnIntWrap() && y != 0 && Long.divideUnsigned(z, y) != x) { - throw new ArithmeticException("multiply out of unsigned integer range"); + throw new CelNumericOverflowException("multiply out of unsigned integer range"); } return z; } @@ -289,7 +282,7 @@ static long uint64Multiply(long x, long y) { public static UnsignedLong uint64Multiply(UnsignedLong x, UnsignedLong y) { if (!y.equals(UnsignedLong.ZERO) && x.compareTo(UnsignedLong.MAX_VALUE.dividedBy(y)) > 0) { - throw new ArithmeticException("multiply out of unsigned integer range"); + throw new CelNumericOverflowException("multiply out of unsigned integer range"); } return x.times(y); } @@ -299,7 +292,7 @@ public static long uint64Subtract(long x, long y, CelOptions celOptions) { // Throw an overflow error if x < y, as unsigned longs. This happens if y has its high // bit set and x does not, or if they have the same high bit and x < y as signed longs. if ((x < 0 && y < 0 && x < y) || (x >= 0 && y >= 0 && x < y) || (x >= 0 && y < 0)) { - throw new ArithmeticException("unsigned subtraction underflow"); + throw new CelNumericOverflowException("unsigned subtraction underflow"); } // fallthrough } @@ -310,7 +303,7 @@ public static UnsignedLong uint64Subtract(UnsignedLong x, UnsignedLong y) { // Throw an overflow error if x < y, as unsigned longs. This happens if y has its high // bit set and x does not, or if they have the same high bit and x < y as signed longs. if (x.compareTo(y) < 0) { - throw new ArithmeticException("unsigned subtraction underflow"); + throw new CelNumericOverflowException("unsigned subtraction underflow"); } return x.minus(y); } diff --git a/runtime/src/main/java/dev/cel/runtime/planner/Attribute.java b/runtime/src/main/java/dev/cel/runtime/planner/Attribute.java index 0ce487ceb..f20c9aadd 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/Attribute.java +++ b/runtime/src/main/java/dev/cel/runtime/planner/Attribute.java @@ -14,64 +14,13 @@ package dev.cel.runtime.planner; - -import com.google.common.collect.ImmutableList; import com.google.errorprone.annotations.Immutable; -import dev.cel.common.types.CelTypeProvider; -import dev.cel.common.types.TypeType; import dev.cel.runtime.GlobalResolver; +/** Represents a resolvable symbol or path (such as a variable or a field selection). */ @Immutable interface Attribute { Object resolve(GlobalResolver ctx); - final class MaybeAttribute implements Attribute { - private final ImmutableList attributes; - - @Override - public Object resolve(GlobalResolver ctx) { - for (Attribute attr : attributes) { - Object value = attr.resolve(ctx); - if (value != null) { - return value; - } - } - - // TODO: Handle unknowns - throw new UnsupportedOperationException("Unknown attributes is not supported yet"); - } - - MaybeAttribute(ImmutableList attributes) { - this.attributes = attributes; - } - } - - final class NamespacedAttribute implements Attribute { - private final ImmutableList namespacedNames; - private final CelTypeProvider typeProvider; - - @Override - public Object resolve(GlobalResolver ctx) { - for (String name : namespacedNames) { - Object value = ctx.resolve(name); - if (value != null) { - // TODO: apply qualifiers - return value; - } - - TypeType type = typeProvider.findType(name).map(TypeType::create).orElse(null); - if (type != null) { - return type; - } - } - - // TODO: Handle unknowns - throw new UnsupportedOperationException("Unknown attributes is not supported yet"); - } - - NamespacedAttribute(CelTypeProvider typeProvider, ImmutableList namespacedNames) { - this.typeProvider = typeProvider; - this.namespacedNames = namespacedNames; - } - } + Attribute addQualifier(Qualifier qualifier); } diff --git a/runtime/src/main/java/dev/cel/runtime/planner/AttributeFactory.java b/runtime/src/main/java/dev/cel/runtime/planner/AttributeFactory.java index bda7c46a6..632c6cd91 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/AttributeFactory.java +++ b/runtime/src/main/java/dev/cel/runtime/planner/AttributeFactory.java @@ -15,35 +15,46 @@ package dev.cel.runtime.planner; import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; import com.google.errorprone.annotations.Immutable; import dev.cel.common.CelContainer; import dev.cel.common.types.CelTypeProvider; -import dev.cel.runtime.planner.Attribute.MaybeAttribute; -import dev.cel.runtime.planner.Attribute.NamespacedAttribute; +import dev.cel.common.values.CelValueConverter; @Immutable final class AttributeFactory { - private final CelContainer unusedContainer; + private final CelContainer container; private final CelTypeProvider typeProvider; + private final CelValueConverter celValueConverter; NamespacedAttribute newAbsoluteAttribute(String... names) { - return new NamespacedAttribute(typeProvider, ImmutableList.copyOf(names)); + return new NamespacedAttribute(typeProvider, celValueConverter, ImmutableSet.copyOf(names)); } - MaybeAttribute newMaybeAttribute(String... names) { - // TODO: Resolve container names + RelativeAttribute newRelativeAttribute(PlannedInterpretable operand) { + return new RelativeAttribute(operand, celValueConverter); + } + + MaybeAttribute newMaybeAttribute(String name) { return new MaybeAttribute( - ImmutableList.of(new NamespacedAttribute(typeProvider, ImmutableList.copyOf(names)))); + this, + ImmutableList.of( + new NamespacedAttribute( + typeProvider, celValueConverter, container.resolveCandidateNames(name)))); } static AttributeFactory newAttributeFactory( - CelContainer celContainer, CelTypeProvider typeProvider) { - return new AttributeFactory(celContainer, typeProvider); + CelContainer celContainer, + CelTypeProvider typeProvider, + CelValueConverter celValueConverter) { + return new AttributeFactory(celContainer, typeProvider, celValueConverter); } - private AttributeFactory(CelContainer container, CelTypeProvider typeProvider) { - this.unusedContainer = container; + private AttributeFactory( + CelContainer container, CelTypeProvider typeProvider, CelValueConverter celValueConverter) { + this.container = container; this.typeProvider = typeProvider; + this.celValueConverter = celValueConverter; } } 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..87a3f60dc 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,7 +26,11 @@ java_library( ":eval_unary", ":eval_var_args_call", ":eval_zero_arity", + ":interpretable_attribute", + ":planned_interpretable", ":planned_program", + ":qualifier", + ":string_qualifier", "//:auto_value", "//common:cel_ast", "//common:container", @@ -34,11 +39,11 @@ java_library( "//common/ast", "//common/types", "//common/types:type_providers", + "//common/values", "//common/values:cel_value_provider", "//runtime:dispatcher", "//runtime:evaluation_exception", "//runtime:evaluation_exception_builder", - "//runtime:interpretable", "//runtime:program", "//runtime:resolved_overload", "@maven//:com_google_code_findbugs_annotations", @@ -51,8 +56,11 @@ java_library( name = "planned_program", srcs = ["PlannedProgram.java"], deps = [ + ":error_metadata", + ":planned_interpretable", + ":strict_error_exception", "//:auto_value", - "//common:runtime_exception", + "//common/exceptions:runtime_exception", "//common/values", "//runtime:activation", "//runtime:evaluation_exception", @@ -68,6 +76,7 @@ java_library( name = "eval_const", srcs = ["EvalConstant.java"], deps = [ + ":planned_interpretable", "//common/values", "//common/values:cel_byte_string", "//runtime:evaluation_listener", @@ -78,27 +87,67 @@ java_library( ], ) +java_library( + name = "interpretable_attribute", + srcs = ["InterpretableAttribute.java"], + deps = [ + ":planned_interpretable", + ":qualifier", + "@maven//:com_google_errorprone_error_prone_annotations", + ], +) + java_library( name = "attribute", srcs = [ "Attribute.java", "AttributeFactory.java", + "MaybeAttribute.java", + "MissingAttribute.java", + "NamespacedAttribute.java", + "RelativeAttribute.java", ], deps = [ + ":eval_helpers", + ":planned_interpretable", + ":qualifier", "//common:container", + "//common/exceptions:attribute_not_found", "//common/types", "//common/types:type_providers", + "//common/values", + "//common/values:cel_value", "//runtime:interpretable", "@maven//:com_google_errorprone_error_prone_annotations", "@maven//:com_google_guava_guava", ], ) +java_library( + name = "qualifier", + srcs = ["Qualifier.java"], + deps = [ + "@maven//:com_google_errorprone_error_prone_annotations", + ], +) + +java_library( + name = "string_qualifier", + srcs = ["StringQualifier.java"], + deps = [ + ":qualifier", + "//common/exceptions:attribute_not_found", + "//common/values", + ], +) + java_library( name = "eval_attribute", srcs = ["EvalAttribute.java"], deps = [ ":attribute", + ":interpretable_attribute", + ":qualifier", "//runtime:evaluation_listener", "//runtime:function_resolver", "//runtime:interpretable", @@ -111,6 +160,7 @@ java_library( name = "eval_zero_arity", srcs = ["EvalZeroArity.java"], deps = [ + ":planned_interpretable", "//runtime:evaluation_exception", "//runtime:evaluation_listener", "//runtime:function_resolver", @@ -123,6 +173,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 +187,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 +202,7 @@ java_library( srcs = ["EvalOr.java"], deps = [ ":eval_helpers", + ":planned_interpretable", "//common/values", "//runtime:evaluation_listener", "//runtime:function_resolver", @@ -161,6 +216,7 @@ java_library( srcs = ["EvalAnd.java"], deps = [ ":eval_helpers", + ":planned_interpretable", "//common/values", "//runtime:evaluation_listener", "//runtime:function_resolver", @@ -173,6 +229,7 @@ java_library( name = "eval_conditional", srcs = ["EvalConditional.java"], deps = [ + ":planned_interpretable", "//runtime:evaluation_exception", "//runtime:evaluation_listener", "//runtime:function_resolver", @@ -185,6 +242,7 @@ java_library( name = "eval_create_struct", srcs = ["EvalCreateStruct.java"], deps = [ + ":planned_interpretable", "//common/types", "//common/values", "//common/values:cel_value_provider", @@ -201,6 +259,7 @@ java_library( name = "eval_create_list", srcs = ["EvalCreateList.java"], deps = [ + ":planned_interpretable", "//runtime:evaluation_exception", "//runtime:evaluation_listener", "//runtime:function_resolver", @@ -214,6 +273,7 @@ java_library( name = "eval_create_map", srcs = ["EvalCreateMap.java"], deps = [ + ":planned_interpretable", "//runtime:evaluation_exception", "//runtime:evaluation_listener", "//runtime:function_resolver", @@ -227,7 +287,39 @@ java_library( name = "eval_helpers", srcs = ["EvalHelpers.java"], deps = [ + ":planned_interpretable", + ":strict_error_exception", + "//common:error_codes", + "//common/exceptions:runtime_exception", "//common/values", "//runtime:interpretable", ], ) + +java_library( + name = "strict_error_exception", + srcs = ["StrictErrorException.java"], + deps = [ + "//common:error_codes", + "//common/exceptions: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..826f7e1fa 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/EvalAttribute.java +++ b/runtime/src/main/java/dev/cel/runtime/planner/EvalAttribute.java @@ -18,16 +18,20 @@ 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 InterpretableAttribute { private final Attribute attr; @Override public Object eval(GlobalResolver resolver) { - return attr.resolve(resolver); + Object resolved = attr.resolve(resolver); + if (resolved instanceof MissingAttribute) { + ((MissingAttribute) resolved).resolve(resolver); + } + + return resolved; } @Override @@ -51,11 +55,18 @@ public Object eval( throw new UnsupportedOperationException("Not yet supported"); } - static EvalAttribute create(Attribute attr) { - return new EvalAttribute(attr); + @Override + public EvalAttribute addQualifier(long exprId, Qualifier qualifier) { + Attribute newAttribute = attr.addQualifier(qualifier); + return create(exprId, newAttribute); + } + + 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..8dae6adfb 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.exceptions.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/InterpretableAttribute.java b/runtime/src/main/java/dev/cel/runtime/planner/InterpretableAttribute.java new file mode 100644 index 000000000..547380c11 --- /dev/null +++ b/runtime/src/main/java/dev/cel/runtime/planner/InterpretableAttribute.java @@ -0,0 +1,27 @@ +// 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; + +@Immutable +abstract class InterpretableAttribute extends PlannedInterpretable { + + abstract InterpretableAttribute addQualifier(long exprId, Qualifier qualifier); + + InterpretableAttribute(long exprId) { + super(exprId); + } +} \ No newline at end of file diff --git a/runtime/src/main/java/dev/cel/runtime/planner/MaybeAttribute.java b/runtime/src/main/java/dev/cel/runtime/planner/MaybeAttribute.java new file mode 100644 index 000000000..542067349 --- /dev/null +++ b/runtime/src/main/java/dev/cel/runtime/planner/MaybeAttribute.java @@ -0,0 +1,81 @@ +// 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.common.collect.ImmutableList; +import com.google.errorprone.annotations.Immutable; +import dev.cel.runtime.GlobalResolver; + +/** + * An attribute that attempts to resolve a variable against a list of potential namespaced + * attributes. This is used during parsed-only evaluation. + */ +@Immutable +final class MaybeAttribute implements Attribute { + private final AttributeFactory attrFactory; + private final ImmutableList attributes; + + @Override + public Object resolve(GlobalResolver ctx) { + MissingAttribute maybeError = null; + for (NamespacedAttribute attr : attributes) { + Object value = attr.resolve(ctx); + if (value == null) { + continue; + } + + if (value instanceof MissingAttribute) { + maybeError = (MissingAttribute) value; + // When the variable is missing in a maybe attribute, defer erroring. + // The variable may exist in other namespaced attributes. + continue; + } + + return value; + } + + return maybeError; + } + + @Override + public Attribute addQualifier(Qualifier qualifier) { + Object strQualifier = qualifier.value(); + ImmutableList.Builder augmentedNamesBuilder = ImmutableList.builder(); + ImmutableList.Builder attributesBuilder = ImmutableList.builder(); + for (NamespacedAttribute attr : attributes) { + if (strQualifier instanceof String && attr.qualifiers().isEmpty()) { + for (String varName : attr.candidateVariableNames()) { + augmentedNamesBuilder.add(varName + "." + strQualifier); + } + } + + attributesBuilder.add(attr.addQualifier(qualifier)); + } + ImmutableList augmentedNames = augmentedNamesBuilder.build(); + ImmutableList.Builder namespacedAttributeBuilder = ImmutableList.builder(); + if (!augmentedNames.isEmpty()) { + namespacedAttributeBuilder.add( + attrFactory.newAbsoluteAttribute(augmentedNames.toArray(new String[0]))); + } + + namespacedAttributeBuilder.addAll(attributesBuilder.build()); + return new MaybeAttribute(attrFactory, namespacedAttributeBuilder.build()); + } + + MaybeAttribute(AttributeFactory attrFactory, ImmutableList attributes) { + this.attrFactory = attrFactory; + this.attributes = attributes; + } +} diff --git a/runtime/src/main/java/dev/cel/runtime/planner/MissingAttribute.java b/runtime/src/main/java/dev/cel/runtime/planner/MissingAttribute.java new file mode 100644 index 000000000..bbb4e0422 --- /dev/null +++ b/runtime/src/main/java/dev/cel/runtime/planner/MissingAttribute.java @@ -0,0 +1,47 @@ +// 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.common.collect.ImmutableSet; +import dev.cel.common.exceptions.CelAttributeNotFoundException; +import dev.cel.runtime.GlobalResolver; + +/** Represents a missing attribute that is surfaced while resolving a struct field or a map key. */ +final class MissingAttribute implements Attribute { + + private final ImmutableSet missingAttributes; + + @Override + public Object resolve(GlobalResolver ctx) { + throw CelAttributeNotFoundException.forFieldResolution(missingAttributes); + } + + @Override + public Attribute addQualifier(Qualifier qualifier) { + throw new UnsupportedOperationException("Unsupported operation"); + } + + static MissingAttribute newMissingAttribute(String... attributeNames) { + return newMissingAttribute(ImmutableSet.copyOf(attributeNames)); + } + + static MissingAttribute newMissingAttribute(ImmutableSet attributeNames) { + return new MissingAttribute(attributeNames); + } + + private MissingAttribute(ImmutableSet missingAttributes) { + this.missingAttributes = missingAttributes; + } +} diff --git a/runtime/src/main/java/dev/cel/runtime/planner/NamespacedAttribute.java b/runtime/src/main/java/dev/cel/runtime/planner/NamespacedAttribute.java new file mode 100644 index 000000000..b90ac0824 --- /dev/null +++ b/runtime/src/main/java/dev/cel/runtime/planner/NamespacedAttribute.java @@ -0,0 +1,126 @@ +// 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.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; +import com.google.errorprone.annotations.Immutable; +import dev.cel.common.types.CelType; +import dev.cel.common.types.CelTypeProvider; +import dev.cel.common.types.EnumType; +import dev.cel.common.types.TypeType; +import dev.cel.common.values.CelValue; +import dev.cel.common.values.CelValueConverter; +import dev.cel.runtime.GlobalResolver; +import java.util.NoSuchElementException; + +@Immutable +final class NamespacedAttribute implements Attribute { + private final ImmutableSet namespacedNames; + private final ImmutableList qualifiers; + private final CelValueConverter celValueConverter; + private final CelTypeProvider typeProvider; + + @Override + public Object resolve(GlobalResolver ctx) { + for (String name : namespacedNames) { + Object value = ctx.resolve(name); + if (value != null) { + if (!qualifiers.isEmpty()) { + return applyQualifiers(value, celValueConverter, qualifiers); + } else { + return value; + } + } + + CelType type = typeProvider.findType(name).orElse(null); + if (type != null) { + if (qualifiers.isEmpty()) { + // Resolution of a fully qualified type name: foo.bar.baz + return TypeType.create(type); + } else { + // This is potentially a fully qualified reference to an enum value + if (type instanceof EnumType && qualifiers.size() == 1) { + EnumType enumType = (EnumType) type; + String strQualifier = (String) qualifiers.get(0).value(); + return enumType + .findNumberByName(strQualifier) + .orElseThrow( + () -> + new NoSuchElementException( + String.format( + "Field %s was not found on enum %s", + enumType.name(), strQualifier))); + } + } + + throw new IllegalStateException( + "Unexpected type resolution when there were remaining qualifiers: " + type.name()); + } + } + + return MissingAttribute.newMissingAttribute(namespacedNames); + } + + ImmutableList qualifiers() { + return qualifiers; + } + + ImmutableSet candidateVariableNames() { + return namespacedNames; + } + + @Override + public NamespacedAttribute addQualifier(Qualifier qualifier) { + return new NamespacedAttribute( + typeProvider, + celValueConverter, + namespacedNames, + ImmutableList.builder().addAll(qualifiers).add(qualifier).build()); + } + + private static Object applyQualifiers( + Object value, CelValueConverter celValueConverter, ImmutableList qualifiers) { + Object obj = celValueConverter.toRuntimeValue(value); + + for (Qualifier qualifier : qualifiers) { + obj = qualifier.qualify(obj); + } + + if (obj instanceof CelValue) { + obj = celValueConverter.unwrap((CelValue) obj); + } + + return obj; + } + + NamespacedAttribute( + CelTypeProvider typeProvider, + CelValueConverter celValueConverter, + ImmutableSet namespacedNames) { + this(typeProvider, celValueConverter, namespacedNames, ImmutableList.of()); + } + + private NamespacedAttribute( + CelTypeProvider typeProvider, + CelValueConverter celValueConverter, + ImmutableSet namespacedNames, + ImmutableList qualifiers) { + this.typeProvider = typeProvider; + this.celValueConverter = celValueConverter; + this.namespacedNames = namespacedNames; + this.qualifiers = qualifiers; + } +} 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..095839bb7 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/PlannedProgram.java +++ b/runtime/src/main/java/dev/cel/runtime/planner/PlannedProgram.java @@ -16,21 +16,22 @@ import com.google.auto.value.AutoValue; import com.google.errorprone.annotations.Immutable; -import dev.cel.common.CelRuntimeException; +import dev.cel.common.exceptions.CelRuntimeException; import dev.cel.common.values.ErrorValue; import dev.cel.runtime.Activation; import dev.cel.runtime.CelEvaluationException; 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..be197649f 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/ProgramPlanner.java +++ b/runtime/src/main/java/dev/cel/runtime/planner/ProgramPlanner.java @@ -37,12 +37,12 @@ import dev.cel.common.types.CelTypeProvider; import dev.cel.common.types.StructType; import dev.cel.common.types.TypeType; +import dev.cel.common.values.CelValueConverter; import dev.cel.common.values.CelValueProvider; import dev.cel.runtime.CelEvaluationException; import dev.cel.runtime.CelEvaluationExceptionBuilder; import dev.cel.runtime.CelResolvedOverload; import dev.cel.runtime.DefaultDispatcher; -import dev.cel.runtime.Interpretable; import dev.cel.runtime.Program; import java.util.NoSuchElementException; import java.util.Optional; @@ -66,22 +66,26 @@ 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()); case IDENT: return planIdent(celExpr, ctx); + case SELECT: + return planSelect(celExpr, ctx); case CALL: return planCall(celExpr, ctx); case LIST: @@ -97,7 +101,28 @@ private Interpretable plan(CelExpr celExpr, PlannerContext ctx) { } } - private Interpretable planConstant(CelConstant celConstant) { + private PlannedInterpretable planSelect(CelExpr celExpr, PlannerContext ctx) { + CelSelect select = celExpr.select(); + PlannedInterpretable operand = plan(select.operand(), ctx); + + InterpretableAttribute attribute; + if (operand instanceof EvalAttribute) { + attribute = (EvalAttribute) operand; + } else { + attribute = + EvalAttribute.create(celExpr.id(), attributeFactory.newRelativeAttribute(operand)); + } + + if (select.testOnly()) { + throw new UnsupportedOperationException("Presence tests not supported yet"); + } + + Qualifier qualifier = StringQualifier.create(select.field()); + + return attribute.addQualifier(celExpr.id(), qualifier); + } + + private PlannedInterpretable planConstant(CelConstant celConstant) { switch (celConstant.getKind()) { case NULL_VALUE: return EvalConstant.create(celConstant.nullValue()); @@ -118,16 +143,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 +172,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 +183,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 +201,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,21 +226,21 @@ 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); ImmutableList entries = struct.entries(); String[] keys = new String[entries.size()]; - Interpretable[] values = new Interpretable[entries.size()]; + PlannedInterpretable[] values = new PlannedInterpretable[entries.size()]; for (int i = 0; i < entries.size(); i++) { Entry entry = entries.get(i); @@ -222,28 +248,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 +277,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); } /** @@ -400,19 +426,23 @@ public static ProgramPlanner newPlanner( CelTypeProvider typeProvider, CelValueProvider valueProvider, DefaultDispatcher dispatcher, + CelValueConverter celValueConverter, CelContainer container) { - return new ProgramPlanner(typeProvider, valueProvider, dispatcher, container); + return new ProgramPlanner( + typeProvider, valueProvider, dispatcher, celValueConverter, container); } private ProgramPlanner( CelTypeProvider typeProvider, CelValueProvider valueProvider, DefaultDispatcher dispatcher, + CelValueConverter celValueConverter, CelContainer container) { this.typeProvider = typeProvider; this.valueProvider = valueProvider; this.dispatcher = dispatcher; this.container = container; - this.attributeFactory = AttributeFactory.newAttributeFactory(container, typeProvider); + this.attributeFactory = + AttributeFactory.newAttributeFactory(container, typeProvider, celValueConverter); } } diff --git a/runtime/src/main/java/dev/cel/runtime/planner/Qualifier.java b/runtime/src/main/java/dev/cel/runtime/planner/Qualifier.java new file mode 100644 index 000000000..82e48e95a --- /dev/null +++ b/runtime/src/main/java/dev/cel/runtime/planner/Qualifier.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.runtime.planner; + +import com.google.errorprone.annotations.Immutable; + +/** + * Represents a qualification step (such as a field selection or map key lookup) applied to an + * intermediate value during attribute resolution. + */ +@Immutable +interface Qualifier { + Object value(); + + Object qualify(Object value); +} diff --git a/runtime/src/main/java/dev/cel/runtime/planner/RelativeAttribute.java b/runtime/src/main/java/dev/cel/runtime/planner/RelativeAttribute.java new file mode 100644 index 000000000..7357d8147 --- /dev/null +++ b/runtime/src/main/java/dev/cel/runtime/planner/RelativeAttribute.java @@ -0,0 +1,70 @@ +// 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.common.collect.ImmutableList; +import com.google.errorprone.annotations.Immutable; +import dev.cel.common.values.CelValueConverter; +import dev.cel.runtime.GlobalResolver; + +/** + * An attribute resolved relative to a base expression (operand) by applying a sequence of + * qualifiers. + */ +@Immutable +final class RelativeAttribute implements Attribute { + + private final PlannedInterpretable operand; + private final CelValueConverter celValueConverter; + private final ImmutableList qualifiers; + + @Override + public Object resolve(GlobalResolver ctx) { + Object obj = EvalHelpers.evalStrictly(operand, ctx); + obj = celValueConverter.toRuntimeValue(obj); + + for (Qualifier qualifier : qualifiers) { + obj = qualifier.qualify(obj); + } + + // TODO: Handle unknowns + + return obj; + } + + @Override + public Attribute addQualifier(Qualifier qualifier) { + return new RelativeAttribute( + this.operand, + celValueConverter, + ImmutableList.builderWithExpectedSize(qualifiers.size() + 1) + .addAll(this.qualifiers) + .add(qualifier) + .build()); + } + + RelativeAttribute(PlannedInterpretable operand, CelValueConverter celValueConverter) { + this(operand, celValueConverter, ImmutableList.of()); + } + + private RelativeAttribute( + PlannedInterpretable operand, + CelValueConverter celValueConverter, + ImmutableList qualifiers) { + this.operand = operand; + this.celValueConverter = celValueConverter; + this.qualifiers = qualifiers; + } +} 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..441a1f27c --- /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.exceptions.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/main/java/dev/cel/runtime/planner/StringQualifier.java b/runtime/src/main/java/dev/cel/runtime/planner/StringQualifier.java new file mode 100644 index 000000000..4ceaa0e51 --- /dev/null +++ b/runtime/src/main/java/dev/cel/runtime/planner/StringQualifier.java @@ -0,0 +1,61 @@ +// 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.exceptions.CelAttributeNotFoundException; +import dev.cel.common.values.SelectableValue; +import java.util.Map; + +/** A qualifier that accesses fields or map keys using a string identifier. */ +final class StringQualifier implements Qualifier { + + private final String value; + + @Override + public String value() { + return value; + } + + @Override + @SuppressWarnings("unchecked") // Qualifications on maps/structs must be a string + public Object qualify(Object obj) { + if (obj instanceof SelectableValue) { + return ((SelectableValue) obj).select(value); + } else if (obj instanceof Map) { + Map map = (Map) obj; + if (!map.containsKey(value)) { + throw CelAttributeNotFoundException.forMissingMapKey(value); + } + + Object mapVal = map.get(value); + + if (mapVal == null) { + throw CelAttributeNotFoundException.of( + String.format("Map value cannot be null for key: %s", value)); + } + return map.get(value); + } + + throw CelAttributeNotFoundException.forFieldResolution(value); + } + + static StringQualifier create(String value) { + return new StringQualifier(value); + } + + private StringQualifier(String value) { + this.value = value; + } +} diff --git a/runtime/src/main/java/dev/cel/runtime/standard/AddOperator.java b/runtime/src/main/java/dev/cel/runtime/standard/AddOperator.java index 783ddd81a..84f4e2050 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/AddOperator.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/AddOperator.java @@ -14,15 +14,13 @@ package dev.cel.runtime.standard; -import static dev.cel.runtime.standard.ArithmeticHelpers.getArithmeticErrorCode; - import com.google.common.collect.ImmutableSet; import com.google.common.primitives.UnsignedLong; import com.google.protobuf.ByteString; import com.google.protobuf.Duration; import com.google.protobuf.Timestamp; import dev.cel.common.CelOptions; -import dev.cel.common.CelRuntimeException; +import dev.cel.common.exceptions.CelNumericOverflowException; import dev.cel.common.internal.DateTimeHelpers; import dev.cel.common.internal.ProtoTimeUtils; import dev.cel.common.values.CelByteString; @@ -61,7 +59,7 @@ public enum AddOverload implements CelStandardOverload { try { return RuntimeHelpers.int64Add(x, y, celOptions); } catch (ArithmeticException e) { - throw new CelRuntimeException(e, getArithmeticErrorCode(e)); + throw new CelNumericOverflowException(e); } })), ADD_UINT64( @@ -75,7 +73,7 @@ public enum AddOverload implements CelStandardOverload { try { return RuntimeHelpers.uint64Add(x, y); } catch (ArithmeticException e) { - throw new CelRuntimeException(e, getArithmeticErrorCode(e)); + throw new CelNumericOverflowException(e); } }); } else { @@ -87,7 +85,7 @@ public enum AddOverload implements CelStandardOverload { try { return RuntimeHelpers.uint64Add(x, y, celOptions); } catch (ArithmeticException e) { - throw new CelRuntimeException(e, getArithmeticErrorCode(e)); + throw new CelNumericOverflowException(e); } }); } diff --git a/runtime/src/main/java/dev/cel/runtime/standard/BUILD.bazel b/runtime/src/main/java/dev/cel/runtime/standard/BUILD.bazel index 041d07dea..9f1bc708b 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/BUILD.bazel +++ b/runtime/src/main/java/dev/cel/runtime/standard/BUILD.bazel @@ -45,11 +45,10 @@ java_library( tags = [ ], deps = [ - ":arithmetic_helpers", ":standard_function", ":standard_overload", "//common:options", - "//common:runtime_exception", + "//common/exceptions:numeric_overflow", "//common/internal:date_time_helpers", "//common/internal:proto_time_utils", "//common/values:cel_byte_string", @@ -67,11 +66,10 @@ cel_android_library( tags = [ ], deps = [ - ":arithmetic_helpers", ":standard_function_android", ":standard_overload_android", "//common:options", - "//common:runtime_exception", + "//common/exceptions:numeric_overflow", "//common/internal:date_time_helpers_android", "//common/internal:proto_time_utils_android", "//common/values:cel_byte_string", @@ -89,11 +87,10 @@ java_library( tags = [ ], deps = [ - ":arithmetic_helpers", ":standard_function", ":standard_overload", "//common:options", - "//common:runtime_exception", + "//common/exceptions:numeric_overflow", "//common/internal:date_time_helpers", "//common/internal:proto_time_utils", "//runtime:function_binding", @@ -110,11 +107,10 @@ cel_android_library( tags = [ ], deps = [ - ":arithmetic_helpers", ":standard_function_android", ":standard_overload_android", "//common:options", - "//common:runtime_exception", + "//common/exceptions:numeric_overflow", "//common/internal:date_time_helpers_android", "//common/internal:proto_time_utils_android", "//runtime:function_binding_android", @@ -132,9 +128,8 @@ java_library( ], deps = [ ":standard_overload", - "//common:error_codes", "//common:options", - "//common:runtime_exception", + "//common/exceptions:bad_format", "//common/internal:safe_string_formatter", "//runtime:function_binding", "//runtime:runtime_equality", @@ -151,9 +146,8 @@ cel_android_library( deps = [ ":standard_function_android", ":standard_overload_android", - "//common:error_codes", "//common:options", - "//common:runtime_exception", + "//common/exceptions:bad_format", "//common/internal:safe_string_formatter", "//runtime:function_binding_android", "//runtime:runtime_equality_android", @@ -232,9 +226,8 @@ java_library( ], deps = [ ":standard_overload", - "//common:error_codes", "//common:options", - "//common:runtime_exception", + "//common/exceptions:bad_format", "//runtime:function_binding", "//runtime:runtime_equality", "//runtime/standard:standard_function", @@ -250,9 +243,8 @@ cel_android_library( deps = [ ":standard_function_android", ":standard_overload_android", - "//common:error_codes", "//common:options", - "//common:runtime_exception", + "//common/exceptions:bad_format", "//runtime:function_binding_android", "//runtime:runtime_equality_android", "@maven_android//:com_google_guava_guava", @@ -266,9 +258,8 @@ java_library( ], deps = [ ":standard_overload", - "//common:error_codes", "//common:options", - "//common:runtime_exception", + "//common/exceptions:bad_format", "//runtime:function_binding", "//runtime:runtime_equality", "//runtime:runtime_helpers", @@ -286,9 +277,8 @@ cel_android_library( deps = [ ":standard_function_android", ":standard_overload_android", - "//common:error_codes", "//common:options", - "//common:runtime_exception", + "//common/exceptions:bad_format", "//runtime:function_binding_android", "//runtime:runtime_equality_android", "//runtime:runtime_helpers_android", @@ -884,9 +874,9 @@ java_library( ], deps = [ ":standard_overload", - "//common:error_codes", "//common:options", - "//common:runtime_exception", + "//common/exceptions:bad_format", + "//common/exceptions:numeric_overflow", "//common/internal:proto_time_utils", "//runtime:function_binding", "//runtime:runtime_equality", @@ -905,9 +895,9 @@ cel_android_library( deps = [ ":standard_function_android", ":standard_overload_android", - "//common:error_codes", "//common:options", - "//common:runtime_exception", + "//common/exceptions:bad_format", + "//common/exceptions:numeric_overflow", "//common/internal:proto_time_utils_android", "//runtime:function_binding_android", "//runtime:runtime_equality_android", @@ -1034,9 +1024,8 @@ java_library( ], deps = [ ":standard_overload", - "//common:error_codes", "//common:options", - "//common:runtime_exception", + "//common/exceptions:invalid_argument", "//runtime:function_binding", "//runtime:runtime_equality", "//runtime:runtime_helpers", @@ -1053,9 +1042,8 @@ cel_android_library( deps = [ ":standard_function_android", ":standard_overload_android", - "//common:error_codes", "//common:options", - "//common:runtime_exception", + "//common/exceptions:invalid_argument", "//runtime:function_binding_android", "//runtime:runtime_equality_android", "//runtime:runtime_helpers_android", @@ -1069,10 +1057,9 @@ java_library( tags = [ ], deps = [ - ":arithmetic_helpers", ":standard_overload", "//common:options", - "//common:runtime_exception", + "//common/exceptions:divide_by_zero", "//runtime:function_binding", "//runtime:runtime_equality", "//runtime:runtime_helpers", @@ -1087,11 +1074,10 @@ cel_android_library( tags = [ ], deps = [ - ":arithmetic_helpers", ":standard_function_android", ":standard_overload_android", "//common:options", - "//common:runtime_exception", + "//common/exceptions:divide_by_zero", "//runtime:function_binding_android", "//runtime:runtime_equality_android", "//runtime:runtime_helpers_android", @@ -1105,10 +1091,9 @@ java_library( tags = [ ], deps = [ - ":arithmetic_helpers", ":standard_overload", "//common:options", - "//common:runtime_exception", + "//common/exceptions:numeric_overflow", "//runtime:function_binding", "//runtime:runtime_equality", "//runtime:runtime_helpers", @@ -1123,11 +1108,10 @@ cel_android_library( tags = [ ], deps = [ - ":arithmetic_helpers", ":standard_function_android", ":standard_overload_android", "//common:options", - "//common:runtime_exception", + "//common/exceptions:numeric_overflow", "//runtime:function_binding_android", "//runtime:runtime_equality_android", "//runtime:runtime_helpers_android", @@ -1141,10 +1125,9 @@ java_library( tags = [ ], deps = [ - ":arithmetic_helpers", ":standard_overload", "//common:options", - "//common:runtime_exception", + "//common/exceptions:divide_by_zero", "//runtime:function_binding", "//runtime:runtime_equality", "//runtime:runtime_helpers", @@ -1159,11 +1142,10 @@ cel_android_library( tags = [ ], deps = [ - ":arithmetic_helpers", ":standard_function_android", ":standard_overload_android", "//common:options", - "//common:runtime_exception", + "//common/exceptions:divide_by_zero", "//runtime:function_binding_android", "//runtime:runtime_equality_android", "//runtime:runtime_helpers_android", @@ -1177,10 +1159,9 @@ java_library( tags = [ ], deps = [ - ":arithmetic_helpers", ":standard_overload", "//common:options", - "//common:runtime_exception", + "//common/exceptions:numeric_overflow", "//runtime:function_binding", "//runtime:runtime_equality", "//runtime:runtime_helpers", @@ -1195,11 +1176,10 @@ cel_android_library( tags = [ ], deps = [ - ":arithmetic_helpers", ":standard_function_android", ":standard_overload_android", "//common:options", - "//common:runtime_exception", + "//common/exceptions:numeric_overflow", "//runtime:function_binding_android", "//runtime:runtime_equality_android", "//runtime:runtime_helpers_android", @@ -1308,9 +1288,8 @@ java_library( ], deps = [ ":standard_overload", - "//common:error_codes", "//common:options", - "//common:runtime_exception", + "//common/exceptions:bad_format", "//common/internal:date_time_helpers", "//common/internal:proto_time_utils", "//common/values:cel_byte_string", @@ -1330,9 +1309,8 @@ cel_android_library( deps = [ ":standard_function_android", ":standard_overload_android", - "//common:error_codes", "//common:options", - "//common:runtime_exception", + "//common/exceptions:bad_format", "//common/internal:date_time_helpers_android", "//common/internal:proto_time_utils_android", "//common/values:cel_byte_string", @@ -1350,9 +1328,8 @@ java_library( ], deps = [ ":standard_overload", - "//common:error_codes", "//common:options", - "//common:runtime_exception", + "//common/exceptions:bad_format", "//common/internal:date_time_helpers", "//common/internal:proto_time_utils", "//runtime:function_binding", @@ -1371,9 +1348,8 @@ cel_android_library( deps = [ ":standard_function_android", ":standard_overload_android", - "//common:error_codes", "//common:options", - "//common:runtime_exception", + "//common/exceptions:bad_format", "//common/internal:date_time_helpers_android", "//common/internal:proto_time_utils_android", "//runtime:function_binding_android", @@ -1390,9 +1366,9 @@ java_library( ], deps = [ ":standard_overload", - "//common:error_codes", "//common:options", - "//common:runtime_exception", + "//common/exceptions:bad_format", + "//common/exceptions:numeric_overflow", "//runtime:function_binding", "//runtime:runtime_equality", "//runtime:runtime_helpers", @@ -1409,9 +1385,9 @@ cel_android_library( deps = [ ":standard_function_android", ":standard_overload_android", - "//common:error_codes", "//common:options", - "//common:runtime_exception", + "//common/exceptions:bad_format", + "//common/exceptions:numeric_overflow", "//runtime:function_binding_android", "//runtime:runtime_equality_android", "//runtime:runtime_helpers_android", @@ -1476,13 +1452,3 @@ cel_android_library( "@maven//:com_google_errorprone_error_prone_annotations", ], ) - -java_library( - name = "arithmetic_helpers", - srcs = ["ArithmeticHelpers.java"], - # used_by_android - visibility = ["//visibility:private"], - deps = [ - "//common:error_codes", - ], -) diff --git a/runtime/src/main/java/dev/cel/runtime/standard/BoolFunction.java b/runtime/src/main/java/dev/cel/runtime/standard/BoolFunction.java index 5d9d3919c..330fbe73f 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/BoolFunction.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/BoolFunction.java @@ -15,9 +15,8 @@ package dev.cel.runtime.standard; import com.google.common.collect.ImmutableSet; -import dev.cel.common.CelErrorCode; import dev.cel.common.CelOptions; -import dev.cel.common.CelRuntimeException; +import dev.cel.common.exceptions.CelBadFormatException; import dev.cel.common.internal.SafeStringFormatter; import dev.cel.runtime.CelFunctionBinding; import dev.cel.runtime.RuntimeEquality; @@ -64,11 +63,9 @@ public enum BoolOverload implements CelStandardOverload { case "0": return false; default: - throw new CelRuntimeException( - new IllegalArgumentException( - SafeStringFormatter.format( - "Type conversion error from 'string' to 'bool': [%s]", str)), - CelErrorCode.BAD_FORMAT); + throw new CelBadFormatException( + SafeStringFormatter.format( + "Type conversion error from 'string' to 'bool': [%s]", str)); } })); diff --git a/runtime/src/main/java/dev/cel/runtime/standard/DivideOperator.java b/runtime/src/main/java/dev/cel/runtime/standard/DivideOperator.java index b9fdad33c..6a45752dc 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/DivideOperator.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/DivideOperator.java @@ -14,12 +14,10 @@ package dev.cel.runtime.standard; -import static dev.cel.runtime.standard.ArithmeticHelpers.getArithmeticErrorCode; - import com.google.common.collect.ImmutableSet; import com.google.common.primitives.UnsignedLong; import dev.cel.common.CelOptions; -import dev.cel.common.CelRuntimeException; +import dev.cel.common.exceptions.CelDivideByZeroException; import dev.cel.runtime.CelFunctionBinding; import dev.cel.runtime.RuntimeEquality; import dev.cel.runtime.RuntimeHelpers; @@ -57,7 +55,7 @@ public enum DivideOverload implements CelStandardOverload { try { return RuntimeHelpers.int64Divide(x, y, celOptions); } catch (ArithmeticException e) { - throw new CelRuntimeException(e, getArithmeticErrorCode(e)); + throw new CelDivideByZeroException(e); } })), DIVIDE_UINT64( diff --git a/runtime/src/main/java/dev/cel/runtime/standard/DoubleFunction.java b/runtime/src/main/java/dev/cel/runtime/standard/DoubleFunction.java index 508df1983..d2c4769b6 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/DoubleFunction.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/DoubleFunction.java @@ -16,9 +16,8 @@ import com.google.common.collect.ImmutableSet; import com.google.common.primitives.UnsignedLong; -import dev.cel.common.CelErrorCode; import dev.cel.common.CelOptions; -import dev.cel.common.CelRuntimeException; +import dev.cel.common.exceptions.CelBadFormatException; import dev.cel.runtime.CelFunctionBinding; import dev.cel.runtime.RuntimeEquality; import java.util.Arrays; @@ -56,7 +55,7 @@ public enum DoubleOverload implements CelStandardOverload { try { return Double.parseDouble(arg); } catch (NumberFormatException e) { - throw new CelRuntimeException(e, CelErrorCode.BAD_FORMAT); + throw new CelBadFormatException(e); } })), UINT64_TO_DOUBLE( diff --git a/runtime/src/main/java/dev/cel/runtime/standard/DurationFunction.java b/runtime/src/main/java/dev/cel/runtime/standard/DurationFunction.java index 72f96f785..b4fd184a6 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/DurationFunction.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/DurationFunction.java @@ -16,9 +16,8 @@ import com.google.common.collect.ImmutableSet; import com.google.protobuf.Duration; -import dev.cel.common.CelErrorCode; import dev.cel.common.CelOptions; -import dev.cel.common.CelRuntimeException; +import dev.cel.common.exceptions.CelBadFormatException; import dev.cel.runtime.CelFunctionBinding; import dev.cel.runtime.RuntimeEquality; import dev.cel.runtime.RuntimeHelpers; @@ -62,13 +61,13 @@ public enum DurationOverload implements CelStandardOverload { try { return RuntimeHelpers.createJavaDurationFromString(d); } catch (IllegalArgumentException e) { - throw new CelRuntimeException(e, CelErrorCode.BAD_FORMAT); + throw new CelBadFormatException(e); } } else { try { return RuntimeHelpers.createDurationFromString(d); } catch (IllegalArgumentException e) { - throw new CelRuntimeException(e, CelErrorCode.BAD_FORMAT); + throw new CelBadFormatException(e); } } })), diff --git a/runtime/src/main/java/dev/cel/runtime/standard/IntFunction.java b/runtime/src/main/java/dev/cel/runtime/standard/IntFunction.java index fda307a9d..0c0768026 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/IntFunction.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/IntFunction.java @@ -17,9 +17,9 @@ import com.google.common.collect.ImmutableSet; import com.google.common.primitives.UnsignedLong; import com.google.protobuf.Timestamp; -import dev.cel.common.CelErrorCode; import dev.cel.common.CelOptions; -import dev.cel.common.CelRuntimeException; +import dev.cel.common.exceptions.CelBadFormatException; +import dev.cel.common.exceptions.CelNumericOverflowException; import dev.cel.common.internal.ProtoTimeUtils; import dev.cel.runtime.CelFunctionBinding; import dev.cel.runtime.RuntimeEquality; @@ -56,9 +56,7 @@ public enum IntOverload implements CelStandardOverload { UnsignedLong.class, (UnsignedLong arg) -> { if (arg.compareTo(UnsignedLong.valueOf(Long.MAX_VALUE)) > 0) { - throw new CelRuntimeException( - new IllegalArgumentException("unsigned out of int range"), - CelErrorCode.NUMERIC_OVERFLOW); + throw new CelNumericOverflowException("unsigned out of int range"); } return arg.longValue(); }); @@ -68,9 +66,7 @@ public enum IntOverload implements CelStandardOverload { Long.class, (Long arg) -> { if (celOptions.errorOnIntWrap() && arg < 0) { - throw new CelRuntimeException( - new IllegalArgumentException("unsigned out of int range"), - CelErrorCode.NUMERIC_OVERFLOW); + throw new CelNumericOverflowException("unsigned out of int range"); } return arg; }); @@ -86,9 +82,7 @@ public enum IntOverload implements CelStandardOverload { return RuntimeHelpers.doubleToLongChecked(arg) .orElseThrow( () -> - new CelRuntimeException( - new IllegalArgumentException("double is out of range for int"), - CelErrorCode.NUMERIC_OVERFLOW)); + new CelNumericOverflowException("double is out of range for int")); } return arg.longValue(); })), @@ -101,7 +95,7 @@ public enum IntOverload implements CelStandardOverload { try { return Long.parseLong(arg); } catch (NumberFormatException e) { - throw new CelRuntimeException(e, CelErrorCode.BAD_FORMAT); + throw new CelBadFormatException(e); } })), TIMESTAMP_TO_INT64( diff --git a/runtime/src/main/java/dev/cel/runtime/standard/MatchesFunction.java b/runtime/src/main/java/dev/cel/runtime/standard/MatchesFunction.java index 7c24f65a2..2d0a504b8 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/MatchesFunction.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/MatchesFunction.java @@ -15,9 +15,8 @@ package dev.cel.runtime.standard; import com.google.common.collect.ImmutableSet; -import dev.cel.common.CelErrorCode; import dev.cel.common.CelOptions; -import dev.cel.common.CelRuntimeException; +import dev.cel.common.exceptions.CelInvalidArgumentException; import dev.cel.runtime.CelFunctionBinding; import dev.cel.runtime.RuntimeEquality; import dev.cel.runtime.RuntimeHelpers; @@ -51,7 +50,7 @@ public enum MatchesOverload implements CelStandardOverload { try { return RuntimeHelpers.matches(string, regexp, celOptions); } catch (RuntimeException e) { - throw new CelRuntimeException(e, CelErrorCode.INVALID_ARGUMENT); + throw new CelInvalidArgumentException(e); } })), // Duplicate receiver-style matches overload. @@ -65,7 +64,7 @@ public enum MatchesOverload implements CelStandardOverload { try { return RuntimeHelpers.matches(string, regexp, celOptions); } catch (RuntimeException e) { - throw new CelRuntimeException(e, CelErrorCode.INVALID_ARGUMENT); + throw new CelInvalidArgumentException(e); } })), ; diff --git a/runtime/src/main/java/dev/cel/runtime/standard/ModuloOperator.java b/runtime/src/main/java/dev/cel/runtime/standard/ModuloOperator.java index e7246851c..787d58de5 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/ModuloOperator.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/ModuloOperator.java @@ -14,12 +14,10 @@ package dev.cel.runtime.standard; -import static dev.cel.runtime.standard.ArithmeticHelpers.getArithmeticErrorCode; - import com.google.common.collect.ImmutableSet; import com.google.common.primitives.UnsignedLong; import dev.cel.common.CelOptions; -import dev.cel.common.CelRuntimeException; +import dev.cel.common.exceptions.CelDivideByZeroException; import dev.cel.runtime.CelFunctionBinding; import dev.cel.runtime.RuntimeEquality; import dev.cel.runtime.RuntimeHelpers; @@ -53,7 +51,7 @@ public enum ModuloOverload implements CelStandardOverload { try { return x % y; } catch (ArithmeticException e) { - throw new CelRuntimeException(e, getArithmeticErrorCode(e)); + throw new CelDivideByZeroException(e); } })), MODULO_UINT64( diff --git a/runtime/src/main/java/dev/cel/runtime/standard/MultiplyOperator.java b/runtime/src/main/java/dev/cel/runtime/standard/MultiplyOperator.java index 7e9e2f352..8a5ad1a56 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/MultiplyOperator.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/MultiplyOperator.java @@ -14,12 +14,10 @@ package dev.cel.runtime.standard; -import static dev.cel.runtime.standard.ArithmeticHelpers.getArithmeticErrorCode; - import com.google.common.collect.ImmutableSet; import com.google.common.primitives.UnsignedLong; import dev.cel.common.CelOptions; -import dev.cel.common.CelRuntimeException; +import dev.cel.common.exceptions.CelNumericOverflowException; import dev.cel.runtime.CelFunctionBinding; import dev.cel.runtime.RuntimeEquality; import dev.cel.runtime.RuntimeHelpers; @@ -53,7 +51,7 @@ public enum MultiplyOverload implements CelStandardOverload { try { return RuntimeHelpers.int64Multiply(x, y, celOptions); } catch (ArithmeticException e) { - throw new CelRuntimeException(e, getArithmeticErrorCode(e)); + throw new CelNumericOverflowException(e); } })), MULTIPLY_DOUBLE( @@ -71,7 +69,7 @@ public enum MultiplyOverload implements CelStandardOverload { try { return RuntimeHelpers.uint64Multiply(x, y); } catch (ArithmeticException e) { - throw new CelRuntimeException(e, getArithmeticErrorCode(e)); + throw new CelNumericOverflowException(e); } }); } else { @@ -83,7 +81,7 @@ public enum MultiplyOverload implements CelStandardOverload { try { return RuntimeHelpers.uint64Multiply(x, y, celOptions); } catch (ArithmeticException e) { - throw new CelRuntimeException(e, getArithmeticErrorCode(e)); + throw new CelNumericOverflowException(e); } }); } diff --git a/runtime/src/main/java/dev/cel/runtime/standard/NegateOperator.java b/runtime/src/main/java/dev/cel/runtime/standard/NegateOperator.java index 2e3f094f5..5ff2a614d 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/NegateOperator.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/NegateOperator.java @@ -14,11 +14,9 @@ package dev.cel.runtime.standard; -import static dev.cel.runtime.standard.ArithmeticHelpers.getArithmeticErrorCode; - import com.google.common.collect.ImmutableSet; import dev.cel.common.CelOptions; -import dev.cel.common.CelRuntimeException; +import dev.cel.common.exceptions.CelNumericOverflowException; import dev.cel.runtime.CelFunctionBinding; import dev.cel.runtime.RuntimeEquality; import dev.cel.runtime.RuntimeHelpers; @@ -51,7 +49,7 @@ public enum NegateOverload implements CelStandardOverload { try { return RuntimeHelpers.int64Negate(x, celOptions); } catch (ArithmeticException e) { - throw new CelRuntimeException(e, getArithmeticErrorCode(e)); + throw new CelNumericOverflowException(e); } })), NEGATE_DOUBLE( diff --git a/runtime/src/main/java/dev/cel/runtime/standard/StringFunction.java b/runtime/src/main/java/dev/cel/runtime/standard/StringFunction.java index c491bfe76..f19a4cd58 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/StringFunction.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/StringFunction.java @@ -20,9 +20,8 @@ import com.google.protobuf.ByteString; import com.google.protobuf.Duration; import com.google.protobuf.Timestamp; -import dev.cel.common.CelErrorCode; import dev.cel.common.CelOptions; -import dev.cel.common.CelRuntimeException; +import dev.cel.common.exceptions.CelBadFormatException; import dev.cel.common.internal.DateTimeHelpers; import dev.cel.common.internal.ProtoTimeUtils; import dev.cel.common.values.CelByteString; @@ -69,10 +68,8 @@ public enum StringOverload implements CelStandardOverload { CelByteString.class, (byteStr) -> { if (!byteStr.isValidUtf8()) { - throw new CelRuntimeException( - new IllegalArgumentException( - "invalid UTF-8 in bytes, cannot convert to string"), - CelErrorCode.BAD_FORMAT); + throw new CelBadFormatException( + "invalid UTF-8 in bytes, cannot convert to string"); } return byteStr.toStringUtf8(); }); @@ -82,10 +79,8 @@ public enum StringOverload implements CelStandardOverload { ByteString.class, (byteStr) -> { if (!byteStr.isValidUtf8()) { - throw new CelRuntimeException( - new IllegalArgumentException( - "invalid UTF-8 in bytes, cannot convert to string"), - CelErrorCode.BAD_FORMAT); + throw new CelBadFormatException( + "invalid UTF-8 in bytes, cannot convert to string"); } return byteStr.toStringUtf8(); }); diff --git a/runtime/src/main/java/dev/cel/runtime/standard/SubtractOperator.java b/runtime/src/main/java/dev/cel/runtime/standard/SubtractOperator.java index 4b0ecd3ae..fe8b75230 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/SubtractOperator.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/SubtractOperator.java @@ -14,14 +14,12 @@ package dev.cel.runtime.standard; -import static dev.cel.runtime.standard.ArithmeticHelpers.getArithmeticErrorCode; - import com.google.common.collect.ImmutableSet; import com.google.common.primitives.UnsignedLong; import com.google.protobuf.Duration; import com.google.protobuf.Timestamp; import dev.cel.common.CelOptions; -import dev.cel.common.CelRuntimeException; +import dev.cel.common.exceptions.CelNumericOverflowException; import dev.cel.common.internal.DateTimeHelpers; import dev.cel.common.internal.ProtoTimeUtils; import dev.cel.runtime.CelFunctionBinding; @@ -58,7 +56,7 @@ public enum SubtractOverload implements CelStandardOverload { try { return RuntimeHelpers.int64Subtract(x, y, celOptions); } catch (ArithmeticException e) { - throw new CelRuntimeException(e, getArithmeticErrorCode(e)); + throw new CelNumericOverflowException(e); } })), SUBTRACT_TIMESTAMP_TIMESTAMP( @@ -104,7 +102,7 @@ public enum SubtractOverload implements CelStandardOverload { try { return RuntimeHelpers.uint64Subtract(x, y); } catch (ArithmeticException e) { - throw new CelRuntimeException(e, getArithmeticErrorCode(e)); + throw new CelNumericOverflowException(e); } }); } else { @@ -116,7 +114,7 @@ public enum SubtractOverload implements CelStandardOverload { try { return RuntimeHelpers.uint64Subtract(x, y, celOptions); } catch (ArithmeticException e) { - throw new CelRuntimeException(e, getArithmeticErrorCode(e)); + throw new CelNumericOverflowException(e); } }); } diff --git a/runtime/src/main/java/dev/cel/runtime/standard/TimestampFunction.java b/runtime/src/main/java/dev/cel/runtime/standard/TimestampFunction.java index 34d3a1dc0..bffac3b06 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/TimestampFunction.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/TimestampFunction.java @@ -16,9 +16,8 @@ import com.google.common.collect.ImmutableSet; import com.google.protobuf.Timestamp; -import dev.cel.common.CelErrorCode; import dev.cel.common.CelOptions; -import dev.cel.common.CelRuntimeException; +import dev.cel.common.exceptions.CelBadFormatException; import dev.cel.common.internal.DateTimeHelpers; import dev.cel.common.internal.ProtoTimeUtils; import dev.cel.runtime.CelFunctionBinding; @@ -56,14 +55,14 @@ public enum TimestampOverload implements CelStandardOverload { try { return DateTimeHelpers.parse(ts); } catch (DateTimeParseException e) { - throw new CelRuntimeException(e, CelErrorCode.BAD_FORMAT); + throw new CelBadFormatException(e); } } else { try { return ProtoTimeUtils.parse(ts); } catch (ParseException e) { - throw new CelRuntimeException(e, CelErrorCode.BAD_FORMAT); + throw new CelBadFormatException(e); } } })), diff --git a/runtime/src/main/java/dev/cel/runtime/standard/UintFunction.java b/runtime/src/main/java/dev/cel/runtime/standard/UintFunction.java index 89424e7fc..0548d8532 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/UintFunction.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/UintFunction.java @@ -17,9 +17,9 @@ import com.google.common.collect.ImmutableSet; import com.google.common.primitives.UnsignedLong; import com.google.common.primitives.UnsignedLongs; -import dev.cel.common.CelErrorCode; import dev.cel.common.CelOptions; -import dev.cel.common.CelRuntimeException; +import dev.cel.common.exceptions.CelBadFormatException; +import dev.cel.common.exceptions.CelNumericOverflowException; import dev.cel.runtime.CelFunctionBinding; import dev.cel.runtime.RuntimeEquality; import dev.cel.runtime.RuntimeHelpers; @@ -61,9 +61,7 @@ public enum UintOverload implements CelStandardOverload { Long.class, (Long arg) -> { if (celOptions.errorOnIntWrap() && arg < 0) { - throw new CelRuntimeException( - new IllegalArgumentException("int out of uint range"), - CelErrorCode.NUMERIC_OVERFLOW); + throw new CelNumericOverflowException("int out of uint range"); } return UnsignedLong.valueOf(arg); }); @@ -73,9 +71,7 @@ public enum UintOverload implements CelStandardOverload { Long.class, (Long arg) -> { if (celOptions.errorOnIntWrap() && arg < 0) { - throw new CelRuntimeException( - new IllegalArgumentException("int out of uint range"), - CelErrorCode.NUMERIC_OVERFLOW); + throw new CelNumericOverflowException("int out of uint range"); } return arg; }); @@ -91,10 +87,7 @@ public enum UintOverload implements CelStandardOverload { if (celOptions.errorOnIntWrap()) { return RuntimeHelpers.doubleToUnsignedChecked(arg) .orElseThrow( - () -> - new CelRuntimeException( - new IllegalArgumentException("double out of uint range"), - CelErrorCode.NUMERIC_OVERFLOW)); + () -> new CelNumericOverflowException("double out of uint range")); } return UnsignedLong.valueOf(BigDecimal.valueOf(arg).toBigInteger()); }); @@ -108,9 +101,8 @@ public enum UintOverload implements CelStandardOverload { .map(UnsignedLong::longValue) .orElseThrow( () -> - new CelRuntimeException( - new IllegalArgumentException("double out of uint range"), - CelErrorCode.NUMERIC_OVERFLOW)); + new CelNumericOverflowException( + "double out of uint range")); } return arg.longValue(); }); @@ -126,7 +118,7 @@ public enum UintOverload implements CelStandardOverload { try { return UnsignedLong.valueOf(arg); } catch (NumberFormatException e) { - throw new CelRuntimeException(e, CelErrorCode.BAD_FORMAT); + throw new CelBadFormatException(e); } }); } else { @@ -137,7 +129,7 @@ public enum UintOverload implements CelStandardOverload { try { return UnsignedLongs.parseUnsignedLong(arg); } catch (NumberFormatException e) { - throw new CelRuntimeException(e, CelErrorCode.BAD_FORMAT); + throw new CelBadFormatException(e); } }); } diff --git a/runtime/src/test/java/dev/cel/runtime/BUILD.bazel b/runtime/src/test/java/dev/cel/runtime/BUILD.bazel index 1541e9c84..4c5a619f3 100644 --- a/runtime/src/test/java/dev/cel/runtime/BUILD.bazel +++ b/runtime/src/test/java/dev/cel/runtime/BUILD.bazel @@ -37,8 +37,11 @@ java_library( "//common:error_codes", "//common:options", "//common:proto_v1alpha1_ast", - "//common:runtime_exception", "//common/ast", + "//common/exceptions:bad_format", + "//common/exceptions:divide_by_zero", + "//common/exceptions:numeric_overflow", + "//common/exceptions:runtime_exception", "//common/internal:cel_descriptor_pools", "//common/internal:converter", "//common/internal:default_message_factory", diff --git a/runtime/src/test/java/dev/cel/runtime/CelEvaluationExceptionBuilderTest.java b/runtime/src/test/java/dev/cel/runtime/CelEvaluationExceptionBuilderTest.java index b3bc32e20..c073a8443 100644 --- a/runtime/src/test/java/dev/cel/runtime/CelEvaluationExceptionBuilderTest.java +++ b/runtime/src/test/java/dev/cel/runtime/CelEvaluationExceptionBuilderTest.java @@ -18,7 +18,8 @@ import com.google.testing.junit.testparameterinjector.TestParameterInjector; import dev.cel.common.CelErrorCode; -import dev.cel.common.CelRuntimeException; +import dev.cel.common.exceptions.CelBadFormatException; +import dev.cel.common.exceptions.CelRuntimeException; import org.junit.Test; import org.junit.runner.RunWith; @@ -87,15 +88,14 @@ public boolean hasPosition(long exprId) { @Test public void builder_fromCelRuntimeException() { IllegalStateException cause = new IllegalStateException("cause error message"); - CelRuntimeException celRuntimeException = - new CelRuntimeException(cause, CelErrorCode.BAD_FORMAT); + CelRuntimeException celRuntimeException = new CelBadFormatException(cause); CelEvaluationExceptionBuilder builder = CelEvaluationExceptionBuilder.newBuilder(celRuntimeException); CelEvaluationException e = builder.build(); assertThat(e).hasMessageThat().isEqualTo("evaluation error: cause error message"); - assertThat(e).hasCauseThat().isEqualTo(cause); + assertThat(e).hasCauseThat().isEqualTo(celRuntimeException); assertThat(e.getErrorCode()).isEqualTo(CelErrorCode.BAD_FORMAT); } } diff --git a/runtime/src/test/java/dev/cel/runtime/CelRuntimeLegacyImplTest.java b/runtime/src/test/java/dev/cel/runtime/CelRuntimeLegacyImplTest.java index c4a041f6a..fa3b5f4ae 100644 --- a/runtime/src/test/java/dev/cel/runtime/CelRuntimeLegacyImplTest.java +++ b/runtime/src/test/java/dev/cel/runtime/CelRuntimeLegacyImplTest.java @@ -18,6 +18,7 @@ import com.google.protobuf.Message; import dev.cel.common.CelException; +import dev.cel.common.exceptions.CelDivideByZeroException; import dev.cel.common.values.CelValueProvider; import dev.cel.compiler.CelCompiler; import dev.cel.compiler.CelCompilerFactory; @@ -39,7 +40,7 @@ public void evalException() throws CelException { CelRuntime runtime = CelRuntimeFactory.standardCelRuntimeBuilder().build(); CelRuntime.Program program = runtime.createProgram(compiler.compile("1/0").getAst()); CelEvaluationException e = Assert.assertThrows(CelEvaluationException.class, program::eval); - assertThat(e).hasCauseThat().isInstanceOf(ArithmeticException.class); + assertThat(e).hasCauseThat().isInstanceOf(CelDivideByZeroException.class); } @Test diff --git a/runtime/src/test/java/dev/cel/runtime/DescriptorMessageProviderTest.java b/runtime/src/test/java/dev/cel/runtime/DescriptorMessageProviderTest.java index 8866d1fa6..6ab1638b1 100644 --- a/runtime/src/test/java/dev/cel/runtime/DescriptorMessageProviderTest.java +++ b/runtime/src/test/java/dev/cel/runtime/DescriptorMessageProviderTest.java @@ -31,7 +31,7 @@ import dev.cel.common.CelDescriptors; import dev.cel.common.CelErrorCode; import dev.cel.common.CelOptions; -import dev.cel.common.CelRuntimeException; +import dev.cel.common.exceptions.CelRuntimeException; import dev.cel.common.internal.CelDescriptorPool; import dev.cel.common.internal.DefaultDescriptorPool; import dev.cel.common.internal.DefaultMessageFactory; @@ -114,8 +114,8 @@ public void createMessage_missingDescriptorError() { () -> provider.createMessage( "google.api.tools.contract.test.MissingMessageTypes", ImmutableMap.of())); - assertThat(e).hasCauseThat().isInstanceOf(IllegalArgumentException.class); assertThat(e.getErrorCode()).isEqualTo(CelErrorCode.ATTRIBUTE_NOT_FOUND); + assertThat(e).hasCauseThat().isNull(); } @Test @@ -159,8 +159,8 @@ public void selectField_mapKeyNotFound() { CelRuntimeException e = Assert.assertThrows( CelRuntimeException.class, () -> provider.selectField(ImmutableMap.of(), "hello")); - assertThat(e).hasCauseThat().isInstanceOf(IllegalArgumentException.class); assertThat(e.getErrorCode()).isEqualTo(CelErrorCode.ATTRIBUTE_NOT_FOUND); + assertThat(e).hasCauseThat().isNull(); } @Test @@ -177,8 +177,8 @@ public void selectField_nonProtoObjectError() { CelRuntimeException e = Assert.assertThrows( CelRuntimeException.class, () -> provider.selectField("hello", "not_a_field")); - assertThat(e).hasCauseThat().isInstanceOf(IllegalArgumentException.class); assertThat(e.getErrorCode()).isEqualTo(CelErrorCode.ATTRIBUTE_NOT_FOUND); + assertThat(e).hasCauseThat().isNull(); } @Test diff --git a/runtime/src/test/java/dev/cel/runtime/ProtoMessageRuntimeEqualityTest.java b/runtime/src/test/java/dev/cel/runtime/ProtoMessageRuntimeEqualityTest.java index 16806c856..dc4607e5f 100644 --- a/runtime/src/test/java/dev/cel/runtime/ProtoMessageRuntimeEqualityTest.java +++ b/runtime/src/test/java/dev/cel/runtime/ProtoMessageRuntimeEqualityTest.java @@ -42,7 +42,7 @@ import com.google.rpc.context.AttributeContext.Request; import dev.cel.common.CelDescriptorUtil; import dev.cel.common.CelOptions; -import dev.cel.common.CelRuntimeException; +import dev.cel.common.exceptions.CelRuntimeException; import dev.cel.common.internal.AdaptingTypes; import dev.cel.common.internal.BidiConverter; import dev.cel.common.internal.DefaultDescriptorPool; diff --git a/runtime/src/test/java/dev/cel/runtime/ProtoMessageRuntimeHelpersTest.java b/runtime/src/test/java/dev/cel/runtime/ProtoMessageRuntimeHelpersTest.java index f6d701741..45b050fd1 100644 --- a/runtime/src/test/java/dev/cel/runtime/ProtoMessageRuntimeHelpersTest.java +++ b/runtime/src/test/java/dev/cel/runtime/ProtoMessageRuntimeHelpersTest.java @@ -36,7 +36,8 @@ import com.google.protobuf.UInt64Value; import com.google.protobuf.Value; import dev.cel.common.CelOptions; -import dev.cel.common.CelRuntimeException; +import dev.cel.common.exceptions.CelNumericOverflowException; +import dev.cel.common.exceptions.CelRuntimeException; import dev.cel.common.internal.DefaultMessageFactory; import dev.cel.common.internal.DynamicProto; import dev.cel.common.values.CelByteString; @@ -91,7 +92,7 @@ public void int64Divide() throws Exception { assertThat(ProtoMessageRuntimeHelpers.int64Divide(Long.MIN_VALUE, -1, CelOptions.LEGACY)) .isEqualTo(Long.MIN_VALUE); assertThrows( - ArithmeticException.class, + CelNumericOverflowException.class, () -> ProtoMessageRuntimeHelpers.int64Divide(Long.MIN_VALUE, -1, CelOptions.DEFAULT)); } @@ -171,7 +172,7 @@ public void uint64Add_signedLongs() throws Exception { assertThat(ProtoMessageRuntimeHelpers.uint64Add(4, 4, CelOptions.DEFAULT)).isEqualTo(8); assertThat(ProtoMessageRuntimeHelpers.uint64Add(-1, 1, CelOptions.LEGACY)).isEqualTo(0); assertThrows( - ArithmeticException.class, + CelNumericOverflowException.class, () -> ProtoMessageRuntimeHelpers.uint64Add(-1, 1, CelOptions.DEFAULT)); } @@ -185,7 +186,7 @@ public void uint64Add_unsignedLongs() throws Exception { UnsignedLong.MAX_VALUE.minus(UnsignedLong.ONE), UnsignedLong.ONE)) .isEqualTo(UnsignedLong.MAX_VALUE); assertThrows( - ArithmeticException.class, + CelNumericOverflowException.class, () -> ProtoMessageRuntimeHelpers.uint64Add(UnsignedLong.MAX_VALUE, UnsignedLong.ONE)); } @@ -202,7 +203,7 @@ public void uint64Multiply_signedLongs() throws Exception { .build())) .isEqualTo(0); assertThrows( - ArithmeticException.class, + CelNumericOverflowException.class, () -> ProtoMessageRuntimeHelpers.uint64Multiply(Long.MIN_VALUE, 2, CelOptions.DEFAULT)); } @@ -213,7 +214,7 @@ public void uint64Multiply_unsignedLongs() throws Exception { UnsignedLong.valueOf(32), UnsignedLong.valueOf(2))) .isEqualTo(UnsignedLong.valueOf(64)); assertThrows( - ArithmeticException.class, + CelNumericOverflowException.class, () -> ProtoMessageRuntimeHelpers.uint64Multiply( UnsignedLong.MAX_VALUE, UnsignedLong.valueOf(2))); @@ -295,13 +296,13 @@ public void uint64Subtract_signedLongs() throws Exception { assertThat(ProtoMessageRuntimeHelpers.uint64Subtract(-1, 2, CelOptions.DEFAULT)).isEqualTo(-3); assertThat(ProtoMessageRuntimeHelpers.uint64Subtract(0, 1, CelOptions.LEGACY)).isEqualTo(-1); assertThrows( - ArithmeticException.class, + CelNumericOverflowException.class, () -> ProtoMessageRuntimeHelpers.uint64Subtract(0, 1, CelOptions.DEFAULT)); assertThrows( - ArithmeticException.class, + CelNumericOverflowException.class, () -> ProtoMessageRuntimeHelpers.uint64Subtract(-3, -1, CelOptions.DEFAULT)); assertThrows( - ArithmeticException.class, + CelNumericOverflowException.class, () -> ProtoMessageRuntimeHelpers.uint64Subtract(55, -40, CelOptions.DEFAULT)); } @@ -314,7 +315,7 @@ public void uint64Subtract_unsignedLongs() throws Exception { UnsignedLong.valueOf(3), UnsignedLong.valueOf(2))) .isEqualTo(UnsignedLong.ONE); assertThrows( - ArithmeticException.class, + CelNumericOverflowException.class, () -> ProtoMessageRuntimeHelpers.uint64Subtract(UnsignedLong.ONE, UnsignedLong.valueOf(2))); } diff --git a/runtime/src/test/java/dev/cel/runtime/planner/BUILD.bazel b/runtime/src/test/java/dev/cel/runtime/planner/BUILD.bazel index 9e3a79ed9..6f15c94d8 100644 --- a/runtime/src/test/java/dev/cel/runtime/planner/BUILD.bazel +++ b/runtime/src/test/java/dev/cel/runtime/planner/BUILD.bazel @@ -23,6 +23,7 @@ java_library( "//common:operator", "//common:options", "//common/ast", + "//common/exceptions:divide_by_zero", "//common/internal:cel_descriptor_pools", "//common/internal:default_message_factory", "//common/internal:dynamic_proto", @@ -33,6 +34,7 @@ java_library( "//common/values", "//common/values:cel_byte_string", "//common/values:cel_value_provider", + "//common/values:proto_message_value", "//common/values:proto_message_value_provider", "//compiler", "//compiler:compiler_builder", 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..e4aba1433 100644 --- a/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java +++ b/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java @@ -38,6 +38,7 @@ import dev.cel.common.CelSource; import dev.cel.common.Operator; import dev.cel.common.ast.CelExpr; +import dev.cel.common.exceptions.CelDivideByZeroException; import dev.cel.common.internal.CelDescriptorPool; import dev.cel.common.internal.DefaultDescriptorPool; import dev.cel.common.internal.DefaultMessageFactory; @@ -51,15 +52,19 @@ import dev.cel.common.types.OptionalType; import dev.cel.common.types.ProtoMessageTypeProvider; import dev.cel.common.types.SimpleType; +import dev.cel.common.types.StructTypeReference; import dev.cel.common.types.TypeType; import dev.cel.common.values.CelByteString; +import dev.cel.common.values.CelValueConverter; import dev.cel.common.values.CelValueProvider; import dev.cel.common.values.NullValue; +import dev.cel.common.values.ProtoCelValueConverter; import dev.cel.common.values.ProtoMessageValueProvider; import dev.cel.compiler.CelCompiler; import dev.cel.compiler.CelCompilerFactory; import dev.cel.expr.conformance.proto3.GlobalEnum; import dev.cel.expr.conformance.proto3.TestAllTypes; +import dev.cel.expr.conformance.proto3.TestAllTypes.NestedMessage; import dev.cel.extensions.CelExtensions; import dev.cel.runtime.CelEvaluationException; import dev.cel.runtime.CelFunctionBinding; @@ -99,17 +104,26 @@ public final class ProgramPlannerTest { private static final DynamicProto DYNAMIC_PROTO = DynamicProto.create(DefaultMessageFactory.create(DESCRIPTOR_POOL)); private static final CelValueProvider VALUE_PROVIDER = - ProtoMessageValueProvider.newInstance(CelOptions.DEFAULT, DYNAMIC_PROTO); + ProtoMessageValueProvider.newInstance(CEL_OPTIONS, DYNAMIC_PROTO); + private static final CelValueConverter CEL_VALUE_CONVERTER = + ProtoCelValueConverter.newInstance(DESCRIPTOR_POOL, DYNAMIC_PROTO); private static final CelContainer CEL_CONTAINER = - CelContainer.newBuilder().setName("cel.expr.conformance.proto3").build(); + CelContainer.newBuilder() + .setName("cel.expr.conformance.proto3") + .addAbbreviations("really.long.abbr") + .build(); private static final ProgramPlanner PLANNER = - ProgramPlanner.newPlanner(TYPE_PROVIDER, VALUE_PROVIDER, newDispatcher(), CEL_CONTAINER); + ProgramPlanner.newPlanner( + TYPE_PROVIDER, VALUE_PROVIDER, newDispatcher(), CEL_VALUE_CONVERTER, CEL_CONTAINER); + private static final CelCompiler CEL_COMPILER = CelCompilerFactory.standardCelCompilerBuilder() + .addVar("msg", StructTypeReference.create(TestAllTypes.getDescriptor().getFullName())) .addVar("map_var", MapType.create(SimpleType.STRING, SimpleType.DYN)) .addVar("int_var", SimpleType.INT) .addVar("dyn_var", SimpleType.DYN) + .addVar("really.long.abbr.ident", SimpleType.DYN) .addFunctionDeclarations( newFunctionDeclaration("zero", newGlobalOverload("zero_overload", SimpleType.INT)), newFunctionDeclaration("error", newGlobalOverload("error_overload", SimpleType.INT)), @@ -296,10 +310,6 @@ public void plan_constant(@TestParameter ConstantTestCase testCase) throws Excep @Test public void plan_ident_enum() throws Exception { - if (isParseOnly) { - // TODO Skip for now, requires attribute qualification - return; - } CelAbstractSyntaxTree ast = compile(GlobalEnum.getDescriptor().getFullName() + "." + GlobalEnum.GAR); Program program = PLANNER.plan(ast); @@ -321,14 +331,6 @@ public void plan_ident_variable() throws Exception { @Test public void planIdent_typeLiteral(@TestParameter TypeLiteralTestCase testCase) throws Exception { - if (isParseOnly) { - if (testCase.equals(TypeLiteralTestCase.DURATION) - || testCase.equals(TypeLiteralTestCase.TIMESTAMP) - || testCase.equals(TypeLiteralTestCase.PROTO_MESSAGE_TYPE)) { - // TODO Skip for now, requires attribute qualification - return; - } - } CelAbstractSyntaxTree ast = compile(testCase.expression); Program program = PLANNER.plan(ast); @@ -337,6 +339,16 @@ public void planIdent_typeLiteral(@TestParameter TypeLiteralTestCase testCase) t assertThat(result).isEqualTo(testCase.type); } + @Test + public void plan_ident_withContainer() throws Exception { + CelAbstractSyntaxTree ast = compile("abbr.ident"); + Program program = PLANNER.plan(ast); + + Object result = program.eval(ImmutableMap.of("really.long.abbr.ident", 1L)); + + assertThat(result).isEqualTo(1); + } + @Test @SuppressWarnings("unchecked") // test only public void plan_createList() throws Exception { @@ -421,7 +433,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,9 +526,9 @@ 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).hasCauseThat().isInstanceOf(ArithmeticException.class); + assertThat(e).hasMessageThat().startsWith("evaluation error at :"); + assertThat(e).hasMessageThat().endsWith("/ by zero"); + assertThat(e).hasCauseThat().isInstanceOf(CelDivideByZeroException.class); assertThat(e.getErrorCode()).isEqualTo(CelErrorCode.DIVIDE_BY_ZERO); } @@ -546,9 +558,9 @@ 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).hasCauseThat().isInstanceOf(ArithmeticException.class); + assertThat(e).hasMessageThat().startsWith("evaluation error at :"); + assertThat(e).hasMessageThat().endsWith("/ by zero"); + assertThat(e).hasCauseThat().isInstanceOf(CelDivideByZeroException.class); assertThat(e.getErrorCode()).isEqualTo(CelErrorCode.DIVIDE_BY_ZERO); } @@ -576,8 +588,9 @@ 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).hasCauseThat().isInstanceOf(ArithmeticException.class); + assertThat(e).hasMessageThat().startsWith("evaluation error at :"); + assertThat(e).hasMessageThat().endsWith("/ by zero"); + assertThat(e).hasCauseThat().isInstanceOf(CelDivideByZeroException.class); assertThat(e.getErrorCode()).isEqualTo(CelErrorCode.DIVIDE_BY_ZERO); } @@ -596,6 +609,140 @@ public void plan_call_withContainer(String expression) throws Exception { assertThat(result).isEqualTo(8); } + @Test + public void plan_select_protoMessageField() throws Exception { + CelAbstractSyntaxTree ast = compile("msg.single_string"); + Program program = PLANNER.plan(ast); + + String result = + (String) + program.eval( + ImmutableMap.of("msg", TestAllTypes.newBuilder().setSingleString("foo").build())); + + assertThat(result).isEqualTo("foo"); + } + + @Test + public void plan_select_nestedProtoMessage() throws Exception { + CelAbstractSyntaxTree ast = compile("msg.single_nested_message"); + NestedMessage nestedMessage = NestedMessage.newBuilder().setBb(42).build(); + Program program = PLANNER.plan(ast); + + Object result = + program.eval( + ImmutableMap.of( + "msg", TestAllTypes.newBuilder().setSingleNestedMessage(nestedMessage).build())); + + assertThat(result).isEqualTo(nestedMessage); + } + + @Test + public void plan_select_nestedProtoMessageField() throws Exception { + CelAbstractSyntaxTree ast = compile("msg.single_nested_message.bb"); + Program program = PLANNER.plan(ast); + + Object result = + program.eval( + ImmutableMap.of( + "msg", + TestAllTypes.newBuilder() + .setSingleNestedMessage(NestedMessage.newBuilder().setBb(42)) + .build())); + + assertThat(result).isEqualTo(42); + } + + @Test + public void plan_select_safeTraversal() throws Exception { + CelAbstractSyntaxTree ast = compile("msg.single_nested_message.bb"); + Program program = PLANNER.plan(ast); + + Object result = program.eval(ImmutableMap.of("msg", TestAllTypes.newBuilder().build())); + + assertThat(result).isEqualTo(0L); + } + + @Test + public void plan_select_onCreateStruct() throws Exception { + CelAbstractSyntaxTree ast = + compile("cel.expr.conformance.proto3.TestAllTypes{ single_string: 'foo'}.single_string"); + Program program = PLANNER.plan(ast); + + Object result = program.eval(); + + assertThat(result).isEqualTo("foo"); + } + + @Test + public void plan_select_onCreateMap() throws Exception { + CelAbstractSyntaxTree ast = compile("{'foo':'bar'}.foo"); + Program program = PLANNER.plan(ast); + + Object result = program.eval(); + + assertThat(result).isEqualTo("bar"); + } + + @Test + public void plan_select_onMapVariable() throws Exception { + CelAbstractSyntaxTree ast = compile("map_var.foo"); + Program program = PLANNER.plan(ast); + + Object result = program.eval(ImmutableMap.of("map_var", ImmutableMap.of("foo", 42L))); + + assertThat(result).isEqualTo(42L); + } + + @Test + public void plan_select_mapVarInputMissing_throws() throws Exception { + CelAbstractSyntaxTree ast = compile("map_var.foo"); + Program program = PLANNER.plan(ast); + String errorMessage = "evaluation error at :7: Error resolving "; + if (isParseOnly) { + errorMessage += + "fields 'cel.expr.conformance.proto3.map_var, cel.expr.conformance.map_var," + + " cel.expr.map_var, cel.map_var, map_var'"; + } else { + errorMessage += "field 'map_var'"; + } + + CelEvaluationException e = + assertThrows(CelEvaluationException.class, () -> program.eval(ImmutableMap.of())); + + assertThat(e).hasMessageThat().contains(errorMessage); + } + + @Test + public void plan_select_mapVarKeyMissing_throws() throws Exception { + CelAbstractSyntaxTree ast = compile("map_var.foo"); + Program program = PLANNER.plan(ast); + + CelEvaluationException e = + assertThrows( + CelEvaluationException.class, + () -> program.eval(ImmutableMap.of("map_var", ImmutableMap.of()))); + assertThat(e) + .hasMessageThat() + .contains("evaluation error at :7: key 'foo' is not present in map"); + } + + @Test + public void plan_select_stringQualificationFail_throws() throws Exception { + CelAbstractSyntaxTree ast = compile("map_var.foo"); + Program program = PLANNER.plan(ast); + + CelEvaluationException e = + assertThrows( + CelEvaluationException.class, + () -> program.eval(ImmutableMap.of("map_var", "bogus string"))); + + assertThat(e) + .hasMessageThat() + .isEqualTo( + "evaluation error at :7: Error resolving field 'foo'. Field selections must be" + + " performed on messages or maps."); + } + private CelAbstractSyntaxTree compile(String expression) throws Exception { CelAbstractSyntaxTree ast = CEL_COMPILER.parse(expression).getAst(); if (isParseOnly) {