diff --git a/common/BUILD b/common/BUILD index 5b91f419e..185af4e27 100644 --- a/common/BUILD +++ b/common/BUILD @@ -793,10 +793,7 @@ cc_library( "//eval/internal:cel_value_equal", "//eval/public:cel_value", "//eval/public:message_wrapper", - "//eval/public/containers:field_backed_list_impl", - "//eval/public/containers:field_backed_map_impl", "//eval/public/structs:cel_proto_wrap_util", - "//eval/public/structs:legacy_type_adapter", "//eval/public/structs:legacy_type_info_apis", "//eval/public/structs:proto_message_type_adapter", "//eval/public/structs:trivial_legacy_type_info_internal", @@ -865,6 +862,8 @@ cc_test( ":value_kind", ":value_testing", "//base:attributes", + "//eval/public:cel_value", + "//eval/public/structs:proto_message_type_adapter", "//internal:parse_text_proto", "//internal:status_macros", "//internal:testing", diff --git a/common/legacy_value.cc b/common/legacy_value.cc index 184a09c0b..9913a7272 100644 --- a/common/legacy_value.cc +++ b/common/legacy_value.cc @@ -43,22 +43,20 @@ #include "common/unknown.h" #include "common/value.h" #include "common/value_kind.h" +#include "common/values/legacy_list_value.h" +#include "common/values/legacy_map_value.h" #include "common/values/list_value_builder.h" #include "common/values/map_value_builder.h" #include "common/values/values.h" #include "eval/internal/cel_value_equal.h" #include "eval/public/cel_value.h" -#include "eval/public/containers/field_backed_list_impl.h" -#include "eval/public/containers/field_backed_map_impl.h" #include "eval/public/message_wrapper.h" #include "eval/public/structs/cel_proto_wrap_util.h" -#include "eval/public/structs/legacy_type_adapter.h" #include "eval/public/structs/legacy_type_info_apis.h" #include "eval/public/structs/proto_message_type_adapter.h" #include "eval/public/structs/trivial_legacy_type_info_internal.h" #include "internal/json.h" #include "internal/status_macros.h" -#include "internal/well_known_types.h" #include "runtime/runtime_options.h" #include "google/protobuf/arena.h" #include "google/protobuf/descriptor.h" @@ -76,13 +74,9 @@ using ::cel::interop_internal::TrivialTypeInfo; using ::google::api::expr::runtime::CelList; using ::google::api::expr::runtime::CelMap; using ::google::api::expr::runtime::CelValue; -using ::google::api::expr::runtime::CreateCelValueFromField; -using ::google::api::expr::runtime::FieldBackedListImpl; -using ::google::api::expr::runtime::FieldBackedMapImpl; using ::google::api::expr::runtime::GetGenericProtoTypeInfoInstance; using ::google::api::expr::runtime::LegacyTypeInfoApis; using ::google::api::expr::runtime::MessageWrapper; -using ::google::api::expr::runtime::internal::GetGenericProtoAccessApisInstance; using ::google::api::expr::runtime::internal::MaybeWrapValueToMessage; absl::Status InvalidMapKeyTypeError(ValueKind kind) { @@ -284,19 +278,17 @@ CelValue LegacyTrivialListValue(google::protobuf::Arena* absl_nonnull arena, } if (auto parsed_repeated_field_value = value.AsParsedRepeatedField(); parsed_repeated_field_value) { - auto maybe_cloned = parsed_repeated_field_value->Clone(arena); - return CelValue::CreateList(google::protobuf::Arena::Create( - arena, &maybe_cloned.message(), maybe_cloned.field(), arena)); + auto wrapped = common_internal::WrapLegacyParsedRepeatedField( + *parsed_repeated_field_value, arena); + return CelValue::CreateList( + common_internal::AsLegacyListValue(wrapped)->cel_list()); } if (auto parsed_json_list_value = value.AsParsedJsonList(); parsed_json_list_value) { - auto maybe_cloned = parsed_json_list_value->Clone(arena); - return CelValue::CreateList(google::protobuf::Arena::Create( - arena, cel::to_address(maybe_cloned), - well_known_types::GetListValueReflectionOrDie( - maybe_cloned->GetDescriptor()) - .GetValuesDescriptor(), - arena)); + auto wrapped = common_internal::WrapLegacyParsedJsonList( + *parsed_json_list_value, arena); + return CelValue::CreateList( + common_internal::AsLegacyListValue(wrapped)->cel_list()); } if (auto custom_list_value = value.AsCustomList(); custom_list_value) { auto status_or_compat_list = common_internal::MakeCompatListValue( @@ -322,19 +314,17 @@ CelValue LegacyTrivialMapValue(google::protobuf::Arena* absl_nonnull arena, } if (auto parsed_map_field_value = value.AsParsedMapField(); parsed_map_field_value) { - auto maybe_cloned = parsed_map_field_value->Clone(arena); - return CelValue::CreateMap(google::protobuf::Arena::Create( - arena, &maybe_cloned.message(), maybe_cloned.field(), arena)); + auto wrapped = common_internal::WrapLegacyParsedMapField( + *parsed_map_field_value, arena); + return CelValue::CreateMap( + common_internal::AsLegacyMapValue(wrapped)->cel_map()); } if (auto parsed_json_map_value = value.AsParsedJsonMap(); parsed_json_map_value) { - auto maybe_cloned = parsed_json_map_value->Clone(arena); - return CelValue::CreateMap(google::protobuf::Arena::Create( - arena, cel::to_address(maybe_cloned), - well_known_types::GetStructReflectionOrDie( - maybe_cloned->GetDescriptor()) - .GetFieldsDescriptor(), - arena)); + auto wrapped = + common_internal::WrapLegacyParsedJsonMap(*parsed_json_map_value, arena); + return CelValue::CreateMap( + common_internal::AsLegacyMapValue(wrapped)->cel_map()); } if (auto custom_map_value = value.AsCustomMap(); custom_map_value) { auto status_or_compat_map = common_internal::MakeCompatMapValue( @@ -352,6 +342,25 @@ CelValue LegacyTrivialMapValue(google::protobuf::Arena* absl_nonnull arena, value.GetRuntimeType().DebugString())))); } +LegacyStructValue ParsedMessageToLegacyStructValue( + const ParsedMessageValue& parsed_message) { + return LegacyStructValue(cel::to_address(parsed_message), + &GetGenericProtoTypeInfoInstance()); +} + +LegacyStructValue MakeLegacyStructValue( + const google::protobuf::Message* absl_nonnull message, + const LegacyTypeInfoApis* legacy_type_info) { + // Guard against edge cases where a custom implementation of Message + // misbehaves. + // Modern value handles this with DCHECKs on value creation, legacy value + // would allow it and just report an ErrorValue on accesses. + if (message->GetReflection() == nullptr || legacy_type_info == nullptr) { + legacy_type_info = TrivialTypeInfo::GetInstance(); + } + return LegacyStructValue(message, legacy_type_info); +} + } // namespace google::api::expr::runtime::CelValue UnsafeLegacyValue( @@ -394,10 +403,6 @@ google::api::expr::runtime::CelValue UnsafeLegacyValue( } } -} // namespace common_internal - -namespace common_internal { - std::string LegacyListValue::DebugString() const { return CelValue::CreateList(impl_).DebugString(); } @@ -837,10 +842,8 @@ absl::Status LegacyStructValue::SerializeTo( ABSL_DCHECK(message_factory != nullptr); ABSL_DCHECK(output != nullptr); - auto message_wrapper = AsMessageWrapper(message_ptr_, legacy_type_info_); if (ABSL_PREDICT_TRUE( - message_wrapper.message_ptr()->SerializePartialToZeroCopyStream( - output))) { + message_ptr_->SerializePartialToZeroCopyStream(output))) { return absl::OkStatus(); } return absl::UnknownError("failed to serialize protocol buffer message"); @@ -918,17 +921,26 @@ absl::Status LegacyStructValue::GetFieldByName( const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, google::protobuf::MessageFactory* absl_nonnull message_factory, google::protobuf::Arena* absl_nonnull arena, Value* absl_nonnull result) const { - auto message_wrapper = AsMessageWrapper(message_ptr_, legacy_type_info_); if (ABSL_PREDICT_FALSE(legacy_type_info_ == TrivialTypeInfo::GetInstance())) { *result = NoSuchFieldError(name); return absl::OkStatus(); } - CEL_ASSIGN_OR_RETURN(auto cel_value, - GetGenericProtoAccessApisInstance().GetField( - name, message_wrapper, unboxing_options, - MemoryManagerRef::Pooling(arena))); - CEL_RETURN_IF_ERROR(ModernValue(arena, cel_value, *result)); - return absl::OkStatus(); + + ParsedMessageValue parsed_message = UnsafeParsedMessageValue(message_ptr_); + const auto* descriptor = parsed_message.GetDescriptor(); + const auto* field = descriptor->FindFieldByName(name); + if (field == nullptr) { + field = descriptor->file()->pool()->FindExtensionByPrintableName(descriptor, + name); + if (field == nullptr) { + *result = NoSuchFieldError(name); + return absl::OkStatus(); + } + } + + return interop_internal::WrapLegacyMessageField( + message_ptr_, field, unboxing_options, descriptor_pool, message_factory, + arena, result); } absl::Status LegacyStructValue::GetFieldByNumber( @@ -980,7 +992,6 @@ absl::Status LegacyStructValue::Qualify( if (ABSL_PREDICT_FALSE(qualifiers.empty())) { return absl::InvalidArgumentError("invalid select qualifier path."); } - auto message_wrapper = AsMessageWrapper(message_ptr_, legacy_type_info_); if (ABSL_PREDICT_FALSE(legacy_type_info_ == TrivialTypeInfo::GetInstance())) { absl::string_view field_name = absl::visit( absl::Overload( @@ -995,12 +1006,13 @@ absl::Status LegacyStructValue::Qualify( *count = -1; return absl::OkStatus(); } - CEL_ASSIGN_OR_RETURN(auto legacy_result, - GetGenericProtoAccessApisInstance().Qualify( - qualifiers, message_wrapper, presence_test, - MemoryManager::Pooling(arena))); - CEL_RETURN_IF_ERROR(ModernValue(arena, legacy_result.value, *result)); - *count = legacy_result.qualifier_count; + + ParsedMessageValue parsed_message = UnsafeParsedMessageValue(message_ptr_); + CEL_RETURN_IF_ERROR(parsed_message.Qualify(qualifiers, presence_test, + descriptor_pool, message_factory, + arena, result, count)); + + interop_internal::WrapLegacyFieldAccessResult(arena, result); return absl::OkStatus(); } @@ -1035,7 +1047,7 @@ absl::Status ModernValue(google::protobuf::Arena* arena, return absl::OkStatus(); case CelValue::Type::kMessage: { auto message_wrapper = legacy_value.MessageWrapperOrDie(); - result = common_internal::LegacyStructValue( + result = common_internal::MakeLegacyStructValue( google::protobuf::DownCastMessage( message_wrapper.message_ptr()), message_wrapper.legacy_type_info()); @@ -1153,7 +1165,7 @@ absl::StatusOr FromLegacyValue(google::protobuf::Arena* arena, legacy_value.BytesOrDie().value()); case CelValue::Type::kMessage: { auto message_wrapper = legacy_value.MessageWrapperOrDie(); - return common_internal::LegacyStructValue( + return common_internal::MakeLegacyStructValue( google::protobuf::DownCastMessage( message_wrapper.message_ptr()), message_wrapper.legacy_type_info()); @@ -1262,6 +1274,33 @@ google::api::expr::runtime::CelValue ModernValueToLegacyValueOrDie( return std::move(*status_or_value); } +void WrapLegacyFieldAccessResult(google::protobuf::Arena* absl_nonnull arena, + Value* absl_nonnull result) { + if (result->IsParsedMessage()) { + *result = common_internal::ParsedMessageToLegacyStructValue( + result->GetParsedMessage()); + } else if (result->IsParsedRepeatedField()) { + *result = + WrapLegacyParsedRepeatedField(result->GetParsedRepeatedField(), arena); + } else if (result->IsParsedJsonList()) { + *result = WrapLegacyParsedJsonList(result->GetParsedJsonList(), arena); + } else if (result->IsParsedMapField()) { + *result = WrapLegacyParsedMapField(result->GetParsedMapField(), arena); + } else if (result->IsParsedJsonMap()) { + *result = WrapLegacyParsedJsonMap(result->GetParsedJsonMap(), arena); + } else if (result->IsList()) { + auto is_empty = result->GetList().IsEmpty(); + if (is_empty.ok() && *is_empty) { + *result = CustomListValue(common_internal::EmptyCompatListValue(), arena); + } + } else if (result->IsMap()) { + auto is_empty = result->GetMap().IsEmpty(); + if (is_empty.ok() && *is_empty) { + *result = CustomMapValue(common_internal::EmptyCompatMapValue(), arena); + } + } +} + TypeValue CreateTypeValueFromView(google::protobuf::Arena* arena, absl::string_view input) { return TypeValue(common_internal::LegacyRuntimeType(input)); @@ -1289,12 +1328,17 @@ const google::protobuf::Message* absl_nullable GetLegacyMessage(const Value& val absl::Status WrapLegacyMessageField( const google::protobuf::Message* absl_nonnull message, const google::protobuf::FieldDescriptor* absl_nonnull field_descriptor, - ProtoWrapperTypeOptions unboxing_option, google::protobuf::Arena* arena, + ProtoWrapperTypeOptions unboxing_option, + const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, + google::protobuf::MessageFactory* absl_nonnull message_factory, google::protobuf::Arena* arena, Value* absl_nonnull out) { - CEL_ASSIGN_OR_RETURN(CelValue result, - CreateCelValueFromField(message, field_descriptor, - unboxing_option, arena)); - return ModernValue(arena, result, *out); + ParsedMessageValue parsed_message = UnsafeParsedMessageValue(message); + CEL_RETURN_IF_ERROR(parsed_message.GetField(field_descriptor, unboxing_option, + descriptor_pool, message_factory, + arena, out)); + WrapLegacyFieldAccessResult(arena, out); + + return absl::OkStatus(); } } // namespace interop_internal diff --git a/common/legacy_value.h b/common/legacy_value.h index 8d0392f7a..a89eb0412 100644 --- a/common/legacy_value.h +++ b/common/legacy_value.h @@ -59,19 +59,32 @@ google::api::expr::runtime::CelValue UnsafeLegacyValue( } // namespace cel +namespace proto2 { +class MessageFactory; +} // namespace proto2 + namespace cel::interop_internal { // Returns the underlying `google::protobuf::Message` of a `cel::Value` if it is a legacy // message with the default type info, or `nullptr` otherwise. const google::protobuf::Message* absl_nullable GetLegacyMessage(const Value& value); +// Helper for wrapping a field accesses for the legacy runtime. +// +// Adapts the output to avoid further allocations when converting to a legacy +// value when possible. +void WrapLegacyFieldAccessResult(google::protobuf::Arena* absl_nonnull arena, + Value* absl_nonnull result); + // Access a field on a legacy message value, writing the result to `out`. // Prefers wrapping legacy values instead of using the modern value // representation. absl::Status WrapLegacyMessageField( const google::protobuf::Message* absl_nonnull message, const google::protobuf::FieldDescriptor* absl_nonnull field_descriptor, - ProtoWrapperTypeOptions unboxing_option, google::protobuf::Arena* arena, + ProtoWrapperTypeOptions unboxing_option, + const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, + google::protobuf::MessageFactory* absl_nonnull message_factory, google::protobuf::Arena* arena, Value* absl_nonnull out); absl::StatusOr FromLegacyValue( diff --git a/common/value.cc b/common/value.cc index fdb18d388..e749a16c6 100644 --- a/common/value.cc +++ b/common/value.cc @@ -22,6 +22,7 @@ #include #include #include +#include #include "google/protobuf/struct.pb.h" #include "absl/base/attributes.h" @@ -1512,10 +1513,14 @@ Value WrapFieldImpl( ABSL_DCHECK(!IsWellKnownMessageType(message->GetDescriptor())); const auto* reflection = message->GetReflection(); + if (ABSL_PREDICT_FALSE(reflection == nullptr)) { + // This only happens for special implementations of Message that + // should not normally be used with CEL. + return ErrorValue(absl::InvalidArgumentError( + absl::StrCat("failed to get reflection for message type: ", + message->GetDescriptor()->full_name()))); + } if (field->is_map()) { - if (reflection->FieldSize(*message, field) == 0) { - return MapValue(); - } if constexpr (Unsafe::value) { return UnsafeParsedMapFieldValue(message, field); } else { @@ -1524,9 +1529,6 @@ Value WrapFieldImpl( } } if (field->is_repeated()) { - if (reflection->FieldSize(*message, field) == 0) { - return ListValue(); - } if constexpr (Unsafe::value) { return UnsafeParsedRepeatedFieldValue(message, field); } else { @@ -1653,6 +1655,13 @@ Value WrapRepeatedFieldImpl( ABSL_DCHECK(arena != nullptr); const auto* reflection = message->GetReflection(); + if (ABSL_PREDICT_FALSE(reflection == nullptr)) { + // This only happens for special implementations of Message that + // should not normally be used with CEL. + return ErrorValue(absl::InvalidArgumentError( + absl::StrCat("failed to get reflection for message type: ", + message->GetDescriptor()->full_name()))); + } const int size = reflection->FieldSize(*message, field); if (ABSL_PREDICT_FALSE(index < 0 || index >= size)) { return ErrorValue(absl::InvalidArgumentError( diff --git a/common/values/custom_map_value.cc b/common/values/custom_map_value.cc index 495b64b7a..90a45f34e 100644 --- a/common/values/custom_map_value.cc +++ b/common/values/custom_map_value.cc @@ -14,7 +14,9 @@ #include #include +#include #include +#include #include "absl/base/attributes.h" #include "absl/base/no_destructor.h" @@ -678,13 +680,22 @@ absl::StatusOr CustomMapValue::Find( CustomMapValueInterface::Content content = content_.To(); ABSL_DCHECK(content.interface != nullptr); - CEL_ASSIGN_OR_RETURN( - ok, content.interface->Find(key, descriptor_pool, message_factory, - arena, result)); + auto status_or_found = content.interface->Find( + key, descriptor_pool, message_factory, arena, result); + if (!status_or_found.ok()) { + *result = ErrorValue(std::move(status_or_found).status()); + return false; + } + ok = *status_or_found; } else { - CEL_ASSIGN_OR_RETURN( - ok, dispatcher_->find(dispatcher_, content_, key, descriptor_pool, - message_factory, arena, result)); + auto status_or_found = + dispatcher_->find(dispatcher_, content_, key, descriptor_pool, + message_factory, arena, result); + if (!status_or_found.ok()) { + *result = ErrorValue(std::move(status_or_found).status()); + return false; + } + ok = *status_or_found; } if (ok) { return true; @@ -721,19 +732,26 @@ absl::Status CustomMapValue::Has( *result = ErrorValue(InvalidMapKeyTypeError(key.kind())); return absl::OkStatus(); } - bool has; if (dispatcher_ == nullptr) { CustomMapValueInterface::Content content = content_.To(); ABSL_DCHECK(content.interface != nullptr); - CEL_ASSIGN_OR_RETURN(has, content.interface->Has(key, descriptor_pool, - message_factory, arena)); - } else { - CEL_ASSIGN_OR_RETURN( - has, dispatcher_->has(dispatcher_, content_, key, descriptor_pool, - message_factory, arena)); + auto status_or_has = + content.interface->Has(key, descriptor_pool, message_factory, arena); + if (!status_or_has.ok()) { + *result = ErrorValue(std::move(status_or_has).status()); + return absl::OkStatus(); + } + *result = BoolValue(*status_or_has); + return absl::OkStatus(); + } + auto status_or_has = dispatcher_->has( + dispatcher_, content_, key, descriptor_pool, message_factory, arena); + if (!status_or_has.ok()) { + *result = ErrorValue(std::move(status_or_has).status()); + return absl::OkStatus(); } - *result = BoolValue(has); + *result = BoolValue(*status_or_has); return absl::OkStatus(); } diff --git a/common/values/custom_map_value.h b/common/values/custom_map_value.h index ca6e1e025..c97841826 100644 --- a/common/values/custom_map_value.h +++ b/common/values/custom_map_value.h @@ -54,6 +54,12 @@ class CustomMapValueInterfaceKeysIterator; class CustomMapValue; using CustomMapValueContent = CustomValueContent; +// Dispatch table for `CustomMapValue`. +// +// See the documentation for `CustomMapValueInterface` for more details on +// composite functions. +// +// See documentation for `UnsafeCustomMapValue` on how to use this class. struct CustomMapValueDispatcher { using GetTypeId = NativeTypeId (*)(const CustomMapValueDispatcher* absl_nonnull dispatcher, @@ -253,6 +259,9 @@ class CustomMapValueInterface { google::protobuf::MessageFactory* absl_nonnull message_factory, google::protobuf::Arena* absl_nonnull arena, Value* absl_nonnull result) const = 0; + // Whether the map has the given key. + // + // A non-ok status is coerced to an ErrorValue (e.g. wrong key type). virtual absl::StatusOr Has( const Value& key, const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, diff --git a/common/values/legacy_list_value.cc b/common/values/legacy_list_value.cc index 60e571452..9e6283503 100644 --- a/common/values/legacy_list_value.cc +++ b/common/values/legacy_list_value.cc @@ -14,22 +14,318 @@ #include "common/values/legacy_list_value.h" +#include +#include +#include + #include "absl/base/nullability.h" +#include "absl/base/optimization.h" #include "absl/log/absl_check.h" #include "absl/status/status.h" #include "absl/types/optional.h" +#include "common/legacy_value.h" #include "common/native_type.h" #include "common/value.h" +#include "common/values/legacy_struct_value.h" #include "common/values/list_value_builder.h" #include "common/values/values.h" #include "eval/public/cel_value.h" +#include "eval/public/structs/proto_message_type_adapter.h" #include "internal/casts.h" +#include "internal/status_macros.h" #include "google/protobuf/arena.h" #include "google/protobuf/descriptor.h" #include "google/protobuf/message.h" namespace cel::common_internal { +namespace { + +LegacyStructValue ParsedMessageToLegacyStructValue( + const ParsedMessageValue& parsed_message) { + return LegacyStructValue( + parsed_message.message(), + &google::api::expr::runtime::GetGenericProtoTypeInfoInstance()); +} + +} // namespace + +class LegacyParsedRepeatedFieldListValue final + : public CustomListValueInterface, + public google::api::expr::runtime::CelList { + public: + // `arena` is expected to be the same arena as the one that the object is + // allocated on. + explicit LegacyParsedRepeatedFieldListValue(ParsedRepeatedFieldValue value, + google::protobuf::Arena* absl_nonnull arena) + : value_(std::move(value)), arena_(arena) { + ABSL_DCHECK(arena != nullptr); + } + + // CelList implementation + int size() const override { return static_cast(value_.Size()); } + + bool empty() const override { return value_.IsEmpty(); } + + google::api::expr::runtime::CelValue operator[](int index) const override { + return Get(arena_, index); + } + + google::api::expr::runtime::CelValue Get(google::protobuf::Arena* arena, + int index) const override { + if (arena == nullptr) { + arena = arena_; + } + if (ABSL_PREDICT_FALSE(index < 0 || index >= size())) { + return google::api::expr::runtime::CelValue::CreateError( + google::protobuf::Arena::Create( + arena, IndexOutOfBoundsError(index).ToStatus())); + } + Value result; + auto status = value_.Get( + static_cast(index), google::protobuf::DescriptorPool::generated_pool(), + google::protobuf::MessageFactory::generated_factory(), arena, &result); + if (ABSL_PREDICT_FALSE(!status.ok())) { + return google::api::expr::runtime::CelValue::CreateError( + google::protobuf::Arena::Create(arena, std::move(status))); + } + return UnsafeLegacyValue(result, /*stable=*/false, arena); + } + + protected: + std::string DebugString() const override { return value_.DebugString(); } + + absl::Status SerializeTo( + const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, + google::protobuf::MessageFactory* absl_nonnull message_factory, + google::protobuf::io::ZeroCopyOutputStream* absl_nonnull output) const override { + return value_.SerializeTo(descriptor_pool, message_factory, output); + } + + absl::Status ConvertToJsonArray( + const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, + google::protobuf::MessageFactory* absl_nonnull message_factory, + google::protobuf::Message* absl_nonnull json) const override { + return value_.ConvertToJsonArray(descriptor_pool, message_factory, json); + } + + absl::Status Equal(const ListValue& other, + const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, + google::protobuf::MessageFactory* absl_nonnull message_factory, + google::protobuf::Arena* absl_nonnull arena, + Value* absl_nonnull result) const override { + return value_.Equal(other, descriptor_pool, message_factory, arena, result); + } + + bool IsZeroValue() const override { return value_.IsZeroValue(); } + + bool IsEmpty() const override { return value_.IsEmpty(); } + + size_t Size() const override { return value_.Size(); } + + absl::Status Get(size_t index, + const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, + google::protobuf::MessageFactory* absl_nonnull message_factory, + google::protobuf::Arena* absl_nonnull arena, + Value* absl_nonnull result) const override { + CEL_RETURN_IF_ERROR( + value_.Get(index, descriptor_pool, message_factory, arena, result)); + interop_internal::WrapLegacyFieldAccessResult(arena, result); + return absl::OkStatus(); + } + + absl::Status ForEach( + ForEachWithIndexCallback callback, + const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, + google::protobuf::MessageFactory* absl_nonnull message_factory, + google::protobuf::Arena* absl_nonnull arena) const override { + return value_.ForEach( + [&](size_t index, const Value& element) -> absl::StatusOr { + if (element.IsParsedMessage()) { + return callback(index, ParsedMessageToLegacyStructValue( + element.GetParsedMessage())); + } + return callback(index, element); + }, + descriptor_pool, message_factory, arena); + } + + absl::StatusOr NewIterator() const override { + return value_.NewIterator(); + } + + absl::Status Contains( + const Value& other, + const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, + google::protobuf::MessageFactory* absl_nonnull message_factory, + google::protobuf::Arena* absl_nonnull arena, + Value* absl_nonnull result) const override { + return value_.Contains(other, descriptor_pool, message_factory, arena, + result); + } + + CustomListValue Clone(google::protobuf::Arena* absl_nonnull arena) const override { + return CustomListValue( + google::protobuf::Arena::Create( + arena, value_.Clone(arena), arena), + arena); + } + + private: + NativeTypeId GetNativeTypeId() const override { + return NativeTypeId::For(); + } + + ParsedRepeatedFieldValue value_; + google::protobuf::Arena* const arena_; +}; + +class LegacyParsedJsonListValue final + : public CustomListValueInterface, + public google::api::expr::runtime::CelList { + public: + // `arena` is expected to be the same arena as the one that the object is + // allocated on. + explicit LegacyParsedJsonListValue(ParsedJsonListValue value, + google::protobuf::Arena* absl_nonnull arena) + : value_(std::move(value)), arena_(arena) { + ABSL_DCHECK(arena != nullptr); + } + + // CelList implementation + int size() const override { return static_cast(value_.Size()); } + + bool empty() const override { return value_.IsEmpty(); } + + google::api::expr::runtime::CelValue operator[](int index) const override { + return Get(arena_, index); + } + + google::api::expr::runtime::CelValue Get(google::protobuf::Arena* arena, + int index) const override { + if (arena == nullptr) { + arena = arena_; + } + if (ABSL_PREDICT_FALSE(index < 0 || index >= size())) { + return google::api::expr::runtime::CelValue::CreateError( + google::protobuf::Arena::Create( + arena, IndexOutOfBoundsError(index).ToStatus())); + } + Value result; + auto status = value_.Get( + static_cast(index), google::protobuf::DescriptorPool::generated_pool(), + google::protobuf::MessageFactory::generated_factory(), arena, &result); + if (ABSL_PREDICT_FALSE(!status.ok())) { + return google::api::expr::runtime::CelValue::CreateError( + google::protobuf::Arena::Create(arena, std::move(status))); + } + return UnsafeLegacyValue(result, /*stable=*/false, arena); + } + + protected: + std::string DebugString() const override { return value_.DebugString(); } + + absl::Status SerializeTo( + const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, + google::protobuf::MessageFactory* absl_nonnull message_factory, + google::protobuf::io::ZeroCopyOutputStream* absl_nonnull output) const override { + return value_.SerializeTo(descriptor_pool, message_factory, output); + } + + absl::Status ConvertToJsonArray( + const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, + google::protobuf::MessageFactory* absl_nonnull message_factory, + google::protobuf::Message* absl_nonnull json) const override { + return value_.ConvertToJsonArray(descriptor_pool, message_factory, json); + } + + absl::Status Equal(const ListValue& other, + const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, + google::protobuf::MessageFactory* absl_nonnull message_factory, + google::protobuf::Arena* absl_nonnull arena, + Value* absl_nonnull result) const override { + return value_.Equal(other, descriptor_pool, message_factory, arena, result); + } + + bool IsZeroValue() const override { return value_.IsZeroValue(); } + + bool IsEmpty() const override { return value_.IsEmpty(); } + + size_t Size() const override { return value_.Size(); } + + absl::Status Get(size_t index, + const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, + google::protobuf::MessageFactory* absl_nonnull message_factory, + google::protobuf::Arena* absl_nonnull arena, + Value* absl_nonnull result) const override { + CEL_RETURN_IF_ERROR( + value_.Get(index, descriptor_pool, message_factory, arena, result)); + if (result->IsParsedMessage()) { + *result = ParsedMessageToLegacyStructValue(result->GetParsedMessage()); + } + return absl::OkStatus(); + } + + absl::Status ForEach( + ForEachWithIndexCallback callback, + const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, + google::protobuf::MessageFactory* absl_nonnull message_factory, + google::protobuf::Arena* absl_nonnull arena) const override { + return value_.ForEach( + [&](size_t index, const Value& element) -> absl::StatusOr { + if (element.IsParsedMessage()) { + return callback(index, ParsedMessageToLegacyStructValue( + element.GetParsedMessage())); + } + return callback(index, element); + }, + descriptor_pool, message_factory, arena); + } + + absl::StatusOr NewIterator() const override { + return value_.NewIterator(); + } + + absl::Status Contains( + const Value& other, + const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, + google::protobuf::MessageFactory* absl_nonnull message_factory, + google::protobuf::Arena* absl_nonnull arena, + Value* absl_nonnull result) const override { + return value_.Contains(other, descriptor_pool, message_factory, arena, + result); + } + + CustomListValue Clone(google::protobuf::Arena* absl_nonnull arena) const override { + return CustomListValue(google::protobuf::Arena::Create( + arena, value_.Clone(arena), arena), + arena); + } + + private: + NativeTypeId GetNativeTypeId() const override { + return NativeTypeId::For(); + } + + ParsedJsonListValue value_; + google::protobuf::Arena* const arena_; +}; + +CustomListValue WrapLegacyParsedRepeatedField( + ParsedRepeatedFieldValue value, google::protobuf::Arena* absl_nonnull arena) { + return CustomListValue( + google::protobuf::Arena::Create( + arena, std::move(value), arena), + arena); +} + +CustomListValue WrapLegacyParsedJsonList(ParsedJsonListValue value, + google::protobuf::Arena* absl_nonnull arena) { + return CustomListValue(google::protobuf::Arena::Create( + arena, std::move(value), arena), + arena); +} + absl::Status LegacyListValue::Equal( const Value& other, const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, @@ -68,6 +364,18 @@ absl::optional AsLegacyListValue(const Value& value) { static_cast( cel::internal::down_cast( custom_list_value->interface()))); + } else if (native_type_id == + NativeTypeId::For()) { + return LegacyListValue(static_cast< + const google::api::expr::runtime::CelList*>( + cel::internal::down_cast( + custom_list_value->interface()))); + } else if (native_type_id == + NativeTypeId::For()) { + return LegacyListValue( + static_cast( + cel::internal::down_cast( + custom_list_value->interface()))); } } return std::nullopt; diff --git a/common/values/legacy_list_value.h b/common/values/legacy_list_value.h index caffcbc25..6dd6a568e 100644 --- a/common/values/legacy_list_value.h +++ b/common/values/legacy_list_value.h @@ -30,6 +30,8 @@ #include "absl/types/optional.h" #include "common/value_kind.h" #include "common/values/custom_list_value.h" +#include "common/values/parsed_json_list_value.h" +#include "common/values/parsed_repeated_field_value.h" #include "common/values/values.h" #include "google/protobuf/arena.h" #include "google/protobuf/descriptor.h" @@ -160,6 +162,15 @@ LegacyListValue GetLegacyListValue(const Value& value); absl::optional AsLegacyListValue(const Value& value); +class LegacyParsedRepeatedFieldListValue; +class LegacyParsedJsonListValue; + +CustomListValue WrapLegacyParsedRepeatedField( + ParsedRepeatedFieldValue value, google::protobuf::Arena* absl_nonnull arena); + +CustomListValue WrapLegacyParsedJsonList(ParsedJsonListValue value, + google::protobuf::Arena* absl_nonnull arena); + } // namespace common_internal } // namespace cel diff --git a/common/values/legacy_map_value.cc b/common/values/legacy_map_value.cc index 5b24b34ba..9a5085409 100644 --- a/common/values/legacy_map_value.cc +++ b/common/values/legacy_map_value.cc @@ -14,22 +14,469 @@ #include "common/values/legacy_map_value.h" +#include +#include +#include +#include +#include +#include + #include "absl/base/nullability.h" +#include "absl/base/optimization.h" #include "absl/log/absl_check.h" #include "absl/status/status.h" +#include "absl/strings/str_cat.h" +#include "absl/strings/string_view.h" #include "absl/types/optional.h" +#include "common/legacy_value.h" +#include "common/memory.h" #include "common/native_type.h" #include "common/value.h" +#include "common/value_kind.h" +#include "common/values/legacy_list_value.h" +#include "common/values/legacy_struct_value.h" #include "common/values/map_value_builder.h" #include "common/values/values.h" #include "eval/public/cel_value.h" +#include "eval/public/structs/proto_message_type_adapter.h" #include "internal/casts.h" +#include "internal/status_macros.h" #include "google/protobuf/arena.h" #include "google/protobuf/descriptor.h" #include "google/protobuf/message.h" namespace cel::common_internal { +namespace { + +LegacyStructValue ParsedMessageToLegacyStructValue( + const ParsedMessageValue& parsed_message) { + return LegacyStructValue( + cel::to_address(parsed_message), + &google::api::expr::runtime::GetGenericProtoTypeInfoInstance()); +} + +bool MatchesMapKeyType(const google::protobuf::FieldDescriptor* absl_nonnull key_desc, + const Value& key) { + switch (key_desc->cpp_type()) { + case google::protobuf::FieldDescriptor::CPPTYPE_BOOL: + return key.IsBool(); + case google::protobuf::FieldDescriptor::CPPTYPE_INT32: + if (key.IsInt()) { + auto val = key.GetInt().NativeValue(); + return val >= std::numeric_limits::min() && + val <= std::numeric_limits::max(); + } + return false; + case google::protobuf::FieldDescriptor::CPPTYPE_INT64: + return key.IsInt(); + case google::protobuf::FieldDescriptor::CPPTYPE_UINT32: + if (key.IsUint()) { + auto val = key.GetUint().NativeValue(); + return val <= std::numeric_limits::max(); + } + return false; + case google::protobuf::FieldDescriptor::CPPTYPE_UINT64: + return key.IsUint(); + case google::protobuf::FieldDescriptor::CPPTYPE_STRING: + return key.IsString(); + default: + return false; + } +} + +absl::Status InvalidMapKeyType(absl::string_view key_type) { + return absl::InvalidArgumentError( + absl::StrCat("Invalid map key type: '", key_type, "'")); +} + +} // namespace + +class LegacyParsedMapFieldMapValue final + : public CustomMapValueInterface, + public google::api::expr::runtime::CelMap { + public: + // `arena` is expected to be the same arena as the one that the object is + // allocated on. + explicit LegacyParsedMapFieldMapValue(ParsedMapFieldValue value, + google::protobuf::Arena* absl_nonnull arena) + : value_(std::move(value)), arena_(arena) { + ABSL_DCHECK(arena != nullptr); + ABSL_DCHECK(value_.field() != nullptr); + } + + std::string DebugString() const override { return value_.DebugString(); } + + absl::Status SerializeTo( + const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, + google::protobuf::MessageFactory* absl_nonnull message_factory, + google::protobuf::io::ZeroCopyOutputStream* absl_nonnull output) const override { + return value_.SerializeTo(descriptor_pool, message_factory, output); + } + + absl::Status ConvertToJsonObject( + const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, + google::protobuf::MessageFactory* absl_nonnull message_factory, + google::protobuf::Message* absl_nonnull json) const override { + return value_.ConvertToJsonObject(descriptor_pool, message_factory, json); + } + + absl::Status Equal(const MapValue& other, + const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, + google::protobuf::MessageFactory* absl_nonnull message_factory, + google::protobuf::Arena* absl_nonnull arena, + Value* absl_nonnull result) const override { + return value_.Equal(other, descriptor_pool, message_factory, arena, result); + } + + bool IsZeroValue() const override { return value_.IsZeroValue(); } + + bool IsEmpty() const override { return value_.IsEmpty(); } + + size_t Size() const override { return value_.Size(); } + + absl::StatusOr Find( + const Value& key, + const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, + google::protobuf::MessageFactory* absl_nonnull message_factory, + google::protobuf::Arena* absl_nonnull arena, + Value* absl_nonnull result) const override { + // Mimic the legacy behavior of complaining about unexpected key type. + const auto* key_field = value_.field()->message_type()->map_key(); + if (!MatchesMapKeyType(key_field, key)) { + return InvalidMapKeyType(cel::ValueKindToString(key.kind())); + } + + CEL_ASSIGN_OR_RETURN( + auto found, + value_.Find(key, descriptor_pool, message_factory, arena, result)); + if (found) { + interop_internal::WrapLegacyFieldAccessResult(arena, result); + } + return found; + } + + absl::StatusOr Has( + const Value& key, + const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, + google::protobuf::MessageFactory* absl_nonnull message_factory, + google::protobuf::Arena* absl_nonnull arena) const override { + const auto* key_field = value_.field()->message_type()->map_key(); + if (!MatchesMapKeyType(key_field, key)) { + return InvalidMapKeyType(key_field->cpp_type_name()); + } + Value result; + CEL_RETURN_IF_ERROR( + value_.Has(key, descriptor_pool, message_factory, arena, &result)); + if (result.IsBool()) { + return result.GetBool().NativeValue(); + } + if (result.IsError()) { + return result.GetError().NativeValue(); + } + return false; + } + + absl::Status ListKeys( + const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, + google::protobuf::MessageFactory* absl_nonnull message_factory, + google::protobuf::Arena* absl_nonnull arena, + ListValue* absl_nonnull result) const override { + return value_.ListKeys(descriptor_pool, message_factory, arena, result); + } + + absl::Status ForEach( + ForEachCallback callback, + const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, + google::protobuf::MessageFactory* absl_nonnull message_factory, + google::protobuf::Arena* absl_nonnull arena) const override { + return value_.ForEach(callback, descriptor_pool, message_factory, arena); + } + + absl::StatusOr NewIterator() const override { + return value_.NewIterator(); + } + + CustomMapValue Clone(google::protobuf::Arena* absl_nonnull arena) const override { + return CustomMapValue(google::protobuf::Arena::Create( + arena, value_.Clone(arena), arena), + arena); + } + + // CelMap implementation + int size() const override { return static_cast(value_.Size()); } + + bool empty() const override { return value_.IsEmpty(); } + + absl::optional operator[]( + google::api::expr::runtime::CelValue key) const override { + return Get(arena_, key); + } + + absl::optional Get( + google::protobuf::Arena* arena, + google::api::expr::runtime::CelValue key) const override { + if (arena == nullptr) { + arena = arena_; + } + if (auto status = + google::api::expr::runtime::CelValue::CheckMapKeyType(key); + !status.ok()) { + status.IgnoreError(); + return absl::nullopt; + } + Value modern_key; + if (ABSL_PREDICT_FALSE(!ModernValue(arena, key, modern_key).ok())) { + return absl::nullopt; + } + Value modern_val; + auto status_or_found = + Find(modern_key, google::protobuf::DescriptorPool::generated_pool(), + google::protobuf::MessageFactory::generated_factory(), arena, &modern_val); + if (!status_or_found.ok() || !*status_or_found) { + return absl::nullopt; + } + return UnsafeLegacyValue(modern_val, /*stable=*/false, arena); + } + + absl::StatusOr Has( + const google::api::expr::runtime::CelValue& key) const override { + CEL_RETURN_IF_ERROR( + google::api::expr::runtime::CelValue::CheckMapKeyType(key)); + google::protobuf::Arena scratch_arena; + Value modern_key; + CEL_RETURN_IF_ERROR(ModernValue(&scratch_arena, key, modern_key)); + return Has(modern_key, google::protobuf::DescriptorPool::generated_pool(), + google::protobuf::MessageFactory::generated_factory(), &scratch_arena); + } + + absl::StatusOr ListKeys() + const override { + return ListKeys(arena_); + } + + absl::StatusOr ListKeys( + google::protobuf::Arena* arena) const override { + if (arena == nullptr) { + arena = arena_; + } + ListValue keys; + CEL_RETURN_IF_ERROR(value_.ListKeys( + google::protobuf::DescriptorPool::generated_pool(), + google::protobuf::MessageFactory::generated_factory(), arena, &keys)); + auto legacy_list = AsLegacyListValue(keys); + if (!legacy_list.has_value()) { + return absl::InternalError("failed to convert list keys to legacy list"); + } + return legacy_list->cel_list(); + } + + private: + NativeTypeId GetNativeTypeId() const override { + return NativeTypeId::For(); + } + + ParsedMapFieldValue value_; + google::protobuf::Arena* const arena_; +}; + +class LegacyParsedJsonMapValue final + : public CustomMapValueInterface, + public google::api::expr::runtime::CelMap { + public: + // `arena` is expected to be the same arena as the one that the object is + // allocated on. + explicit LegacyParsedJsonMapValue(ParsedJsonMapValue value, + google::protobuf::Arena* absl_nonnull arena) + : value_(std::move(value)), arena_(arena) { + ABSL_DCHECK(arena != nullptr); + } + + std::string DebugString() const override { return value_.DebugString(); } + + absl::Status SerializeTo( + const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, + google::protobuf::MessageFactory* absl_nonnull message_factory, + google::protobuf::io::ZeroCopyOutputStream* absl_nonnull output) const override { + return value_.SerializeTo(descriptor_pool, message_factory, output); + } + + absl::Status ConvertToJsonObject( + const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, + google::protobuf::MessageFactory* absl_nonnull message_factory, + google::protobuf::Message* absl_nonnull json) const override { + return value_.ConvertToJsonObject(descriptor_pool, message_factory, json); + } + + absl::Status Equal(const MapValue& other, + const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, + google::protobuf::MessageFactory* absl_nonnull message_factory, + google::protobuf::Arena* absl_nonnull arena, + Value* absl_nonnull result) const override { + return value_.Equal(other, descriptor_pool, message_factory, arena, result); + } + + bool IsZeroValue() const override { return value_.IsZeroValue(); } + + bool IsEmpty() const override { return value_.IsEmpty(); } + + size_t Size() const override { return value_.Size(); } + + absl::StatusOr Find( + const Value& key, + const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, + google::protobuf::MessageFactory* absl_nonnull message_factory, + google::protobuf::Arena* absl_nonnull arena, + Value* absl_nonnull result) const override { + if (!key.IsString()) { + return InvalidMapKeyType(cel::ValueKindToString(key.kind())); + } + CEL_ASSIGN_OR_RETURN( + auto found, + value_.Find(key, descriptor_pool, message_factory, arena, result)); + if (found && result->IsParsedMessage()) { + *result = ParsedMessageToLegacyStructValue(result->GetParsedMessage()); + } + return found; + } + + absl::StatusOr Has( + const Value& key, + const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, + google::protobuf::MessageFactory* absl_nonnull message_factory, + google::protobuf::Arena* absl_nonnull arena) const override { + if (!key.IsString()) { + return InvalidMapKeyType(cel::ValueKindToString(key.kind())); + } + Value result; + CEL_RETURN_IF_ERROR( + value_.Has(key, descriptor_pool, message_factory, arena, &result)); + if (result.IsBool()) { + return result.GetBool().NativeValue(); + } + if (result.IsError()) { + return result.GetError().NativeValue(); + } + return false; + } + + absl::Status ListKeys( + const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, + google::protobuf::MessageFactory* absl_nonnull message_factory, + google::protobuf::Arena* absl_nonnull arena, + ListValue* absl_nonnull result) const override { + return value_.ListKeys(descriptor_pool, message_factory, arena, result); + } + + absl::Status ForEach( + ForEachCallback callback, + const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, + google::protobuf::MessageFactory* absl_nonnull message_factory, + google::protobuf::Arena* absl_nonnull arena) const override { + return value_.ForEach(callback, descriptor_pool, message_factory, arena); + } + + absl::StatusOr NewIterator() const override { + return value_.NewIterator(); + } + + CustomMapValue Clone(google::protobuf::Arena* absl_nonnull arena) const override { + return CustomMapValue(google::protobuf::Arena::Create( + arena, value_.Clone(arena), arena), + arena); + } + + // CelMap implementation + int size() const override { return static_cast(value_.Size()); } + + bool empty() const override { return value_.IsEmpty(); } + + absl::optional operator[]( + google::api::expr::runtime::CelValue key) const override { + return Get(arena_, key); + } + + absl::optional Get( + google::protobuf::Arena* arena, + google::api::expr::runtime::CelValue key) const override { + if (arena == nullptr) { + arena = arena_; + } + if (auto status = + google::api::expr::runtime::CelValue::CheckMapKeyType(key); + !status.ok()) { + status.IgnoreError(); + return absl::nullopt; + } + Value modern_key; + if (ABSL_PREDICT_FALSE(!ModernValue(arena, key, modern_key).ok())) { + return absl::nullopt; + } + Value modern_val; + auto status_or_found = value_.Find( + modern_key, google::protobuf::DescriptorPool::generated_pool(), + google::protobuf::MessageFactory::generated_factory(), arena, &modern_val); + if (!status_or_found.ok() || !*status_or_found) { + return absl::nullopt; + } + return UnsafeLegacyValue(modern_val, /*stable=*/false, arena); + } + + absl::StatusOr Has( + const google::api::expr::runtime::CelValue& key) const override { + CEL_RETURN_IF_ERROR( + google::api::expr::runtime::CelValue::CheckMapKeyType(key)); + google::protobuf::Arena scratch_arena; + Value modern_key; + CEL_RETURN_IF_ERROR(ModernValue(&scratch_arena, key, modern_key)); + return Has(modern_key, google::protobuf::DescriptorPool::generated_pool(), + google::protobuf::MessageFactory::generated_factory(), &scratch_arena); + } + + absl::StatusOr ListKeys() + const override { + return ListKeys(arena_); + } + + absl::StatusOr ListKeys( + google::protobuf::Arena* arena) const override { + if (arena == nullptr) { + arena = arena_; + } + ListValue keys; + CEL_RETURN_IF_ERROR(value_.ListKeys( + google::protobuf::DescriptorPool::generated_pool(), + google::protobuf::MessageFactory::generated_factory(), arena, &keys)); + auto legacy_list = AsLegacyListValue(keys); + if (!legacy_list.has_value()) { + return absl::InternalError("failed to convert list keys to legacy list"); + } + return legacy_list->cel_list(); + } + + private: + NativeTypeId GetNativeTypeId() const override { + return NativeTypeId::For(); + } + + ParsedJsonMapValue value_; + google::protobuf::Arena* const arena_; +}; + +CustomMapValue WrapLegacyParsedMapField(ParsedMapFieldValue value, + google::protobuf::Arena* absl_nonnull arena) { + return CustomMapValue(google::protobuf::Arena::Create( + arena, std::move(value), arena), + arena); +} + +CustomMapValue WrapLegacyParsedJsonMap(ParsedJsonMapValue value, + google::protobuf::Arena* absl_nonnull arena) { + return CustomMapValue(google::protobuf::Arena::Create( + arena, std::move(value), arena), + arena); +} + absl::Status LegacyMapValue::Equal( const Value& other, const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, @@ -68,6 +515,18 @@ absl::optional AsLegacyMapValue(const Value& value) { static_cast( cel::internal::down_cast( custom_map_value->interface()))); + } else if (native_type_id == + NativeTypeId::For()) { + return LegacyMapValue( + static_cast( + cel::internal::down_cast( + custom_map_value->interface()))); + } else if (native_type_id == + NativeTypeId::For()) { + return LegacyMapValue( + static_cast( + cel::internal::down_cast( + custom_map_value->interface()))); } } return std::nullopt; diff --git a/common/values/legacy_map_value.h b/common/values/legacy_map_value.h index c83b7fc2f..53f39a8de 100644 --- a/common/values/legacy_map_value.h +++ b/common/values/legacy_map_value.h @@ -25,11 +25,12 @@ #include "absl/base/nullability.h" #include "absl/status/status.h" #include "absl/status/statusor.h" -#include "absl/strings/cord.h" #include "absl/strings/string_view.h" #include "absl/types/optional.h" #include "common/value_kind.h" #include "common/values/custom_map_value.h" +#include "common/values/parsed_json_map_value.h" +#include "common/values/parsed_map_field_value.h" #include "common/values/values.h" #include "google/protobuf/arena.h" #include "google/protobuf/descriptor.h" @@ -178,6 +179,15 @@ LegacyMapValue GetLegacyMapValue(const Value& value); absl::optional AsLegacyMapValue(const Value& value); +class LegacyParsedMapFieldMapValue; +class LegacyParsedJsonMapValue; + +CustomMapValue WrapLegacyParsedMapField(ParsedMapFieldValue value, + google::protobuf::Arena* absl_nonnull arena); + +CustomMapValue WrapLegacyParsedJsonMap(ParsedJsonMapValue value, + google::protobuf::Arena* absl_nonnull arena); + } // namespace common_internal } // namespace cel diff --git a/common/values/legacy_struct_value_test.cc b/common/values/legacy_struct_value_test.cc new file mode 100644 index 000000000..d5d9ac285 --- /dev/null +++ b/common/values/legacy_struct_value_test.cc @@ -0,0 +1,279 @@ +// 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. + +#include "common/values/legacy_struct_value.h" + +#include + +#include "google/protobuf/struct.pb.h" +#include "absl/status/status.h" +#include "absl/status/status_matchers.h" +#include "absl/strings/string_view.h" +#include "common/legacy_value.h" +#include "common/value.h" +#include "common/value_testing.h" +#include "common/values/legacy_list_value.h" +#include "common/values/legacy_map_value.h" +#include "eval/public/cel_value.h" +#include "eval/public/structs/proto_message_type_adapter.h" +#include "internal/testing.h" +#include "runtime/runtime_options.h" +#include "cel/expr/conformance/proto3/test_all_types.pb.h" + +namespace cel { +namespace { + +using ::absl_testing::IsOk; +using ::absl_testing::IsOkAndHolds; +using ::absl_testing::StatusIs; +using ::cel::expr::conformance::proto3::TestAllTypes; +using ::google::api::expr::runtime::CelValue; +using ::testing::NotNull; + +using LegacyStructValueTest = common_internal::ValueTest<>; + +TEST_F(LegacyStructValueTest, RepeatedFieldAccess) { + TestAllTypes message; + message.add_repeated_int32(10); + message.add_repeated_int32(20); + + common_internal::LegacyStructValue struct_value( + &message, &google::api::expr::runtime::GetGenericProtoTypeInfoInstance()); + + Value field_value; + ASSERT_THAT(struct_value.GetFieldByName( + "repeated_int32", ProtoWrapperTypeOptions::kUnsetProtoDefault, + descriptor_pool(), message_factory(), arena(), &field_value), + IsOk()); + + EXPECT_TRUE(field_value.IsList()); + auto list_value = field_value.GetList(); + EXPECT_THAT(list_value.Size(), IsOkAndHolds(2)); + + // Verify legacy CelList interface + auto legacy_list = common_internal::AsLegacyListValue(field_value); + ASSERT_TRUE(legacy_list.has_value()); + const auto* cel_list = legacy_list->cel_list(); + ASSERT_THAT(cel_list, NotNull()); + EXPECT_EQ(cel_list->size(), 2); + + CelValue elem0 = cel_list->Get(arena(), 0); + ASSERT_TRUE(elem0.IsInt64()); + EXPECT_EQ(elem0.Int64OrDie(), 10); + + CelValue elem1 = cel_list->Get(arena(), 1); + ASSERT_TRUE(elem1.IsInt64()); + EXPECT_EQ(elem1.Int64OrDie(), 20); +} + +TEST_F(LegacyStructValueTest, RepeatedMessageFieldAccess) { + TestAllTypes message; + auto* elem0 = message.add_repeated_nested_message(); + elem0->set_bb(42); + + common_internal::LegacyStructValue struct_value( + &message, &google::api::expr::runtime::GetGenericProtoTypeInfoInstance()); + + Value field_value; + ASSERT_THAT(struct_value.GetFieldByName( + "repeated_nested_message", + ProtoWrapperTypeOptions::kUnsetProtoDefault, + descriptor_pool(), message_factory(), arena(), &field_value), + IsOk()); + + EXPECT_TRUE(field_value.IsList()); + auto list_value = field_value.GetList(); + EXPECT_THAT(list_value.Size(), IsOkAndHolds(1)); + + Value first_elem; + ASSERT_THAT(list_value.Get(0, descriptor_pool(), message_factory(), arena(), + &first_elem), + IsOk()); + EXPECT_TRUE(common_internal::IsLegacyStructValue(first_elem)); + + // Verify via CelList + auto legacy_list = common_internal::AsLegacyListValue(field_value); + ASSERT_TRUE(legacy_list.has_value()); + const auto* cel_list = legacy_list->cel_list(); + ASSERT_THAT(cel_list, NotNull()); + + CelValue cel_elem = cel_list->Get(arena(), 0); + ASSERT_TRUE(cel_elem.IsMessage()); + EXPECT_EQ(cel_elem.MessageOrDie()->GetDescriptor(), elem0->GetDescriptor()); + EXPECT_EQ( + static_cast(cel_elem.MessageOrDie()) + ->bb(), + 42); +} + +TEST_F(LegacyStructValueTest, MapFieldAccess) { + TestAllTypes message; + (*message.mutable_map_string_string())["hello"] = "world"; + + common_internal::LegacyStructValue struct_value( + &message, &google::api::expr::runtime::GetGenericProtoTypeInfoInstance()); + + Value field_value; + ASSERT_THAT( + struct_value.GetFieldByName( + "map_string_string", ProtoWrapperTypeOptions::kUnsetProtoDefault, + descriptor_pool(), message_factory(), arena(), &field_value), + IsOk()); + + EXPECT_TRUE(field_value.IsMap()); + auto map_value = field_value.GetMap(); + EXPECT_THAT(map_value.Size(), IsOkAndHolds(1)); + + // Verify legacy CelMap interface + auto legacy_map = common_internal::AsLegacyMapValue(field_value); + ASSERT_TRUE(legacy_map.has_value()); + const auto* cel_map = legacy_map->cel_map(); + ASSERT_THAT(cel_map, NotNull()); + EXPECT_EQ(cel_map->size(), 1); + + std::string key_str = "hello"; + CelValue cel_key = CelValue::CreateString(&key_str); + auto cel_result = cel_map->Get(arena(), cel_key); + ASSERT_TRUE(cel_result.has_value()); + ASSERT_TRUE(cel_result->IsString()); + EXPECT_EQ(cel_result->StringOrDie().value(), "world"); + + auto has_res = cel_map->Has(cel_key); + ASSERT_THAT(has_res, IsOk()); + EXPECT_TRUE(*has_res); +} + +TEST_F(LegacyStructValueTest, MapFieldKeyTypeValidation) { + TestAllTypes message; + (*message.mutable_map_int32_int32())[1] = 2; + + common_internal::LegacyStructValue struct_value( + &message, &google::api::expr::runtime::GetGenericProtoTypeInfoInstance()); + + Value field_value; + ASSERT_THAT( + struct_value.GetFieldByName( + "map_int32_int32", ProtoWrapperTypeOptions::kUnsetProtoDefault, + descriptor_pool(), message_factory(), arena(), &field_value), + IsOk()); + + auto legacy_map = common_internal::AsLegacyMapValue(field_value); + ASSERT_TRUE(legacy_map.has_value()); + const auto* cel_map = legacy_map->cel_map(); + ASSERT_THAT(cel_map, NotNull()); + + // Valid key + CelValue int_key = CelValue::CreateInt64(1); + auto has_res = cel_map->Has(int_key); + ASSERT_THAT(has_res, IsOk()); + EXPECT_TRUE(*has_res); + + // Invalid key type (string key on int32 map) + std::string str_key_val = "1"; + CelValue str_key = CelValue::CreateString(&str_key_val); + auto invalid_has_res = cel_map->Has(str_key); + EXPECT_THAT(invalid_has_res, StatusIs(absl::StatusCode::kInvalidArgument)); +} + +TEST_F(LegacyStructValueTest, JsonStructAccess) { + TestAllTypes message; + auto* struct_field = message.mutable_single_struct(); + (*struct_field->mutable_fields())["key"].set_string_value("value"); + + common_internal::LegacyStructValue struct_value( + &message, &google::api::expr::runtime::GetGenericProtoTypeInfoInstance()); + + Value field_value; + ASSERT_THAT(struct_value.GetFieldByName( + "single_struct", ProtoWrapperTypeOptions::kUnsetProtoDefault, + descriptor_pool(), message_factory(), arena(), &field_value), + IsOk()); + + EXPECT_TRUE(field_value.IsMap()); + auto map_value = field_value.GetMap(); + EXPECT_THAT(map_value.Size(), IsOkAndHolds(1)); + + // Verify legacy CelMap interface + auto legacy_map = common_internal::AsLegacyMapValue(field_value); + ASSERT_TRUE(legacy_map.has_value()); + const auto* cel_map = legacy_map->cel_map(); + ASSERT_THAT(cel_map, NotNull()); + EXPECT_EQ(cel_map->size(), 1); + + std::string key_str = "key"; + CelValue cel_key = CelValue::CreateString(&key_str); + auto cel_result = cel_map->Get(arena(), cel_key); + ASSERT_TRUE(cel_result.has_value()); + ASSERT_TRUE(cel_result->IsString()); + EXPECT_EQ(cel_result->StringOrDie().value(), "value"); +} + +TEST_F(LegacyStructValueTest, JsonListAccess) { + TestAllTypes message; + auto* list_field = message.mutable_single_value()->mutable_list_value(); + list_field->add_values()->set_string_value("item"); + + common_internal::LegacyStructValue struct_value( + &message, &google::api::expr::runtime::GetGenericProtoTypeInfoInstance()); + + Value field_value; + ASSERT_THAT(struct_value.GetFieldByName( + "single_value", ProtoWrapperTypeOptions::kUnsetProtoDefault, + descriptor_pool(), message_factory(), arena(), &field_value), + IsOk()); + + EXPECT_TRUE(field_value.IsList()); + auto list_value = field_value.GetList(); + EXPECT_THAT(list_value.Size(), IsOkAndHolds(1)); + + // Verify legacy CelList interface + auto legacy_list = common_internal::AsLegacyListValue(field_value); + ASSERT_TRUE(legacy_list.has_value()); + const auto* cel_list = legacy_list->cel_list(); + ASSERT_THAT(cel_list, NotNull()); + EXPECT_EQ(cel_list->size(), 1); + + CelValue elem = cel_list->Get(arena(), 0); + ASSERT_TRUE(elem.IsString()); + EXPECT_EQ(elem.StringOrDie().value(), "item"); +} + +TEST_F(LegacyStructValueTest, SingularMessageAccess) { + TestAllTypes message; + message.mutable_single_nested_message()->set_bb(100); + + common_internal::LegacyStructValue struct_value( + &message, &google::api::expr::runtime::GetGenericProtoTypeInfoInstance()); + + Value field_value; + ASSERT_THAT( + struct_value.GetFieldByName( + "single_nested_message", ProtoWrapperTypeOptions::kUnsetProtoDefault, + descriptor_pool(), message_factory(), arena(), &field_value), + IsOk()); + + EXPECT_TRUE(common_internal::IsLegacyStructValue(field_value)); + auto nested_struct = common_internal::GetLegacyStructValue(field_value); + + Value bb_value; + ASSERT_THAT(nested_struct.GetFieldByName( + "bb", ProtoWrapperTypeOptions::kUnsetProtoDefault, + descriptor_pool(), message_factory(), arena(), &bb_value), + IsOk()); + EXPECT_TRUE(bb_value.IsInt()); + EXPECT_EQ(bb_value.GetInt().NativeValue(), 100); +} + +} // namespace +} // namespace cel diff --git a/common/values/parsed_message_value.cc b/common/values/parsed_message_value.cc index b0e881998..03d2d461d 100644 --- a/common/values/parsed_message_value.cc +++ b/common/values/parsed_message_value.cc @@ -382,7 +382,7 @@ absl::Status ParsedMessageValue::GetField( ABSL_DCHECK(arena != nullptr); ABSL_DCHECK(result != nullptr); - if (arena_ == nullptr) { + if (is_unsafe()) { *result = Value::WrapFieldUnsafe(unboxing_options, value_, field, descriptor_pool, message_factory, arena); } else { diff --git a/common/values/parsed_message_value.h b/common/values/parsed_message_value.h index 2e356d3e8..3cad912e7 100644 --- a/common/values/parsed_message_value.h +++ b/common/values/parsed_message_value.h @@ -192,7 +192,7 @@ class ParsedMessageValue final explicit ParsedMessageValue( const google::protobuf::Message* absl_nonnull value ABSL_ATTRIBUTE_LIFETIME_BOUND) - : value_(value), arena_(value->GetArena()) { + : value_(value), arena_(nullptr) { ABSL_DCHECK(value != nullptr); ABSL_DCHECK(!value_ || !IsWellKnownMessageType(value_->GetDescriptor())) << value_->GetTypeName() << " is a well known type"; @@ -210,9 +210,14 @@ class ParsedMessageValue final return absl::OkStatus(); } + bool is_unsafe() const { return arena_ == nullptr; } + const google::protobuf::Message* absl_nonnull value_; - // Arena that is attributed as owning the value. May be null to indicate that - // the value is managed externally. + + // The arena attributed as Owning this value. Null if the value is created by + // UnsafeParsedMessageValue() or derived from such a value. This is used to + // identify externally managed messages and propagating the unsafe field + // access behavior. google::protobuf::Arena* absl_nullable arena_; }; diff --git a/eval/eval/BUILD b/eval/eval/BUILD index 507ff6f0e..329ee71f4 100644 --- a/eval/eval/BUILD +++ b/eval/eval/BUILD @@ -828,6 +828,7 @@ cc_test( "@com_google_absl//absl/status:status_matchers", "@com_google_absl//absl/status:statusor", "@com_google_absl//absl/strings", + "@com_google_absl//absl/types:span", "@com_google_cel_spec//proto/cel/expr:syntax_cc_proto", "@com_google_cel_spec//proto/cel/expr/conformance/proto3:test_all_types_cc_proto", "@com_google_protobuf//:protobuf", diff --git a/eval/eval/select_step.cc b/eval/eval/select_step.cc index 0b31c3c13..1a65d93a1 100644 --- a/eval/eval/select_step.cc +++ b/eval/eval/select_step.cc @@ -2,6 +2,7 @@ #include #include +#include #include #include @@ -219,7 +220,7 @@ absl::Status SelectStep::Evaluate(ExecutionFrame* frame) const { optional_arg = arg.GetOptional(); } - if (!(optional_arg || arg->Is() || arg->Is())) { + if (!(optional_arg || arg.IsMap() || arg.IsStruct())) { frame->value_stack().PopAndPush(cel::ErrorValue(InvalidSelectTargetError()), std::move(result_trail)); return absl::OkStatus(); @@ -467,7 +468,8 @@ absl::Status ProtoSelectStep::EvaluateLegacyMessageGetField( return absl::OkStatus(); } return cel::interop_internal::WrapLegacyMessageField( - legacy_message, field_descriptor_, unboxing_option_, frame->arena(), + legacy_message, field_descriptor_, unboxing_option_, + frame->descriptor_pool(), frame->message_factory(), frame->arena(), &frame->value_stack().Peek()); } diff --git a/eval/eval/select_step_test.cc b/eval/eval/select_step_test.cc index 494def40f..2ab09fafa 100644 --- a/eval/eval/select_step_test.cc +++ b/eval/eval/select_step_test.cc @@ -13,6 +13,7 @@ #include "absl/status/status_matchers.h" #include "absl/status/statusor.h" #include "absl/strings/string_view.h" +#include "absl/types/span.h" #include "base/attribute.h" #include "base/attribute_set.h" #include "base/type_provider.h" @@ -92,7 +93,6 @@ class SelectStepTest : public testing::Test { // Helper method. Creates simple pipeline containing Select step and runs it. absl::StatusOr RunExpression(const CelValue target, absl::string_view field, bool test, - absl::string_view unknown_path, RunExpressionOptions options) { ExecutionPath path; @@ -118,6 +118,8 @@ class SelectStepTest : public testing::Test { runtime_options.unknown_processing = cel::UnknownProcessingOptions::kAttributeOnly; } + // Force the creation of a message factory at the env level. + static_cast(env_->MutableMessageFactory()); CelExpressionFlatImpl cel_expr( env_, FlatExpression(std::move(path), /*comprehension_slot_count=*/0, env_->type_registry.GetComposedTypeProvider(), @@ -132,35 +134,20 @@ class SelectStepTest : public testing::Test { absl::string_view field, bool test, RunExpressionOptions options) { return RunExpression(CelProtoWrapper::CreateMessage(message, &arena_), - field, test, "", options); + field, test, options); } absl::StatusOr RunExpression(const TestMessage* message, absl::string_view field, bool test, - absl::string_view unknown_path, RunExpressionOptions options) { return RunExpression(CelProtoWrapper::CreateMessage(message, &arena_), - field, test, unknown_path, options); - } - - absl::StatusOr RunExpression(const TestMessage* message, - absl::string_view field, bool test, - RunExpressionOptions options) { - return RunExpression(message, field, test, "", options); - } - - absl::StatusOr RunExpression(const CelMap* map_value, - absl::string_view field, bool test, - absl::string_view unknown_path, - RunExpressionOptions options) { - return RunExpression(CelValue::CreateMap(map_value), field, test, - unknown_path, options); + field, test, options); } absl::StatusOr RunExpression(const CelMap* map_value, absl::string_view field, bool test, RunExpressionOptions options) { - return RunExpression(map_value, field, test, "", options); + return RunExpression(CelValue::CreateMap(map_value), field, test, options); } protected: @@ -189,8 +176,7 @@ TEST_P(SelectStepConformanceTest, SelectTargetNotStructOrMap) { ASSERT_OK_AND_ASSIGN( CelValue result, RunExpression(CelValue::CreateStringView("some_value"), "some_field", - /*test=*/false, - /*unknown_path=*/"", options)); + /*test=*/false, options)); ASSERT_TRUE(result.IsError()); EXPECT_THAT(*result.ErrorOrDie(), @@ -326,7 +312,7 @@ TEST_F(SelectStepTest, MapPresenseIsErrorTest) { CelProtoWrapper::CreateMessage(&message, &arena_)); ASSERT_OK_AND_ASSIGN(CelValue result, cel_expr.Evaluate(activation, &arena_)); - EXPECT_TRUE(result.IsError()); + ASSERT_TRUE(result.IsError()); EXPECT_EQ(result.ErrorOrDie()->code(), absl::StatusCode::kInvalidArgument); } @@ -547,17 +533,37 @@ TEST_P(SelectStepConformanceTest, GlobalExtensionsMessageTest) { } TEST_P(SelectStepConformanceTest, GlobalExtensionsMessageUnsetTest) { - TestExtensions exts; + // Implementation details: + // The test environment is a dynamic descriptor pool with the same definition + // as the linked proto. + // Use a dynamic message with the expected factory and pool. Otherwise, + // we can end up in a state where we're comparing messages with different + // prototypes. + const google::protobuf::Descriptor* descriptor = + env_->descriptor_pool->FindMessageTypeByName( + TestExtensions::descriptor()->full_name()); + ASSERT_NE(descriptor, nullptr); + const google::protobuf::FieldDescriptor* field = + env_->descriptor_pool->FindExtensionByPrintableName( + descriptor, "google.api.expr.runtime.nested_ext"); + ASSERT_NE(field, nullptr); + ASSERT_TRUE(field->containing_type() == descriptor); + const auto* prototype = + env_->MutableMessageFactory()->GetPrototype(descriptor); + ASSERT_NE(prototype, nullptr); + const auto* msg_default = &prototype->GetReflection()->GetMessage( + *prototype, field, env_->MutableMessageFactory()); + RunExpressionOptions options; options.enable_unknowns = GetParam(); ASSERT_OK_AND_ASSIGN( CelValue result, - RunExpression(&exts, "google.api.expr.runtime.nested_ext", false, - options)); + RunExpression(CelProtoWrapper::CreateMessage(prototype, &arena_), + "google.api.expr.runtime.nested_ext", false, options)); ASSERT_TRUE(result.IsMessage()); - EXPECT_THAT(result.MessageOrDie(), Eq(&TestExtensions::default_instance())); + EXPECT_THAT(result.MessageOrDie(), Eq(msg_default)); } TEST_P(SelectStepConformanceTest, GlobalExtensionsWrapperTest) { @@ -653,18 +659,15 @@ TEST_P(SelectStepConformanceTest, NullMessageAccessor) { CelValue value = CelValue::CreateMessageWrapper( CelValue::MessageWrapper(&message, TrivialTypeInfo::GetInstance())); - ASSERT_OK_AND_ASSIGN(CelValue result, - RunExpression(value, "message_value", - /*test=*/false, - /*unknown_path=*/"", options)); + ASSERT_OK_AND_ASSIGN(CelValue result, RunExpression(value, "message_value", + /*test=*/false, options)); ASSERT_TRUE(result.IsError()); EXPECT_THAT(*result.ErrorOrDie(), StatusIs(absl::StatusCode::kNotFound)); // same for has ASSERT_OK_AND_ASSIGN(result, RunExpression(value, "message_value", - /*test=*/true, - /*unknown_path=*/"", options)); + /*test=*/true, options)); ASSERT_TRUE(result.IsError()); EXPECT_THAT(*result.ErrorOrDie(), StatusIs(absl::StatusCode::kNotFound)); diff --git a/eval/public/structs/proto_message_type_adapter_test.cc b/eval/public/structs/proto_message_type_adapter_test.cc index 529052025..b44c17062 100644 --- a/eval/public/structs/proto_message_type_adapter_test.cc +++ b/eval/public/structs/proto_message_type_adapter_test.cc @@ -1188,10 +1188,12 @@ TEST(ProtoMesssageTypeAdapter, InteropFieldAccess) { message.GetDescriptor()->FindFieldByName("string_value"); ASSERT_NE(field, nullptr); cel::Value field_value; - ASSERT_THAT(cel::interop_internal::WrapLegacyMessageField( - &message, field, ProtoWrapperTypeOptions::kUnsetNull, &arena, - &field_value), - IsOk()); + ASSERT_THAT( + cel::interop_internal::WrapLegacyMessageField( + &message, field, ProtoWrapperTypeOptions::kUnsetNull, + google::protobuf::DescriptorPool::generated_pool(), + google::protobuf::MessageFactory::generated_factory(), &arena, &field_value), + IsOk()); EXPECT_THAT(field_value, cel::test::StringValueIs("hello")); }