diff --git a/common/src/main/java/dev/cel/common/values/BUILD.bazel b/common/src/main/java/dev/cel/common/values/BUILD.bazel index e9b4be4f1..3a78091bf 100644 --- a/common/src/main/java/dev/cel/common/values/BUILD.bazel +++ b/common/src/main/java/dev/cel/common/values/BUILD.bazel @@ -57,6 +57,7 @@ java_library( tags = [ ], deps = [ + "//common/values", "@maven//:com_google_errorprone_error_prone_annotations", "@maven//:com_google_guava_guava", ], @@ -68,6 +69,7 @@ cel_android_library( tags = [ ], deps = [ + "//common/values:values_android", "@maven//:com_google_errorprone_error_prone_annotations", "@maven_android//:com_google_guava_guava", ], @@ -207,13 +209,13 @@ java_library( tags = [ ], deps = [ - ":base_proto_message_value_provider", ":proto_message_value", "//common:options", "//common/annotations", "//common/internal:dynamic_proto", "//common/internal:proto_message_factory", - "//common/values:base_proto_cel_value_converter", + "//common/values", + "//common/values:cel_value_provider", "@maven//:com_google_errorprone_error_prone_annotations", "@maven//:com_google_protobuf_protobuf_java", ], 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 c3f3727a1..ae0b40ef7 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,13 @@ @SuppressWarnings("unchecked") // Unchecked cast of generics due to type-erasure (ex: MapValue). @Internal @Immutable -public abstract class CelValueConverter { +public class CelValueConverter { + + private static final CelValueConverter DEFAULT_INSTANCE = new CelValueConverter(); + + public static CelValueConverter getDefaultInstance() { + return DEFAULT_INSTANCE; + } /** Adapts a {@link CelValue} to a plain old Java Object. */ public Object unwrap(CelValue celValue) { diff --git a/common/src/main/java/dev/cel/common/values/CelValueProvider.java b/common/src/main/java/dev/cel/common/values/CelValueProvider.java index 717834660..20ae865e7 100644 --- a/common/src/main/java/dev/cel/common/values/CelValueProvider.java +++ b/common/src/main/java/dev/cel/common/values/CelValueProvider.java @@ -27,4 +27,8 @@ public interface CelValueProvider { * a wrapper. */ Optional newValue(String structType, Map fields); + + default CelValueConverter celValueConverter() { + return CelValueConverter.getDefaultInstance(); + } } diff --git a/common/src/main/java/dev/cel/common/values/ProtoCelValueConverter.java b/common/src/main/java/dev/cel/common/values/ProtoCelValueConverter.java index 08f30b9d1..9400ae961 100644 --- a/common/src/main/java/dev/cel/common/values/ProtoCelValueConverter.java +++ b/common/src/main/java/dev/cel/common/values/ProtoCelValueConverter.java @@ -82,7 +82,13 @@ public Object toRuntimeValue(Object value) { } if (value instanceof MessageOrBuilder) { - MessageOrBuilder message = (MessageOrBuilder) value; + Message message; + if (value instanceof Message.Builder) { + message = ((Message.Builder) value).build(); + } else { + message = (Message) value; + } + // Attempt to convert the proto from a dynamic message into a concrete message if possible. if (message instanceof DynamicMessage) { message = dynamicProto.maybeAdaptDynamicMessage((DynamicMessage) message); diff --git a/common/src/main/java/dev/cel/common/values/ProtoMessageValueProvider.java b/common/src/main/java/dev/cel/common/values/ProtoMessageValueProvider.java index 5bf2927ab..a05658c8f 100644 --- a/common/src/main/java/dev/cel/common/values/ProtoMessageValueProvider.java +++ b/common/src/main/java/dev/cel/common/values/ProtoMessageValueProvider.java @@ -34,13 +34,13 @@ */ @Immutable @Internal -public class ProtoMessageValueProvider extends BaseProtoMessageValueProvider { +public class ProtoMessageValueProvider implements CelValueProvider { private final ProtoAdapter protoAdapter; private final ProtoMessageFactory protoMessageFactory; private final ProtoCelValueConverter protoCelValueConverter; @Override - public BaseProtoCelValueConverter protoCelValueConverter() { + public CelValueConverter celValueConverter() { return protoCelValueConverter; } diff --git a/common/src/test/java/dev/cel/common/values/StructValueTest.java b/common/src/test/java/dev/cel/common/values/StructValueTest.java index 1db147882..b8d6371a8 100644 --- a/common/src/test/java/dev/cel/common/values/StructValueTest.java +++ b/common/src/test/java/dev/cel/common/values/StructValueTest.java @@ -31,7 +31,6 @@ import dev.cel.common.types.CelTypeProvider; import dev.cel.common.types.SimpleType; import dev.cel.common.types.StructType; -import dev.cel.expr.conformance.proto3.TestAllTypes; import java.util.Map; import java.util.Optional; import org.junit.Test; @@ -185,7 +184,7 @@ public void evaluate_usingMultipleProviders_selectFieldFromCustomClass() throws .setValueProvider( CombinedCelValueProvider.combine( ProtoMessageValueProvider.newInstance( - CelOptions.DEFAULT, DynamicProto.create(typeName -> Optional.empty())), + CelOptions.DEFAULT, DynamicProto.create(unused -> Optional.empty())), CUSTOM_STRUCT_VALUE_PROVIDER)) .build(); CelAbstractSyntaxTree ast = cel.compile("custom_struct{data: 5}.data").getAst(); @@ -195,36 +194,8 @@ public void evaluate_usingMultipleProviders_selectFieldFromCustomClass() throws assertThat(result).isEqualTo(5L); } - @Test - public void evaluate_usingMultipleProviders_selectFieldFromProtobufMessage() throws Exception { - Cel cel = - CelFactory.standardCelBuilder() - .setOptions(CelOptions.current().enableCelValue(true).build()) - .addMessageTypes(TestAllTypes.getDescriptor()) - .setTypeProvider(CUSTOM_STRUCT_TYPE_PROVIDER) - .setValueProvider( - CombinedCelValueProvider.combine( - ProtoMessageValueProvider.newInstance( - CelOptions.DEFAULT, - // Note: this is unideal. Future iterations should make DynamicProto - // completely an internal concern, and not expose it at all. - DynamicProto.create( - typeName -> { - if (typeName.equals(TestAllTypes.getDescriptor().getFullName())) { - return Optional.of(TestAllTypes.newBuilder()); - } - return Optional.empty(); - })), - CUSTOM_STRUCT_VALUE_PROVIDER)) - .build(); - CelAbstractSyntaxTree ast = - cel.compile("cel.expr.conformance.proto3.TestAllTypes{single_string: 'foo'}.single_string") - .getAst(); - - String result = (String) cel.createProgram(ast).eval(); - - assertThat(result).isEqualTo("foo"); - } + // TODO: Bring back evaluate_usingMultipleProviders_selectFieldFromProtobufMessage + // once planner is exposed from factory @SuppressWarnings("Immutable") // Test only private static class CelCustomStructValue extends StructValue { diff --git a/runtime/BUILD.bazel b/runtime/BUILD.bazel index 244145960..a42ee7fb4 100644 --- a/runtime/BUILD.bazel +++ b/runtime/BUILD.bazel @@ -271,3 +271,12 @@ java_library( "//runtime/src/main/java/dev/cel/runtime:variable_resolver", ], ) + +java_library( + name = "runtime_planner_impl", + testonly = 1, # TODO: Move to factory when ready for exposure + visibility = ["//:internal"], + exports = [ + "//runtime/src/main/java/dev/cel/runtime:runtime_planner_impl", + ], +) diff --git a/runtime/src/main/java/dev/cel/runtime/BUILD.bazel b/runtime/src/main/java/dev/cel/runtime/BUILD.bazel index d253edba3..f77c48d96 100644 --- a/runtime/src/main/java/dev/cel/runtime/BUILD.bazel +++ b/runtime/src/main/java/dev/cel/runtime/BUILD.bazel @@ -801,6 +801,48 @@ cel_android_library( ], ) +java_library( + name = "runtime_planner_impl", + testonly = 1, + srcs = ["CelRuntimeImpl.java"], + tags = [ + ], + deps = [ + "//:auto_value", + "//common:cel_ast", + "//common:cel_descriptor_util", + "//common:cel_descriptors", + "//common:container", + "//common:options", + "//common/annotations", + "//common/internal:cel_descriptor_pools", + "//common/internal:default_message_factory", + "//common/internal:dynamic_proto", + "//common/types:default_type_provider", + "//common/types:message_type_provider", + "//common/types:type_providers", + "//common/values", + "//common/values:cel_value_provider", + "//common/values:combined_cel_value_provider", + "//common/values:proto_message_value_provider", + "//runtime", + "//runtime:dispatcher", + "//runtime:evaluation_listener", + "//runtime:function_binding", + "//runtime:function_resolver", + "//runtime:program", + "//runtime:proto_message_runtime_helpers", + "//runtime:runtime_equality", + "//runtime:standard_functions", + "//runtime:variable_resolver", + "//runtime/planner:program_planner", + "@maven//:com_google_errorprone_error_prone_annotations", + "@maven//:com_google_guava_guava", + "@maven//:com_google_protobuf_protobuf_java", + "@maven//:org_jspecify_jspecify", + ], +) + java_library( name = "runtime", srcs = RUNTIME_SOURCES, @@ -829,6 +871,7 @@ java_library( "//common:cel_ast", "//common:cel_descriptor_util", "//common:cel_descriptors", + "//common:container", "//common:options", "//common/annotations", "//common/internal:cel_descriptor_pools", @@ -836,6 +879,7 @@ java_library( "//common/internal:dynamic_proto", "//common/internal:proto_message_factory", "//common/types:cel_types", + "//common/types:type_providers", "//common/values:cel_value_provider", "//common/values:proto_message_value_provider", "//runtime:variable_resolver", diff --git a/runtime/src/main/java/dev/cel/runtime/CelRuntimeBuilder.java b/runtime/src/main/java/dev/cel/runtime/CelRuntimeBuilder.java index e1e3c1b51..87f11fde2 100644 --- a/runtime/src/main/java/dev/cel/runtime/CelRuntimeBuilder.java +++ b/runtime/src/main/java/dev/cel/runtime/CelRuntimeBuilder.java @@ -21,7 +21,9 @@ import com.google.protobuf.Descriptors.FileDescriptor; import com.google.protobuf.ExtensionRegistry; import com.google.protobuf.Message; +import dev.cel.common.CelContainer; import dev.cel.common.CelOptions; +import dev.cel.common.types.CelTypeProvider; import dev.cel.common.values.CelValueProvider; import java.util.function.Function; @@ -48,6 +50,14 @@ public interface CelRuntimeBuilder { @CanIgnoreReturnValue CelRuntimeBuilder addFunctionBindings(Iterable bindings); + /** Adds bindings for functions that are allowed to be late-bound (resolved at execution time). */ + @CanIgnoreReturnValue + CelRuntimeBuilder addLateBoundFunctions(String... lateBoundFunctionNames); + + /** Adds bindings for functions that are allowed to be late-bound (resolved at execution time). */ + @CanIgnoreReturnValue + CelRuntimeBuilder addLateBoundFunctions(Iterable lateBoundFunctionNames); + /** * Add message {@link Descriptor}s to the builder for type-checking and object creation at * interpretation time. @@ -123,6 +133,13 @@ public interface CelRuntimeBuilder { @CanIgnoreReturnValue CelRuntimeBuilder addFileTypes(FileDescriptorSet fileDescriptorSet); + /** + * Sets the {@link CelTypeProvider} for resolving CEL types during evaluation, such as a fully + * qualified type name to a struct or an enum value. + */ + @CanIgnoreReturnValue + CelRuntimeBuilder setTypeProvider(CelTypeProvider celTypeProvider); + /** * Set a custom type factory for the runtime. * @@ -145,7 +162,7 @@ public interface CelRuntimeBuilder { * support proto messages in addition to custom struct values, protobuf value provider must be * configured first before the custom value provider. * - *

Note {@link CelOptions#enableCelValue()} must be enabled or this method will be a no-op. + *

Note that this option is only supported for planner-based runtime. */ @CanIgnoreReturnValue CelRuntimeBuilder setValueProvider(CelValueProvider celValueProvider); @@ -179,6 +196,15 @@ public interface CelRuntimeBuilder { @CanIgnoreReturnValue CelRuntimeBuilder setExtensionRegistry(ExtensionRegistry extensionRegistry); + + /** + * Set the {@link CelContainer} to use as the namespace for resolving CEL expression variables and + * functions. + */ + @CanIgnoreReturnValue + CelRuntimeBuilder setContainer(CelContainer container); + + /** Build a new instance of the {@code CelRuntime}. */ @CheckReturnValue CelRuntime build(); diff --git a/runtime/src/main/java/dev/cel/runtime/CelRuntimeImpl.java b/runtime/src/main/java/dev/cel/runtime/CelRuntimeImpl.java new file mode 100644 index 000000000..1417007e0 --- /dev/null +++ b/runtime/src/main/java/dev/cel/runtime/CelRuntimeImpl.java @@ -0,0 +1,467 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package dev.cel.runtime; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.auto.value.AutoValue; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import com.google.errorprone.annotations.CanIgnoreReturnValue; +import com.google.errorprone.annotations.Immutable; +import com.google.protobuf.DescriptorProtos; +import com.google.protobuf.Descriptors; +import com.google.protobuf.ExtensionRegistry; +import com.google.protobuf.Message; +import dev.cel.common.CelAbstractSyntaxTree; +import dev.cel.common.CelContainer; +import dev.cel.common.CelDescriptorUtil; +import dev.cel.common.CelDescriptors; +import dev.cel.common.CelOptions; +import dev.cel.common.annotations.Internal; +import dev.cel.common.internal.CelDescriptorPool; +import dev.cel.common.internal.CombinedDescriptorPool; +import dev.cel.common.internal.DefaultDescriptorPool; +import dev.cel.common.internal.DefaultMessageFactory; +import dev.cel.common.internal.DynamicProto; +// CEL-Internal-1 +import dev.cel.common.types.CelTypeProvider; +import dev.cel.common.types.DefaultTypeProvider; +import dev.cel.common.types.ProtoMessageTypeProvider; +import dev.cel.common.values.CelValueConverter; +import dev.cel.common.values.CelValueProvider; +import dev.cel.common.values.CombinedCelValueProvider; +import dev.cel.common.values.ProtoMessageValueProvider; +import dev.cel.runtime.planner.ProgramPlanner; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.function.Function; +import org.jspecify.annotations.Nullable; + +@AutoValue +@Internal +@Immutable +abstract class CelRuntimeImpl implements CelRuntime { + + abstract ProgramPlanner planner(); + + abstract CelOptions options(); + + abstract CelContainer container(); + + abstract ImmutableMap functionBindings(); + + abstract ImmutableSet fileDescriptors(); + + // Callers must guarantee that a custom runtime library is immutable. CEL provided ones are + // immutable by default. + @SuppressWarnings("Immutable") + @AutoValue.CopyAnnotations + abstract ImmutableSet runtimeLibraries(); + + abstract ImmutableSet lateBoundFunctionNames(); + + abstract CelStandardFunctions standardFunctions(); + + abstract @Nullable CelTypeProvider typeProvider(); + + abstract @Nullable CelValueProvider valueProvider(); + + // Extension registry is unmodifiable. Just not marked as such from Protobuf's implementation. + @SuppressWarnings("Immutable") + @AutoValue.CopyAnnotations + abstract @Nullable ExtensionRegistry extensionRegistry(); + + @Override + public Program createProgram(CelAbstractSyntaxTree ast) throws CelEvaluationException { + return toRuntimeProgram(planner().plan(ast)); + } + + public Program toRuntimeProgram(dev.cel.runtime.Program program) { + return new Program() { + + @Override + public Object eval() throws CelEvaluationException { + return program.eval(); + } + + @Override + public Object eval(Map mapValue) throws CelEvaluationException { + return program.eval(mapValue); + } + + @Override + public Object eval(Map mapValue, CelFunctionResolver lateBoundFunctionResolver) + throws CelEvaluationException { + return program.eval(mapValue, lateBoundFunctionResolver); + } + + @Override + public Object eval(Message message) throws CelEvaluationException { + throw new UnsupportedOperationException("Not yet supported."); + } + + @Override + public Object eval(CelVariableResolver resolver) throws CelEvaluationException { + return program.eval(resolver); + } + + @Override + public Object eval( + CelVariableResolver resolver, CelFunctionResolver lateBoundFunctionResolver) + throws CelEvaluationException { + return program.eval(resolver, lateBoundFunctionResolver); + } + + @Override + public Object trace(CelEvaluationListener listener) throws CelEvaluationException { + throw new UnsupportedOperationException("Trace is not yet supported."); + } + + @Override + public Object trace(Map mapValue, CelEvaluationListener listener) + throws CelEvaluationException { + throw new UnsupportedOperationException("Trace is not yet supported."); + } + + @Override + public Object trace(Message message, CelEvaluationListener listener) + throws CelEvaluationException { + throw new UnsupportedOperationException("Trace is not yet supported."); + } + + @Override + public Object trace(CelVariableResolver resolver, CelEvaluationListener listener) + throws CelEvaluationException { + throw new UnsupportedOperationException("Trace is not yet supported."); + } + + @Override + public Object trace( + CelVariableResolver resolver, + CelFunctionResolver lateBoundFunctionResolver, + CelEvaluationListener listener) + throws CelEvaluationException { + throw new UnsupportedOperationException("Trace is not yet supported."); + } + + @Override + public Object trace( + Map mapValue, + CelFunctionResolver lateBoundFunctionResolver, + CelEvaluationListener listener) + throws CelEvaluationException { + throw new UnsupportedOperationException("Trace is not yet supported."); + } + + @Override + public Object advanceEvaluation(UnknownContext context) throws CelEvaluationException { + throw new UnsupportedOperationException("Unsupported operation."); + } + }; + } + + @Override + public abstract Builder toRuntimeBuilder(); + + static Builder newBuilder() { + return new AutoValue_CelRuntimeImpl.Builder() + .setStandardFunctions(CelStandardFunctions.newBuilder().build()) + .setContainer(CelContainer.newBuilder().build()) + .setExtensionRegistry(ExtensionRegistry.getEmptyRegistry()); + } + + @AutoValue.Builder + abstract static class Builder implements CelRuntimeBuilder { + + public abstract Builder setPlanner(ProgramPlanner planner); + + @Override + public abstract Builder setOptions(CelOptions options); + + @Override + public abstract Builder setStandardFunctions(CelStandardFunctions standardFunctions); + + @Override + public abstract Builder setExtensionRegistry(ExtensionRegistry extensionRegistry); + + @Override + public abstract Builder setTypeProvider(CelTypeProvider celTypeProvider); + + @Override + public abstract Builder setValueProvider(CelValueProvider celValueProvider); + + @Override + public abstract Builder setContainer(CelContainer container); + + abstract CelOptions options(); + + abstract CelContainer container(); + + abstract CelTypeProvider typeProvider(); + + abstract CelValueProvider valueProvider(); + + abstract CelStandardFunctions standardFunctions(); + + abstract ExtensionRegistry extensionRegistry(); + + abstract ImmutableSet.Builder fileDescriptorsBuilder(); + + abstract ImmutableSet.Builder runtimeLibrariesBuilder(); + + abstract ImmutableSet.Builder lateBoundFunctionNamesBuilder(); + + private final Map mutableFunctionBindings = new HashMap<>(); + + @Override + @CanIgnoreReturnValue + public Builder addFunctionBindings(CelFunctionBinding... bindings) { + checkNotNull(bindings); + return addFunctionBindings(Arrays.asList(bindings)); + } + + @Override + @CanIgnoreReturnValue + public Builder addFunctionBindings(Iterable bindings) { + checkNotNull(bindings); + bindings.forEach(o -> mutableFunctionBindings.putIfAbsent(o.getOverloadId(), o)); + return this; + } + + @Override + @CanIgnoreReturnValue + public Builder addLateBoundFunctions(String... lateBoundFunctionNames) { + checkNotNull(lateBoundFunctionNames); + return addLateBoundFunctions(Arrays.asList(lateBoundFunctionNames)); + } + + @Override + @CanIgnoreReturnValue + public Builder addLateBoundFunctions(Iterable lateBoundFunctionNames) { + checkNotNull(lateBoundFunctionNames); + this.lateBoundFunctionNamesBuilder().addAll(lateBoundFunctionNames); + return this; + } + + @Override + @CanIgnoreReturnValue + public Builder addMessageTypes(Descriptors.Descriptor... descriptors) { + checkNotNull(descriptors); + return addMessageTypes(Arrays.asList(descriptors)); + } + + @Override + @CanIgnoreReturnValue + public Builder addMessageTypes(Iterable descriptors) { + checkNotNull(descriptors); + return addFileTypes(CelDescriptorUtil.getFileDescriptorsForDescriptors(descriptors)); + } + + @Override + @CanIgnoreReturnValue + public Builder addFileTypes(DescriptorProtos.FileDescriptorSet fileDescriptorSet) { + checkNotNull(fileDescriptorSet); + return addFileTypes( + CelDescriptorUtil.getFileDescriptorsFromFileDescriptorSet(fileDescriptorSet)); + } + + @Override + @CanIgnoreReturnValue + public Builder addFileTypes(Descriptors.FileDescriptor... fileDescriptors) { + checkNotNull(fileDescriptors); + return addFileTypes(Arrays.asList(fileDescriptors)); + } + + @Override + @CanIgnoreReturnValue + public Builder addFileTypes(Iterable fileDescriptors) { + checkNotNull(fileDescriptors); + this.fileDescriptorsBuilder().addAll(fileDescriptors); + return this; + } + + @Override + @CanIgnoreReturnValue + public Builder addLibraries(CelRuntimeLibrary... libraries) { + checkNotNull(libraries); + return this.addLibraries(Arrays.asList(libraries)); + } + + @Override + @CanIgnoreReturnValue + public Builder addLibraries(Iterable libraries) { + checkNotNull(libraries); + this.runtimeLibrariesBuilder().addAll(libraries); + return this; + } + + abstract Builder setFunctionBindings(ImmutableMap value); + + @Override + public Builder setTypeFactory(Function typeFactory) { + throw new UnsupportedOperationException("Unsupported. Use a custom value provider instead."); + } + + @Override + public Builder setStandardEnvironmentEnabled(boolean value) { + throw new UnsupportedOperationException( + "Unsupported. Subset the environment using setStandardFunctions instead."); + } + + /** Throws if an unsupported flag in CelOptions is toggled. */ + private static void assertAllowedCelOptions(CelOptions celOptions) { + String prefix = "Misconfigured CelOptions: "; + if (!celOptions.enableUnsignedLongs()) { + throw new IllegalArgumentException(prefix + "enableUnsignedLongs cannot be disabled."); + } + if (!celOptions.unwrapWellKnownTypesOnFunctionDispatch()) { + throw new IllegalArgumentException( + prefix + "unwrapWellKnownTypesOnFunctionDispatch cannot be disabled."); + } + + // Disallowed options in favor of subsetting + String subsettingError = "Subset the environment instead using setStandardFunctions method."; + if (!celOptions.enableStringConcatenation()) { + throw new IllegalArgumentException( + prefix + "enableStringConcatenation cannot be disabled. " + subsettingError); + } + + if (!celOptions.enableStringConversion()) { + throw new IllegalArgumentException( + prefix + "enableStringConversion cannot be disabled. " + subsettingError); + } + + if (!celOptions.enableListConcatenation()) { + throw new IllegalArgumentException( + prefix + "enableListConcatenation cannot be disabled. " + subsettingError); + } + + if (!celOptions.enableTimestampEpoch()) { + throw new IllegalArgumentException( + prefix + "enableTimestampEpoch cannot be disabled. " + subsettingError); + } + + if (!celOptions.enableHeterogeneousNumericComparisons()) { + throw new IllegalArgumentException( + prefix + + "enableHeterogeneousNumericComparisons cannot be disabled. " + + subsettingError); + } + } + + abstract CelRuntimeImpl autoBuild(); + + private static DefaultDispatcher newDispatcher( + CelStandardFunctions standardFunctions, + Collection customFunctionBindings, + RuntimeEquality runtimeEquality, + CelOptions options) { + DefaultDispatcher.Builder builder = DefaultDispatcher.newBuilder(); + for (CelFunctionBinding binding : + standardFunctions.newFunctionBindings(runtimeEquality, options)) { + builder.addOverload( + binding.getOverloadId(), + binding.getArgTypes(), + binding.isStrict(), + binding.getDefinition()); + } + + for (CelFunctionBinding binding : customFunctionBindings) { + builder.addOverload( + binding.getOverloadId(), + binding.getArgTypes(), + binding.isStrict(), + binding.getDefinition()); + } + + return builder.build(); + } + + private static CelDescriptorPool newDescriptorPool( + CelDescriptors celDescriptors, + ExtensionRegistry extensionRegistry) { + ImmutableList.Builder descriptorPools = new ImmutableList.Builder<>(); + + descriptorPools.add(DefaultDescriptorPool.create(celDescriptors, extensionRegistry)); + + return CombinedDescriptorPool.create(descriptorPools.build()); + } + + @Override + public CelRuntime build() { + assertAllowedCelOptions(options()); + CelDescriptors celDescriptors = + CelDescriptorUtil.getAllDescriptorsFromFileDescriptor(fileDescriptorsBuilder().build()); + + CelDescriptorPool descriptorPool = + newDescriptorPool( + celDescriptors, + extensionRegistry()); + DefaultMessageFactory defaultMessageFactory = DefaultMessageFactory.create(descriptorPool); + DynamicProto dynamicProto = DynamicProto.create(defaultMessageFactory); + CelValueProvider protoMessageValueProvider = + ProtoMessageValueProvider.newInstance(options(), dynamicProto); + CelValueConverter celValueConverter = protoMessageValueProvider.celValueConverter(); + if (valueProvider() != null) { + protoMessageValueProvider = + CombinedCelValueProvider.combine(protoMessageValueProvider, valueProvider()); + } + + RuntimeEquality runtimeEquality = + RuntimeEquality.create( + ProtoMessageRuntimeHelpers.create(dynamicProto, options()), options()); + ImmutableSet runtimeLibraries = runtimeLibrariesBuilder().build(); + // Add libraries, such as extensions + for (CelRuntimeLibrary celLibrary : runtimeLibraries) { + if (celLibrary instanceof CelInternalRuntimeLibrary) { + ((CelInternalRuntimeLibrary) celLibrary) + .setRuntimeOptions(this, runtimeEquality, options()); + } else { + celLibrary.setRuntimeOptions(this); + } + } + + CelTypeProvider combinedTypeProvider = + new CelTypeProvider.CombinedCelTypeProvider( + new ProtoMessageTypeProvider(celDescriptors), DefaultTypeProvider.getInstance()); + if (typeProvider() != null) { + combinedTypeProvider = + new CelTypeProvider.CombinedCelTypeProvider(combinedTypeProvider, typeProvider()); + } + + DefaultDispatcher dispatcher = + newDispatcher( + standardFunctions(), mutableFunctionBindings.values(), runtimeEquality, options()); + + ProgramPlanner planner = + ProgramPlanner.newPlanner( + combinedTypeProvider, + protoMessageValueProvider, + dispatcher, + celValueConverter, + container(), + options(), + lateBoundFunctionNamesBuilder().build()); + setPlanner(planner); + + setFunctionBindings(ImmutableMap.copyOf(mutableFunctionBindings)); + return autoBuild(); + } + } +} diff --git a/runtime/src/main/java/dev/cel/runtime/CelRuntimeLegacyImpl.java b/runtime/src/main/java/dev/cel/runtime/CelRuntimeLegacyImpl.java index 983f68055..60c2642d7 100644 --- a/runtime/src/main/java/dev/cel/runtime/CelRuntimeLegacyImpl.java +++ b/runtime/src/main/java/dev/cel/runtime/CelRuntimeLegacyImpl.java @@ -29,6 +29,7 @@ import com.google.protobuf.ExtensionRegistry; import com.google.protobuf.Message; import dev.cel.common.CelAbstractSyntaxTree; +import dev.cel.common.CelContainer; import dev.cel.common.CelDescriptorUtil; import dev.cel.common.CelDescriptors; import dev.cel.common.CelOptions; @@ -40,6 +41,7 @@ import dev.cel.common.internal.DynamicProto; // CEL-Internal-1 import dev.cel.common.internal.ProtoMessageFactory; +import dev.cel.common.types.CelTypeProvider; import dev.cel.common.types.CelTypes; import dev.cel.common.values.CelValueProvider; import dev.cel.common.values.ProtoMessageValueProvider; @@ -161,6 +163,18 @@ public CelRuntimeBuilder addFunctionBindings(Iterable bindin return this; } + @Override + public CelRuntimeBuilder addLateBoundFunctions(String... lateBoundFunctionNames) { + throw new UnsupportedOperationException( + "This method is not supported for the legacy runtime"); + } + + @Override + public CelRuntimeBuilder addLateBoundFunctions(Iterable lateBoundFunctionNames) { + throw new UnsupportedOperationException( + "This method is not supported for the legacy runtime"); + } + @Override public CelRuntimeBuilder addMessageTypes(Descriptor... descriptors) { return addMessageTypes(Arrays.asList(descriptors)); @@ -189,9 +203,9 @@ public CelRuntimeBuilder addFileTypes(FileDescriptorSet fileDescriptorSet) { } @Override - public CelRuntimeBuilder setTypeFactory(Function typeFactory) { - this.customTypeFactory = typeFactory; - return this; + public CelRuntimeBuilder setTypeProvider(CelTypeProvider celTypeProvider) { + throw new UnsupportedOperationException( + "setTypeProvider is not supported for legacy runtime"); } @Override @@ -200,6 +214,12 @@ public CelRuntimeBuilder setValueProvider(CelValueProvider celValueProvider) { return this; } + @Override + public CelRuntimeBuilder setTypeFactory(Function typeFactory) { + this.customTypeFactory = typeFactory; + return this; + } + @Override public CelRuntimeBuilder setStandardEnvironmentEnabled(boolean value) { standardEnvironmentEnabled = value; @@ -232,6 +252,12 @@ public CelRuntimeBuilder setExtensionRegistry(ExtensionRegistry extensionRegistr return this; } + @Override + public CelRuntimeBuilder setContainer(CelContainer container) { + throw new UnsupportedOperationException( + "This method is not supported for the legacy runtime"); + } + /** Build a new {@code CelRuntimeLegacyImpl} instance from the builder config. */ @Override public CelRuntimeLegacyImpl build() { 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 b6c90dc92..59f39fc91 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel +++ b/runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel @@ -314,6 +314,7 @@ java_library( name = "eval_create_list", srcs = ["EvalCreateList.java"], deps = [ + ":eval_helpers", ":execution_frame", ":planned_interpretable", "//runtime:evaluation_exception", 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 e519b968c..389a21a82 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/EvalCreateList.java +++ b/runtime/src/main/java/dev/cel/runtime/planner/EvalCreateList.java @@ -30,7 +30,7 @@ final class EvalCreateList extends PlannedInterpretable { public Object eval(GlobalResolver resolver, ExecutionFrame frame) throws CelEvaluationException { ImmutableList.Builder builder = ImmutableList.builderWithExpectedSize(values.length); for (PlannedInterpretable value : values) { - builder.add(value.eval(resolver, frame)); + builder.add(EvalHelpers.evalStrictly(value, resolver, frame)); } return builder.build(); } 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 6e7fd7e68..ee86c9dd6 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/EvalHelpers.java +++ b/runtime/src/main/java/dev/cel/runtime/planner/EvalHelpers.java @@ -38,6 +38,9 @@ static Object evalStrictly( PlannedInterpretable interpretable, GlobalResolver resolver, ExecutionFrame frame) { try { return interpretable.eval(resolver, frame); + } catch (LocalizedEvaluationException e) { + // Already localized - propagate as-is to preserve inner expression ID + throw e; } catch (CelRuntimeException e) { // Wrap with current interpretable's location throw new LocalizedEvaluationException(e, interpretable.exprId()); 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 113ee05e8..b5d43728b 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/ProgramPlanner.java +++ b/runtime/src/main/java/dev/cel/runtime/planner/ProgramPlanner.java @@ -20,7 +20,7 @@ import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.errorprone.annotations.CheckReturnValue; -import javax.annotation.concurrent.ThreadSafe; +import com.google.errorprone.annotations.Immutable; import dev.cel.common.CelAbstractSyntaxTree; import dev.cel.common.CelContainer; import dev.cel.common.CelOptions; @@ -57,7 +57,7 @@ * {@code ProgramPlanner} resolves functions, types, and identifiers at plan time given a * parsed-only or a type-checked expression. */ -@ThreadSafe +@Immutable @Internal public final class ProgramPlanner { diff --git a/runtime/src/main/java/dev/cel/runtime/planner/RelativeAttribute.java b/runtime/src/main/java/dev/cel/runtime/planner/RelativeAttribute.java index a913849f6..54eb26f21 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/RelativeAttribute.java +++ b/runtime/src/main/java/dev/cel/runtime/planner/RelativeAttribute.java @@ -16,6 +16,7 @@ import com.google.common.collect.ImmutableList; import com.google.errorprone.annotations.Immutable; +import dev.cel.common.values.CelValue; import dev.cel.common.values.CelValueConverter; import dev.cel.runtime.GlobalResolver; @@ -40,7 +41,9 @@ public Object resolve(GlobalResolver ctx, ExecutionFrame frame) { } // TODO: Handle unknowns - + if (obj instanceof CelValue) { + obj = celValueConverter.unwrap((CelValue) obj); + } return obj; } diff --git a/runtime/src/test/java/dev/cel/runtime/BUILD.bazel b/runtime/src/test/java/dev/cel/runtime/BUILD.bazel index ed76fdbe7..30691fa4a 100644 --- a/runtime/src/test/java/dev/cel/runtime/BUILD.bazel +++ b/runtime/src/test/java/dev/cel/runtime/BUILD.bazel @@ -19,8 +19,8 @@ java_library( # keep sorted exclude = [ "CelLiteInterpreterTest.java", - "CelValueInterpreterTest.java", "InterpreterTest.java", + "PlannerInterpreterTest.java", ] + ANDROID_TESTS, ), deps = [ @@ -125,16 +125,17 @@ java_library( ) java_library( - name = "cel_value_interpreter_test", + name = "planner_interpreter_test", testonly = 1, srcs = [ - "CelValueInterpreterTest.java", + "PlannerInterpreterTest.java", ], deps = [ - # "//java/com/google/testing/testsize:annotations", + "//extensions", + "//runtime:runtime_planner_impl", "//testing:base_interpreter_test", - "@maven//:junit_junit", "@maven//:com_google_testparameterinjector_test_parameter_injector", + "@maven//:junit_junit", ], ) @@ -203,8 +204,8 @@ junit4_test_suites( src_dir = "src/test/java", deps = [ ":cel_lite_interpreter_test", - ":cel_value_interpreter_test", ":interpreter_test", + ":planner_interpreter_test", ":tests", ], ) diff --git a/runtime/src/test/java/dev/cel/runtime/CelValueInterpreterTest.java b/runtime/src/test/java/dev/cel/runtime/CelValueInterpreterTest.java deleted file mode 100644 index ad3fae082..000000000 --- a/runtime/src/test/java/dev/cel/runtime/CelValueInterpreterTest.java +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright 2023 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// https://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package dev.cel.runtime; - -import com.google.testing.junit.testparameterinjector.TestParameterInjector; -// import com.google.testing.testsize.MediumTest; -import dev.cel.testing.BaseInterpreterTest; -import org.junit.runner.RunWith; - -/** Tests for {@link Interpreter} and related functionality using {@code CelValue}. */ -// @MediumTest -@RunWith(TestParameterInjector.class) -public class CelValueInterpreterTest extends BaseInterpreterTest { - - public CelValueInterpreterTest() { - super(newBaseCelOptions().toBuilder().enableCelValue(true).build()); - } - - @Override - public void wrappers() throws Exception { - // Field selection on repeated wrappers broken. - // This test along with CelValue adapter will be removed in a separate CL - skipBaselineVerification(); - } -} diff --git a/runtime/src/test/java/dev/cel/runtime/PlannerInterpreterTest.java b/runtime/src/test/java/dev/cel/runtime/PlannerInterpreterTest.java new file mode 100644 index 000000000..4e3a00a34 --- /dev/null +++ b/runtime/src/test/java/dev/cel/runtime/PlannerInterpreterTest.java @@ -0,0 +1,60 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package dev.cel.runtime; + +import com.google.testing.junit.testparameterinjector.TestParameterInjector; +import dev.cel.extensions.CelExtensions; +import dev.cel.testing.BaseInterpreterTest; +import org.junit.runner.RunWith; + +/** Interpreter tests using ProgramPlanner */ +@RunWith(TestParameterInjector.class) +public class PlannerInterpreterTest extends BaseInterpreterTest { + + public PlannerInterpreterTest() { + super( + CelRuntimeImpl.newBuilder() + .addLateBoundFunctions("record") + // CEL-Internal-2 + .setOptions(newBaseCelOptions()) + .addLibraries(CelExtensions.optional()) + .addFileTypes(TEST_FILE_DESCRIPTORS) + .build()); + } + + @Override + public void unknownField() { + // TODO: Unknown support not implemented yet + skipBaselineVerification(); + } + + @Override + public void unknownResultSet() { + // TODO: Unknown support not implemented yet + skipBaselineVerification(); + } + + @Override + public void typeComparisons() { + // TODO: type() standard function needs to be implemented first. + skipBaselineVerification(); + } + + @Override + public void optional_errors() { + // TODO: Fix error message for function dispatch failures + skipBaselineVerification(); + } +}