blob: f1828d85c8c38f54578432deace942510451081c [file]
// Protocol Buffers - Google's data interchange format
// Copyright 2025 Google LLC. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd
// This test requires fasttable support to work properly.
// Run with `--//third_party/upb:fasttable_enabled=true`.
#include <cstdint>
#include <string>
#include <tuple>
#include <gtest/gtest.h>
#include "absl/strings/match.h" // IWYU pragma: keep
#include "absl/strings/string_view.h"
#include "upb/base/descriptor_constants.h"
#include "upb/base/status.hpp"
#include "upb/base/string_view.h"
#include "upb/mem/arena.hpp"
#include "upb/message/accessors.h"
#include "upb/message/message.h"
#include "upb/mini_descriptor/decode.h"
#include "upb/mini_descriptor/internal/encode.hpp"
#include "upb/mini_descriptor/link.h"
#include "upb/mini_table/field.h"
#include "upb/mini_table/message.h"
#include "upb/wire/decode.h"
#include "upb/wire/decode_fast/combinations.h"
#include "upb/wire/test_util/field_types.h"
#include "upb/wire/test_util/make_mini_table.h"
#include "upb/wire/test_util/wire_message.h"
// Must be last.
#include "upb/port/def.inc"
namespace upb {
namespace test {
namespace {
struct UnknownFieldTestCase {
uint32_t field_number;
wire_types::WireValue value;
std::string name;
template <typename Sink>
friend void AbslStringify(Sink& sink, const UnknownFieldTestCase& tc) {
sink.Append("{field_number: ");
sink.Append(std::to_string(tc.field_number));
sink.Append(", name: \"");
sink.Append(tc.name);
sink.Append("\"}");
}
};
class UnknownFieldTest
: public testing::TestWithParam<std::tuple<UnknownFieldTestCase, bool>> {};
TEST_P(UnknownFieldTest, UnknownFieldFastPath) {
const auto& param = GetParam();
const auto& test_case = std::get<0>(param);
const bool extensible = std::get<1>(param);
uint32_t unknown_field_num = test_case.field_number;
char trace_buf[64];
upb::Arena msg_arena;
upb::Arena mt_arena;
// Create a MiniTable with field 1. Any other field number will be unknown.
auto [mt, field] =
extensible
? MiniTable::MakeExtensibleSingleFieldTable<field_types::Int32>(
1, kUpb_DecodeFast_Scalar, mt_arena.ptr())
: MiniTable::MakeSingleFieldTable<field_types::Int32>(
1, kUpb_DecodeFast_Scalar, mt_arena.ptr());
upb_Message* msg = upb_Message_New(mt, msg_arena.ptr());
std::string payload = ToBinaryPayload(
wire_types::WireMessage{{unknown_field_num, test_case.value}});
upb_DecodeStatus result =
upb_DecodeWithTrace(payload.data(), payload.size(), msg, mt, nullptr, 0,
msg_arena.ptr(), trace_buf, sizeof(trace_buf));
ASSERT_EQ(result, kUpb_DecodeStatus_Ok) << upb_DecodeStatus_String(result);
// Verify it was saved as an unknown field.
ASSERT_TRUE(upb_Message_HasUnknown(msg));
// Verify the contents of the unknown field.
uintptr_t iter = kUpb_Message_UnknownBegin;
upb_StringView unknown_data;
std::string captured_unknown;
while (upb_Message_NextUnknown(msg, &unknown_data, &iter)) {
captured_unknown.append(unknown_data.data, unknown_data.size);
}
EXPECT_EQ(captured_unknown, payload);
#if !defined(NDEBUG) && UPB_FASTTABLE
// Because it was parsed on the fast path and we natively support
// >2 byte tags inline, we should not see fallback ('<')
// or mini table ('M') in the trace output regardless of tag size.
EXPECT_FALSE(absl::StrContains(trace_buf, "<"));
EXPECT_FALSE(absl::StrContains(trace_buf, "M"));
#endif
}
INSTANTIATE_TEST_SUITE_P(
AllWireTypes, UnknownFieldTest,
testing::Combine(
testing::Values(
UnknownFieldTestCase{2, wire_types::Varint{123}, "Varint"},
UnknownFieldTestCase{2, wire_types::Varint{255},
"VarintHighBitValue"},
UnknownFieldTestCase{2, wire_types::Delimited{"Hello World"},
"Delimited"},
UnknownFieldTestCase{3, wire_types::Varint{123},
"VarintWithCollision"},
UnknownFieldTestCase{100, wire_types::Fixed64{0x1234567890ABCDEF},
"Fixed64"},
UnknownFieldTestCase{16, wire_types::Fixed32{0x12345678},
"Fixed32"},
UnknownFieldTestCase{2048, wire_types::Varint{123}, "LargeTag"}),
testing::Bool()),
[](const testing::TestParamInfo<UnknownFieldTest::ParamType>& info) {
const auto& test_case = std::get<0>(info.param);
const bool extensible = std::get<1>(info.param);
return test_case.name + (extensible ? "Extensible" : "Normal");
});
TEST(UnknownFieldSpecialTest, UnknownVarintFollowedByUnknownGroup) {
char trace_buf[64];
upb::Arena arena;
// Create a MiniTable with field 1. Any other field number will be unknown.
auto [mt, field] = MiniTable::MakeSingleFieldTable<field_types::Int32>(
1, kUpb_DecodeFast_Scalar, arena.ptr());
upb_Message* msg = upb_Message_New(mt, arena.ptr());
ASSERT_NE(msg, nullptr);
// Payload: Unknown Varint (field 2), then Unknown Group (field 3).
std::string payload = ToBinaryPayload(wire_types::WireMessage{
{2, wire_types::Varint{123}},
{3, wire_types::Group{{4, wire_types::Varint{456}}}},
});
upb_DecodeStatus result =
upb_DecodeWithTrace(payload.data(), payload.size(), msg, mt, nullptr, 0,
arena.ptr(), trace_buf, sizeof(trace_buf));
// We expect success, but due to the bug it might return Malformed.
EXPECT_EQ(result, kUpb_DecodeStatus_Ok) << upb_DecodeStatus_String(result);
}
TEST(UnknownFieldSpecialTest, UnknownVarintFollowedByEndGroupInGroup) {
char trace_buf[64];
upb::Arena arena;
// 1. Create Child MiniTable (the message itself).
auto [child_mt, child_field] =
MiniTable::MakeSingleFieldTable<field_types::Int32>(
1, kUpb_DecodeFast_Scalar, arena.ptr());
// 2. Create Parent MiniTable with Group field (field 5).
upb::MtDataEncoder parent_encoder;
parent_encoder.StartMessage(0);
parent_encoder.PutField(kUpb_FieldType_Group, 5, 0); // Field 5, no modifiers
upb::Status status;
const upb_MiniTable* parent_mt = upb_MiniTable_Build(
parent_encoder.data().data(), parent_encoder.data().size(), arena.ptr(),
status.ptr());
ASSERT_TRUE(status.ok()) << status.error_message();
const upb_MiniTableField* parent_field =
upb_MiniTable_GetFieldByIndex(parent_mt, 0);
ASSERT_NE(parent_field, nullptr);
// 3. Link Parent's Group field to Child MiniTable.
upb_MiniTableField* group_field =
const_cast<upb_MiniTableField*>(parent_field);
ASSERT_TRUE(upb_MiniTable_SetSubMessage(const_cast<upb_MiniTable*>(parent_mt),
group_field, child_mt));
upb_Message* parent_msg = upb_Message_New(parent_mt, arena.ptr());
ASSERT_NE(parent_msg, nullptr);
// 4. Build Payload.
// We want to test batching INSIDE the group.
// The group is field 5.
// Inside group: Unknown Varint (field 2, value 123).
std::string payload = ToBinaryPayload(wire_types::WireMessage{
{5, wire_types::Group{{2, wire_types::Varint{123}}}}});
upb_DecodeStatus result = upb_DecodeWithTrace(
payload.data(), payload.size(), parent_msg, parent_mt, nullptr, 0,
arena.ptr(), trace_buf, sizeof(trace_buf));
// It should be OK because EndGroup is expected.
EXPECT_EQ(result, kUpb_DecodeStatus_Ok) << upb_DecodeStatus_String(result);
// 5. Verify the Child message has the Unknown Varint.
const upb_Message* child_msg =
upb_Message_GetMessage(parent_msg, group_field);
ASSERT_NE(child_msg, nullptr);
ASSERT_TRUE(upb_Message_HasUnknown(child_msg));
// Verify contents.
std::string captured_unknown;
uintptr_t iter = kUpb_Message_UnknownBegin;
upb_StringView unknown_data;
while (upb_Message_NextUnknown(child_msg, &unknown_data, &iter)) {
captured_unknown.append(unknown_data.data, unknown_data.size);
}
// The captured unknown should be just the varint part.
// Field 2 Varint: (2 << 3) | 0 = 16 (0x10), value = 123 (0x7B)
EXPECT_EQ(captured_unknown, "\x10\x7B");
}
} // namespace
} // namespace test
} // namespace upb