Validate tag match before treating unlinked submessages as unknown fields. Otherwise colliding fields will incorrectly end up in unknowns. PiperOrigin-RevId: 966858004
diff --git a/upb/wire/decode_fast/field_message.c b/upb/wire/decode_fast/field_message.c index a627089..817ccdd 100644 --- a/upb/wire/decode_fast/field_message.c +++ b/upb/wire/decode_fast/field_message.c
@@ -80,7 +80,14 @@ if (subtablep == NULL) { // Unlinked messages are treated as unknown fields. Go straight to unknown - // decoder. + // decoder if the tag matches. + uint16_t expected = upb_DecodeFastData_GetExpectedTag(*data); + uint16_t actual = upb_DecodeFastData2_GetOriginalTag(data2); + if (UPB_UNLIKELY(!upb_DecodeFast_TagMatches(expected, actual, tagsize))) { + UPB_DECODEFAST_EXIT(kUpb_DecodeFastNext_FallbackMismatchedSlot, ret); + return; + } + #ifndef NDEBUG uint16_t case_offset = upb_DecodeFastData_GetCaseOffset(*data); if (case_offset != 0) {
diff --git a/upb/wire/decode_test.cc b/upb/wire/decode_test.cc index 30ab776..b423b98 100644 --- a/upb/wire/decode_test.cc +++ b/upb/wire/decode_test.cc
@@ -20,6 +20,8 @@ #include "absl/strings/ascii.h" #include "absl/strings/str_cat.h" #include "absl/strings/string_view.h" +#include "upb/base/descriptor_constants.h" +#include "upb/base/status.h" #include "upb/base/string_view.h" #include "upb/base/upcast.h" #include "upb/mem/arena.h" @@ -32,6 +34,7 @@ #include "upb/message/message.h" #include "upb/message/unknown_fields.h" #include "upb/mini_descriptor/decode.h" +#include "upb/mini_descriptor/internal/encode.hpp" #include "upb/mini_descriptor/link.h" #include "upb/mini_table/extension.h" #include "upb/mini_table/extension_registry.h" @@ -936,6 +939,41 @@ } } +TEST(DecodeTest, UnlinkedSubMessageFastTableSlotCollision) { + Arena mt_arena; + + // Build a message where: + // - Field 16 is an unlinked submessage (slot 16) + // - Field 32 is a bool field that collides on the same fasttable slot (slot + // 16) + upb::MtDataEncoder e; + e.StartMessage(0); + e.PutField(kUpb_FieldType_Message, 16, 0); + e.PutField(kUpb_FieldType_Bool, 32, 0); + + upb_Status status; + upb_Status_Clear(&status); + const upb_MiniTable* mt = upb_MiniTable_Build( + e.data().data(), e.data().size(), mt_arena.ptr(), &status); + ASSERT_TRUE(upb_Status_IsOk(&status)) << upb_Status_ErrorMessage(&status); + + const upb_MiniTableField* bool_field = + upb_MiniTable_FindFieldByNumber(mt, 32); + ASSERT_NE(bool_field, nullptr); + + // Field 32 (tag 256, varint: 0x80, 0x02), value = 1 (true) + std::string payload("\x80\x02\x01"); + for (int options : GetDecodeOptionsToTest()) { + Arena msg_arena; + upb_Message* msg = upb_Message_New(mt, msg_arena.ptr()); + upb_DecodeStatus result = upb_Decode(payload.data(), payload.size(), msg, + mt, nullptr, options, msg_arena.ptr()); + EXPECT_EQ(result, kUpb_DecodeStatus_Ok) << upb_DecodeStatus_String(result); + EXPECT_TRUE(upb_Message_GetBool(msg, bool_field, false)); + EXPECT_FALSE(upb_Message_HasUnknown(msg)); + } +} + } // namespace } // namespace test