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