| // 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 |
| |
| #include "upb/wire/decode.h" |
| |
| #include <array> |
| #include <cstdint> |
| #include <cstring> |
| #include <memory> |
| #include <optional> |
| #include <string> |
| #include <type_traits> |
| #include <vector> |
| |
| #include <gtest/gtest.h> |
| #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" |
| #include "upb/mem/arena.hpp" |
| #include "upb/message/accessors.h" |
| #include "upb/message/accessors.hpp" |
| #include "upb/message/array.h" |
| #include "upb/message/internal/accessors.h" |
| #include "upb/message/internal/message.h" |
| #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" |
| #include "upb/mini_table/field.h" |
| #include "upb/mini_table/message.h" |
| #include "upb/test/test.upb.h" |
| #include "upb/test/test.upb_minitable.h" |
| #include "upb/wire/decode_fast/combinations.h" |
| #include "upb/wire/decode_test.upb_minitable.h" |
| #include "upb/wire/encode.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 { |
| |
| std::vector<int> GetDecodeOptionsToTest() { |
| #if UPB_FASTTABLE |
| return {0, kUpb_DecodeOption_DisableFastTable}; |
| #else |
| return {0}; |
| #endif |
| } |
| |
| #ifndef NDEBUG |
| std::string GetExpectedConsecutiveUnknownsTrace(int options) { |
| #if UPB_FASTTABLE |
| if (!(options & kUpb_DecodeOption_DisableFastTable)) { |
| return "D"; |
| } |
| #endif |
| return "M"; |
| } |
| #endif |
| |
| template <typename T> |
| std::optional<T> GetOptionalField(upb_Message* msg, |
| const upb_MiniTableField* field) { |
| if (upb_Message_HasBaseField(msg, field)) { |
| return GetMessageBaseField<T>(msg, field, T{}); |
| } else { |
| return std::nullopt; |
| } |
| } |
| |
| template <typename T> |
| class FieldTypeTest : public testing::Test {}; |
| |
| TYPED_TEST_SUITE(FieldTypeTest, FieldTypes); |
| |
| std::string ExpectedSingleFieldTrace(const upb_MiniTable* mt, |
| const upb_MiniTableField* field) { |
| #ifdef NDEBUG |
| return ""; |
| #else |
| return MiniTable::HasFastTableEntry(mt, field) ? "DF" : "M"; |
| #endif |
| } |
| |
| std::string ExpectedRepeatedFieldTrace(const upb_MiniTable* mt, |
| const upb_MiniTableField* field, |
| int count) { |
| #ifdef NDEBUG |
| return ""; |
| #else |
| if (MiniTable::HasFastTableEntry(mt, field)) { |
| // Fasttable repeated fields have a fast path where we bypass dispatch if |
| // the same tag is encountered consecutively. |
| return absl::StrCat("D", std::string(count, 'F')); |
| } else { |
| return std::string(count, 'M'); |
| } |
| #endif |
| } |
| |
| std::string FilteredTrace(absl::string_view trace) { |
| std::string filtered; |
| for (char c : trace) { |
| if (!absl::ascii_islower(c)) filtered.push_back(c); |
| } |
| return filtered; |
| } |
| |
| TYPED_TEST(FieldTypeTest, DecodeOptionalMaxValue) { |
| char trace_buf[64]; |
| using Value = typename TypeParam::Value; |
| upb::Arena arena; |
| auto [mt, field] = MiniTable::MakeSingleFieldTable<TypeParam>( |
| 1, kUpb_DecodeFast_Scalar, arena.ptr()); |
| upb_Message* msg = upb_Message_New(mt, arena.ptr()); |
| std::string payload = ToBinaryPayload(wire_types::WireMessage{ |
| {1, TypeParam::WireValue(Value(TypeParam::kMax))}}); |
| upb_DecodeStatus result = |
| upb_DecodeWithTrace(payload.data(), payload.size(), msg, mt, nullptr, 0, |
| arena.ptr(), trace_buf, sizeof(trace_buf)); |
| ASSERT_EQ(result, kUpb_DecodeStatus_Ok) << upb_DecodeStatus_String(result); |
| EXPECT_EQ(GetOptionalField<Value>(msg, field), TypeParam::kMax); |
| EXPECT_EQ(absl::string_view(trace_buf), ExpectedSingleFieldTrace(mt, field)); |
| } |
| |
| TYPED_TEST(FieldTypeTest, DecodeOptionalMinValue) { |
| char trace_buf[64]; |
| using Value = typename TypeParam::Value; |
| upb::Arena arena; |
| auto [mt, field] = MiniTable::MakeSingleFieldTable<TypeParam>( |
| 1, kUpb_DecodeFast_Scalar, arena.ptr()); |
| upb_Message* msg = upb_Message_New(mt, arena.ptr()); |
| std::string payload = ToBinaryPayload(wire_types::WireMessage{ |
| {1, TypeParam::WireValue(Value(TypeParam::kMin))}}); |
| upb_DecodeStatus result = |
| upb_DecodeWithTrace(payload.data(), payload.size(), msg, mt, nullptr, 0, |
| arena.ptr(), trace_buf, sizeof(trace_buf)); |
| ASSERT_EQ(result, kUpb_DecodeStatus_Ok) << upb_DecodeStatus_String(result); |
| EXPECT_EQ(GetOptionalField<Value>(msg, field), TypeParam::kMin); |
| EXPECT_EQ(absl::string_view(trace_buf), ExpectedSingleFieldTrace(mt, field)); |
| } |
| |
| TYPED_TEST(FieldTypeTest, DecodeOneofMaxValue) { |
| char trace_buf[64]; |
| using Value = typename TypeParam::Value; |
| upb::Arena arena; |
| auto [mt, field] = MiniTable::MakeSingleFieldTable<TypeParam>( |
| 1, kUpb_DecodeFast_Oneof, arena.ptr()); |
| upb_Message* msg = upb_Message_New(mt, arena.ptr()); |
| std::string payload = ToBinaryPayload(wire_types::WireMessage{ |
| {1, TypeParam::WireValue(Value(TypeParam::kMax))}}); |
| upb_DecodeStatus result = |
| upb_DecodeWithTrace(payload.data(), payload.size(), msg, mt, nullptr, 0, |
| arena.ptr(), trace_buf, sizeof(trace_buf)); |
| ASSERT_EQ(result, kUpb_DecodeStatus_Ok) << upb_DecodeStatus_String(result); |
| EXPECT_EQ(GetOptionalField<Value>(msg, field), TypeParam::kMax); |
| EXPECT_EQ(absl::string_view(trace_buf), ExpectedSingleFieldTrace(mt, field)); |
| } |
| |
| TYPED_TEST(FieldTypeTest, DecodeRepeated) { |
| char trace_buf[64]; |
| using Value = typename TypeParam::Value; |
| Value value; |
| if constexpr (std::is_same_v<Value, std::string>) { |
| for (int i = 0; i < 1000; ++i) { |
| value.append("hello world! "); |
| } |
| } else { |
| value = TypeParam::kMax; |
| } |
| upb::Arena msg_arena; |
| upb::Arena mt_arena; |
| auto [mt, field] = MiniTable::MakeSingleFieldTable<TypeParam>( |
| 1, kUpb_DecodeFast_Repeated, mt_arena.ptr()); |
| upb_Message* msg = upb_Message_New(mt, msg_arena.ptr()); |
| std::string payload = ToBinaryPayload(wire_types::WireMessage{ |
| {1, TypeParam::WireValue(Value(TypeParam::kZero))}, |
| {1, TypeParam::WireValue(Value(TypeParam::kMin))}, |
| {1, TypeParam::WireValue(Value(TypeParam::kMax))}, |
| }); |
| 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); |
| EXPECT_EQ(GetRepeatedField<Value>(msg, field), |
| (std::vector<Value>{Value(TypeParam::kZero), Value(TypeParam::kMin), |
| Value(TypeParam::kMax)})); |
| EXPECT_EQ(FilteredTrace(absl::string_view(trace_buf)), |
| ExpectedRepeatedFieldTrace(mt, field, 3)); |
| } |
| |
| template <typename T> |
| class PackedTest : public testing::Test {}; |
| |
| TYPED_TEST_SUITE(PackedTest, PackableFieldTypes); |
| |
| TYPED_TEST(PackedTest, DecodePackedDataForPackedField) { |
| char trace_buf[64]; |
| using Value = typename TypeParam::Value; |
| upb::Arena msg_arena; |
| upb::Arena mt_arena; |
| auto [mt, field] = MiniTable::MakeSingleFieldTable<TypeParam>( |
| 1, kUpb_DecodeFast_Packed, mt_arena.ptr()); |
| upb_Message* msg = upb_Message_New(mt, msg_arena.ptr()); |
| std::string packed_value = |
| ToBinaryPayload(TypeParam::WireValue(TypeParam::kZero)) + |
| ToBinaryPayload(TypeParam::WireValue(TypeParam::kMin)) + |
| ToBinaryPayload(TypeParam::WireValue(TypeParam::kMax)); |
| std::string payload = ToBinaryPayload( |
| wire_types::WireMessage{{1, wire_types::Delimited{packed_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); |
| EXPECT_EQ(GetRepeatedField<Value>(msg, field), |
| (std::vector<Value>{Value(TypeParam::kZero), Value(TypeParam::kMin), |
| Value(TypeParam::kMax)})); |
| EXPECT_EQ(absl::string_view(trace_buf), ExpectedSingleFieldTrace(mt, field)); |
| } |
| |
| TYPED_TEST(PackedTest, DecodeTruncatedPackedField) { |
| char trace_buf[64]; |
| upb::Arena msg_arena; |
| upb::Arena mt_arena; |
| auto [mt, field] = MiniTable::MakeSingleFieldTable<TypeParam>( |
| 1, kUpb_DecodeFast_Packed, mt_arena.ptr()); |
| upb_Message* msg = upb_Message_New(mt, msg_arena.ptr()); |
| std::string packed_value = |
| ToBinaryPayload(TypeParam::WireValue(TypeParam::kZero)) + |
| ToBinaryPayload(TypeParam::WireValue(TypeParam::kMin)) + |
| // For varint fields, this will be a multi-byte varint, such that |
| // truncating the last byte will result in an invalid varint. |
| ToBinaryPayloadWithLongVarints(TypeParam::WireValue(TypeParam::kMax), 2, |
| 2); |
| packed_value.resize(packed_value.size() - 1); // Truncate the last byte. |
| std::string payload = ToBinaryPayload( |
| wire_types::WireMessage{{1, wire_types::Delimited{packed_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_Malformed) |
| << upb_DecodeStatus_String(result); |
| } |
| |
| TYPED_TEST(PackedTest, DecodeEmptyPackedField) { |
| char trace_buf[64]; |
| using Value = typename TypeParam::Value; |
| upb::Arena msg_arena; |
| upb::Arena mt_arena; |
| auto [mt, field] = MiniTable::MakeSingleFieldTable<TypeParam>( |
| 1, kUpb_DecodeFast_Packed, mt_arena.ptr()); |
| upb_Message* msg = upb_Message_New(mt, msg_arena.ptr()); |
| std::string payload = |
| ToBinaryPayload(wire_types::WireMessage{{1, wire_types::Delimited{""}}}); |
| 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); |
| EXPECT_EQ(GetRepeatedField<Value>(msg, field), (std::vector<Value>{})); |
| EXPECT_EQ(absl::string_view(trace_buf), ExpectedSingleFieldTrace(mt, field)); |
| } |
| |
| TYPED_TEST(PackedTest, DecodePackedDataForUnpackedField) { |
| // Schema says this is not a packed field, but we supply packed wire format. |
| char trace_buf[64]; |
| using Value = typename TypeParam::Value; |
| upb::Arena msg_arena; |
| upb::Arena mt_arena; |
| auto [mt, field] = MiniTable::MakeSingleFieldTable<TypeParam>( |
| 1, kUpb_DecodeFast_Repeated, mt_arena.ptr()); |
| upb_Message* msg = upb_Message_New(mt, msg_arena.ptr()); |
| std::string packed_value = ToBinaryPayload(TypeParam::WireValue(0)) + |
| ToBinaryPayload(TypeParam::WireValue(1 << 10)) + |
| ToBinaryPayload(TypeParam::WireValue(1 << 20)); |
| std::string payload = ToBinaryPayload( |
| wire_types::WireMessage{{1, wire_types::Delimited{packed_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); |
| EXPECT_EQ(GetRepeatedField<Value>(msg, field), |
| (std::vector<Value>{0, static_cast<Value>(1 << 10), |
| static_cast<Value>(1 << 20)})); |
| // Even though there is a mismatch, we can still parse this fast. |
| EXPECT_EQ(absl::string_view(trace_buf), ExpectedSingleFieldTrace(mt, field)); |
| } |
| |
| TYPED_TEST(PackedTest, DecodeUnpackedDataForPackedField) { |
| // Schema says this is a packed field, but we supply unpacked wire format. |
| char trace_buf[64]; |
| using Value = typename TypeParam::Value; |
| upb::Arena msg_arena; |
| upb::Arena mt_arena; |
| auto [mt, field] = MiniTable::MakeSingleFieldTable<TypeParam>( |
| 1, kUpb_DecodeFast_Packed, mt_arena.ptr()); |
| upb_Message* msg = upb_Message_New(mt, msg_arena.ptr()); |
| std::string payload = ToBinaryPayload(wire_types::WireMessage{ |
| {1, TypeParam::WireValue(0)}, |
| {1, TypeParam::WireValue(1 << 10)}, |
| {1, TypeParam::WireValue(1 << 20)}, |
| }); |
| 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); |
| EXPECT_EQ(GetRepeatedField<Value>(msg, field), |
| (std::vector<Value>{0, static_cast<Value>(1 << 10), |
| static_cast<Value>(1 << 20)})); |
| // Even though there is a mismatch, we can still parse this fast. |
| EXPECT_EQ(FilteredTrace(absl::string_view(trace_buf)), |
| ExpectedRepeatedFieldTrace(mt, field, 3)); |
| } |
| |
| TEST(RepeatedFieldTest, RepeatedMessageFallback) { |
| Arena mt_arena; |
| Arena msg_arena; |
| |
| auto [sub_mt, sub_field] = |
| test::MiniTable::MakeSingleFieldTable<test::field_types::Int32>( |
| 1, kUpb_DecodeFast_Scalar, mt_arena.ptr()); |
| |
| auto [mt, field] = |
| test::MiniTable::MakeSingleFieldTable<test::field_types::Message>( |
| 1, kUpb_DecodeFast_Repeated, mt_arena.ptr()); |
| |
| const upb_MiniTable* subs[1] = {sub_mt}; |
| bool linked = |
| upb_MiniTable_Link(const_cast<upb_MiniTable*>(mt), subs, 1, nullptr, 0); |
| ASSERT_TRUE(linked); |
| |
| upb_Message* msg = upb_Message_New(mt, msg_arena.ptr()); |
| |
| // Payload: |
| // Element 1: tag 1, len 2, int32 value 5 |
| // Element 2: tag 1, len 2 (parsed as overlong 3-byte varint to trigger |
| // fasttable fallback), int32 value 6 |
| std::string payload("\x0a\x02\x08\x05\x0a\x82\x80\x00\x08\x06", 10); |
| upb_DecodeStatus result = upb_Decode(payload.data(), payload.size(), msg, mt, |
| nullptr, 0, msg_arena.ptr()); |
| |
| // Fasttable fallback used to drop the first element for repeated messages |
| // because array size wasn't updated. |
| ASSERT_EQ(result, kUpb_DecodeStatus_Ok) << upb_DecodeStatus_String(result); |
| |
| const upb_Array* arr = upb_Message_GetArray(msg, field); |
| ASSERT_NE(arr, nullptr); |
| EXPECT_EQ(upb_Array_Size(arr), 2u); |
| } |
| |
| TEST(RepeatedFieldTest, RepeatedMessageLongVarintSizeFastPath) { |
| char trace_buf[64]; |
| Arena mt_arena; |
| Arena msg_arena; |
| |
| auto [sub_mt, sub_field] = |
| test::MiniTable::MakeSingleFieldTable<test::field_types::Int32>( |
| 1, kUpb_DecodeFast_Scalar, mt_arena.ptr()); |
| |
| auto [mt, field] = |
| test::MiniTable::MakeSingleFieldTable<test::field_types::Message>( |
| 1, kUpb_DecodeFast_Repeated, mt_arena.ptr()); |
| |
| const upb_MiniTable* subs[1] = {sub_mt}; |
| bool linked = |
| upb_MiniTable_Link(const_cast<upb_MiniTable*>(mt), subs, 1, nullptr, 0); |
| ASSERT_TRUE(linked); |
| |
| upb_Message* msg = upb_Message_New(mt, msg_arena.ptr()); |
| |
| // Payload: |
| // Element 1: tag 1, len 2, int32 value 5 |
| // Element 2: tag 1, len 2 (parsed as overlong 3-byte varint), int32 value 6 |
| std::string payload("\x0a\x02\x08\x05\x0a\x82\x80\x00\x08\x06", 10); |
| 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); |
| #if !defined(NDEBUG) |
| #if UPB_FASTTABLE |
| EXPECT_EQ(FilteredTrace(absl::string_view(trace_buf)), "DDFFDFF"); |
| #else |
| EXPECT_EQ(FilteredTrace(absl::string_view(trace_buf)), "MMMM"); |
| #endif |
| #endif |
| } |
| |
| TEST(RepeatedFieldTest, LongRepeatedField) { |
| auto trace_buf = std::make_unique<std::array<char, 1024>>(); |
| using TypeParam = field_types::Fixed64; |
| using Value = typename TypeParam::Value; |
| upb::Arena msg_arena; |
| upb::Arena mt_arena; |
| auto [mt, field] = MiniTable::MakeSingleFieldTable<TypeParam>( |
| 1, kUpb_DecodeFast_Packed, mt_arena.ptr()); |
| upb_Message* msg = upb_Message_New(mt, msg_arena.ptr()); |
| wire_types::WireMessage wire_msg; |
| std::vector<Value> expected; |
| for (int i = 0; i < 256; ++i) { |
| wire_msg.push_back({1, TypeParam::WireValue(i)}); |
| expected.push_back(i); |
| } |
| std::string payload = ToBinaryPayload(wire_msg); |
| upb_DecodeStatus result = upb_DecodeWithTrace( |
| payload.data(), payload.size(), msg, mt, nullptr, 0, msg_arena.ptr(), |
| trace_buf->data(), trace_buf->size()); |
| ASSERT_EQ(result, kUpb_DecodeStatus_Ok) << upb_DecodeStatus_String(result); |
| EXPECT_EQ(GetRepeatedField<Value>(msg, field), expected); |
| |
| // We can't easily check the trace here because the large array size will |
| // force reallocations that cause fallbacks to the MiniTable decoder. |
| } |
| |
| TYPED_TEST(PackedTest, DecodeTruncatedPackedFieldMaxLen) { |
| char trace_buf[64]; |
| upb::Arena msg_arena; |
| upb::Arena mt_arena; |
| auto [mt, field] = MiniTable::MakeSingleFieldTable<TypeParam>( |
| 1, kUpb_DecodeFast_Packed, mt_arena.ptr()); |
| upb_Message* msg = upb_Message_New(mt, msg_arena.ptr()); |
| // Malformed payload with the maximum allowed varint length but only one byte |
| // of data. |
| std::string payload = "\012\xff\xff\xff\xff\x07\000\000\000\000"; |
| 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_Malformed) |
| << upb_DecodeStatus_String(result); |
| } |
| |
| TYPED_TEST(PackedTest, DecodeTruncatedPackedFieldShortLength) { |
| char trace_buf[64]; |
| upb::Arena msg_arena; |
| upb::Arena mt_arena; |
| auto [mt, field] = MiniTable::MakeSingleFieldTable<TypeParam>( |
| 1, kUpb_DecodeFast_Packed, mt_arena.ptr()); |
| upb_Message* msg = upb_Message_New(mt, msg_arena.ptr()); |
| // Malformed payload with the maximum allowed varint length but only one byte |
| // of data. |
| std::string payload = "\012\001"; |
| 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_Malformed) |
| << upb_DecodeStatus_String(result); |
| } |
| TEST(DecodeTest, EmptyMiniTableDecodedAsUnknown) { |
| Arena mt_arena; |
| Arena msg_arena; |
| |
| upb_MiniTable* empty_mt = |
| (upb_MiniTable*)upb_Arena_Malloc(mt_arena.ptr(), sizeof(upb_MiniTable)); |
| memset(empty_mt, 0, sizeof(upb_MiniTable)); |
| empty_mt->UPB_PRIVATE(size) = sizeof(upb_Message); |
| empty_mt->UPB_ONLYBITS(field_count) = 0; |
| |
| upb_Message* msg = upb_Message_New(empty_mt, msg_arena.ptr()); |
| |
| // An arbitrary payload that should be parsed as unknown: |
| // field 1, length-delimited, length 2, data="\x08\x05" |
| std::string payload("\x0a\x02\x08\x05"); |
| |
| upb_DecodeStatus result = upb_Decode(payload.data(), payload.size(), msg, |
| empty_mt, nullptr, 0, msg_arena.ptr()); |
| |
| ASSERT_EQ(result, kUpb_DecodeStatus_Ok) << upb_DecodeStatus_String(result); |
| |
| EXPECT_TRUE(upb_Message_HasUnknown(msg)); |
| |
| uintptr_t iter = kUpb_Message_UnknownBegin; |
| upb_MessageUnknown data; |
| ASSERT_TRUE(upb_Message_NextUnknown2(msg, &data, &iter)); |
| ASSERT_EQ(data.type, kUpb_MessageUnknownType_StringView); |
| EXPECT_EQ(absl::string_view(data.value.bytes.data, data.value.bytes.size), |
| payload); |
| EXPECT_FALSE(upb_Message_NextUnknown2(msg, &data, &iter)); |
| } |
| |
| TEST(DecodeTest, ConsecutiveUnknownFieldsWithoutAlias) { |
| char trace_buf[64]; |
| Arena mt_arena; |
| |
| auto [mt, field] = MiniTable::MakeSingleFieldTable<field_types::Int32>( |
| 1, kUpb_DecodeFast_Scalar, mt_arena.ptr()); |
| |
| // Field 2: tag 2, varint, value 2 -> \x10\x02 |
| // Field 3: tag 3, varint, value 3 -> \x18\x03 |
| std::string payload("\x10\x02\x18\x03", 4); |
| |
| for (int options : GetDecodeOptionsToTest()) { |
| Arena msg_arena; |
| upb_Message* msg = upb_Message_New(mt, msg_arena.ptr()); |
| memset(trace_buf, 0, sizeof(trace_buf)); |
| |
| upb_DecodeStatus result = upb_DecodeWithTrace( |
| payload.data(), payload.size(), msg, mt, nullptr, options, |
| msg_arena.ptr(), trace_buf, sizeof(trace_buf)); |
| |
| ASSERT_EQ(result, kUpb_DecodeStatus_Ok) << upb_DecodeStatus_String(result); |
| |
| EXPECT_TRUE(upb_Message_HasUnknown(msg)); |
| |
| uintptr_t iter = kUpb_Message_UnknownBegin; |
| upb_MessageUnknown data; |
| |
| // We expect them to be merged. |
| ASSERT_TRUE(upb_Message_NextUnknown2(msg, &data, &iter)); |
| ASSERT_EQ(data.type, kUpb_MessageUnknownType_StringView); |
| EXPECT_EQ(absl::string_view(data.value.bytes.data, data.value.bytes.size), |
| payload); |
| EXPECT_FALSE(upb_Message_NextUnknown2(msg, &data, &iter)); |
| |
| #ifndef NDEBUG |
| // Assert that consecutive unknown fields optimization took effect, decoding |
| // both unknown fields in a single step (trace "M" instead of "MM"). |
| EXPECT_EQ(absl::string_view(trace_buf), |
| GetExpectedConsecutiveUnknownsTrace(options)); |
| #endif |
| } |
| } |
| |
| TEST(DecodeTest, ConsecutiveUnknownFieldsWithAlias) { |
| char trace_buf[64]; |
| Arena mt_arena; |
| |
| auto [mt, field] = MiniTable::MakeSingleFieldTable<field_types::Int32>( |
| 1, kUpb_DecodeFast_Scalar, mt_arena.ptr()); |
| |
| // Field 2: tag 2, varint, value 2 -> \x10\x02 |
| // Field 3: tag 3, varint, value 3 -> \x18\x03 |
| std::string payload("\x10\x02\x18\x03", 4); |
| |
| for (int extra_options : GetDecodeOptionsToTest()) { |
| Arena msg_arena; |
| upb_Message* msg = upb_Message_New(mt, msg_arena.ptr()); |
| memset(trace_buf, 0, sizeof(trace_buf)); |
| |
| int options = extra_options | kUpb_DecodeOption_AliasString; |
| upb_DecodeStatus result = upb_DecodeWithTrace( |
| payload.data(), payload.size(), msg, mt, nullptr, options, |
| msg_arena.ptr(), trace_buf, sizeof(trace_buf)); |
| |
| ASSERT_EQ(result, kUpb_DecodeStatus_Ok) << upb_DecodeStatus_String(result); |
| |
| EXPECT_TRUE(upb_Message_HasUnknown(msg)); |
| |
| uintptr_t iter = kUpb_Message_UnknownBegin; |
| upb_MessageUnknown data; |
| |
| ASSERT_TRUE(upb_Message_NextUnknown2(msg, &data, &iter)); |
| ASSERT_EQ(data.type, kUpb_MessageUnknownType_StringView); |
| EXPECT_EQ(absl::string_view(data.value.bytes.data, data.value.bytes.size), |
| payload); |
| EXPECT_FALSE(upb_Message_NextUnknown2(msg, &data, &iter)); |
| |
| #ifndef NDEBUG |
| EXPECT_EQ(absl::string_view(trace_buf), |
| GetExpectedConsecutiveUnknownsTrace(options)); |
| #endif |
| } |
| } |
| |
| TEST(DecodeTest, MaxDepthPayloadParsesSuccessfully) { |
| upb::Arena mt_arena; |
| upb::Arena msg_arena; |
| |
| // Construct recursive message to allow testing arbitrary depths. |
| auto [mt, field] = |
| test::MiniTable::MakeSingleFieldTable<test::field_types::Message>( |
| 1, kUpb_DecodeFast_Scalar, mt_arena.ptr()); |
| const upb_MiniTable* subs[1] = {mt}; // Submessage is of own type. |
| bool linked = |
| upb_MiniTable_Link(const_cast<upb_MiniTable*>(mt), subs, 1, nullptr, 0); |
| ASSERT_TRUE(linked); |
| |
| // We'll set a small depth limit to make it easy to test. |
| const int kMaxDepth = 10; |
| int options = upb_Decode_LimitDepth(0, kMaxDepth); |
| |
| auto make_payload = [](int depth) { |
| std::string payload; |
| for (int i = 0; i < depth; ++i) { |
| // field 1, delimited |
| payload += '\n'; |
| // length (remaining payload) |
| // Each level adds 2 bytes (tag + length byte). |
| payload.push_back(static_cast<char>((depth - i - 1) * 2)); |
| } |
| return payload; |
| }; |
| |
| // Test depth kMaxDepth - should succeed. |
| { |
| std::string payload = make_payload(kMaxDepth); |
| 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); |
| } |
| |
| // Test depth kMaxDepth + 1 - should fail. |
| { |
| std::string payload = make_payload(kMaxDepth + 1); |
| 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_MaxDepthExceeded) |
| << upb_DecodeStatus_String(result); |
| } |
| } |
| |
| TEST(DecodeTest, DecodeNonCanonicalExtensionAsUnknown) { |
| upb::Arena arena; |
| |
| // 1. Create base msg which starts empty |
| upb_test_ModelWithExtensions* msg = |
| upb_test_ModelWithExtensions_new(arena.ptr()); |
| |
| // 2. Create parsed submessage ("World") |
| upb_Message* extension1 = |
| UPB_UPCAST(upb_test_ModelExtension1_new(arena.ptr())); |
| upb_test_ModelExtension1_set_str((upb_test_ModelExtension1*)extension1, |
| upb_StringView_FromString("World")); |
| |
| // 3. msg has a non-canonical extension A |
| EXPECT_TRUE(UPB_PRIVATE(_upb_Message_SetNonCanonicalExtension)( |
| UPB_UPCAST(msg), upb_test_ModelExtension1_model_ext_ext, &extension1, |
| arena.ptr())); |
| |
| // Verify extension count is 0 before encoding/decoding. |
| EXPECT_EQ((int)upb_Message_ExtensionCount(UPB_UPCAST(msg)), 0); |
| |
| // 5. Obtain encoded non-canonical extension A by serializing msg |
| char* buf; |
| size_t size; |
| upb_EncodeStatus enc_status = |
| upb_Encode(UPB_UPCAST(msg), &upb_0test__ModelWithExtensions_msg_init, 0, |
| arena.ptr(), &buf, &size); |
| ASSERT_EQ(enc_status, kUpb_EncodeStatus_Ok); |
| ASSERT_GT(size, 0u); |
| |
| // 6. Decode with extreg = nullptr (so the encoded extension A is decoded as |
| // unknown bytes) |
| upb_DecodeStatus dec_status = upb_Decode( |
| buf, size, UPB_UPCAST(msg), &upb_0test__ModelWithExtensions_msg_init, |
| /*extreg=*/nullptr, 0, arena.ptr()); |
| ASSERT_EQ(dec_status, kUpb_DecodeStatus_Ok); |
| |
| // 7. Verify that we end up with exactly one non-canonical extension A + one |
| // unknown bytes block representing A |
| int non_canonical_count = 0; |
| int unknown_bytes_count = 0; |
| uintptr_t iter = kUpb_Message_UnknownBegin; |
| upb_MessageUnknown data; |
| while (upb_Message_NextUnknown2(UPB_UPCAST(msg), &data, &iter)) { |
| if (data.type == kUpb_MessageUnknownType_NonCanonicalExtension) { |
| non_canonical_count++; |
| } else if (data.type == kUpb_MessageUnknownType_StringView) { |
| unknown_bytes_count++; |
| } |
| } |
| EXPECT_EQ(non_canonical_count, 1); |
| EXPECT_EQ(unknown_bytes_count, 1); |
| |
| // Verify extension APIs: there are zero canonical extensions. |
| EXPECT_EQ((int)upb_Message_ExtensionCount(UPB_UPCAST(msg)), 0); |
| uintptr_t ext_iter = kUpb_Message_ExtensionBegin; |
| const upb_MiniTableExtension* ext_out = nullptr; |
| upb_MessageValue val_out; |
| EXPECT_FALSE(upb_Message_NextExtension(UPB_UPCAST(msg), &ext_out, &val_out, |
| &ext_iter)); |
| } |
| |
| TEST(DecodeTest, DecodeExtensionAsUnknownWithPreexistingUnknown) { |
| upb::Arena arena; |
| |
| // 1. Create a temporary message to serialize the extension |
| upb_test_ModelWithExtensions* tmp_msg = |
| upb_test_ModelWithExtensions_new(arena.ptr()); |
| |
| // 2. Create parsed submessage ("World") |
| upb_Message* extension1 = |
| UPB_UPCAST(upb_test_ModelExtension1_new(arena.ptr())); |
| upb_test_ModelExtension1_set_str((upb_test_ModelExtension1*)extension1, |
| upb_StringView_FromString("World")); |
| |
| // 3. Attach to tmp_msg as a non-canonical extension so we can serialize it to |
| // get the bytes |
| EXPECT_TRUE(UPB_PRIVATE(_upb_Message_SetNonCanonicalExtension)( |
| UPB_UPCAST(tmp_msg), upb_test_ModelExtension1_model_ext_ext, &extension1, |
| arena.ptr())); |
| |
| // 5. Obtain encoded extension A by serializing tmp_msg |
| char* buf; |
| size_t size; |
| upb_EncodeStatus enc_status = |
| upb_Encode(UPB_UPCAST(tmp_msg), &upb_0test__ModelWithExtensions_msg_init, |
| 0, arena.ptr(), &buf, &size); |
| ASSERT_EQ(enc_status, kUpb_EncodeStatus_Ok); |
| ASSERT_GT(size, 0u); |
| |
| // 6. Create destination message and put the serialized bytes as an unknown |
| // field on msg |
| upb_test_ModelWithExtensions* msg = |
| upb_test_ModelWithExtensions_new(arena.ptr()); |
| bool add_ok = UPB_PRIVATE(_upb_Message_AddUnknown)( |
| UPB_UPCAST(msg), buf, size, arena.ptr(), kUpb_AddUnknown_Alias); |
| ASSERT_TRUE(add_ok); |
| |
| // Verify extension count is 0 before decoding. |
| EXPECT_EQ((int)upb_Message_ExtensionCount(UPB_UPCAST(msg)), 0); |
| |
| // 7. Decode with extreg = nullptr (so the encoded extension A is decoded as |
| // unknown bytes) |
| upb_DecodeStatus dec_status = upb_Decode( |
| buf, size, UPB_UPCAST(msg), &upb_0test__ModelWithExtensions_msg_init, |
| /*extreg=*/nullptr, 0, arena.ptr()); |
| ASSERT_EQ(dec_status, kUpb_DecodeStatus_Ok); |
| |
| // 8. Verify that we end up with exactly two unknown bytes blocks representing |
| // A |
| int non_canonical_count = 0; |
| int unknown_bytes_count = 0; |
| uintptr_t iter = kUpb_Message_UnknownBegin; |
| upb_MessageUnknown data; |
| while (upb_Message_NextUnknown2(UPB_UPCAST(msg), &data, &iter)) { |
| if (data.type == kUpb_MessageUnknownType_NonCanonicalExtension) { |
| non_canonical_count++; |
| } else if (data.type == kUpb_MessageUnknownType_StringView) { |
| unknown_bytes_count++; |
| } |
| } |
| EXPECT_EQ(non_canonical_count, 0); |
| EXPECT_EQ(unknown_bytes_count, 2); |
| |
| // Verify extension APIs: there are zero canonical extensions. |
| EXPECT_EQ((int)upb_Message_ExtensionCount(UPB_UPCAST(msg)), 0); |
| uintptr_t ext_iter = kUpb_Message_ExtensionBegin; |
| const upb_MiniTableExtension* ext_out = nullptr; |
| upb_MessageValue val_out; |
| EXPECT_FALSE(upb_Message_NextExtension(UPB_UPCAST(msg), &ext_out, &val_out, |
| &ext_iter)); |
| } |
| |
| TEST(DecodeTest, DecodeGroupFieldFromDelimitedWireFormatAsUnknown) { |
| upb::Arena mt_arena; |
| upb::Arena msg_arena; |
| |
| // 1. Create Parent MiniTable containing a repeated Group field directly. |
| auto [parent_mt, parent_field] = |
| test::MiniTable::MakeSingleFieldTable<test::field_types::Group>( |
| 5, kUpb_DecodeFast_Repeated, mt_arena.ptr()); |
| |
| // 2. Build length-delimited wire payload for Group field 5: |
| // Tag 5 Delimited = 42 (0x2a), length = 2, child field 1 = 123 ("\x08\x7b"). |
| std::string payload("\x2a\x02\x08\x7b", 4); |
| |
| // 3. Parse the payload into Parent Message. |
| upb_Message* parent_msg = upb_Message_New(parent_mt, msg_arena.ptr()); |
| upb_DecodeStatus result = |
| upb_Decode(payload.data(), payload.size(), parent_msg, parent_mt, nullptr, |
| 0, msg_arena.ptr()); |
| |
| // 4. Verify parsing succeeded cleanly. |
| ASSERT_EQ(result, kUpb_DecodeStatus_Ok) << upb_DecodeStatus_String(result); |
| |
| // 5. Verify repeated Group field 5 was NOT populated as a known field. |
| const upb_Array* arr = upb_Message_GetArray(parent_msg, parent_field); |
| EXPECT_EQ(arr, nullptr); |
| |
| // 6. Verify the wire payload was instead preserved inside the Unknown field |
| // set. |
| EXPECT_TRUE(upb_Message_HasUnknown(parent_msg)); |
| |
| uintptr_t iter = kUpb_Message_UnknownBegin; |
| upb_MessageUnknown data; |
| ASSERT_TRUE(upb_Message_NextUnknown2(parent_msg, &data, &iter)); |
| ASSERT_EQ(data.type, kUpb_MessageUnknownType_StringView); |
| EXPECT_EQ(absl::string_view(data.value.bytes.data, data.value.bytes.size), |
| payload); |
| EXPECT_FALSE(upb_Message_NextUnknown2(parent_msg, &data, &iter)); |
| } |
| |
| TEST(DecodeTest, ConsecutiveUnknownFieldsWithGroup) { |
| char trace_buf[64]; |
| Arena mt_arena; |
| |
| auto [mt, field] = MiniTable::MakeSingleFieldTable<field_types::Int32>( |
| 1, kUpb_DecodeFast_Scalar, mt_arena.ptr()); |
| |
| // Field 2: StartGroup -> \x13 |
| // Field 3: Varint, value 123 -> \x18\x7b |
| // Field 2: EndGroup -> \x14 |
| // Field 4: Varint, value 456 -> \x20\xc8\x03 |
| std::string payload("\x13\x18\x7b\x14\x20\xc8\x03", 7); |
| |
| for (int options : GetDecodeOptionsToTest()) { |
| Arena msg_arena; |
| upb_Message* msg = upb_Message_New(mt, msg_arena.ptr()); |
| memset(trace_buf, 0, sizeof(trace_buf)); |
| |
| upb_DecodeStatus result = upb_DecodeWithTrace( |
| payload.data(), payload.size(), msg, mt, nullptr, options, |
| msg_arena.ptr(), trace_buf, sizeof(trace_buf)); |
| |
| ASSERT_EQ(result, kUpb_DecodeStatus_Ok) << upb_DecodeStatus_String(result); |
| |
| EXPECT_TRUE(upb_Message_HasUnknown(msg)); |
| |
| uintptr_t iter = kUpb_Message_UnknownBegin; |
| upb_MessageUnknown data; |
| |
| // We expect them to be merged. |
| ASSERT_TRUE(upb_Message_NextUnknown2(msg, &data, &iter)); |
| ASSERT_EQ(data.type, kUpb_MessageUnknownType_StringView); |
| EXPECT_EQ(absl::string_view(data.value.bytes.data, data.value.bytes.size), |
| payload); |
| EXPECT_FALSE(upb_Message_NextUnknown2(msg, &data, &iter)); |
| |
| #ifndef NDEBUG |
| const char* expected = "M"; |
| #if UPB_FASTTABLE |
| if (!(options & kUpb_DecodeOption_DisableFastTable)) { |
| expected = "D<M"; |
| } |
| #endif |
| EXPECT_EQ(absl::string_view(trace_buf), expected); |
| #endif |
| } |
| } |
| |
| TEST(DecodeTest, MessageSetConsecutiveUnknowns) { |
| Arena mt_arena; |
| |
| const upb_MiniTable* mset_mt = &upb_0decode_0test__TestMessageSet_msg_init; |
| const upb_MiniTableExtension* ext = upb_decode_test_ext_message_set_ext; |
| |
| upb_ExtensionRegistry* reg = upb_ExtensionRegistry_New(mt_arena.ptr()); |
| ASSERT_TRUE(reg != nullptr); |
| EXPECT_EQ(upb_ExtensionRegistry_Add(reg, ext), |
| kUpb_ExtensionRegistryStatus_Ok); |
| |
| // 5. Construct the payload. |
| // Field 10 (Varint, value 1) is unknown: \x50\x01 |
| // Field 1 (StartGroup, representing the MessageSet Item) |
| // Inside the group: |
| // Field 2 (type_id = 2000): \x10\xd0\x0f |
| // Field 3 (message: empty message, length 0): \x1a\x00 |
| // Field 1 (EndGroup): \x0c |
| std::string payload("\x50\x01\x0b\x10\xd0\x0f\x1a\x00\x0c", 9); |
| |
| for (int options : GetDecodeOptionsToTest()) { |
| Arena msg_arena; |
| upb_Message* msg = upb_Message_New(mset_mt, msg_arena.ptr()); |
| ASSERT_TRUE(msg != nullptr); |
| |
| // Parse the payload. |
| upb_DecodeStatus result = |
| upb_Decode(payload.data(), payload.size(), msg, mset_mt, reg, options, |
| msg_arena.ptr()); |
| |
| ASSERT_EQ(result, kUpb_DecodeStatus_Ok) << upb_DecodeStatus_String(result); |
| |
| // Check if the extension was successfully parsed. |
| EXPECT_TRUE(upb_Message_HasExtension(msg, ext)); |
| } |
| } |
| |
| TEST(DecodeTest, FieldZeroRejected) { |
| Arena mt_arena; |
| |
| // 1. Empty message with field 0 varint payload. |
| { |
| upb_MiniTable* empty_mt = |
| (upb_MiniTable*)upb_Arena_Malloc(mt_arena.ptr(), sizeof(upb_MiniTable)); |
| memset(empty_mt, 0, sizeof(upb_MiniTable)); |
| empty_mt->UPB_PRIVATE(size) = sizeof(upb_Message); |
| empty_mt->UPB_ONLYBITS(field_count) = 0; |
| |
| std::string payload("\x00\x00", 2); |
| for (int options : GetDecodeOptionsToTest()) { |
| Arena msg_arena; |
| upb_Message* msg = upb_Message_New(empty_mt, msg_arena.ptr()); |
| upb_DecodeStatus result = |
| upb_Decode(payload.data(), payload.size(), msg, empty_mt, nullptr, |
| options, msg_arena.ptr()); |
| EXPECT_EQ(result, kUpb_DecodeStatus_Malformed); |
| } |
| } |
| |
| // 2. Field 0 varint inside unknown group. |
| { |
| auto [mt, field] = MiniTable::MakeSingleFieldTable<field_types::Int32>( |
| 1, kUpb_DecodeFast_Scalar, mt_arena.ptr()); |
| |
| // Field 2 (StartGroup) containing Field 0 varint. |
| std::string payload("\x13\x00\x00\x14", 4); |
| 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_Malformed); |
| } |
| } |
| |
| // 3. Field 0 varint inside MessageSet item. |
| { |
| const upb_MiniTable* mset_mt = &upb_0decode_0test__TestMessageSet_msg_init; |
| // Field 1 (StartGroup for MessageSet Item) containing Field 0 varint. |
| std::string payload("\x0b\x00\x00\x0c", 4); |
| for (int options : GetDecodeOptionsToTest()) { |
| Arena msg_arena; |
| upb_Message* msg = upb_Message_New(mset_mt, msg_arena.ptr()); |
| upb_DecodeStatus result = |
| upb_Decode(payload.data(), payload.size(), msg, mset_mt, nullptr, |
| options, msg_arena.ptr()); |
| EXPECT_EQ(result, kUpb_DecodeStatus_Malformed); |
| } |
| } |
| } |
| |
| 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 |
| } // namespace upb |