diff --git a/eval/compiler/BUILD b/eval/compiler/BUILD index f57a59ebf..56d2b0e17 100644 --- a/eval/compiler/BUILD +++ b/eval/compiler/BUILD @@ -31,8 +31,10 @@ cc_library( ":resolver", "//base:ast", "//base:data", + "//common:ast", "//common:expr", "//common:type", + "//common:type_spec_resolver", "//common:value", "//eval/eval:evaluator_core", "//eval/eval:expression_step_logic", @@ -147,21 +149,11 @@ cc_test( ":cel_expression_builder_flat_impl", ":constant_folding", ":flat_expr_builder", - ":flat_expr_builder_extensions", ":qualified_reference_resolver", "//base:builtins", - "//checker:validation_result", - "//common:ast", - "//common:decl", - "//common:expr", "//common:function_descriptor", "//common:kind", - "//common:type", "//common:value", - "//compiler", - "//compiler:compiler_factory", - "//compiler:standard_library", - "//eval/eval:evaluator_core", "//eval/eval:expression_step_logic", "//eval/public:activation", "//eval/public:ast_rewrite", diff --git a/eval/compiler/flat_expr_builder.cc b/eval/compiler/flat_expr_builder.cc index ec15a759d..2eb3d9515 100644 --- a/eval/compiler/flat_expr_builder.cc +++ b/eval/compiler/flat_expr_builder.cc @@ -1860,13 +1860,7 @@ void FlatExprVisitor::MaybeResolveType(const cel::Expr& expr) { if (it == type_map_.end()) { return; } - absl::StatusOr type = cel::ConvertTypeSpecToType( - it->second, extension_context_.type_reflector(), - extension_context_.MutableArena()); - if (!type.ok()) { - return; - } - extension_context_.SetResolvedType(expr.id(), *type); + extension_context_.TryResolveType(expr.id(), it->second); } void LogicalCondVisitor::PreVisit(const cel::Expr* expr) { diff --git a/eval/compiler/flat_expr_builder_extensions.cc b/eval/compiler/flat_expr_builder_extensions.cc index 3f308125a..ac62f5fd7 100644 --- a/eval/compiler/flat_expr_builder_extensions.cc +++ b/eval/compiler/flat_expr_builder_extensions.cc @@ -14,8 +14,10 @@ #include "eval/compiler/flat_expr_builder_extensions.h" #include +#include #include #include +#include #include #include @@ -27,7 +29,10 @@ #include "absl/status/status.h" #include "absl/status/statusor.h" #include "absl/types/variant.h" +#include "common/ast.h" #include "common/expr.h" +#include "common/type.h" +#include "common/type_spec_resolver.h" #include "eval/eval/evaluator_core.h" namespace google::api::expr::runtime { @@ -350,6 +355,21 @@ absl::Status PlannerContext::ReplaceSubplan(const cel::Expr& node, return absl::OkStatus(); } +std::optional PlannerContext::TryResolveType( + int64_t expr_id, const cel::TypeSpec& ast_type) { + auto it = resolved_types_.find(expr_id); + if (it != resolved_types_.end()) { + return it->second; + } + absl::StatusOr type = + cel::ConvertTypeSpecToType(ast_type, type_reflector_, MutableArena()); + if (!type.ok()) { + return std::nullopt; + } + resolved_types_.insert(it, {expr_id, *type}); + return *type; +} + void ProgramBuilder::Reset() { root_ = nullptr; current_ = nullptr; diff --git a/eval/compiler/flat_expr_builder_extensions.h b/eval/compiler/flat_expr_builder_extensions.h index d6d3abb17..fb7a6d798 100644 --- a/eval/compiler/flat_expr_builder_extensions.h +++ b/eval/compiler/flat_expr_builder_extensions.h @@ -381,6 +381,9 @@ class PlannerContext { return std::nullopt; } + std::optional TryResolveType(int64_t expr_id, + const cel::TypeSpec& ast_type); + void SetResolvedType(int64_t expr_id, cel::Type type) { resolved_types_[expr_id] = std::move(type); } diff --git a/eval/compiler/flat_expr_builder_test.cc b/eval/compiler/flat_expr_builder_test.cc index da83692d5..a0781b8d0 100644 --- a/eval/compiler/flat_expr_builder_test.cc +++ b/eval/compiler/flat_expr_builder_test.cc @@ -18,7 +18,6 @@ #include #include #include -#include #include #include #include @@ -36,22 +35,12 @@ #include "absl/time/time.h" #include "absl/types/span.h" #include "base/builtins.h" -#include "checker/validation_result.h" -#include "common/ast.h" -#include "common/decl.h" -#include "common/expr.h" #include "common/function_descriptor.h" #include "common/kind.h" -#include "common/type.h" #include "common/value.h" -#include "compiler/compiler.h" -#include "compiler/compiler_factory.h" -#include "compiler/standard_library.h" #include "eval/compiler/cel_expression_builder_flat_impl.h" #include "eval/compiler/constant_folding.h" -#include "eval/compiler/flat_expr_builder_extensions.h" #include "eval/compiler/qualified_reference_resolver.h" -#include "eval/eval/evaluator_core.h" #include "eval/eval/expression_step_logic.h" #include "eval/public/activation.h" #include "eval/public/ast_rewrite.h" @@ -3417,22 +3406,33 @@ TEST(FlatExprBuilderTest, ListAppendComprehensionWithTargetFailsPlanning) { } TEST(FlatExprBuilderTest, ProgramOptimizerResolvesTypesWhenRequested) { - std::shared_ptr env = - NewTestingRuntimeEnv(); - ASSERT_OK_AND_ASSIGN(std::unique_ptr compiler_builder, - cel::NewCompilerBuilder(env->descriptor_pool)); - ASSERT_THAT(compiler_builder->AddLibrary(cel::StandardCompilerLibrary()), - IsOk()); - ASSERT_THAT(compiler_builder->GetCheckerBuilder().AddVariable( - cel::MakeVariableDecl("x", cel::IntType())), - IsOk()); - ASSERT_OK_AND_ASSIGN(std::unique_ptr compiler, - compiler_builder->Build()); - ASSERT_OK_AND_ASSIGN(cel::ValidationResult validation_result, - compiler->Compile("x == 1")); - ASSERT_TRUE(validation_result.IsValid()); - ASSERT_OK_AND_ASSIGN(std::unique_ptr ast, - validation_result.ReleaseAst()); + CheckedExpr expr; + ASSERT_TRUE(google::protobuf::TextFormat::ParseFromString( + R"pb( + type_map { + key: 1 + value { primitive: BOOL } + } + type_map { + key: 2 + value { primitive: INT64 } + } + expr { + id: 1 + call_expr { + function: "_==_" + args { + id: 2 + ident_expr { name: "x" } + } + args { + id: 3 + const_expr { int64_value: 1 } + } + } + } + )pb", + &expr)); class TypeRecordingOptimizer : public ProgramOptimizer { public: @@ -3467,7 +3467,7 @@ TEST(FlatExprBuilderTest, ProgramOptimizerResolvesTypesWhenRequested) { cel::RuntimeOptions options; options.enable_typed_field_access = false; - CelExpressionBuilderFlatImpl builder(env, options); + CelExpressionBuilderFlatImpl builder(NewTestingRuntimeEnv(), options); ASSERT_THAT(RegisterBuiltinFunctions(builder.GetRegistry()), IsOk()); absl::flat_hash_map pre_visit_types; @@ -3480,17 +3480,14 @@ TEST(FlatExprBuilderTest, ProgramOptimizerResolvesTypesWhenRequested) { post_visit_types); }); - ASSERT_OK_AND_ASSIGN(FlatExpression flat_expr, - builder.flat_expr_builder().CreateExpressionImpl( - std::move(ast), /*issues=*/nullptr)); + ASSERT_OK_AND_ASSIGN(std::unique_ptr cel_expr, + builder.CreateExpression(&expr)); EXPECT_THAT(pre_visit_types, - testing::UnorderedElementsAre(testing::Pair(1, cel::IntType()), - testing::Pair(2, cel::BoolType()), - testing::Pair(3, cel::IntType()))); + testing::UnorderedElementsAre(testing::Pair(1, cel::BoolType()), + testing::Pair(2, cel::IntType()))); EXPECT_THAT(post_visit_types, - testing::UnorderedElementsAre(testing::Pair(1, cel::IntType()), - testing::Pair(2, cel::BoolType()), - testing::Pair(3, cel::IntType()))); + testing::UnorderedElementsAre(testing::Pair(1, cel::BoolType()), + testing::Pair(2, cel::IntType()))); } } // namespace diff --git a/extensions/BUILD b/extensions/BUILD index 93b08e6da..d75332632 100644 --- a/extensions/BUILD +++ b/extensions/BUILD @@ -329,6 +329,7 @@ cc_library( "//common:expr", "//common:function_descriptor", "//common:kind", + "//common:legacy_value", "//common:native_type", "//common:type", "//common:value", @@ -337,6 +338,7 @@ cc_library( "//eval/eval:attribute_trail", "//eval/eval:evaluator_core", "//eval/eval:expression_step_base", + "//extensions/protobuf/internal:map_reflection", "//internal:casts", "//internal:number", "//internal:status_macros", @@ -346,6 +348,7 @@ cc_library( "//runtime/internal:runtime_friend_access", "//runtime/internal:runtime_impl", "@com_google_absl//absl/algorithm:container", + "@com_google_absl//absl/base:core_headers", "@com_google_absl//absl/base:nullability", "@com_google_absl//absl/container:flat_hash_map", "@com_google_absl//absl/functional:overload", @@ -394,15 +397,19 @@ cc_test( "//internal:status_macros", "//internal:testing", "//parser", + "//runtime", "//runtime:activation", "//runtime:function_adapter", "//runtime:function_registry", + "//runtime:runtime_builder", "//runtime:runtime_issue", "//runtime:runtime_options", + "//runtime:standard_runtime_builder_factory", "//runtime:type_registry", "//runtime/internal:issue_collector", "//runtime/internal:runtime_env", "//runtime/internal:runtime_env_testing", + "//testutil:test_external_extensions_descriptor_set", "@com_google_absl//absl/base:nullability", "@com_google_absl//absl/container:flat_hash_map", "@com_google_absl//absl/log:absl_check", diff --git a/extensions/select_optimization.cc b/extensions/select_optimization.cc index 7747eeafe..ab9affe08 100644 --- a/extensions/select_optimization.cc +++ b/extensions/select_optimization.cc @@ -17,6 +17,7 @@ #include #include #include +#include #include #include #include @@ -26,12 +27,14 @@ #include "absl/algorithm/container.h" #include "absl/base/nullability.h" +#include "absl/base/optimization.h" #include "absl/container/flat_hash_map.h" #include "absl/functional/overload.h" #include "absl/log/absl_check.h" #include "absl/status/status.h" #include "absl/status/statusor.h" #include "absl/strings/match.h" +#include "absl/strings/str_cat.h" #include "absl/strings/string_view.h" #include "absl/types/span.h" #include "absl/types/variant.h" @@ -43,6 +46,7 @@ #include "common/expr.h" #include "common/function_descriptor.h" #include "common/kind.h" +#include "common/legacy_value.h" #include "common/native_type.h" #include "common/type.h" #include "common/value.h" @@ -51,6 +55,7 @@ #include "eval/eval/attribute_trail.h" #include "eval/eval/evaluator_core.h" #include "eval/eval/expression_step_base.h" +#include "extensions/protobuf/internal/map_reflection.h" #include "internal/casts.h" #include "internal/number.h" #include "internal/status_macros.h" @@ -61,7 +66,9 @@ #include "runtime/runtime_options.h" #include "google/protobuf/arena.h" #include "google/protobuf/descriptor.h" +#include "google/protobuf/map_field.h" #include "google/protobuf/message.h" +#include "google/protobuf/reflection.h" namespace cel::extensions { namespace { @@ -153,7 +160,7 @@ Expr MakeSelectPathExpr( // Returns a single select operation based on the inferred type of the operand // and the field name. If the operand type doesn't define the field, returns // nullopt. -std::optional GetSelectInstruction( +std::optional MakeFieldStepAst( const StructType& runtime_type, PlannerContext& planner_context, absl::string_view field_name) { auto field_or = planner_context.type_reflector() @@ -165,6 +172,30 @@ std::optional GetSelectInstruction( return std::nullopt; } +// Returns a qualifier instruction derived from a unoptimized ast. +absl::StatusOr MakeIndexStepAst( + const Constant& constant) { + if (constant.has_int_value()) { + return QualifierInstruction(constant.int_value()); + } else if (constant.has_uint_value()) { + return QualifierInstruction(constant.uint_value()); + } else if (constant.has_bool_value()) { + return QualifierInstruction(constant.bool_value()); + } else if (constant.has_string_value()) { + return QualifierInstruction(constant.string_value()); + } else if (constant.has_double_value()) { + cel::internal::Number number(constant.double_value()); + if (number.LosslessConvertibleToInt()) { + return QualifierInstruction(number.AsInt()); + } else if (number.LosslessConvertibleToUint()) { + return QualifierInstruction(number.AsUint()); + } + } + + return absl::InvalidArgumentError("invalid index constant for cel.attribute"); +} + +// Derives select step info from the optimized AST. absl::StatusOr SelectQualifierFromList(const ListExpr& list) { // Type codes may follow [number, name]; they're unused here. if (list.elements().size() < 2 || list.elements().size() > 4) { @@ -190,29 +221,7 @@ absl::StatusOr SelectQualifierFromList(const ListExpr& list) { field_name.const_expr().string_value()}; } -// Returns a qualifier instruction derived from a unoptimized ast. -absl::StatusOr SelectInstructionFromConstant( - const Constant& constant) { - if (constant.has_int_value()) { - return QualifierInstruction(constant.int_value()); - } else if (constant.has_uint_value()) { - return QualifierInstruction(constant.uint_value()); - } else if (constant.has_bool_value()) { - return QualifierInstruction(constant.bool_value()); - } else if (constant.has_string_value()) { - return QualifierInstruction(constant.string_value()); - } else if (constant.has_double_value()) { - cel::internal::Number number(constant.double_value()); - if (number.LosslessConvertibleToInt()) { - return QualifierInstruction(number.AsInt()); - } else if (number.LosslessConvertibleToUint()) { - return QualifierInstruction(number.AsUint()); - } - } - - return absl::InvalidArgumentError("invalid index constant for cel.attribute"); -} - +// Derives index step info from the optimized AST. absl::StatusOr SelectQualifierFromConstant( const Constant& constant) { if (constant.has_int_value()) { @@ -411,20 +420,20 @@ class RewriterImpl : public AstRewriterBase { const std::string& field_name = select.field(); // Select optimization can generalize to lists and maps, but for now only // support message traversal. - const TypeSpec checker_type = ast_.GetTypeOrDyn(operand.id()); - + const TypeSpec ast_type = ast_.GetTypeOrDyn(operand.id()); std::optional rt_type = - (checker_type.has_message_type()) - ? GetRuntimeType(checker_type.message_type().type()) - : std::nullopt; - if (rt_type.has_value() && (*rt_type).Is()) { + planner_context_.TryResolveType(operand.id(), ast_type); + if (!rt_type.has_value()) { + return; + } + if (rt_type->IsStruct()) { const StructType& runtime_type = rt_type->GetStruct(); std::optional field_or = - GetSelectInstruction(runtime_type, planner_context_, field_name); + MakeFieldStepAst(runtime_type, planner_context_, field_name); if (field_or.has_value()) { candidates_[&expr] = std::move(field_or).value(); } - } else if (checker_type.has_map_type()) { + } else if (rt_type->IsMap()) { candidates_[&expr] = QualifierInstruction(field_name); } // else @@ -439,8 +448,7 @@ class RewriterImpl : public AstRewriterBase { const auto& qualifier_expr = call.args()[1]; if (qualifier_expr.has_const_expr()) { - auto qualifier_or = - SelectInstructionFromConstant(qualifier_expr.const_expr()); + auto qualifier_or = MakeIndexStepAst(qualifier_expr.const_expr()); if (!qualifier_or.ok()) { // TODO(uncreated-issue/54): should warn, but by default warnings fail overall // program planning. @@ -548,11 +556,6 @@ class RewriterImpl : public AstRewriterBase { return candidates_.find(operand) != candidates_.end(); } - std::optional GetRuntimeType(absl::string_view type_name) { - return planner_context_.type_reflector().FindType(type_name).value_or( - std::nullopt); - } - void SetProgressStatus(const absl::Status& status) { if (progress_status_.ok() && !status.ok()) { progress_status_ = status; @@ -568,12 +571,101 @@ class RewriterImpl : public AstRewriterBase { absl::Status progress_status_; }; +struct FieldSelectStep { + const google::protobuf::Descriptor* absl_nonnull containing_type; + const google::protobuf::FieldDescriptor* absl_nonnull field; +}; + +struct MapSelectStep { + const google::protobuf::FieldDescriptor* absl_nonnull map_field; + const google::protobuf::FieldDescriptor* absl_nonnull key_field; + const google::protobuf::FieldDescriptor* absl_nonnull value_field; + // Store the error to propagate on access at runtime. + // The map key might alias string data owned by the SelectOptimizationImpl + // object. + absl::StatusOr key; +}; + +struct ListSelectStep { + const google::protobuf::FieldDescriptor* absl_nonnull repeated_field; +}; + +using TypedSelectStep = + std::variant; + +absl::StatusOr CreateProtoMapKey( + const google::protobuf::FieldDescriptor* absl_nonnull key_desc, + const cel::AttributeQualifier& key) { + google::protobuf::MapKey proto_key; + switch (key_desc->cpp_type()) { + case google::protobuf::FieldDescriptor::CPPTYPE_BOOL: + if (key.kind() != cel::Kind::kBool) { + return runtime_internal::CreateInvalidMapKeyTypeError( + key_desc->cpp_type_name()); + } + proto_key.SetBoolValue(*key.GetBoolKey()); + return proto_key; + case google::protobuf::FieldDescriptor::CPPTYPE_INT32: { + if (key.kind() != cel::Kind::kInt64) { + return runtime_internal::CreateInvalidMapKeyTypeError( + key_desc->cpp_type_name()); + } + int64_t key_value = *key.GetInt64Key(); + if (key_value > std::numeric_limits::max() || + key_value < std::numeric_limits::lowest()) { + return absl::OutOfRangeError("integer overflow"); + } + proto_key.SetInt32Value(static_cast(key_value)); + return proto_key; + } + case google::protobuf::FieldDescriptor::CPPTYPE_INT64: + if (key.kind() != cel::Kind::kInt64) { + return runtime_internal::CreateInvalidMapKeyTypeError( + key_desc->cpp_type_name()); + } + proto_key.SetInt64Value(*key.GetInt64Key()); + return proto_key; + case google::protobuf::FieldDescriptor::CPPTYPE_STRING: + if (key.kind() != cel::Kind::kString) { + return runtime_internal::CreateInvalidMapKeyTypeError( + key_desc->cpp_type_name()); + } + // This is an alias. The underlying string should be stable. + proto_key.SetStringValue(*key.AsString()); + return proto_key; + case google::protobuf::FieldDescriptor::CPPTYPE_UINT32: { + if (key.kind() != cel::Kind::kUint64) { + return runtime_internal::CreateInvalidMapKeyTypeError( + key_desc->cpp_type_name()); + } + uint64_t key_value = *key.GetUint64Key(); + if (key_value > std::numeric_limits::max()) { + return absl::OutOfRangeError("unsigned integer overflow"); + } + proto_key.SetUInt32Value(static_cast(key_value)); + return proto_key; + } + case google::protobuf::FieldDescriptor::CPPTYPE_UINT64: + if (key.kind() != cel::Kind::kUint64) { + return runtime_internal::CreateInvalidMapKeyTypeError( + key_desc->cpp_type_name()); + } + proto_key.SetUInt64Value(*key.GetUint64Key()); + return proto_key; + default: + return runtime_internal::CreateInvalidMapKeyTypeError( + key_desc->cpp_type_name()); + } +} + class OptimizedSelectImpl { public: OptimizedSelectImpl(std::vector select_path, + std::vector typed_select_path, std::vector qualifiers, bool presence_test, SelectOptimizationOptions options) : select_path_(std::move(select_path)), + typed_select_path_(std::move(typed_select_path)), qualifiers_(std::move(qualifiers)), presence_test_(presence_test), options_(options) @@ -588,8 +680,8 @@ class OptimizedSelectImpl { OptimizedSelectImpl(OptimizedSelectImpl&&) = default; OptimizedSelectImpl& operator=(OptimizedSelectImpl&&) = delete; - absl::StatusOr ApplySelect(ExecutionFrameBase& frame, - const StructValue& struct_value) const; + absl::Status ApplySelect(ExecutionFrameBase& frame, + Value& result_in_out) const; AttributeTrail GetAttributeTrail(const AttributeTrail& operand_trail) const; @@ -600,8 +692,12 @@ class OptimizedSelectImpl { } private: + absl::Status ContinueFromValue(ExecutionFrameBase& frame, Value step_result, + size_t next_index, Value& result_in_out) const; + std::optional attribute_; std::vector select_path_; + std::vector typed_select_path_; std::vector qualifiers_; bool presence_test_; SelectOptimizationOptions options_; @@ -638,34 +734,352 @@ absl::StatusOr> CheckForMarkedAttributes( return std::nullopt; } -absl::StatusOr OptimizedSelectImpl::ApplySelect( - ExecutionFrameBase& frame, const StructValue& struct_value) const { - auto value_or = - (options_.force_fallback_implementation) +bool SupportsCachedFieldDescriptor(const google::protobuf::Message* absl_nonnull message, + const TypedSelectStep& step) { + if (const FieldSelectStep* field_step = absl::get_if(&step); + field_step != nullptr) { + ABSL_DCHECK_EQ(field_step->field->containing_type(), + field_step->containing_type); + ABSL_DCHECK_EQ(field_step->containing_type->file()->pool(), + field_step->field->file()->pool()); + return message->GetDescriptor() == field_step->containing_type; + } + return false; +} + +Value WrapMessageField( + const google::protobuf::Message* absl_nonnull message, + const google::protobuf::FieldDescriptor* absl_nonnull field_descriptor, + bool is_unsafe, const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, + google::protobuf::MessageFactory* absl_nonnull message_factory, + google::protobuf::Arena* absl_nonnull arena) { + if (is_unsafe) { + return Value::WrapFieldUnsafe(ProtoWrapperTypeOptions::kUnsetNull, message, + field_descriptor, descriptor_pool, + message_factory, arena); + } + return Value::WrapField(ProtoWrapperTypeOptions::kUnsetNull, message, + field_descriptor, descriptor_pool, message_factory, + arena); +} + +Value WrapMapElement( + const google::protobuf::MapValueConstRef& value_ref, + const google::protobuf::Message* absl_nonnull message, + const google::protobuf::FieldDescriptor* absl_nonnull value_descriptor, + bool is_unsafe, const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, + google::protobuf::MessageFactory* absl_nonnull message_factory, + google::protobuf::Arena* absl_nonnull arena) { + if (is_unsafe) { + return Value::WrapMapFieldValueUnsafe(value_ref, message, value_descriptor, + descriptor_pool, message_factory, + arena); + } + return Value::WrapMapFieldValue(value_ref, message, value_descriptor, + descriptor_pool, message_factory, arena); +} + +Value WrapRepeatedElement( + int index, const google::protobuf::Message* absl_nonnull message, + const google::protobuf::FieldDescriptor* absl_nonnull repeated_descriptor, + bool is_unsafe, const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, + google::protobuf::MessageFactory* absl_nonnull message_factory, + google::protobuf::Arena* absl_nonnull arena) { + if (is_unsafe) { + return Value::WrapRepeatedFieldUnsafe(index, message, repeated_descriptor, + descriptor_pool, message_factory, + arena); + } + return Value::WrapRepeatedField(index, message, repeated_descriptor, + descriptor_pool, message_factory, arena); +} + +absl::StatusOr QualifyOrFallback( + ExecutionFrameBase& frame, const StructValue& struct_value, + absl::Span select_path, bool presence_test, + bool force_fallback_implementation) { + absl::StatusOr> value_and_count = + (force_fallback_implementation) ? absl::UnimplementedError("Forced fallback impl") - : struct_value.Qualify(select_path_, presence_test_, + : struct_value.Qualify(select_path, presence_test, frame.descriptor_pool(), frame.message_factory(), frame.arena()); - if (!value_or.ok()) { - if (value_or.status().code() == absl::StatusCode::kUnimplemented) { - return FallbackSelect(struct_value, select_path_, presence_test_, + if (!value_and_count.ok()) { + if (value_and_count.status().code() == absl::StatusCode::kUnimplemented) { + return FallbackSelect(struct_value, select_path, presence_test, frame.descriptor_pool(), frame.message_factory(), frame.arena()); } - return value_or.status(); + return value_and_count.status(); + } + + if (value_and_count->second < 0 || + value_and_count->second >= select_path.size()) { + return std::move(value_and_count->first); + } + + return FallbackSelect(value_and_count->first, + select_path.subspan(value_and_count->second), + presence_test, frame.descriptor_pool(), + frame.message_factory(), frame.arena()); +} + +absl::Status OptimizedSelectImpl::ContinueFromValue( + ExecutionFrameBase& frame, Value step_result, size_t next_index, + Value& result_in_out) const { + if (step_result.IsError() || next_index >= select_path_.size()) { + result_in_out = std::move(step_result); + return absl::OkStatus(); + } + absl::Span remaining = + absl::MakeConstSpan(select_path_).subspan(next_index); + if (step_result.IsStruct()) { + CEL_ASSIGN_OR_RETURN( + result_in_out, + QualifyOrFallback(frame, step_result.GetStruct(), remaining, + presence_test_, + options_.force_fallback_implementation)); + return absl::OkStatus(); + } + CEL_ASSIGN_OR_RETURN(result_in_out, + FallbackSelect(step_result, remaining, presence_test_, + frame.descriptor_pool(), + frame.message_factory(), frame.arena())); + return absl::OkStatus(); +} + +absl::Status OptimizedSelectImpl::ApplySelect(ExecutionFrameBase& frame, + Value& result_in_out) const { + if (options_.force_fallback_implementation || typed_select_path_.empty()) { + CEL_ASSIGN_OR_RETURN( + result_in_out, + QualifyOrFallback(frame, result_in_out.GetStruct(), select_path_, + presence_test_, + options_.force_fallback_implementation)); + return absl::OkStatus(); + } + const google::protobuf::Message* current_message = nullptr; + bool is_unsafe = false; + if (result_in_out.IsParsedMessage()) { + ParsedMessageValue parsed_message = result_in_out.GetParsedMessage(); + current_message = &(*parsed_message); + is_unsafe = + cel::interop_internal::IsUnsafeParsedMessageValue(parsed_message); + } else if (const google::protobuf::Message* legacy_message = + cel::interop_internal::GetLegacyMessage(result_in_out); + legacy_message != nullptr) { + current_message = legacy_message; + is_unsafe = true; + } else { + CEL_ASSIGN_OR_RETURN( + result_in_out, + QualifyOrFallback(frame, result_in_out.GetStruct(), select_path_, + presence_test_, + options_.force_fallback_implementation)); + return absl::OkStatus(); + } + + if (!SupportsCachedFieldDescriptor(current_message, typed_select_path_[0])) { + CEL_ASSIGN_OR_RETURN( + result_in_out, + QualifyOrFallback(frame, result_in_out.GetStruct(), select_path_, + presence_test_, + options_.force_fallback_implementation)); + return absl::OkStatus(); } - if (value_or->second < 0 || value_or->second >= select_path_.size()) { - return std::move(value_or->first); + for (size_t i = 0; i < typed_select_path_.size(); ++i) { + const TypedSelectStep& step = typed_select_path_[i]; + + // handle singular + if (const FieldSelectStep* field_step = + absl::get_if(&step); + field_step != nullptr) { + const google::protobuf::FieldDescriptor* field_descriptor = field_step->field; + // We consumed the whole select path. Materialize the leaf. + if (i + 1 == select_path_.size()) { + if (presence_test_) { + result_in_out = + BoolValue{cel::UnsafeParsedMessageValue(current_message) + .HasField(field_descriptor)}; + return absl::OkStatus(); + } + result_in_out = WrapMessageField( + current_message, field_descriptor, is_unsafe, + frame.descriptor_pool(), frame.message_factory(), frame.arena()); + return absl::OkStatus(); + } + + if (i + 1 >= typed_select_path_.size()) { + Value step_result = WrapMessageField( + current_message, field_descriptor, is_unsafe, + frame.descriptor_pool(), frame.message_factory(), frame.arena()); + return ContinueFromValue(frame, std::move(step_result), i + 1, + result_in_out); + } + + if (field_descriptor->is_repeated()) { + // The next step in `typed_select_path_` is a `MapSelectStep` or + // `ListSelectStep` that operates on `current_message`. + continue; + } + + const google::protobuf::Message* next_message = + ¤t_message->GetReflection()->GetMessage(*current_message, + field_descriptor); + // Check for messages extended by overlays. This is redundant if all + // messages are from hermetic pools, which we cannot guarantee. + if (!SupportsCachedFieldDescriptor(next_message, + typed_select_path_[i + 1])) { + Value step_result = WrapMessageField( + current_message, field_descriptor, is_unsafe, + frame.descriptor_pool(), frame.message_factory(), frame.arena()); + return ContinueFromValue(frame, std::move(step_result), i + 1, + result_in_out); + } + current_message = next_message; + continue; + } + + // Handle map + if (const MapSelectStep* map_step = absl::get_if(&step); + map_step != nullptr) { + if (i + 1 == select_path_.size() && presence_test_) { + const AttributeQualifier& qualifier = + absl::get(select_path_[i]); + if (qualifier.kind() != cel::Kind::kString) { + result_in_out = ErrorValue::From( + runtime_internal::CreateNoMatchingOverloadError("has"), + frame.arena()); + return absl::OkStatus(); + } + if (!map_step->key.ok()) { + result_in_out = + ErrorValue::From(map_step->key.status(), frame.arena()); + return absl::OkStatus(); + } + bool found = protobuf_internal::ContainsMapKey( + *current_message->GetReflection(), *current_message, + *map_step->map_field, *map_step->key); + result_in_out = BoolValue{found}; + return absl::OkStatus(); + } + + if (!map_step->key.ok()) { + result_in_out = ErrorValue::From(map_step->key.status(), frame.arena()); + return absl::OkStatus(); + } + google::protobuf::MapValueConstRef value_ref; + bool found = protobuf_internal::LookupMapValue( + *current_message->GetReflection(), *current_message, + *map_step->map_field, *map_step->key, &value_ref); + if (!found) { + const AttributeQualifier& qualifier = + absl::get(select_path_[i]); + result_in_out = ErrorValue::From( + runtime_internal::CreateNoSuchKeyError(qualifier.ToString()), + frame.arena()); + return absl::OkStatus(); + } + + if (i + 1 == select_path_.size()) { + result_in_out = WrapMapElement( + value_ref, current_message, map_step->value_field, is_unsafe, + frame.descriptor_pool(), frame.message_factory(), frame.arena()); + return absl::OkStatus(); + } + + // We don't have a typed select step for the next step, so we fall back. + if (i + 1 >= typed_select_path_.size()) { + Value step_result = WrapMapElement( + value_ref, current_message, map_step->value_field, is_unsafe, + frame.descriptor_pool(), frame.message_factory(), frame.arena()); + return ContinueFromValue(frame, std::move(step_result), i + 1, + result_in_out); + } + + const google::protobuf::Message* next_message = &value_ref.GetMessageValue(); + if (!SupportsCachedFieldDescriptor(next_message, + typed_select_path_[i + 1])) { + Value step_result = WrapMapElement( + value_ref, current_message, map_step->value_field, is_unsafe, + frame.descriptor_pool(), frame.message_factory(), frame.arena()); + return ContinueFromValue(frame, std::move(step_result), i + 1, + result_in_out); + } + current_message = next_message; + continue; + } + + // handle list + if (const ListSelectStep* list_step = absl::get_if(&step); + list_step != nullptr) { + if (i + 1 == select_path_.size() && presence_test_) { + result_in_out = ErrorValue::From( + runtime_internal::CreateNoMatchingOverloadError("has"), + frame.arena()); + return absl::OkStatus(); + } + + const AttributeQualifier& qualifier = + absl::get(select_path_[i]); + if (qualifier.kind() != cel::Kind::kInt64) { + result_in_out = + ErrorValue::From(runtime_internal::CreateNoMatchingOverloadError( + cel::builtin::kIndex), + frame.arena()); + return absl::OkStatus(); + } + int64_t index = *qualifier.GetInt64Key(); + const google::protobuf::Reflection* reflection = current_message->GetReflection(); + int size = + reflection->FieldSize(*current_message, list_step->repeated_field); + if (index < 0 || index >= size) { + result_in_out = ErrorValue::From( + absl::InvalidArgumentError(absl::StrCat( + "index out of bounds: index=", index, " size=", size)), + frame.arena()); + return absl::OkStatus(); + } + int int_index = static_cast(index); + + if (i + 1 == select_path_.size()) { + result_in_out = WrapRepeatedElement( + int_index, current_message, list_step->repeated_field, is_unsafe, + frame.descriptor_pool(), frame.message_factory(), frame.arena()); + return absl::OkStatus(); + } + + // We don't have a typed select step for the next step, so we fall back. + if (i + 1 >= typed_select_path_.size()) { + Value step_result = WrapRepeatedElement( + int_index, current_message, list_step->repeated_field, is_unsafe, + frame.descriptor_pool(), frame.message_factory(), frame.arena()); + return ContinueFromValue(frame, std::move(step_result), i + 1, + result_in_out); + } + + const google::protobuf::Message* next_message = &reflection->GetRepeatedMessage( + *current_message, list_step->repeated_field, int_index); + if (!SupportsCachedFieldDescriptor(next_message, + typed_select_path_[i + 1])) { + Value step_result = WrapRepeatedElement( + int_index, current_message, list_step->repeated_field, is_unsafe, + frame.descriptor_pool(), frame.message_factory(), frame.arena()); + return ContinueFromValue(frame, std::move(step_result), i + 1, + result_in_out); + } + current_message = next_message; + continue; + } + + ABSL_UNREACHABLE(); + return absl::InternalError("Unknown select step type"); } - return FallbackSelect( - value_or->first, - absl::MakeConstSpan(select_path_).subspan(value_or->second), - presence_test_, frame.descriptor_pool(), frame.message_factory(), - frame.arena()); + return absl::OkStatus(); } AttributeTrail OptimizedSelectImpl::GetAttributeTrail( @@ -691,31 +1105,16 @@ class StackMachineImpl : public ExpressionStepBase { void Evaluate(ExecutionFrame* frame) const override; private: - // Get the effective attribute for the optimized select expression. - // Assumes the operand is the top of stack if the attribute wasn't known at - // plan time. - AttributeTrail GetAttributeTrail(ExecutionFrame* frame) const; - OptimizedSelectImpl impl_; }; -AttributeTrail StackMachineImpl::GetAttributeTrail( - ExecutionFrame* frame) const { - const auto& attr = frame->value_stack().PeekAttribute(); - return impl_.GetAttributeTrail(attr); -} - void StackMachineImpl::Evaluate(ExecutionFrame* frame) const { - // Default empty. - AttributeTrail attribute_trail; // TODO(uncreated-issue/51): add support for variable qualifiers and string literal // variable names. - constexpr size_t kStackInputs = 1; - // For now, we expect the operand to be top of stack. - const Value& operand = frame->value_stack().Peek(); + Value& top = frame->value_stack().Peek(); - if (operand->Is() || operand->Is()) { + if (top.Is() || top.Is()) { // Just forward the error which is already top of stack. return; } @@ -725,7 +1124,8 @@ void StackMachineImpl::Evaluate(ExecutionFrame* frame) const { // When possible, this is computed at plan time based on the optimized // select arguments. // TODO(uncreated-issue/51): add support variable qualifiers - attribute_trail = GetAttributeTrail(frame); + AttributeTrail& attribute_trail = frame->value_stack().PeekAttribute(); + attribute_trail = impl_.GetAttributeTrail(attribute_trail); absl::StatusOr> value = CheckForMarkedAttributes(*frame, attribute_trail); if (!value.ok()) { @@ -733,27 +1133,21 @@ void StackMachineImpl::Evaluate(ExecutionFrame* frame) const { return; } if (value->has_value()) { - frame->value_stack().Pop(kStackInputs); - frame->value_stack().Push(std::move(*value).value(), - std::move(attribute_trail)); + top = std::move(*value).value(); return; } } - if (!operand->Is()) { + if (!top.Is()) { frame->Abort(absl::InvalidArgumentError( "Expected struct type for select optimization.")); return; } - absl::StatusOr result = impl_.ApplySelect(*frame, operand.GetStruct()); - if (!result.ok()) { - frame->Abort(std::move(result).status()); - return; + absl::Status status = impl_.ApplySelect(*frame, top); + if (!status.ok()) { + frame->Abort(std::move(status)); } - - frame->value_stack().Pop(kStackInputs); - frame->value_stack().Push(*std::move(result), std::move(attribute_trail)); } class SelectOptimizer : public ProgramOptimizer { @@ -771,6 +1165,74 @@ class SelectOptimizer : public ProgramOptimizer { SelectOptimizationOptions options_; }; +std::vector BuildTypedSelectPath( + PlannerContext& context, const Expr& operand, + absl::Span instructions) { + std::vector typed_select_path; + std::optional current_type = context.GetResolvedType(operand.id()); + for (const SelectQualifier& instruction : instructions) { + if (!current_type.has_value()) { + break; + } + if (const FieldSpecifier* field_specifier = + absl::get_if(&instruction); + field_specifier != nullptr) { + if (!current_type->IsMessage()) { + break; + } + cel::MessageType message_type = current_type->GetMessage(); + absl::StatusOr> field = + context.type_reflector().FindStructTypeFieldByName( + message_type, field_specifier->name); + if (!field.ok() || !field->has_value() || !(*field)->IsMessage()) { + break; + } + const google::protobuf::Descriptor* containing_descriptor = + message_type.descriptor(); + const google::protobuf::FieldDescriptor* field_descriptor = + (*field)->GetMessage().descriptor(); + if (containing_descriptor->file()->pool() != + field_descriptor->file()->pool()) { + break; + } + typed_select_path.push_back( + FieldSelectStep{containing_descriptor, field_descriptor}); + current_type = (*field)->GetType(); + } else if (const AttributeQualifier* attr_qualifier = + absl::get_if(&instruction); + attr_qualifier != nullptr) { + if (typed_select_path.empty() || + !absl::holds_alternative(typed_select_path.back())) { + break; + } + const google::protobuf::FieldDescriptor* prev_field = + absl::get(typed_select_path.back()).field; + if (prev_field->is_map() && current_type->IsMap()) { + const google::protobuf::Descriptor* entry_descriptor = prev_field->message_type(); + const google::protobuf::FieldDescriptor* key_field = entry_descriptor->map_key(); + const google::protobuf::FieldDescriptor* value_field = + entry_descriptor->map_value(); + if (key_field == nullptr || value_field == nullptr) { + break; + } + typed_select_path.push_back( + MapSelectStep{prev_field, key_field, value_field, + CreateProtoMapKey(key_field, *attr_qualifier)}); + current_type = current_type->GetMap().GetValue(); + } else if (prev_field->is_repeated() && !prev_field->is_map() && + current_type->IsList()) { + typed_select_path.push_back(ListSelectStep{prev_field}); + current_type = current_type->GetList().GetElement(); + } else { + break; + } + } else { + break; + } + } + return typed_select_path; +} + absl::Status SelectOptimizer::OnPostVisit(PlannerContext& context, const Expr& node) { if (!node.has_call_expr()) { @@ -827,7 +1289,14 @@ absl::Status SelectOptimizer::OnPostVisit(PlannerContext& context, return absl::OkStatus(); } - OptimizedSelectImpl impl(std::move(instructions), std::move(qualifiers), + std::vector typed_select_path; + if (options_.enable_field_descriptor_caching && + context.options().enable_typed_field_access) { + typed_select_path = BuildTypedSelectPath(context, operand, instructions); + } + + OptimizedSelectImpl impl(std::move(instructions), + std::move(typed_select_path), std::move(qualifiers), presence_test, options_); google::api::expr::runtime::ExecutionPath path; @@ -873,6 +1342,9 @@ google::api::expr::runtime::ProgramOptimizerFactory CreateSelectOptimizationProgramOptimizer( const SelectOptimizationOptions& options) { return [=](PlannerContext& context, const Ast& ast) { + if (options.enable_field_descriptor_caching) { + context.set_resolve_types(); + } return std::make_unique(options); }; } diff --git a/extensions/select_optimization.h b/extensions/select_optimization.h index 8beeb1bed..e362094a3 100644 --- a/extensions/select_optimization.h +++ b/extensions/select_optimization.h @@ -44,6 +44,12 @@ struct SelectOptimizationOptions { // optimization is already applied by a compiler / static optimizer, this // option has no effect. int min_field_depth_for_optimization = 0; + + // Whether to try to cache field descriptor lookups for the select + // optimization at plan time. + // + // This is temporary to make the change rollback safe for existing users. + bool enable_field_descriptor_caching = false; }; // Enable select optimization on the given RuntimeBuilder, replacing long diff --git a/extensions/select_optimization_test.cc b/extensions/select_optimization_test.cc index 7e15dc897..220f17e14 100644 --- a/extensions/select_optimization_test.cc +++ b/extensions/select_optimization_test.cc @@ -18,6 +18,7 @@ #include #include #include +#include #include #include @@ -45,6 +46,7 @@ #include "common/expr.h" #include "common/kind.h" #include "common/native_type.h" +#include "common/type.h" #include "common/typeinfo.h" #include "common/value.h" #include "common/values/custom_struct_value.h" @@ -71,12 +73,17 @@ #include "runtime/internal/issue_collector.h" #include "runtime/internal/runtime_env.h" #include "runtime/internal/runtime_env_testing.h" +#include "runtime/runtime.h" +#include "runtime/runtime_builder.h" #include "runtime/runtime_issue.h" #include "runtime/runtime_options.h" +#include "runtime/standard_runtime_builder_factory.h" #include "runtime/type_registry.h" +#include "testutil/test_external_extensions_descriptor_set.h" #include "cel/expr/conformance/proto2/test_all_types.pb.h" #include "google/protobuf/arena.h" #include "google/protobuf/descriptor.h" +#include "google/protobuf/dynamic_message.h" #include "google/protobuf/extension_set.h" #include "google/protobuf/message.h" #include "google/protobuf/text_format.h" @@ -825,8 +832,11 @@ TEST_F(SelectOptimizationTest, ProgramOptimizerUnoptimizedAst) { google::protobuf::LinkMessageReflection(); FlatExprBuilder builder(env_, runtime_options_); + SelectOptimizationOptions select_options; + select_options.enable_field_descriptor_caching = true; - builder.AddProgramOptimizer(CreateSelectOptimizationProgramOptimizer()); + builder.AddProgramOptimizer( + CreateSelectOptimizationProgramOptimizer(select_options)); // nested_test_all_types.child.payload.standalone_message.bb ASSERT_OK_AND_ASSIGN( @@ -859,6 +869,46 @@ TEST_F(SelectOptimizationTest, ProgramOptimizerUnoptimizedAst) { EXPECT_EQ(result.GetInt().NativeValue(), 42); } +TEST_F(SelectOptimizationTest, FieldDescriptorCachingDisabled) { + google::protobuf::LinkMessageReflection(); + + FlatExprBuilder builder(env_, runtime_options_); + SelectOptimizationOptions select_options; + select_options.enable_field_descriptor_caching = false; + + builder.AddAstTransform( + std::make_unique(select_options)); + builder.AddProgramOptimizer( + CreateSelectOptimizationProgramOptimizer(select_options)); + + ASSERT_OK_AND_ASSIGN( + std::unique_ptr ast, + CompileForTestCase( + "nested_test_all_types.child.payload.standalone_message.bb")); + + ASSERT_OK_AND_ASSIGN(FlatExpression plan, + builder.CreateExpressionImpl(std::move(ast), nullptr)); + + NestedTestAllTypes* var = google::protobuf::Arena::Create(&arena_); + var->mutable_child()->mutable_payload()->mutable_standalone_message()->set_bb( + 42); + + cel::Activation act; + act.InsertOrAssignValue("nested_test_all_types", + cel::ParsedMessageValue(var, &arena_)); + + auto state = plan.MakeEvaluatorState(env_->descriptor_pool.get(), + env_->MutableMessageFactory(), &arena_); + ASSERT_OK_AND_ASSIGN( + Value result, + plan.EvaluateWithCallback( + act, /*embedder_context=*/nullptr, + google::api::expr::runtime::EvaluationListener(), state)); + + ASSERT_TRUE(result->Is()) << result->DebugString(); + EXPECT_EQ(result.GetInt().NativeValue(), 42); +} + TEST_F(SelectOptimizationTest, MissingAttributeIndependentOfUnknown) { google::protobuf::LinkMessageReflection(); @@ -867,9 +917,13 @@ TEST_F(SelectOptimizationTest, MissingAttributeIndependentOfUnknown) { options.enable_missing_attribute_errors = true; FlatExprBuilder builder(env_, options); + SelectOptimizationOptions select_options; + select_options.enable_field_descriptor_caching = true; - builder.AddAstTransform(std::make_unique()); - builder.AddProgramOptimizer(CreateSelectOptimizationProgramOptimizer()); + builder.AddAstTransform( + std::make_unique(select_options)); + builder.AddProgramOptimizer( + CreateSelectOptimizationProgramOptimizer(select_options)); ASSERT_OK_AND_ASSIGN( std::unique_ptr ast, @@ -920,9 +974,13 @@ TEST_F(SelectOptimizationTest, NullUnboxingOptionHonored) { options.enable_empty_wrapper_null_unboxing = true; FlatExprBuilder builder(env_, options); + SelectOptimizationOptions select_options; + select_options.enable_field_descriptor_caching = true; - builder.AddAstTransform(std::make_unique()); - builder.AddProgramOptimizer(CreateSelectOptimizationProgramOptimizer()); + builder.AddAstTransform( + std::make_unique(select_options)); + builder.AddProgramOptimizer( + CreateSelectOptimizationProgramOptimizer(select_options)); // nested_test_all_types.payload.single_int64_wrapper ASSERT_OK_AND_ASSIGN( @@ -933,16 +991,64 @@ TEST_F(SelectOptimizationTest, NullUnboxingOptionHonored) { builder.CreateExpressionImpl(std::move(ast), nullptr)); cel::Activation act; - // activation only uses a ptr to the underlying message, persist them. - NestedTestAllTypes var; + NestedTestAllTypes generated_var; ASSERT_TRUE(google::protobuf::TextFormat::ParseFromString( R"pb( payload {} )pb", - &var)); - ASSERT_THAT(TestBindLegacyMessage("nested_test_all_types", var, &arena_, act), - IsOk()); + &generated_var)); + google::protobuf::Message* var = + runtime_internal::MakeTestingEnvDynamicProto(generated_var, &arena_); + ASSERT_THAT( + TestBindLegacyMessage("nested_test_all_types", *var, &arena_, act), + IsOk()); + + auto state = plan.MakeEvaluatorState(env_->descriptor_pool.get(), + env_->MutableMessageFactory(), &arena_); + ASSERT_OK_AND_ASSIGN( + Value result, + plan.EvaluateWithCallback( + act, /*embedder_context=*/nullptr, + google::api::expr::runtime::EvaluationListener(), state)); + + ASSERT_TRUE(result->Is()) << result->DebugString(); +} + +TEST_F(SelectOptimizationTest, NullUnboxingOptionHonoredModernParsedMessage) { + google::protobuf::LinkMessageReflection(); + + RuntimeOptions options = runtime_options_; + options.enable_empty_wrapper_null_unboxing = true; + + FlatExprBuilder builder(env_, options); + SelectOptimizationOptions select_options; + select_options.enable_field_descriptor_caching = true; + + builder.AddAstTransform( + std::make_unique(select_options)); + builder.AddProgramOptimizer( + CreateSelectOptimizationProgramOptimizer(select_options)); + + // nested_test_all_types.payload.single_int64_wrapper + ASSERT_OK_AND_ASSIGN( + std::unique_ptr ast, + CompileForTestCase("nested_test_all_types.payload.single_int64_wrapper")); + + ASSERT_OK_AND_ASSIGN(FlatExpression plan, + builder.CreateExpressionImpl(std::move(ast), nullptr)); + + cel::Activation act; + NestedTestAllTypes generated_var; + ASSERT_TRUE(google::protobuf::TextFormat::ParseFromString( + R"pb( + payload {} + )pb", + &generated_var)); + google::protobuf::Message* var = + runtime_internal::MakeTestingEnvDynamicProto(generated_var, &arena_); + act.InsertOrAssignValue("nested_test_all_types", + cel::ParsedMessageValue(var, &arena_)); auto state = plan.MakeEvaluatorState(env_->descriptor_pool.get(), env_->MutableMessageFactory(), &arena_); @@ -969,10 +1075,19 @@ struct ProgramOptimizerTestCase { class SelectOptimizationProgramOptimizerTest : public SelectOptimizationTest, - public testing::WithParamInterface {}; + public testing::WithParamInterface< + std::tuple> { + protected: + const ProgramOptimizerTestCase& test_case() const { + return std::get<0>(GetParam()); + } + + bool enable_field_descriptor_caching() const { + return std::get<1>(GetParam()); + } +}; TEST_P(SelectOptimizationProgramOptimizerTest, Default) { - const ProgramOptimizerTestCase& test_case = GetParam(); google::protobuf::LinkMessageReflection(); RuntimeOptions options = runtime_options_; @@ -980,42 +1095,185 @@ TEST_P(SelectOptimizationProgramOptimizerTest, Default) { options.enable_missing_attribute_errors = true; FlatExprBuilder builder(env_, options); + SelectOptimizationOptions select_options; + select_options.enable_field_descriptor_caching = + enable_field_descriptor_caching(); - builder.AddAstTransform(std::make_unique()); - builder.AddProgramOptimizer(CreateSelectOptimizationProgramOptimizer()); + builder.AddAstTransform( + std::make_unique(select_options)); + builder.AddProgramOptimizer( + CreateSelectOptimizationProgramOptimizer(select_options)); ASSERT_OK_AND_ASSIGN(std::unique_ptr ast, - CompileForTestCase(test_case.expr)); + CompileForTestCase(test_case().expr)); ASSERT_OK_AND_ASSIGN(FlatExpression plan, builder.CreateExpressionImpl(std::move(ast), nullptr)); cel::Activation act; - // activation only uses a ptr to the underlying message, persist them. - std::vector> vars; - for (const auto& entry : test_case.vars) { - vars.push_back(std::make_unique()); + for (const auto& entry : test_case().vars) { + NestedTestAllTypes generated_var; ASSERT_TRUE( - google::protobuf::TextFormat::ParseFromString(entry.second, vars.back().get())); - ASSERT_THAT(TestBindLegacyMessage(entry.first, *vars.back(), &arena_, act), - IsOk()); + google::protobuf::TextFormat::ParseFromString(entry.second, &generated_var)); + google::protobuf::Message* var = + runtime_internal::MakeTestingEnvDynamicProto(generated_var, &arena_); + ASSERT_THAT(TestBindLegacyMessage(entry.first, *var, &arena_, act), IsOk()); } - if (test_case.setup_activation != nullptr) { - ASSERT_THAT(test_case.setup_activation(&arena_, act), IsOk()); + if (test_case().setup_activation != nullptr) { + ASSERT_THAT(test_case().setup_activation(&arena_, act), IsOk()); } - auto state = plan.MakeEvaluatorState(env_->descriptor_pool.get(), - env_->MutableMessageFactory(), &arena_); + google::api::expr::runtime::FlatExpressionEvaluatorState state = + plan.MakeEvaluatorState(env_->descriptor_pool.get(), + env_->MutableMessageFactory(), &arena_); + absl::StatusOr result = plan.EvaluateWithCallback( + act, /*embedder_context=*/nullptr, + google::api::expr::runtime::EvaluationListener(), state); + + ASSERT_NO_FATAL_FAILURE(test_case().expectations(result)); +} + +TEST_P(SelectOptimizationProgramOptimizerTest, ModernParsedMessage) { + google::protobuf::LinkMessageReflection(); + + RuntimeOptions options = runtime_options_; + options.unknown_processing = UnknownProcessingOptions::kAttributeAndFunction; + options.enable_missing_attribute_errors = true; + + FlatExprBuilder builder(env_, options); + SelectOptimizationOptions select_options; + select_options.enable_field_descriptor_caching = + enable_field_descriptor_caching(); + + builder.AddAstTransform( + std::make_unique(select_options)); + builder.AddProgramOptimizer( + CreateSelectOptimizationProgramOptimizer(select_options)); + + ASSERT_OK_AND_ASSIGN(std::unique_ptr ast, + CompileForTestCase(test_case().expr)); + + ASSERT_OK_AND_ASSIGN(FlatExpression plan, + builder.CreateExpressionImpl(std::move(ast), nullptr)); + + cel::Activation act; + for (const auto& entry : test_case().vars) { + NestedTestAllTypes generated_var; + ASSERT_TRUE( + google::protobuf::TextFormat::ParseFromString(entry.second, &generated_var)); + google::protobuf::Message* var = + runtime_internal::MakeTestingEnvDynamicProto(generated_var, &arena_); + act.InsertOrAssignValue(entry.first, cel::ParsedMessageValue(var, &arena_)); + } + + if (test_case().setup_activation != nullptr) { + ASSERT_THAT(test_case().setup_activation(&arena_, act), IsOk()); + } + + google::api::expr::runtime::FlatExpressionEvaluatorState state = + plan.MakeEvaluatorState(env_->descriptor_pool.get(), + env_->MutableMessageFactory(), &arena_); + absl::StatusOr result = plan.EvaluateWithCallback( + act, /*embedder_context=*/nullptr, + google::api::expr::runtime::EvaluationListener(), state); + + ASSERT_NO_FATAL_FAILURE(test_case().expectations(result)); +} + +TEST_P(SelectOptimizationProgramOptimizerTest, UnsafeParsedMessage) { + google::protobuf::LinkMessageReflection(); + + RuntimeOptions options = runtime_options_; + options.unknown_processing = UnknownProcessingOptions::kAttributeAndFunction; + options.enable_missing_attribute_errors = true; + + FlatExprBuilder builder(env_, options); + SelectOptimizationOptions select_options; + select_options.enable_field_descriptor_caching = + enable_field_descriptor_caching(); + + builder.AddAstTransform( + std::make_unique(select_options)); + builder.AddProgramOptimizer( + CreateSelectOptimizationProgramOptimizer(select_options)); + + ASSERT_OK_AND_ASSIGN(std::unique_ptr ast, + CompileForTestCase(test_case().expr)); + + ASSERT_OK_AND_ASSIGN(FlatExpression plan, + builder.CreateExpressionImpl(std::move(ast), nullptr)); + + cel::Activation act; + for (const auto& entry : test_case().vars) { + NestedTestAllTypes generated_var; + ASSERT_TRUE( + google::protobuf::TextFormat::ParseFromString(entry.second, &generated_var)); + google::protobuf::Message* var = + runtime_internal::MakeTestingEnvDynamicProto(generated_var, &arena_); + act.InsertOrAssignValue(entry.first, cel::UnsafeParsedMessageValue(var)); + } + + if (test_case().setup_activation != nullptr) { + ASSERT_THAT(test_case().setup_activation(&arena_, act), IsOk()); + } + + google::api::expr::runtime::FlatExpressionEvaluatorState state = + plan.MakeEvaluatorState(env_->descriptor_pool.get(), + env_->MutableMessageFactory(), &arena_); absl::StatusOr result = plan.EvaluateWithCallback( act, /*embedder_context=*/nullptr, google::api::expr::runtime::EvaluationListener(), state); - ASSERT_NO_FATAL_FAILURE(test_case.expectations(result)); + ASSERT_NO_FATAL_FAILURE(test_case().expectations(result)); +} + +TEST_P(SelectOptimizationProgramOptimizerTest, DifferentDescriptorPool) { + google::protobuf::LinkMessageReflection(); + + RuntimeOptions options = runtime_options_; + options.unknown_processing = UnknownProcessingOptions::kAttributeAndFunction; + options.enable_missing_attribute_errors = true; + + FlatExprBuilder builder(env_, options); + SelectOptimizationOptions select_options; + select_options.enable_field_descriptor_caching = + enable_field_descriptor_caching(); + + builder.AddAstTransform( + std::make_unique(select_options)); + builder.AddProgramOptimizer( + CreateSelectOptimizationProgramOptimizer(select_options)); + + ASSERT_OK_AND_ASSIGN(std::unique_ptr ast, + CompileForTestCase(test_case().expr)); + + ASSERT_OK_AND_ASSIGN(FlatExpression plan, + builder.CreateExpressionImpl(std::move(ast), nullptr)); + + cel::Activation act; + for (const auto& entry : test_case().vars) { + NestedTestAllTypes* var = + google::protobuf::Arena::Create(&arena_); + ASSERT_TRUE(google::protobuf::TextFormat::ParseFromString(entry.second, var)); + act.InsertOrAssignValue(entry.first, cel::ParsedMessageValue(var, &arena_)); + } + + if (test_case().setup_activation != nullptr) { + ASSERT_THAT(test_case().setup_activation(&arena_, act), IsOk()); + } + + google::api::expr::runtime::FlatExpressionEvaluatorState state = + plan.MakeEvaluatorState(env_->descriptor_pool.get(), + env_->MutableMessageFactory(), &arena_); + absl::StatusOr result = plan.EvaluateWithCallback( + act, /*embedder_context=*/nullptr, + google::api::expr::runtime::EvaluationListener(), state); + + ASSERT_NO_FATAL_FAILURE(test_case().expectations(result)); } TEST_P(SelectOptimizationProgramOptimizerTest, ForceFallbackImpl) { - const ProgramOptimizerTestCase& test_case = GetParam(); google::protobuf::LinkMessageReflection(); RuntimeOptions options = runtime_options_; @@ -1025,13 +1283,16 @@ TEST_P(SelectOptimizationProgramOptimizerTest, ForceFallbackImpl) { FlatExprBuilder builder(env_, options); SelectOptimizationOptions select_options; select_options.force_fallback_implementation = true; + select_options.enable_field_descriptor_caching = + enable_field_descriptor_caching(); - builder.AddAstTransform(std::make_unique()); + builder.AddAstTransform( + std::make_unique(select_options)); builder.AddProgramOptimizer( CreateSelectOptimizationProgramOptimizer(select_options)); ASSERT_OK_AND_ASSIGN(std::unique_ptr ast, - CompileForTestCase(test_case.expr)); + CompileForTestCase(test_case().expr)); ASSERT_OK_AND_ASSIGN(FlatExpression plan, builder.CreateExpressionImpl(std::move(ast), nullptr)); @@ -1039,7 +1300,7 @@ TEST_P(SelectOptimizationProgramOptimizerTest, ForceFallbackImpl) { cel::Activation act; // activation only uses a ptr to the underlying message, persist them. std::vector> vars; - for (const auto& entry : test_case.vars) { + for (const auto& entry : test_case().vars) { vars.push_back(std::make_unique()); ASSERT_TRUE( google::protobuf::TextFormat::ParseFromString(entry.second, vars.back().get())); @@ -1047,724 +1308,1041 @@ TEST_P(SelectOptimizationProgramOptimizerTest, ForceFallbackImpl) { IsOk()); } - if (test_case.setup_activation != nullptr) { - ASSERT_THAT(test_case.setup_activation(&arena_, act), IsOk()); + if (test_case().setup_activation != nullptr) { + ASSERT_THAT(test_case().setup_activation(&arena_, act), IsOk()); } - auto state = plan.MakeEvaluatorState(env_->descriptor_pool.get(), - env_->MutableMessageFactory(), &arena_); + google::api::expr::runtime::FlatExpressionEvaluatorState state = + plan.MakeEvaluatorState(env_->descriptor_pool.get(), + env_->MutableMessageFactory(), &arena_); absl::StatusOr result = plan.EvaluateWithCallback( act, /*embedder_context=*/nullptr, google::api::expr::runtime::EvaluationListener(), state); - ASSERT_NO_FATAL_FAILURE(test_case.expectations(result)); + ASSERT_NO_FATAL_FAILURE(test_case().expectations(result)); } INSTANTIATE_TEST_SUITE_P( TestCases, SelectOptimizationProgramOptimizerTest, - testing::ValuesIn({ - { - "chained_select_success", - "nested_test_all_types.child.payload.standalone_message.bb", - {{"nested_test_all_types", - R"pb( - child { payload { standalone_message { bb: 42 } } } - )pb"}}, - ActivationSetupFn(), - [](const absl::StatusOr& got) { - ASSERT_OK_AND_ASSIGN(Value result, got); - ASSERT_TRUE(result->Is()) << result->DebugString(); - EXPECT_EQ(result.GetInt().NativeValue(), 42); + testing::Combine( + testing::ValuesIn({ + { + "chained_select_success", + "nested_test_all_types.child.payload.standalone_message.bb", + {{"nested_test_all_types", + R"pb( + child { payload { standalone_message { bb: 42 } } } + )pb"}}, + ActivationSetupFn(), + [](const absl::StatusOr& got) { + ASSERT_OK_AND_ASSIGN(Value result, got); + ASSERT_TRUE(result->Is()) << result->DebugString(); + EXPECT_EQ(result.GetInt().NativeValue(), 42); + }, }, - }, - { - "chained_select_defaults_success", - "nested_test_all_types.child.payload.standalone_message.bb", - {{"nested_test_all_types", R"pb()pb"}}, - ActivationSetupFn(), - - [](const absl::StatusOr& got) { - ASSERT_OK_AND_ASSIGN(Value result, got); - ASSERT_TRUE(result->Is()) << result->DebugString(); - EXPECT_EQ(result.GetInt().NativeValue(), 0); + { + "chained_select_defaults_success", + "nested_test_all_types.child.payload.standalone_message.bb", + {{"nested_test_all_types", R"pb()pb"}}, + ActivationSetupFn(), + + [](const absl::StatusOr& got) { + ASSERT_OK_AND_ASSIGN(Value result, got); + ASSERT_TRUE(result->Is()) << result->DebugString(); + EXPECT_EQ(result.GetInt().NativeValue(), 0); + }, }, - }, - { - "chained_select_partial_success", - "nested_test_all_types.child.payload.standalone_message.bb", - {}, - [](google::protobuf::Arena* arena, Activation& act) { - auto* custom_struct = - google::protobuf::Arena::Create(arena, - IntValue(42)); - act.InsertOrAssignValue("nested_test_all_types", - CustomStructValue(custom_struct, arena)); - return absl::OkStatus(); + { + "chained_select_partial_success", + "nested_test_all_types.child.payload.standalone_message.bb", + {}, + [](google::protobuf::Arena* arena, Activation& act) { + auto* custom_struct = + google::protobuf::Arena::Create( + arena, IntValue(42)); + act.InsertOrAssignValue( + "nested_test_all_types", + CustomStructValue(custom_struct, arena)); + return absl::OkStatus(); + }, + [](const absl::StatusOr& got) { + ASSERT_OK_AND_ASSIGN(Value result, got); + ASSERT_TRUE(result->Is()) << result->DebugString(); + EXPECT_EQ(result.GetInt().NativeValue(), 42); + }, }, - [](const absl::StatusOr& got) { - ASSERT_OK_AND_ASSIGN(Value result, got); - ASSERT_TRUE(result->Is()) << result->DebugString(); - EXPECT_EQ(result.GetInt().NativeValue(), 42); + { + "chained_select_presence_partial_present", + "has(nested_test_all_types.child.payload.standalone_message." + "bb)", + {}, + [](google::protobuf::Arena* arena, Activation& act) { + auto* custom_struct = + google::protobuf::Arena::Create( + arena, IntValue(42)); + act.InsertOrAssignValue( + "nested_test_all_types", + CustomStructValue(custom_struct, arena)); + return absl::OkStatus(); + }, + [](const absl::StatusOr& got) { + ASSERT_OK_AND_ASSIGN(Value result, got); + ASSERT_TRUE(result->Is()) << result->DebugString(); + EXPECT_TRUE(result.GetBool().NativeValue()); + }, }, - }, - { - "chained_select_presence_partial_present", - "has(nested_test_all_types.child.payload.standalone_message.bb)", - {}, - [](google::protobuf::Arena* arena, Activation& act) { - auto* custom_struct = - google::protobuf::Arena::Create(arena, - IntValue(42)); - act.InsertOrAssignValue("nested_test_all_types", - CustomStructValue(custom_struct, arena)); - return absl::OkStatus(); + { + "chained_select_not_bound", + "nested_test_all_types.child.payload.standalone_message.bb", + {}, // not set + ActivationSetupFn(), + + [](const absl::StatusOr& got) { + ASSERT_OK_AND_ASSIGN(Value result, got); + ASSERT_TRUE(result->Is()) + << result->DebugString(); + EXPECT_THAT(result.GetError().NativeValue(), + StatusIs(absl::StatusCode::kUnknown, + HasSubstr("nested_test_all_types"))); + }, }, - [](const absl::StatusOr& got) { - ASSERT_OK_AND_ASSIGN(Value result, got); - ASSERT_TRUE(result->Is()) << result->DebugString(); - EXPECT_TRUE(result.GetBool().NativeValue()); + { + // Some clients will use maps to represent a protobuf message at + // runtime. This is not yet supported. + "chained_select_map_as_root_unsupported", + "nested_test_all_types.child.payload.standalone_message.bb", + {}, // not set + [](google::protobuf::Arena* arena, Activation& act) -> absl::Status { + auto builder = cel::NewMapValueBuilder(arena); + CEL_RETURN_IF_ERROR(builder->Put( + cel::StringValue::WrapUnsafe("child"), cel::NullValue())); + + auto value = std::move(*builder).Build(); + + act.InsertOrAssignValue("nested_test_all_types", + std::move(value)); + + return absl::OkStatus(); + }, + + [](const absl::StatusOr& got) { + EXPECT_THAT(got.status(), + StatusIs(absl::StatusCode::kInvalidArgument)); + }, }, - }, - { - "chained_select_not_bound", - "nested_test_all_types.child.payload.standalone_message.bb", - {}, // not set - ActivationSetupFn(), - - [](const absl::StatusOr& got) { - ASSERT_OK_AND_ASSIGN(Value result, got); - ASSERT_TRUE(result->Is()) << result->DebugString(); - EXPECT_THAT(result.GetError().NativeValue(), - StatusIs(absl::StatusCode::kUnknown, - HasSubstr("nested_test_all_types"))); + { + // Some clients will use maps to represent a protobuf at + // runtime, this is not yet supported. + "chained_select_noncontainer_as_root_unsupported", + "nested_test_all_types.child.payload.standalone_message.bb", + {}, // not set + [](google::protobuf::Arena* arena, Activation& act) { + act.InsertOrAssignValue("nested_test_all_types", + cel::DurationValue(absl::Seconds(1))); + return absl::OkStatus(); + }, + + [](const absl::StatusOr& got) { + EXPECT_THAT(got.status(), + StatusIs(absl::StatusCode::kInvalidArgument)); + }, }, - }, - { - // Some clients will use maps to represent a protobuf message at - // runtime. This is not yet supported. - "chained_select_map_as_root_unsupported", - "nested_test_all_types.child.payload.standalone_message.bb", - {}, // not set - [](google::protobuf::Arena* arena, Activation& act) -> absl::Status { - auto builder = cel::NewMapValueBuilder(arena); - CEL_RETURN_IF_ERROR(builder->Put( - cel::StringValue::WrapUnsafe("child"), cel::NullValue())); - - auto value = std::move(*builder).Build(); - - act.InsertOrAssignValue("nested_test_all_types", - std::move(value)); - - return absl::OkStatus(); + { + "complex_select_success", + "((false)? a.child.child : b.child).child.payload.single_int64", + {{"a", ""}, + {"b", + R"pb( + child { child { payload { single_int64: -42 } } } + )pb"}}, + ActivationSetupFn(), + + [](const absl::StatusOr& got) { + ASSERT_OK_AND_ASSIGN(Value result, got); + ASSERT_TRUE(result->Is()) << result->DebugString(); + EXPECT_EQ(result.GetInt().NativeValue(), -42); + }, }, - - [](const absl::StatusOr& got) { - EXPECT_THAT(got.status(), - StatusIs(absl::StatusCode::kInvalidArgument)); + { + "chained_select_presence_present", + "has(nested_test_all_types.child.payload.standalone_message." + "bb)", + {{"nested_test_all_types", + R"pb( + child { payload { standalone_message { bb: 2 } } } + )pb"}}, + ActivationSetupFn(), + [](const absl::StatusOr& got) { + ASSERT_OK_AND_ASSIGN(Value result, got); + ASSERT_TRUE(result->Is()) << result->DebugString(); + EXPECT_TRUE(result.GetBool().NativeValue()); + }, }, - }, - { - // Some clients will use maps to represent a protobuf at runtime, - // this is not yet supported. - "chained_select_noncontainer_as_root_unsupported", - "nested_test_all_types.child.payload.standalone_message.bb", - {}, // not set - [](google::protobuf::Arena* arena, Activation& act) { - act.InsertOrAssignValue("nested_test_all_types", - cel::DurationValue(absl::Seconds(1))); - return absl::OkStatus(); + { + "chained_select_presence_not_present", + "has(nested_test_all_types.child.payload.standalone_message." + "bb)", + {{"nested_test_all_types", + R"pb( + child { payload { standalone_message {} } } + )pb"}}, + ActivationSetupFn(), + [](const absl::StatusOr& got) { + ASSERT_OK_AND_ASSIGN(Value result, got); + ASSERT_TRUE(result->Is()) << result->DebugString(); + EXPECT_FALSE(result.GetBool().NativeValue()); + }, }, - - [](const absl::StatusOr& got) { - EXPECT_THAT(got.status(), - StatusIs(absl::StatusCode::kInvalidArgument)); + { + "select_with_map_supported", + "nested_test_all_types.payload.map_string_message['$not_a_" + "field']." + "bb", + {{"nested_test_all_types", + R"pb( + payload { + map_string_message { + key: "$not_a_field", + value { bb: 5 } + } + } + )pb"}}, + ActivationSetupFn(), + + [](const absl::StatusOr& got) { + ASSERT_OK_AND_ASSIGN(Value result, got); + ASSERT_TRUE(result->Is()) << result->DebugString(); + EXPECT_EQ(result.GetInt().NativeValue(), 5); + }, }, - }, - { - "complex_select_success", - "((false)? a.child.child : b.child).child.payload.single_int64", - {{"a", ""}, - {"b", - R"pb( - child { child { payload { single_int64: -42 } } } - )pb"}}, - ActivationSetupFn(), - - [](const absl::StatusOr& got) { - ASSERT_OK_AND_ASSIGN(Value result, got); - ASSERT_TRUE(result->Is()) << result->DebugString(); - EXPECT_EQ(result.GetInt().NativeValue(), -42); + { + "select_with_map_no_such_key", + "nested_test_all_types.payload.map_string_message['$not_a_" + "field']." + "bb", + {{"nested_test_all_types", + R"pb( + payload { + map_string_message { + key: "a_different_field", + value { bb: 5 } + } + } + )pb"}}, + ActivationSetupFn(), + [](const absl::StatusOr& got) { + ASSERT_OK_AND_ASSIGN(Value result, got); + ASSERT_TRUE(result->Is()) + << result->DebugString(); + EXPECT_THAT(result.GetError().NativeValue(), + StatusIs(absl::StatusCode::kNotFound, + AllOf(HasSubstr("Key not found"), + HasSubstr("$not_a_field")))); + }, }, - }, - { - "chained_select_presence_present", - "has(nested_test_all_types.child.payload.standalone_message.bb)", - {{"nested_test_all_types", - R"pb( - child { payload { standalone_message { bb: 2 } } } - )pb"}}, - ActivationSetupFn(), - [](const absl::StatusOr& got) { - ASSERT_OK_AND_ASSIGN(Value result, got); - ASSERT_TRUE(result->Is()) << result->DebugString(); - EXPECT_TRUE(result.GetBool().NativeValue()); + { + "select_with_repeated_supported", + "nested_test_all_types.payload.repeated_nested_message[1].bb", + {{"nested_test_all_types", + R"pb( + payload { + repeated_nested_message {} + repeated_nested_message { bb: 7 } + } + )pb"}}, + ActivationSetupFn(), + [](const absl::StatusOr& got) { + ASSERT_OK_AND_ASSIGN(Value result, got); + ASSERT_TRUE(result->Is()) << result->DebugString(); + EXPECT_EQ(result.GetInt().NativeValue(), 7); + }, }, - }, - { - "chained_select_presence_not_present", - "has(nested_test_all_types.child.payload.standalone_message.bb)", - {{"nested_test_all_types", - R"pb( - child { payload { standalone_message {} } } - )pb"}}, - ActivationSetupFn(), - [](const absl::StatusOr& got) { - ASSERT_OK_AND_ASSIGN(Value result, got); - ASSERT_TRUE(result->Is()) << result->DebugString(); - EXPECT_FALSE(result.GetBool().NativeValue()); + { + "select_with_repeated_index_out_of_bounds", + "nested_test_all_types.payload.repeated_nested_message[1].bb", + {{"nested_test_all_types", + R"pb( + payload { repeated_nested_message {} } + )pb"}}, + ActivationSetupFn(), + [](const absl::StatusOr& got) { + ASSERT_OK_AND_ASSIGN(Value result, got); + ASSERT_TRUE(result->Is()) + << result->DebugString(); + EXPECT_THAT(result.GetError().NativeValue(), + StatusIs(absl::StatusCode::kInvalidArgument, + HasSubstr("index out of bounds"))); + }, }, - }, - { - "select_with_map_supported", - "nested_test_all_types.payload.map_string_message['$not_a_field']." - "bb", - {{"nested_test_all_types", - R"pb( - payload { - map_string_message { - key: "$not_a_field", - value { bb: 5 } - } - } - )pb"}}, - ActivationSetupFn(), - - [](const absl::StatusOr& got) { - ASSERT_OK_AND_ASSIGN(Value result, got); - ASSERT_TRUE(result->Is()) << result->DebugString(); - EXPECT_EQ(result.GetInt().NativeValue(), 5); + { + // -2^32, which would read element 0 if truncated to an int. + "select_with_repeated_index_beyond_int32", + "nested_test_all_types.payload.repeated_nested_message" + "[-4294967296].bb", + {{"nested_test_all_types", + R"pb( + payload { repeated_nested_message {} } + )pb"}}, + ActivationSetupFn(), + [](const absl::StatusOr& got) { + ASSERT_OK_AND_ASSIGN(Value result, got); + ASSERT_TRUE(result->Is()) + << result->DebugString(); + EXPECT_THAT(result.GetError().NativeValue(), + StatusIs(absl::StatusCode::kInvalidArgument)); + }, }, - }, - { - "select_with_map_no_such_key", - "nested_test_all_types.payload.map_string_message['$not_a_field']." - "bb", - {{"nested_test_all_types", - R"pb( - payload { - map_string_message { - key: "a_different_field", - value { bb: 5 } - } - } - )pb"}}, - ActivationSetupFn(), - [](const absl::StatusOr& got) { - ASSERT_OK_AND_ASSIGN(Value result, got); - ASSERT_TRUE(result->Is()) << result->DebugString(); - EXPECT_THAT(result.GetError().NativeValue(), - StatusIs(absl::StatusCode::kNotFound, - AllOf(HasSubstr("Key not found"), - HasSubstr("$not_a_field")))); + { + "unknown_field", + "((false)? a.child.child : b.child).child.payload.single_int64", + {{"a", ""}, + {"b", + R"pb( + child { child { payload { single_int64: -42 } } } + )pb"}}, + [](google::protobuf::Arena*, Activation& act) { + return act.SetUnknownPatterns({AttributePattern( + "b", {AttributeQualifierPattern::OfString("child"), + AttributeQualifierPattern::OfString("child")})}); + }, + + [](const absl::StatusOr& got) { + ASSERT_OK_AND_ASSIGN(Value result, got); + ASSERT_TRUE(result->Is()) + << result->DebugString(); + EXPECT_THAT( + result.GetUnknown().ToAttributeSet(), + ElementsAre(Eq(Attribute( + "b", { + AttributeQualifier::OfString("child"), + AttributeQualifier::OfString("child"), + AttributeQualifier::OfString("payload"), + AttributeQualifier::OfString("single_int64"), + })))); + }, }, - }, - { - "select_with_repeated_supported", - "nested_test_all_types.payload.repeated_nested_message[1].bb", - {{"nested_test_all_types", - R"pb( - payload { - repeated_nested_message {} - repeated_nested_message { bb: 7 } - } - )pb"}}, - ActivationSetupFn(), - [](const absl::StatusOr& got) { - ASSERT_OK_AND_ASSIGN(Value result, got); - ASSERT_TRUE(result->Is()) << result->DebugString(); - EXPECT_EQ(result.GetInt().NativeValue(), 7); + { + "unknown_field_partial", + "((false)? a.child.child : b.child).child.payload.single_int64", + {{"a", ""}, + {"b", + R"pb( + child { child { payload { single_int64: -42 } } } + )pb"}}, + [](google::protobuf::Arena*, Activation& act) { + return act.SetUnknownPatterns({AttributePattern( + "b", {AttributeQualifierPattern::OfString("child"), + AttributeQualifierPattern::OfString("child"), + AttributeQualifierPattern::OfString("child")})}); + }, + + [](const absl::StatusOr& got) { + ASSERT_OK_AND_ASSIGN(Value result, got); + ASSERT_TRUE(result->Is()) << result->DebugString(); + EXPECT_EQ(result.GetInt().NativeValue(), -42); + }, }, - }, - { - "select_with_repeated_index_out_of_bounds", - "nested_test_all_types.payload.repeated_nested_message[1].bb", - {{"nested_test_all_types", - R"pb( - payload { repeated_nested_message {} } - )pb"}}, - ActivationSetupFn(), - [](const absl::StatusOr& got) { - ASSERT_OK_AND_ASSIGN(Value result, got); - ASSERT_TRUE(result->Is()) << result->DebugString(); - EXPECT_THAT(result.GetError().NativeValue(), - StatusIs(absl::StatusCode::kInvalidArgument, - HasSubstr("index out of bounds"))); + { + "unknown_ident", + "((false)? a.child.child : b.child).child.payload.single_int64", + {{"a", ""}, + {"b", + R"pb( + child { child { payload { single_int64: -42 } } } + )pb"}}, + [](google::protobuf::Arena*, Activation& act) { + return act.SetUnknownPatterns({ + AttributePattern("b", {}), + }); + }, + + [](const absl::StatusOr& got) { + ASSERT_OK_AND_ASSIGN(Value result, got); + ASSERT_TRUE(result->Is()) + << result->DebugString(); + EXPECT_THAT(result.GetUnknown().ToAttributeSet(), + ElementsAre(Truly([](const Attribute& attr) { + return attr.variable_name() == "b"; + }))); + }, }, - }, - { - // -2^32, which would read element 0 if truncated to an int. - "select_with_repeated_index_beyond_int32", - "nested_test_all_types.payload.repeated_nested_message" - "[-4294967296].bb", - {{"nested_test_all_types", - R"pb( - payload { repeated_nested_message {} } - )pb"}}, - ActivationSetupFn(), - [](const absl::StatusOr& got) { - ASSERT_OK_AND_ASSIGN(Value result, got); - ASSERT_TRUE(result->Is()) << result->DebugString(); - EXPECT_THAT(result.GetError().NativeValue(), - StatusIs(absl::StatusCode::kInvalidArgument)); + { + "unknown_pruned", + "((false)? a.child.child : b.child).child.payload.single_int64", + {{"a", ""}, + {"b", + R"pb( + child { child { payload { single_int64: -42 } } } + )pb"}}, + [](google::protobuf::Arena*, Activation& act) { + return act.SetUnknownPatterns({ + AttributePattern("a", {}), + }); + }, + [](const absl::StatusOr& got) { + ASSERT_OK_AND_ASSIGN(Value result, got); + ASSERT_TRUE(result->Is()) << result->DebugString(); + EXPECT_EQ(result.GetInt().NativeValue(), -42); + }, }, - }, - { - "unknown_field", - "((false)? a.child.child : b.child).child.payload.single_int64", - {{"a", ""}, - {"b", - R"pb( - child { child { payload { single_int64: -42 } } } - )pb"}}, - [](google::protobuf::Arena*, Activation& act) { - return act.SetUnknownPatterns({AttributePattern( - "b", {AttributeQualifierPattern::OfString("child"), - AttributeQualifierPattern::OfString("child")})}); + { + "missing_field", + "custom_predicate(nested_test_all_types.child.payload." + "standalone_" + "message)", + {{"nested_test_all_types", + R"pb( + child { payload { standalone_message { bb: 20 } } } + )pb"}}, + [](google::protobuf::Arena*, Activation& act) { + return act.SetMissingPatterns({AttributePattern( + "nested_test_all_types", + { + AttributeQualifierPattern::OfString("child"), + })}); + }, + + [](const absl::StatusOr& got) { + ASSERT_OK_AND_ASSIGN(Value result, got); + ASSERT_TRUE(result->Is()) + << result->DebugString(); + EXPECT_THAT(result.GetError().NativeValue().message(), + HasSubstr("nested_test_all_types.child.payload." + "standalone_message")); + }, }, - - [](const absl::StatusOr& got) { - ASSERT_OK_AND_ASSIGN(Value result, got); - ASSERT_TRUE(result->Is()) << result->DebugString(); - EXPECT_THAT( - result.GetUnknown().ToAttributeSet(), - ElementsAre(Eq(Attribute( - "b", { - AttributeQualifier::OfString("child"), - AttributeQualifier::OfString("child"), - AttributeQualifier::OfString("payload"), - AttributeQualifier::OfString("single_int64"), - })))); + { + "missing_field_partial", + "custom_predicate(nested_test_all_types.child.payload." + "standalone_" + "message)", + {{"nested_test_all_types", + R"pb( + child { payload { standalone_message { bb: 20 } } } + )pb"}}, + [](google::protobuf::Arena*, Activation& act) { + return act.SetMissingPatterns({AttributePattern( + "b", {AttributeQualifierPattern::OfString("child"), + AttributeQualifierPattern::OfString("child")})}); + }, + + [](const absl::StatusOr& got) { + ASSERT_OK_AND_ASSIGN(Value result, got); + ASSERT_TRUE(result->Is()) << result->DebugString(); + EXPECT_TRUE(result.GetBool().NativeValue()); + }, }, - }, - { - "unknown_field_partial", - "((false)? a.child.child : b.child).child.payload.single_int64", - {{"a", ""}, - {"b", - R"pb( - child { child { payload { single_int64: -42 } } } - )pb"}}, - [](google::protobuf::Arena*, Activation& act) { - return act.SetUnknownPatterns({AttributePattern( - "b", {AttributeQualifierPattern::OfString("child"), - AttributeQualifierPattern::OfString("child"), - AttributeQualifierPattern::OfString("child")})}); + { + "select_wrapper_int_leaf", + "nested_test_all_types.payload.single_int64_wrapper", + {{"nested_test_all_types", + R"pb( + payload { single_int64_wrapper { value: 10 } } + )pb"}}, + nullptr, + [](const absl::StatusOr& got) { + ASSERT_OK_AND_ASSIGN(Value result, got); + ASSERT_TRUE(result->Is()) << result->DebugString(); + EXPECT_EQ(result.GetInt().NativeValue(), 10); + }, }, - - [](const absl::StatusOr& got) { - ASSERT_OK_AND_ASSIGN(Value result, got); - ASSERT_TRUE(result->Is()) << result->DebugString(); - EXPECT_EQ(result.GetInt().NativeValue(), -42); + { + "select_repeated_leaf", + "nested_test_all_types.payload.repeated_int64", + {{"nested_test_all_types", + R"pb( + payload { repeated_int64: 10 } + )pb"}}, + nullptr, + [](const absl::StatusOr& got) { + ASSERT_OK_AND_ASSIGN(Value result, got); + ASSERT_TRUE(result->Is()) << result->DebugString(); + }, }, - }, - { - "unknown_ident", - "((false)? a.child.child : b.child).child.payload.single_int64", - {{"a", ""}, - {"b", - R"pb( - child { child { payload { single_int64: -42 } } } - )pb"}}, - [](google::protobuf::Arena*, Activation& act) { - return act.SetUnknownPatterns({ - AttributePattern("b", {}), - }); + { + "select_map_leaf", + "nested_test_all_types.payload.map_string_int64", + {{"nested_test_all_types", + R"pb( + payload { map_string_int64 { key: "key", value: 12 } } + )pb"}}, + nullptr, + [](const absl::StatusOr& got) { + ASSERT_OK_AND_ASSIGN(Value result, got); + ASSERT_TRUE(result->Is()) << result->DebugString(); + }, }, - - [](const absl::StatusOr& got) { - ASSERT_OK_AND_ASSIGN(Value result, got); - ASSERT_TRUE(result->Is()) << result->DebugString(); - EXPECT_THAT(result.GetUnknown().ToAttributeSet(), - ElementsAre(Truly([](const Attribute& attr) { - return attr.variable_name() == "b"; - }))); + { + "select_with_map_dot", + "nested_test_all_types.payload.map_string_message.field_like_" + "key." + "bb", + {{"nested_test_all_types", + R"pb( + payload { + map_string_message { + key: "field_like_key", + value { bb: 42 } + } + } + )pb"}}, + nullptr, + [](const absl::StatusOr& got) { + ASSERT_OK_AND_ASSIGN(Value result, got); + ASSERT_TRUE(result->Is()) << result->DebugString(); + EXPECT_EQ(result.GetInt().NativeValue(), 42); + }, }, - }, - { - "unknown_pruned", - "((false)? a.child.child : b.child).child.payload.single_int64", - {{"a", ""}, - {"b", - R"pb( - child { child { payload { single_int64: -42 } } } - )pb"}}, - [](google::protobuf::Arena*, Activation& act) { - return act.SetUnknownPatterns({ - AttributePattern("a", {}), - }); + { + "select_with_map_bool", + "nested_test_all_types.payload.map_bool_message[false].bb", + {{"nested_test_all_types", + R"pb( + payload { + map_bool_message { + key: false, + value { bb: 42 } + } + } + )pb"}}, + nullptr, + [](const absl::StatusOr& got) { + ASSERT_OK_AND_ASSIGN(Value result, got); + ASSERT_TRUE(result->Is()) << result->DebugString(); + EXPECT_EQ(result.GetInt().NativeValue(), 42); + }, }, - [](const absl::StatusOr& got) { - ASSERT_OK_AND_ASSIGN(Value result, got); - ASSERT_TRUE(result->Is()) << result->DebugString(); - EXPECT_EQ(result.GetInt().NativeValue(), -42); + { + "select_with_map_int", + "nested_test_all_types.payload.map_int64_message[-1].bb", + {{"nested_test_all_types", + R"pb( + payload { + map_int64_message { + key: -1, + value { bb: 42 } + } + } + )pb"}}, + nullptr, + [](const absl::StatusOr& got) { + ASSERT_OK_AND_ASSIGN(Value result, got); + ASSERT_TRUE(result->Is()) << result->DebugString(); + EXPECT_EQ(result.GetInt().NativeValue(), 42); + }, }, - }, - { - "missing_field", - "custom_predicate(nested_test_all_types.child.payload.standalone_" - "message)", - {{"nested_test_all_types", - R"pb( - child { payload { standalone_message { bb: 20 } } } - )pb"}}, - [](google::protobuf::Arena*, Activation& act) { - return act.SetMissingPatterns({AttributePattern( - "nested_test_all_types", - { - AttributeQualifierPattern::OfString("child"), - })}); + { + "select_with_map_uint", + "nested_test_all_types.payload.map_uint64_message[1u].bb", + {{"nested_test_all_types", + R"pb( + payload { + map_uint64_message { + key: 1, + value { bb: 42 } + } + } + )pb"}}, + nullptr, + [](const absl::StatusOr& got) { + ASSERT_OK_AND_ASSIGN(Value result, got); + ASSERT_TRUE(result->Is()) << result->DebugString(); + EXPECT_EQ(result.GetInt().NativeValue(), 42); + }, }, - - [](const absl::StatusOr& got) { - ASSERT_OK_AND_ASSIGN(Value result, got); - ASSERT_TRUE(result->Is()) << result->DebugString(); - EXPECT_THAT(result.GetError().NativeValue().message(), - HasSubstr("nested_test_all_types.child.payload." - "standalone_message")); + { + "select_with_repeated", + "nested_test_all_types.payload.repeated_nested_message[1].bb", + {{"nested_test_all_types", + R"pb( + payload { + repeated_nested_message {} + repeated_nested_message { bb: 42 } + } + )pb"}}, + nullptr, + [](const absl::StatusOr& got) { + ASSERT_OK_AND_ASSIGN(Value result, got); + ASSERT_TRUE(result->Is()) << result->DebugString(); + EXPECT_EQ(result.GetInt().NativeValue(), 42); + }, }, - }, - { - "missing_field_partial", - "custom_predicate(nested_test_all_types.child.payload.standalone_" - "message)", - {{"nested_test_all_types", - R"pb( - child { payload { standalone_message { bb: 20 } } } - )pb"}}, - [](google::protobuf::Arena*, Activation& act) { - return act.SetMissingPatterns({AttributePattern( - "b", {AttributeQualifierPattern::OfString("child"), - AttributeQualifierPattern::OfString("child")})}); + { + "select_with_any", + "nested_test_all_types.payload.single_any.single_int64", + {{"nested_test_all_types", + R"pb( + payload { + single_any { + [type.googleapis.com/cel.expr.conformance.proto2 + .TestAllTypes] { single_int64: 42 } + } + } + )pb"}}, + nullptr, + [](const absl::StatusOr& got) { + ASSERT_OK_AND_ASSIGN(Value result, got); + ASSERT_TRUE(result->Is()) << result->DebugString(); + EXPECT_EQ(result.GetInt().NativeValue(), 42); + }, }, - - [](const absl::StatusOr& got) { - ASSERT_OK_AND_ASSIGN(Value result, got); - ASSERT_TRUE(result->Is()) << result->DebugString(); - EXPECT_TRUE(result.GetBool().NativeValue()); + { + "has_repeated_leaf_true", + "has(nested_test_all_types.payload.repeated_int64)", + {{"nested_test_all_types", + R"pb( + payload { repeated_int64: 42 } + )pb"}}, + nullptr, + [](const absl::StatusOr& got) { + ASSERT_OK_AND_ASSIGN(Value result, got); + ASSERT_TRUE(result->Is()) << result->DebugString(); + EXPECT_TRUE(result.GetBool().NativeValue()); + }, }, - }, - { - "select_wrapper_int_leaf", - "nested_test_all_types.payload.single_int64_wrapper", - {{"nested_test_all_types", - R"pb( - payload { single_int64_wrapper { value: 10 } } - )pb"}}, - nullptr, - [](const absl::StatusOr& got) { - ASSERT_OK_AND_ASSIGN(Value result, got); - ASSERT_TRUE(result->Is()) << result->DebugString(); - EXPECT_EQ(result.GetInt().NativeValue(), 10); + { + "has_repeated_leaf_false", + "has(nested_test_all_types.payload.repeated_int64)", + {{"nested_test_all_types", + R"pb( + payload {} + )pb"}}, + nullptr, + [](const absl::StatusOr& got) { + ASSERT_OK_AND_ASSIGN(Value result, got); + ASSERT_TRUE(result->Is()) << result->DebugString(); + EXPECT_FALSE(result.GetBool().NativeValue()); + }, }, - }, - { - "select_repeated_leaf", - "nested_test_all_types.payload.repeated_int64", - {{"nested_test_all_types", - R"pb( - payload { repeated_int64: 10 } - )pb"}}, - nullptr, - [](const absl::StatusOr& got) { - ASSERT_OK_AND_ASSIGN(Value result, got); - ASSERT_TRUE(result->Is()) << result->DebugString(); + { + "has_map_leaf_true", + "has(nested_test_all_types.payload.map_string_int64)", + {{"nested_test_all_types", + R"pb( + payload { map_string_int64 { key: "string" value: 12 } } + )pb"}}, + nullptr, + [](const absl::StatusOr& got) { + ASSERT_OK_AND_ASSIGN(Value result, got); + ASSERT_TRUE(result->Is()) << result->DebugString(); + EXPECT_TRUE(result.GetBool().NativeValue()); + }, }, - }, - { - "select_map_leaf", - "nested_test_all_types.payload.map_string_int64", - {{"nested_test_all_types", - R"pb( - payload { map_string_int64 { key: "key", value: 12 } } - )pb"}}, - nullptr, - [](const absl::StatusOr& got) { - ASSERT_OK_AND_ASSIGN(Value result, got); - ASSERT_TRUE(result->Is()) << result->DebugString(); + { + "has_map_leaf_false", + "has(nested_test_all_types.payload.map_string_int64)", + {{"nested_test_all_types", + R"pb( + payload {} + )pb"}}, + nullptr, + [](const absl::StatusOr& got) { + ASSERT_OK_AND_ASSIGN(Value result, got); + ASSERT_TRUE(result->Is()) << result->DebugString(); + EXPECT_FALSE(result.GetBool().NativeValue()); + }, }, - }, - { - "select_with_map_dot", - "nested_test_all_types.payload.map_string_message.field_like_key." - "bb", - {{"nested_test_all_types", - R"pb( - payload { - map_string_message { - key: "field_like_key", - value { bb: 42 } - } - } - )pb"}}, - nullptr, - [](const absl::StatusOr& got) { - ASSERT_OK_AND_ASSIGN(Value result, got); - ASSERT_TRUE(result->Is()) << result->DebugString(); - EXPECT_EQ(result.GetInt().NativeValue(), 42); + { + "has_map_field_like_key", + "has(nested_test_all_types.payload.map_string_int64.field_like_" + "key)", + {{"nested_test_all_types", + R"pb( + payload { + map_string_int64 { key: "field_like_key" value: 12 } + } + )pb"}}, + nullptr, + [](const absl::StatusOr& got) { + ASSERT_OK_AND_ASSIGN(Value result, got); + ASSERT_TRUE(result->Is()) << result->DebugString(); + EXPECT_TRUE(result.GetBool().NativeValue()); + }, }, - }, - { - "select_with_map_bool", - "nested_test_all_types.payload.map_bool_message[false].bb", - {{"nested_test_all_types", - R"pb( - payload { - map_bool_message { - key: false, - value { bb: 42 } - } - } - )pb"}}, - nullptr, - [](const absl::StatusOr& got) { - ASSERT_OK_AND_ASSIGN(Value result, got); - ASSERT_TRUE(result->Is()) << result->DebugString(); - EXPECT_EQ(result.GetInt().NativeValue(), 42); + { + "has_map_field_like_key_false", + "has(nested_test_all_types.payload.map_string_int64.field_like_" + "key)", + {{"nested_test_all_types", + R"pb( + payload { map_string_int64 { key: "wrong_key" value: 12 } } + )pb"}}, + nullptr, + [](const absl::StatusOr& got) { + ASSERT_OK_AND_ASSIGN(Value result, got); + ASSERT_TRUE(result->Is()) << result->DebugString(); + EXPECT_FALSE(result.GetBool().NativeValue()); + }, }, - }, - { - "select_with_map_int", - "nested_test_all_types.payload.map_int64_message[-1].bb", - {{"nested_test_all_types", - R"pb( - payload { - map_int64_message { - key: -1, - value { bb: 42 } - } - } - )pb"}}, - nullptr, - [](const absl::StatusOr& got) { - ASSERT_OK_AND_ASSIGN(Value result, got); - ASSERT_TRUE(result->Is()) << result->DebugString(); - EXPECT_EQ(result.GetInt().NativeValue(), 42); + { + "select_wrong_runtime_type", + "test_all_types.single_int64", + {{}}, + [](google::protobuf::Arena* arena, Activation& activation) { + activation.InsertOrAssignValue("test_all_types", + cel::IntValue(42)); + return absl::OkStatus(); + }, + [](const absl::StatusOr& got) { + EXPECT_THAT(got, StatusIs(absl::StatusCode::kInvalidArgument, + HasSubstr("Expected struct type"))); + }, }, - }, - { - "select_with_map_uint", - "nested_test_all_types.payload.map_uint64_message[1u].bb", - {{"nested_test_all_types", - R"pb( - payload { - map_uint64_message { - key: 1, - value { bb: 42 } - } - } - )pb"}}, - nullptr, - [](const absl::StatusOr& got) { - ASSERT_OK_AND_ASSIGN(Value result, got); - ASSERT_TRUE(result->Is()) << result->DebugString(); - EXPECT_EQ(result.GetInt().NativeValue(), 42); + { + "select_with_struct", + "nested_test_all_types.payload.single_struct['key']['subkey']", + {{"nested_test_all_types", + R"pb(payload { + single_struct { + fields { + key: "key" + value { + struct_value { + fields { + key: "subkey" + value { bool_value: true } + } + } + } + } + } + })pb"}}, + nullptr, + [](const absl::StatusOr& got) { + ASSERT_OK_AND_ASSIGN(Value result, got); + ASSERT_TRUE(result->Is()) << result->DebugString(); + EXPECT_TRUE(result.GetBool().NativeValue()); + }, + }, + { + "select_with_list_value", + "nested_test_all_types.payload.list_value[0]['subkey']", + {{"nested_test_all_types", + R"pb(payload { + list_value { + values { + struct_value { + fields { + key: "subkey" + value { bool_value: true } + } + } + } + } + })pb"}}, + nullptr, + [](const absl::StatusOr& got) { + ASSERT_OK_AND_ASSIGN(Value result, got); + ASSERT_TRUE(result->Is()) << result->DebugString(); + EXPECT_TRUE(result.GetBool().NativeValue()); + }, }, - }, - { - "select_with_repeated", - "nested_test_all_types.payload.repeated_nested_message[1].bb", - {{"nested_test_all_types", - R"pb( - payload { - repeated_nested_message {} - repeated_nested_message { bb: 42 } - } - )pb"}}, - nullptr, - [](const absl::StatusOr& got) { - ASSERT_OK_AND_ASSIGN(Value result, got); - ASSERT_TRUE(result->Is()) << result->DebugString(); - EXPECT_EQ(result.GetInt().NativeValue(), 42); + { + "select_with_value", + "nested_test_all_types.payload.single_value['key']['subkey']", + {{"nested_test_all_types", + R"pb(payload { + single_value { + struct_value { + fields { + key: "key" + value { + struct_value { + fields { + key: "subkey" + value { bool_value: true } + } + } + } + } + } + } + })pb"}}, + nullptr, + [](const absl::StatusOr& got) { + ASSERT_OK_AND_ASSIGN(Value result, got); + ASSERT_TRUE(result->Is()) << result->DebugString(); + EXPECT_TRUE(result.GetBool().NativeValue()); + }, }, - }, - { - "select_with_any", - "nested_test_all_types.payload.single_any.single_int64", - {{"nested_test_all_types", - R"pb( - payload { - single_any { - [type.googleapis.com/cel.expr.conformance.proto2 - .TestAllTypes] { single_int64: 42 } - } - } - )pb"}}, - nullptr, - [](const absl::StatusOr& got) { - ASSERT_OK_AND_ASSIGN(Value result, got); - ASSERT_TRUE(result->Is()) << result->DebugString(); - EXPECT_EQ(result.GetInt().NativeValue(), 42); + { + "select_with_map_int32", + "nested_test_all_types.payload.map_int32_message[-1].bb", + {{"nested_test_all_types", + R"pb( + payload { + map_int32_message { + key: -1, + value { bb: 42 } + } + } + )pb"}}, + nullptr, + [](const absl::StatusOr& got) { + ASSERT_OK_AND_ASSIGN(Value result, got); + ASSERT_TRUE(result->Is()) << result->DebugString(); + EXPECT_EQ(result.GetInt().NativeValue(), 42); + }, }, - }, - { - "has_repeated_leaf_true", - "has(nested_test_all_types.payload.repeated_int64)", - {{"nested_test_all_types", - R"pb( - payload { repeated_int64: 42 } - )pb"}}, - nullptr, - [](const absl::StatusOr& got) { - ASSERT_OK_AND_ASSIGN(Value result, got); - ASSERT_TRUE(result->Is()) << result->DebugString(); - EXPECT_TRUE(result.GetBool().NativeValue()); + { + "select_with_map_int32_overflow", + "nested_test_all_types.payload.map_int32_message[2147483648]." + "bb", + {{"nested_test_all_types", + R"pb( + payload { + map_int32_message { + key: 1, + value { bb: 42 } + } + } + )pb"}}, + nullptr, + [](const absl::StatusOr& got) { + ASSERT_OK_AND_ASSIGN(Value result, got); + ASSERT_TRUE(result->Is()) + << result->DebugString(); + EXPECT_THAT(result.GetError().NativeValue(), + testing::AnyOf( + StatusIs(absl::StatusCode::kOutOfRange, + HasSubstr("integer overflow")), + StatusIs(absl::StatusCode::kInvalidArgument, + HasSubstr("Invalid map key type")))); + }, }, - }, - { - "has_repeated_leaf_false", - "has(nested_test_all_types.payload.repeated_int64)", - {{"nested_test_all_types", - R"pb( - payload {} - )pb"}}, - nullptr, - [](const absl::StatusOr& got) { - ASSERT_OK_AND_ASSIGN(Value result, got); - ASSERT_TRUE(result->Is()) << result->DebugString(); - EXPECT_FALSE(result.GetBool().NativeValue()); + { + "select_with_map_int32_underflow", + "nested_test_all_types.payload.map_int32_message[-2147483649]." + "bb", + {{"nested_test_all_types", + R"pb( + payload { + map_int32_message { + key: -1, + value { bb: 42 } + } + } + )pb"}}, + nullptr, + [](const absl::StatusOr& got) { + ASSERT_OK_AND_ASSIGN(Value result, got); + ASSERT_TRUE(result->Is()) + << result->DebugString(); + EXPECT_THAT(result.GetError().NativeValue(), + testing::AnyOf( + StatusIs(absl::StatusCode::kOutOfRange, + HasSubstr("integer overflow")), + StatusIs(absl::StatusCode::kInvalidArgument, + HasSubstr("Invalid map key type")))); + }, }, - }, - { - "has_map_leaf_true", - "has(nested_test_all_types.payload.map_string_int64)", - {{"nested_test_all_types", - R"pb( - payload { map_string_int64 { key: "string" value: 12 } } - )pb"}}, - nullptr, - [](const absl::StatusOr& got) { - ASSERT_OK_AND_ASSIGN(Value result, got); - ASSERT_TRUE(result->Is()) << result->DebugString(); - EXPECT_TRUE(result.GetBool().NativeValue()); + { + "select_with_map_uint32", + "nested_test_all_types.payload.map_uint32_message[1u].bb", + {{"nested_test_all_types", + R"pb( + payload { + map_uint32_message { + key: 1, + value { bb: 42 } + } + } + )pb"}}, + nullptr, + [](const absl::StatusOr& got) { + ASSERT_OK_AND_ASSIGN(Value result, got); + ASSERT_TRUE(result->Is()) << result->DebugString(); + EXPECT_EQ(result.GetInt().NativeValue(), 42); + }, }, - }, - { - "has_map_leaf_false", - "has(nested_test_all_types.payload.map_string_int64)", - {{"nested_test_all_types", - R"pb( - payload {} - )pb"}}, - nullptr, - [](const absl::StatusOr& got) { - ASSERT_OK_AND_ASSIGN(Value result, got); - ASSERT_TRUE(result->Is()) << result->DebugString(); - EXPECT_FALSE(result.GetBool().NativeValue()); + { + "select_with_map_uint32_overflow", + "nested_test_all_types.payload.map_uint32_message[4294967296u]." + "bb", + {{"nested_test_all_types", + R"pb( + payload { + map_uint32_message { + key: 1, + value { bb: 42 } + } + } + )pb"}}, + nullptr, + [](const absl::StatusOr& got) { + ASSERT_OK_AND_ASSIGN(Value result, got); + ASSERT_TRUE(result->Is()) + << result->DebugString(); + EXPECT_THAT(result.GetError().NativeValue(), + testing::AnyOf( + StatusIs(absl::StatusCode::kOutOfRange, + HasSubstr("unsigned integer over")), + StatusIs(absl::StatusCode::kInvalidArgument, + HasSubstr("Invalid map key type")))); + }, }, - }, - { - "has_map_field_like_key", - "has(nested_test_all_types.payload.map_string_int64.field_like_" - "key)", - {{"nested_test_all_types", - R"pb( - payload { map_string_int64 { key: "field_like_key" value: 12 } } - )pb"}}, - nullptr, - [](const absl::StatusOr& got) { - ASSERT_OK_AND_ASSIGN(Value result, got); - ASSERT_TRUE(result->Is()) << result->DebugString(); - EXPECT_TRUE(result.GetBool().NativeValue()); + { + "select_map_element_leaf", + "nested_test_all_types.payload.map_string_int64['key']", + {{"nested_test_all_types", + R"pb( + payload { map_string_int64 { key: "key", value: 12 } } + )pb"}}, + nullptr, + [](const absl::StatusOr& got) { + ASSERT_OK_AND_ASSIGN(Value result, got); + ASSERT_TRUE(result->Is()) << result->DebugString(); + EXPECT_EQ(result.GetInt().NativeValue(), 12); + }, }, - }, - { - "has_map_field_like_key_false", - "has(nested_test_all_types.payload.map_string_int64.field_like_" - "key)", - {{"nested_test_all_types", - R"pb( - payload { map_string_int64 { key: "wrong_key" value: 12 } } - )pb"}}, - nullptr, - [](const absl::StatusOr& got) { - ASSERT_OK_AND_ASSIGN(Value result, got); - ASSERT_TRUE(result->Is()) << result->DebugString(); - EXPECT_FALSE(result.GetBool().NativeValue()); + { + "select_map_dot_leaf", + "nested_test_all_types.payload.map_string_int64.field_like_key", + {{"nested_test_all_types", + R"pb( + payload { + map_string_int64 { key: "field_like_key", value: 12 } + } + )pb"}}, + nullptr, + [](const absl::StatusOr& got) { + ASSERT_OK_AND_ASSIGN(Value result, got); + ASSERT_TRUE(result->Is()) << result->DebugString(); + EXPECT_EQ(result.GetInt().NativeValue(), 12); + }, }, - }, - { - "select_wrong_runtime_type", - "test_all_types.single_int64", - {{}}, - [](google::protobuf::Arena* arena, Activation& activation) { - activation.InsertOrAssignValue("test_all_types", - cel::IntValue(42)); - return absl::OkStatus(); + { + "select_repeated_element_leaf", + "nested_test_all_types.payload.repeated_int64[1]", + {{"nested_test_all_types", + R"pb( + payload { repeated_int64: 10 repeated_int64: 42 } + )pb"}}, + nullptr, + [](const absl::StatusOr& got) { + ASSERT_OK_AND_ASSIGN(Value result, got); + ASSERT_TRUE(result->Is()) << result->DebugString(); + EXPECT_EQ(result.GetInt().NativeValue(), 42); + }, }, - [](const absl::StatusOr& got) { - EXPECT_THAT(got, StatusIs(absl::StatusCode::kInvalidArgument, - HasSubstr("Expected struct type"))); + { + "select_with_repeated_negative_index", + "nested_test_all_types.payload.repeated_nested_message[-1].bb", + {{"nested_test_all_types", + R"pb( + payload { repeated_nested_message { bb: 42 } } + )pb"}}, + nullptr, + [](const absl::StatusOr& got) { + ASSERT_OK_AND_ASSIGN(Value result, got); + ASSERT_TRUE(result->Is()) + << result->DebugString(); + EXPECT_THAT(result.GetError().NativeValue(), + StatusIs(absl::StatusCode::kInvalidArgument)); + }, }, - }, - { - "select_with_struct", - "nested_test_all_types.payload.single_struct['key']['subkey']", - {{"nested_test_all_types", - R"pb(payload { - single_struct { - fields { - key: "key" - value { - struct_value { + { + "select_with_map_struct", + "nested_test_all_types.payload.map_string_struct['key']['" + "subkey']", + {{"nested_test_all_types", + R"pb(payload { + map_string_struct { + key: "key" + value { fields { key: "subkey" value { bool_value: true } } } } - } - } - })pb"}}, - nullptr, - [](const absl::StatusOr& got) { - ASSERT_OK_AND_ASSIGN(Value result, got); - ASSERT_TRUE(result->Is()) << result->DebugString(); - EXPECT_TRUE(result.GetBool().NativeValue()); + })pb"}}, + nullptr, + [](const absl::StatusOr& got) { + ASSERT_OK_AND_ASSIGN(Value result, got); + ASSERT_TRUE(result->Is()) << result->DebugString(); + EXPECT_TRUE(result.GetBool().NativeValue()); + }, + }, + { + "select_with_map_list_value", + "nested_test_all_types.payload.map_string_list_value['key'][0][" + "'subkey']", + {{"nested_test_all_types", + R"pb(payload { + map_string_list_value { + key: "key" + value { + values { + struct_value { + fields { + key: "subkey" + value { bool_value: true } + } + } + } + } + } + })pb"}}, + nullptr, + [](const absl::StatusOr& got) { + ASSERT_OK_AND_ASSIGN(Value result, got); + ASSERT_TRUE(result->Is()) << result->DebugString(); + EXPECT_TRUE(result.GetBool().NativeValue()); + }, + }, + { + "select_with_map_value", + "nested_test_all_types.payload.map_string_value['key']['subkey'" + "]", + {{"nested_test_all_types", + R"pb(payload { + map_string_value { + key: "key" + value { + struct_value { + fields { + key: "subkey" + value { bool_value: true } + } + } + } + } + })pb"}}, + nullptr, + [](const absl::StatusOr& got) { + ASSERT_OK_AND_ASSIGN(Value result, got); + ASSERT_TRUE(result->Is()) << result->DebugString(); + EXPECT_TRUE(result.GetBool().NativeValue()); + }, }, - }, - { - "select_with_list_value", - "nested_test_all_types.payload.list_value[0]['subkey']", - {{"nested_test_all_types", - R"pb(payload { - list_value { - values { - struct_value { + { + "select_with_repeated_struct", + "nested_test_all_types.payload.repeated_struct[0]['subkey']", + {{"nested_test_all_types", + R"pb(payload { + repeated_struct { fields { key: "subkey" value { bool_value: true } } } - } - } - })pb"}}, - nullptr, - [](const absl::StatusOr& got) { - ASSERT_OK_AND_ASSIGN(Value result, got); - ASSERT_TRUE(result->Is()) << result->DebugString(); - EXPECT_TRUE(result.GetBool().NativeValue()); + })pb"}}, + nullptr, + [](const absl::StatusOr& got) { + ASSERT_OK_AND_ASSIGN(Value result, got); + ASSERT_TRUE(result->Is()) << result->DebugString(); + EXPECT_TRUE(result.GetBool().NativeValue()); + }, }, - }, - { - "select_with_value", - "nested_test_all_types.payload.single_value['key']['subkey']", - {{"nested_test_all_types", - R"pb(payload { - single_value { - struct_value { - fields { - key: "key" - value { + { + "select_with_repeated_list_value", + "nested_test_all_types.payload.repeated_list_value[0][0]['" + "subkey']", + {{"nested_test_all_types", + R"pb(payload { + repeated_list_value { + values { struct_value { fields { key: "subkey" @@ -1773,20 +2351,58 @@ INSTANTIATE_TEST_SUITE_P( } } } - } - } - })pb"}}, - nullptr, - [](const absl::StatusOr& got) { - ASSERT_OK_AND_ASSIGN(Value result, got); - ASSERT_TRUE(result->Is()) << result->DebugString(); - EXPECT_TRUE(result.GetBool().NativeValue()); + })pb"}}, + nullptr, + [](const absl::StatusOr& got) { + ASSERT_OK_AND_ASSIGN(Value result, got); + ASSERT_TRUE(result->Is()) << result->DebugString(); + EXPECT_TRUE(result.GetBool().NativeValue()); + }, }, - }, - }), - - [](const testing::TestParamInfo& info) { - return info.param.case_name; + { + "select_with_repeated_value", + "nested_test_all_types.payload.repeated_value[0]['subkey']", + {{"nested_test_all_types", + R"pb(payload { + repeated_value { + struct_value { + fields { + key: "subkey" + value { bool_value: true } + } + } + } + })pb"}}, + nullptr, + [](const absl::StatusOr& got) { + ASSERT_OK_AND_ASSIGN(Value result, got); + ASSERT_TRUE(result->Is()) << result->DebugString(); + EXPECT_TRUE(result.GetBool().NativeValue()); + }, + }, + { + "select_with_bad_any_fallback", + "nested_test_all_types.payload.single_any['subkey']", + {{"nested_test_all_types", + R"pb(payload { + single_any { + type_url: "type.googleapis.com/UnknownType" + value: "invalid" + } + })pb"}}, + nullptr, + [](const absl::StatusOr& got) { + ASSERT_OK_AND_ASSIGN(Value result, got); + ASSERT_TRUE(result->Is()) + << result->DebugString(); + }, + }, + }), + testing::Bool()), + [](const testing::TestParamInfo< + SelectOptimizationProgramOptimizerTest::ParamType>& info) { + return absl::StrCat(std::get<0>(info.param).case_name, "_", + std::get<1>(info.param) ? "cached" : "uncached"); }); // Tests covering unexpected / malformed ASTs. @@ -1831,6 +2447,18 @@ class SelectOptimizationUnexpectedAstTest : public SelectOptimizationTest { return result; } + Expr NextUint(uint64_t value) { + Expr result = NextExpr(); + result.mutable_const_expr().set_uint64_value(value); + return result; + } + + Expr NextBool(bool value) { + Expr result = NextExpr(); + result.mutable_const_expr().set_bool_value(value); + return result; + } + // Plans `fn(ident, args...)` with only the select program optimizer. template absl::Status PlanSelectCall(absl::string_view fn, Exprs... args) { @@ -1844,6 +2472,41 @@ class SelectOptimizationUnexpectedAstTest : public SelectOptimizationTest { return builder.CreateExpressionImpl(std::move(ast), nullptr).status(); } + // Helper for testing malformed / type mismatched optimized select chains. + absl::StatusOr EvalTypedSelectCall(absl::string_view fn, + Expr select_list, + const NestedTestAllTypes& msg) { + auto ast = std::make_unique(NextExpr(), SourceInfo()); + CallExpr& call = ast->mutable_root_expr().mutable_call_expr(); + call.set_function(fn); + Expr ident = NextExpr(); + int64_t ident_id = ident.id(); + ident.mutable_ident_expr().set_name("ident"); + call.mutable_args().push_back(std::move(ident)); + call.mutable_args().push_back(std::move(select_list)); + ast->mutable_type_map()[ident_id] = TypeSpec( + MessageTypeSpec("cel.expr.conformance.proto2.NestedTestAllTypes")); + + SelectOptimizationOptions select_options; + select_options.enable_field_descriptor_caching = true; + FlatExprBuilder builder(env_, runtime_options_); + builder.AddProgramOptimizer( + CreateSelectOptimizationProgramOptimizer(select_options)); + CEL_ASSIGN_OR_RETURN(FlatExpression plan, + builder.CreateExpressionImpl(std::move(ast), nullptr)); + + cel::Activation act; + google::protobuf::Message* var = + runtime_internal::MakeTestingEnvDynamicProto(msg, &arena_); + act.InsertOrAssignValue("ident", cel::ParsedMessageValue(var, &arena_)); + google::api::expr::runtime::FlatExpressionEvaluatorState state = + plan.MakeEvaluatorState(env_->descriptor_pool.get(), + env_->MutableMessageFactory(), &arena_); + return plan.EvaluateWithCallback( + act, /*embedder_context=*/nullptr, + google::api::expr::runtime::EvaluationListener(), state); + } + protected: int64_t next_id_; }; @@ -2040,5 +2703,275 @@ TEST_F(SelectOptimizationUnexpectedAstTest, HasSubstr("Invalid cel.attribute select list"))); } +TEST_F(SelectOptimizationUnexpectedAstTest, CachedFieldDescriptorMapKeyErrors) { + NestedTestAllTypes msg; + + // map (field 63) with int key + { + ASSERT_OK_AND_ASSIGN( + Value result, + EvalTypedSelectCall( + kCelAttribute, + NextList(NextList(NextInt(2), NextString("payload")), + NextList(NextInt(63), NextString("map_bool_bool")), + NextInt(1)), + msg)); + ASSERT_TRUE(result->Is()) << result->DebugString(); + EXPECT_THAT(result.GetError().NativeValue(), + StatusIs(absl::StatusCode::kInvalidArgument, + HasSubstr("Invalid map key type"))); + } + + // map (field 77) with string key + { + ASSERT_OK_AND_ASSIGN( + Value result, + EvalTypedSelectCall( + kCelAttribute, + NextList(NextList(NextInt(2), NextString("payload")), + NextList(NextInt(77), NextString("map_int32_int32")), + NextString("bad_key")), + msg)); + ASSERT_TRUE(result->Is()) << result->DebugString(); + EXPECT_THAT(result.GetError().NativeValue(), + StatusIs(absl::StatusCode::kInvalidArgument, + HasSubstr("Invalid map key type"))); + } + + // map (field 89) with bool key + { + ASSERT_OK_AND_ASSIGN( + Value result, + EvalTypedSelectCall( + kCelAttribute, + NextList(NextList(NextInt(2), NextString("payload")), + NextList(NextInt(89), NextString("map_int64_int64")), + NextBool(true)), + msg)); + ASSERT_TRUE(result->Is()) << result->DebugString(); + EXPECT_THAT(result.GetError().NativeValue(), + StatusIs(absl::StatusCode::kInvalidArgument, + HasSubstr("Invalid map key type"))); + } + + // map (field 201) with int key + { + ASSERT_OK_AND_ASSIGN( + Value result, + EvalTypedSelectCall( + kCelAttribute, + NextList(NextList(NextInt(2), NextString("payload")), + NextList(NextInt(201), NextString("map_uint32_uint32")), + NextInt(1)), + msg)); + ASSERT_TRUE(result->Is()) << result->DebugString(); + EXPECT_THAT(result.GetError().NativeValue(), + StatusIs(absl::StatusCode::kInvalidArgument, + HasSubstr("Invalid map key type"))); + } + + // map (field 213) with int key + { + ASSERT_OK_AND_ASSIGN( + Value result, + EvalTypedSelectCall( + kCelAttribute, + NextList(NextList(NextInt(2), NextString("payload")), + NextList(NextInt(213), NextString("map_uint64_uint64")), + NextInt(1)), + msg)); + ASSERT_TRUE(result->Is()) << result->DebugString(); + EXPECT_THAT(result.GetError().NativeValue(), + StatusIs(absl::StatusCode::kInvalidArgument, + HasSubstr("Invalid map key type"))); + } + + // map (field 61) with int key + { + ASSERT_OK_AND_ASSIGN( + Value result, + EvalTypedSelectCall( + kCelAttribute, + NextList(NextList(NextInt(2), NextString("payload")), + NextList(NextInt(61), NextString("map_string_string")), + NextInt(1)), + msg)); + ASSERT_TRUE(result->Is()) << result->DebugString(); + EXPECT_THAT(result.GetError().NativeValue(), + StatusIs(absl::StatusCode::kInvalidArgument, + HasSubstr("Invalid map key type"))); + } +} + +TEST_F(SelectOptimizationUnexpectedAstTest, + CachedFieldDescriptorMapPresenceErrors) { + NestedTestAllTypes msg; + + // has(ident.payload.map_int64_int64[1]) -> non-string qualifier for has() + { + ASSERT_OK_AND_ASSIGN( + Value result, + EvalTypedSelectCall( + kCelHasField, + NextList(NextList(NextInt(2), NextString("payload")), + NextList(NextInt(89), NextString("map_int64_int64")), + NextInt(1)), + msg)); + ASSERT_TRUE(result->Is()) << result->DebugString(); + EXPECT_THAT(result.GetError().NativeValue(), + StatusIs(absl::StatusCode::kUnknown, + HasSubstr("No matching overloads found : has"))); + } + + // has(ident.payload.map_int64_int64.key) -> string qualifier on int64 map + { + ASSERT_OK_AND_ASSIGN( + Value result, + EvalTypedSelectCall( + kCelHasField, + NextList(NextList(NextInt(2), NextString("payload")), + NextList(NextInt(89), NextString("map_int64_int64")), + NextString("key")), + msg)); + ASSERT_TRUE(result->Is()) << result->DebugString(); + EXPECT_THAT(result.GetError().NativeValue(), + StatusIs(absl::StatusCode::kInvalidArgument, + HasSubstr("Invalid map key type"))); + } +} + +TEST_F(SelectOptimizationUnexpectedAstTest, CachedFieldDescriptorListErrors) { + NestedTestAllTypes msg; + + // has(ident.payload.repeated_int32[0]) -> presence test on list element + { + ASSERT_OK_AND_ASSIGN( + Value result, + EvalTypedSelectCall( + kCelHasField, + NextList(NextList(NextInt(2), NextString("payload")), + NextList(NextInt(31), NextString("repeated_int32")), + NextInt(0)), + msg)); + ASSERT_TRUE(result->Is()) << result->DebugString(); + EXPECT_THAT(result.GetError().NativeValue(), + StatusIs(absl::StatusCode::kUnknown, + HasSubstr("No matching overloads found : has"))); + } + + // ident.payload.repeated_int32['bad_index'] -> non-int64 list index + { + ASSERT_OK_AND_ASSIGN( + Value result, + EvalTypedSelectCall( + kCelAttribute, + NextList(NextList(NextInt(2), NextString("payload")), + NextList(NextInt(31), NextString("repeated_int32")), + NextString("bad_index")), + msg)); + ASSERT_TRUE(result->Is()) << result->DebugString(); + EXPECT_THAT(result.GetError().NativeValue(), + StatusIs(absl::StatusCode::kUnknown, + HasSubstr("No matching overloads found : _[_]"))); + } +} + +// Tests for overlay pool with cross-pool extensions. +// We specifically recommend against doing this, but some clients do it. +// Behavior is not totally consistent with normal select implementation so +// this test is just confirming we don't crash or return garbage. +TEST_F(SelectOptimizationTest, + CachedFieldDescriptorCrossPoolExtensionFallback) { + auto overlay_pool = std::make_shared( + google::protobuf::DescriptorPool::generated_pool()); + ASSERT_NE( + overlay_pool->BuildFile(test::GetTestExternalExtensionsFileDescriptor()), + nullptr); + + CompilerOptions compiler_options; + compiler_options.parser_options.enable_quoted_identifiers = true; + ASSERT_OK_AND_ASSIGN(std::unique_ptr compiler_builder, + cel::NewCompilerBuilder(overlay_pool, compiler_options)); + ASSERT_THAT(compiler_builder->AddLibrary(cel::StandardCompilerLibrary()), + IsOk()); + const google::protobuf::Descriptor* desc = overlay_pool->FindMessageTypeByName( + "cel.expr.conformance.proto2.NestedTestAllTypes"); + ASSERT_NE(desc, nullptr); + ASSERT_THAT(compiler_builder->GetCheckerBuilder().AddVariable( + MakeVariableDecl("ident", MessageType(desc))), + IsOk()); + ASSERT_OK_AND_ASSIGN(std::unique_ptr compiler, + compiler_builder->Build()); + ASSERT_OK_AND_ASSIGN( + cel::ValidationResult validation, + compiler->Compile( + "ident.payload.`cel.cpp.testutil.test_external_extensions_message`." + "int64_field")); + ASSERT_TRUE(validation.IsValid()) << validation.FormatError(); + ASSERT_OK_AND_ASSIGN(std::unique_ptr ast, validation.ReleaseAst()); + + ASSERT_OK_AND_ASSIGN( + cel::RuntimeBuilder runtime_builder, + cel::CreateStandardRuntimeBuilder(overlay_pool, runtime_options_)); + SelectOptimizationOptions select_options; + select_options.enable_field_descriptor_caching = true; + ASSERT_THAT(EnableSelectOptimization(runtime_builder, select_options), + IsOk()); + ASSERT_OK_AND_ASSIGN(std::unique_ptr runtime, + std::move(runtime_builder).Build()); + ASSERT_OK_AND_ASSIGN(std::unique_ptr program, + runtime->CreateProgram(std::move(ast))); + + const google::protobuf::FieldDescriptor* payload_field = + desc->FindFieldByName("payload"); + ASSERT_NE(payload_field, nullptr); + const google::protobuf::FieldDescriptor* ext_field = overlay_pool->FindExtensionByName( + "cel.cpp.testutil.test_external_extensions_message"); + ASSERT_NE(ext_field, nullptr); + const google::protobuf::FieldDescriptor* int64_field = + ext_field->message_type()->FindFieldByName("int64_field"); + ASSERT_NE(int64_field, nullptr); + + google::protobuf::DynamicMessageFactory overlay_factory(overlay_pool.get()); + google::protobuf::Message* msg = overlay_factory.GetPrototype(desc)->New(&arena_); + google::protobuf::Message* payload = msg->GetReflection()->MutableMessage( + msg, payload_field, &overlay_factory); + google::protobuf::Message* ext_msg = payload->GetReflection()->MutableMessage( + payload, ext_field, &overlay_factory); + ext_msg->GetReflection()->SetInt64(ext_msg, int64_field, 99); + + // This works for the SelectOptimization, but not normal field access. Here, + // we are able to resolve the extension using the message reflection extension + // table (by number) without having to look up against the descriptor pool + // directly. + cel::Activation act; + act.InsertOrAssignValue("ident", cel::ParsedMessageValue(msg, &arena_)); + cel::EvaluateOptions eval_options; + eval_options.message_factory = &overlay_factory; + ASSERT_OK_AND_ASSIGN(Value result, + program->Evaluate(&arena_, act, eval_options)); + ASSERT_TRUE(result->Is()) << result->DebugString(); + EXPECT_EQ(result.GetInt().NativeValue(), 99); + + // Also test when the extension message is unset, where Reflection::GetMessage + // has to construct the default message instance. + google::protobuf::Message* unset_msg = overlay_factory.GetPrototype(desc)->New(&arena_); + act.InsertOrAssignValue("ident", cel::ParsedMessageValue(unset_msg, &arena_)); + ASSERT_OK_AND_ASSIGN(Value unset_result, + program->Evaluate(&arena_, act, eval_options)); + ASSERT_TRUE(unset_result->Is()) << unset_result->DebugString(); + EXPECT_EQ(unset_result.GetInt().NativeValue(), 0); + + // Also test when `payload` comes from a different MessageFactory (the + // generated factory) while `runtime` uses its default DynamicMessageFactory. + NestedTestAllTypes* empty_generated = + NestedTestAllTypes::default_instance().New(&arena_); + act.InsertOrAssignValue("ident", + cel::ParsedMessageValue(empty_generated, &arena_)); + ASSERT_OK_AND_ASSIGN(Value underlay_result, program->Evaluate(&arena_, act)); + EXPECT_TRUE(underlay_result->Is()) + << underlay_result->DebugString(); +} + } // namespace } // namespace cel::extensions