Skip to content

Commit 26d08d5

Browse files
anandoleecopybara-github
authored andcommitted
Fix Python cpp memory crash when MergeFrom Oneof.
Maybe release oneof messages before MergeFrom in python cpp extension PiperOrigin-RevId: 819802492
1 parent 143e3ea commit 26d08d5

File tree

2 files changed

+92
-0
lines changed

2 files changed

+92
-0
lines changed

python/google/protobuf/internal/message_test.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1001,6 +1001,50 @@ def testOneofMessageMergeFrom(self, message_module):
10011001
self.assertEqual('oneof_nested_message',
10021002
m2.child.payload.WhichOneof('oneof_field'))
10031003

1004+
def testOneofReleaseMergeFrom(self, message_module):
1005+
m = unittest_pb2.TestOneof2()
1006+
m.foo_message.moo_int = 123
1007+
reference = m.foo_message
1008+
self.assertEqual(m.foo_message.moo_int, 123)
1009+
m2 = unittest_pb2.TestOneof2()
1010+
m2.foo_lazy_message.moo_int = 456
1011+
m.MergeFrom(m2)
1012+
self.assertEqual(reference.moo_int, 123)
1013+
self.assertEqual(m.foo_message.moo_int, 0)
1014+
m.foo_message.CopyFrom(reference)
1015+
self.assertEqual(m.foo_message.moo_int, 123)
1016+
1017+
def testNestedOneofRleaseMergeFrom(self, message_module):
1018+
m = message_module.NestedTestAllTypes()
1019+
m.payload.oneof_nested_message.bb = 1
1020+
m.child.payload.oneof_nested_message.bb = 2
1021+
ref1 = m.payload.oneof_nested_message
1022+
ref2 = m.child.payload.oneof_nested_message
1023+
other = message_module.NestedTestAllTypes()
1024+
other.payload.oneof_uint32 = 22
1025+
other.child.payload.oneof_string = 'hi'
1026+
self.assertEqual(ref1.bb, 1)
1027+
self.assertEqual(ref2.bb, 2)
1028+
m.MergeFrom(other)
1029+
# oneof messages are released
1030+
self.assertEqual(ref1.bb, 1)
1031+
self.assertEqual(ref2.bb, 2)
1032+
self.assertEqual(m.payload.oneof_nested_message.bb, 0)
1033+
self.assertEqual(m.child.payload.oneof_nested_message.bb, 0)
1034+
self.assertEqual(m.payload.oneof_uint32, 22)
1035+
self.assertEqual(m.child.payload.oneof_string, 'hi')
1036+
1037+
def testOneofNotReleaseMergeFrom(self, message_module):
1038+
m = message_module.NestedTestAllTypes()
1039+
m.payload.oneof_nested_message.bb = 1
1040+
ref = m.payload.oneof_nested_message
1041+
other = message_module.NestedTestAllTypes()
1042+
other.payload.oneof_nested_message.bb = 2
1043+
self.assertEqual(ref.bb, 1)
1044+
m.MergeFrom(other)
1045+
# oneof message is not released
1046+
self.assertEqual(ref.bb, 2)
1047+
10041048
def testOneofNestedMessageInit(self, message_module):
10051049
m = message_module.TestAllTypes(
10061050
oneof_nested_message=message_module.TestAllTypes.NestedMessage())

python/google/protobuf/pyext/message.cc

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -764,6 +764,50 @@ static int MaybeReleaseOverlappingOneofField(CMessage* cmessage,
764764
return 0;
765765
}
766766

767+
int MaybeReleaseOneofBeforeMerge(CMessage* self, const Message& other) {
768+
if (!self->composite_fields) {
769+
return 0;
770+
}
771+
772+
Message* message = self->message;
773+
const Reflection* reflection = message->GetReflection();
774+
PyMessageFactory* factory = GetFactoryForMessage(self);
775+
std::vector<const FieldDescriptor*> fields_to_release;
776+
std::vector<const FieldDescriptor*> nested_message_fields;
777+
for (const auto& item : *self->composite_fields) {
778+
const FieldDescriptor* descriptor = item.first;
779+
if (descriptor->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE &&
780+
// For normal repeated message, MergeFrom will append the messages.
781+
// For message map with same keys, it is overwrite
782+
!descriptor->is_repeated() &&
783+
reflection->HasField(*message, descriptor)) {
784+
if (reflection->HasField(other, descriptor)) {
785+
nested_message_fields.push_back(descriptor);
786+
} else {
787+
// Release oneof message if the other message has set a different oneof
788+
const OneofDescriptor* oneof = descriptor->containing_oneof();
789+
if (oneof && reflection->HasOneof(other, oneof)) {
790+
fields_to_release.push_back(descriptor);
791+
}
792+
}
793+
}
794+
}
795+
for (const FieldDescriptor* field : nested_message_fields) {
796+
if (MaybeReleaseOneofBeforeMerge(
797+
reinterpret_cast<CMessage*>(
798+
self->composite_fields->find(field)->second),
799+
reflection->GetMessage(other, field, factory->message_factory)) <
800+
0) {
801+
return -1;
802+
}
803+
}
804+
for (const FieldDescriptor* field : fields_to_release) {
805+
if (InternalReleaseFieldByDescriptor(self, field) < 0) {
806+
return -1;
807+
}
808+
}
809+
return 0;
810+
}
767811
// After a Merge, visit every sub-message that was read-only, and
768812
// eventually update their pointer if the Merge operation modified them.
769813
int FixupMessageAfterMerge(CMessage* self) {
@@ -1846,6 +1890,10 @@ PyObject* MergeFrom(CMessage* self, PyObject* arg) {
18461890
}
18471891
AssureWritable(self);
18481892

1893+
if (MaybeReleaseOneofBeforeMerge(self, *other_message->message) < 0) {
1894+
return nullptr;
1895+
}
1896+
18491897
self->message->MergeFrom(*other_message->message);
18501898
// Child message might be lazily created before MergeFrom. Make sure they
18511899
// are mutable at this point if child messages are really created.

0 commit comments

Comments
 (0)