From 65f57ea9dadb5fdd3240aea219e9321572933473 Mon Sep 17 00:00:00 2001 From: Jason Aragorn Tobias Lunn Date: Thu, 23 Jul 2026 07:37:06 -0700 Subject: [PATCH] Automated Code Change PiperOrigin-RevId: 952751478 --- pybind11_protobuf/check_unknown_fields.cc | 22 +++++++----- pybind11_protobuf/tests/extension_module.cc | 9 +++++ pybind11_protobuf/tests/extension_test.py | 39 +++++++++++++++++++++ 3 files changed, 62 insertions(+), 8 deletions(-) diff --git a/pybind11_protobuf/check_unknown_fields.cc b/pybind11_protobuf/check_unknown_fields.cc index dca0e25..b7fa430 100644 --- a/pybind11_protobuf/check_unknown_fields.cc +++ b/pybind11_protobuf/check_unknown_fields.cc @@ -95,18 +95,24 @@ bool HasUnknownFields::FindUnknownFieldsRecursive( const ::google::protobuf::Message* sub_message, uint32_t depth) { const ::google::protobuf::Reflection& reflection = *sub_message->GetReflection(); - // If there are unknown fields, stop searching. + // Check if any unknown fields on this message correspond to extensions that + // are known by Python's descriptor pool (i.e., Python imported the extension, + // but C++ did not link the corresponding cc_proto_library). + // Iterate through all unknown fields, as earlier entries might be unknown + // regular fields or extensions not registered in Python. const ::google::protobuf::UnknownFieldSet& unknown_field_set = reflection.GetUnknownFields(*sub_message); if (!unknown_field_set.empty()) { unknown_field_parent_descriptor = sub_message->GetDescriptor(); - unknown_field_number = unknown_field_set.field(0).number(); - - // Stop only if the extension is known by Python. - if (py_proto_api->GetDefaultDescriptorPool()->FindExtensionByNumber( - unknown_field_parent_descriptor, unknown_field_number)) { - field_fqn_parts.resize(depth); - return true; + for (int i = 0; i < unknown_field_set.field_count(); ++i) { + int field_num = unknown_field_set.field(i).number(); + // Stop and report as soon as an extension known by Python is found. + if (py_proto_api->GetDefaultDescriptorPool()->FindExtensionByNumber( + unknown_field_parent_descriptor, field_num)) { + unknown_field_number = field_num; + field_fqn_parts.resize(depth); + return true; + } } } diff --git a/pybind11_protobuf/tests/extension_module.cc b/pybind11_protobuf/tests/extension_module.cc index c14c8ac..b121956 100644 --- a/pybind11_protobuf/tests/extension_module.cc +++ b/pybind11_protobuf/tests/extension_module.cc @@ -69,6 +69,15 @@ PYBIND11_MODULE(extension_module, m) { [](const BaseMessage& inout) -> const BaseMessage& { return inout; }, py::arg("message"), py::return_value_policy::copy); + m.def( + "parse_base_message_from_bytes", + [](const py::bytes& bytes) -> BaseMessage { + BaseMessage msg; + msg.ParseFromString(bytes); + return msg; + }, + py::arg("bytes")); + DefReserialize(m, "reserialize_base_message"); DefReserialize(m, "reserialize_nest_level2"); DefReserialize(m, "reserialize_nest_repeated"); diff --git a/pybind11_protobuf/tests/extension_test.py b/pybind11_protobuf/tests/extension_test.py index 6c9e297..d5f07cc 100644 --- a/pybind11_protobuf/tests/extension_test.py +++ b/pybind11_protobuf/tests/extension_test.py @@ -213,5 +213,44 @@ def test_reserialize_allow_python_unknown_fields(self): b = m.reserialize_base_message(a) self.assertEqual(a.SerializeToString(), b.SerializeToString()) + def test_reserialize_multiple_unknown_fields(self): + # Concatenating two serialized messages places their fields into the wire + # stream in sequential order: + # 1. 'inner' contains field 2001 (AllowUnknownInnerExtension.hook), which is + # unknown to both C++ and Python for BaseMessage. + # 2. 'msg_with_ext' contains field 1003 (MessageInOtherFile extension), + # which is unknown to C++ (unlinked cc_proto_library), but is known to + # Python's descriptor pool. + # When BaseMessage parses raw_data in C++, its UnknownFieldSet preserves + # this order: field(0) = 2001, field(1) = 1003. + inner = get_allow_unknown_inner(63) + msg_with_ext = get_py_message(in_other_file_value=63) + raw_data = inner.SerializeToString() + msg_with_ext.SerializeToString() + + # When unknown_field_exception_is_expected() is True (e.g., in fast_cpp + # mode with unknown fields disallowed), pybind11_protobuf scans + # UnknownFieldSet and raises ValueError upon detecting field 1003. + # Otherwise (e.g., pure Python / upb or allowed mode), it falls back to + # serialization, allowing Python to read the extension losslessly. + if unknown_field_exception_is_expected(): + with self.assertRaises(ValueError) as ctx: + m.parse_base_message_from_bytes(raw_data) + self.assertStartsWith( + str(ctx.exception), + 'Proto Message of type pybind11.test.BaseMessage has an' + ' Unknown Field: 1003 (') + self.assertEndsWith( + str(ctx.exception), + 'extension.proto). Please add the required `cc_proto_library` `deps`.' + ' Only if there is no alternative to suppressing this error, use' + ' `pybind11_protobuf::AllowUnknownFieldsFor(' + '"pybind11.test.BaseMessage", "");`' + ' (Warning: suppressions may mask critical bugs.)') + else: + b = m.parse_base_message_from_bytes(raw_data) + b_value = b.Extensions[extension_in_other_file_pb2.MessageInOtherFile + .message_in_other_file_extension].value + self.assertEqual(63, b_value) + if __name__ == '__main__': absltest.main()