Fix messageset conformance for pure Python to match Py-C++ and Py-upb

MessageSet is a legacy affordance holdover from pre-proto2 days. Unlike the rest of Protobuf, it has first-wins instead of last-wins semantic in the case of duplicate ids or values within the same message set entry.

No serializer would ever write such sequences, but the same hypothetical byte sequence being parsed differently by two implementations is undesirable.

Note that other nonconformance fixes would commonly be held to breaking change releases to avoid any possible disruption of single-language users who may be relying on it as a load-bearing bug. However, the nature of this particular case is such that there is little reason to hold it back.

PiperOrigin-RevId: 964043226
This commit is contained in:
Protobuf Team Bot 2026-08-13 06:21:14 -07:00 committed by Copybara-Service
parent d0be1f2cb0
commit 02a9c9dec8
2 changed files with 17 additions and 9 deletions

View file

@ -35,6 +35,3 @@ Recommended.Proto3.ProtobufInput.RejectInvalidUtf8.String.Singular
# Recommended.Proto2.ProtobufInput.RejectInvalidUtf8.String.Oneof # Should have failed to parse, but didn't.
# Recommended.Proto2.ProtobufInput.RejectInvalidUtf8.String.Repeated # Should have failed to parse, but didn't.
# Recommended.Proto2.ProtobufInput.RejectInvalidUtf8.String.Singular # Should have failed to parse, but didn't.
Recommended.Proto2.ProtobufInput.ValidMessageSetEncoding.DuplicateDifferentTypeId.ProtobufOutput # Output was not equivalent to reference message
Recommended.Proto2.ProtobufInput.ValidMessageSetEncoding.DuplicateValue.ProtobufOutput # Output was not equivalent to reference message
Recommended.Proto2.ProtobufInput.ValidMessageSetEncoding.DuplicateValueOutOfOrder.ProtobufOutput # Output was not equivalent to reference message

View file

@ -902,10 +902,15 @@ def MessageSetItemDecoder(descriptor):
while 1:
tag_bytes, pos = local_ReadTag(buffer, pos)
if tag_bytes == type_id_tag_bytes:
type_id, pos = local_DecodeVarint(buffer, pos)
temp_type_id, pos = local_DecodeVarint(buffer, pos)
if type_id == -1:
type_id = temp_type_id
elif tag_bytes == message_tag_bytes:
size, message_start = local_DecodeVarint(buffer, pos)
pos = message_end = message_start + size
size, start = local_DecodeVarint(buffer, pos)
if message_start == -1:
message_start = start
message_end = start + size
pos = start + size
elif tag_bytes == item_end_tag_bytes:
break
else:
@ -967,15 +972,21 @@ def UnknownMessageSetItemDecoder():
def DecodeUnknownItem(buffer):
pos = 0
end = len(buffer)
type_id = -1
message_start = -1
message_end = -1
while 1:
tag_bytes, pos = ReadTag(buffer, pos)
if tag_bytes == type_id_tag_bytes:
type_id, pos = _DecodeVarint(buffer, pos)
temp_type_id, pos = _DecodeVarint(buffer, pos)
if type_id == -1:
type_id = temp_type_id
elif tag_bytes == message_tag_bytes:
size, message_start = _DecodeVarint(buffer, pos)
pos = message_end = message_start + size
size, start = _DecodeVarint(buffer, pos)
if message_start == -1:
message_start = start
message_end = start + size
pos = start + size
elif tag_bytes == item_end_tag_bytes:
break
else: