| // 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 <initializer_list> |
| #include <memory> |
| #include <optional> |
| #include <string> |
| #include <type_traits> |
| #include <utility> |
| #include <vector> |
| |
| #include <gtest/gtest.h> |
| #include "absl/log/absl_check.h" |
| #include "absl/strings/ascii.h" |
| #include "absl/strings/escaping.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/status.hpp" |
| #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/map.h" // IWYU pragma: keep |
| #include "upb/message/internal/message.h" |
| #include "upb/message/map.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/internal/modifiers.h" |
| #include "upb/mini_descriptor/link.h" |
| #include "upb/mini_table/enum.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" |
| #include "upb/wire/types.h" |
| |
| // Must be last. |
| #include "upb/port/def.inc" |
| |
| namespace upb { |
| namespace test { |
| |
| namespace { |
| |
| inline upb_MessageValue MsgVal(int32_t val) { |
| upb_MessageValue v; |
| v.int32_val = val; |
| return v; |
| } |
| |
| inline upb_MessageValue MsgVal(int64_t val) { |
| upb_MessageValue v; |
| v.int64_val = val; |
| return v; |
| } |
| |
| inline upb_MessageValue MsgVal(uint32_t val) { |
| upb_MessageValue v; |
| v.uint32_val = val; |
| return v; |
| } |
| |
| inline upb_MessageValue MsgVal(uint64_t val) { |
| upb_MessageValue v; |
| v.uint64_val = val; |
| return v; |
| } |
| |
| inline upb_MessageValue MsgVal(bool val) { |
| upb_MessageValue v; |
| v.bool_val = val; |
| return v; |
| } |
| |
| inline upb_MessageValue MsgVal(upb_StringView val) { |
| upb_MessageValue v; |
| v.str_val = val; |
| return v; |
| } |
| |
| 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 |
| constexpr absl::string_view 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 |
| constexpr absl::string_view 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"). |
| constexpr absl::string_view 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 |
| constexpr absl::string_view 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)); |
| } |
| } |
| |
| TEST(DecodeTest, SetSubMessageMapValidation) { |
| Arena arena; |
| upb_Status status; |
| upb_Status_Clear(&status); |
| |
| // 1. Parent message table with repeated field (valid for map), scalar field, |
| // and oneof field. |
| upb::MtDataEncoder parent_enc; |
| parent_enc.StartMessage(kUpb_MessageModifier_IsExtendable); |
| parent_enc.PutField(kUpb_FieldType_Message, 1, kUpb_FieldModifier_IsRepeated); |
| parent_enc.PutField(kUpb_FieldType_Message, 2, 0); // scalar message |
| parent_enc.PutField(kUpb_FieldType_Message, 3, 0); // oneof message |
| parent_enc.PutField(kUpb_FieldType_Group, 4, 0); // group field |
| parent_enc.StartOneof(); |
| parent_enc.PutOneofField(3); |
| upb_MiniTable* parent_mt = upb_MiniTable_Build( |
| parent_enc.data().data(), parent_enc.data().size(), arena.ptr(), &status); |
| ASSERT_TRUE(upb_Status_IsOk(&status)) << upb_Status_ErrorMessage(&status); |
| upb_MiniTableField* repeated_field = const_cast<upb_MiniTableField*>( |
| upb_MiniTable_GetFieldByIndex(parent_mt, 0)); |
| upb_MiniTableField* scalar_field = const_cast<upb_MiniTableField*>( |
| upb_MiniTable_GetFieldByIndex(parent_mt, 1)); |
| upb_MiniTableField* oneof_field = const_cast<upb_MiniTableField*>( |
| upb_MiniTable_GetFieldByIndex(parent_mt, 2)); |
| upb_MiniTableField* group_field = const_cast<upb_MiniTableField*>( |
| upb_MiniTable_GetFieldByIndex(parent_mt, 3)); |
| |
| // 2. Valid map entry table |
| upb::MtDataEncoder map_enc; |
| map_enc.EncodeMap(kUpb_FieldType_Int32, kUpb_FieldType_Message, 0, 0); |
| upb_MiniTable* map_entry = upb_MiniTable_Build( |
| map_enc.data().data(), map_enc.data().size(), arena.ptr(), &status); |
| ASSERT_TRUE(upb_Status_IsOk(&status)) << upb_Status_ErrorMessage(&status); |
| |
| // 3. Submessage for value field |
| auto [sub_mt, sub_field] = |
| MiniTable::MakeSingleFieldTable<field_types::Int32>( |
| 1, kUpb_DecodeFast_Scalar, arena.ptr()); |
| |
| // Valid linking: parent repeated field -> map_entry |
| EXPECT_TRUE( |
| upb_MiniTable_SetSubMessage(parent_mt, repeated_field, map_entry)); |
| EXPECT_TRUE(upb_MiniTableField_IsMap(repeated_field)); |
| |
| // Valid linking: map_entry field 2 (value) -> sub_mt |
| upb_MiniTableField* val_field = const_cast<upb_MiniTableField*>( |
| upb_MiniTable_GetFieldByIndex(map_entry, 1)); |
| EXPECT_TRUE(upb_MiniTable_SetSubMessage(map_entry, val_field, sub_mt)); |
| |
| // Invalid: linking map_entry to a scalar (non-repeated) field |
| EXPECT_FALSE(upb_MiniTable_SetSubMessage(parent_mt, scalar_field, map_entry)); |
| |
| // Invalid: linking map_entry to a oneof field |
| EXPECT_FALSE(upb_MiniTable_SetSubMessage(parent_mt, oneof_field, map_entry)); |
| |
| // Invalid: linking map_entry to a group field |
| EXPECT_FALSE(upb_MiniTable_SetSubMessage(parent_mt, group_field, map_entry)); |
| |
| // Invalid: map_entry field 1 (key) -> sub_mt (key cannot have submessage) |
| upb_MiniTableField* key_field = const_cast<upb_MiniTableField*>( |
| upb_MiniTable_GetFieldByIndex(map_entry, 0)); |
| EXPECT_FALSE(upb_MiniTable_SetSubMessage(map_entry, key_field, sub_mt)); |
| |
| // Invalid: linking map_entry to an extension field |
| upb::MtDataEncoder ext_enc; |
| ext_enc.EncodeExtension(kUpb_FieldType_Message, 100, |
| kUpb_FieldModifier_IsRepeated); |
| upb_MiniTableExtension* ext = |
| upb_MiniTableExtension_Build(ext_enc.data().data(), ext_enc.data().size(), |
| parent_mt, arena.ptr(), &status); |
| ASSERT_TRUE(upb_Status_IsOk(&status)) << upb_Status_ErrorMessage(&status); |
| EXPECT_FALSE(upb_MiniTableExtension_SetSubMessage(ext, map_entry)); |
| |
| // Invalid: table_is_map && sub_is_map (nested map entry) |
| EXPECT_FALSE(upb_MiniTable_SetSubMessage(map_entry, val_field, map_entry)); |
| } |
| |
| TEST(DecodeTest, SetSubEnumMapValidation) { |
| Arena arena; |
| upb_Status status; |
| upb_Status_Clear(&status); |
| |
| upb::MtDataEncoder map_enum_enc; |
| map_enum_enc.EncodeMap(kUpb_FieldType_Int32, kUpb_FieldType_Enum, 0, |
| kUpb_FieldModifier_IsClosedEnum); |
| upb_MiniTable* map_enum_entry = |
| upb_MiniTable_Build(map_enum_enc.data().data(), |
| map_enum_enc.data().size(), arena.ptr(), &status); |
| ASSERT_TRUE(upb_Status_IsOk(&status)) << upb_Status_ErrorMessage(&status); |
| |
| upb_MiniTableField* key_field = const_cast<upb_MiniTableField*>( |
| upb_MiniTable_GetFieldByIndex(map_enum_entry, 0)); |
| upb_MiniTableField* val_field = const_cast<upb_MiniTableField*>( |
| upb_MiniTable_GetFieldByIndex(map_enum_entry, 1)); |
| |
| // Enum with 0 |
| upb::MtDataEncoder enum_with_0_enc; |
| enum_with_0_enc.StartEnum(); |
| enum_with_0_enc.PutEnumValue(0); |
| enum_with_0_enc.PutEnumValue(1); |
| enum_with_0_enc.EndEnum(); |
| upb_MiniTableEnum* enum_with_0 = upb_MiniTableEnum_Build( |
| enum_with_0_enc.data().data(), enum_with_0_enc.data().size(), arena.ptr(), |
| &status); |
| ASSERT_TRUE(upb_Status_IsOk(&status)) << upb_Status_ErrorMessage(&status); |
| |
| // Enum without 0 |
| upb::MtDataEncoder enum_without_0_enc; |
| enum_without_0_enc.StartEnum(); |
| enum_without_0_enc.PutEnumValue(1); |
| enum_without_0_enc.PutEnumValue(2); |
| enum_without_0_enc.EndEnum(); |
| upb_MiniTableEnum* enum_without_0 = upb_MiniTableEnum_Build( |
| enum_without_0_enc.data().data(), enum_without_0_enc.data().size(), |
| arena.ptr(), &status); |
| ASSERT_TRUE(upb_Status_IsOk(&status)) << upb_Status_ErrorMessage(&status); |
| |
| // Invalid: linking subenum to field 1 (key) of map entry |
| EXPECT_FALSE( |
| upb_MiniTable_SetSubEnum(map_enum_entry, key_field, enum_with_0)); |
| |
| // Invalid: linking subenum lacking 0 to field 2 (value) of map entry |
| EXPECT_FALSE( |
| upb_MiniTable_SetSubEnum(map_enum_entry, val_field, enum_without_0)); |
| |
| // Valid: linking subenum with 0 to field 2 (value) of map entry |
| EXPECT_TRUE(upb_MiniTable_SetSubEnum(map_enum_entry, val_field, enum_with_0)); |
| } |
| |
| TEST(DecodeTest, BuildMalformedMapDescriptorValidation) { |
| Arena arena; |
| |
| // Invalid: map mini-descriptor with 1 field (field_count != 2) |
| { |
| upb_Status status; |
| upb_Status_Clear(&status); |
| upb_MiniTable* bad_mt = |
| upb_MiniTable_Build("M\x01", 2, arena.ptr(), &status); |
| EXPECT_FALSE(upb_Status_IsOk(&status)); |
| EXPECT_EQ(bad_mt, nullptr); |
| } |
| |
| // Invalid: map mini-descriptor with float key |
| { |
| upb_Status status; |
| upb_Status_Clear(&status); |
| upb::MtDataEncoder bad_enc; |
| bad_enc.EncodeMap(kUpb_FieldType_Float, kUpb_FieldType_Int32, 0, 0); |
| upb_MiniTable* bad_mt = upb_MiniTable_Build( |
| bad_enc.data().data(), bad_enc.data().size(), arena.ptr(), &status); |
| EXPECT_FALSE(upb_Status_IsOk(&status)); |
| EXPECT_EQ(bad_mt, nullptr); |
| } |
| |
| // Invalid: map mini-descriptor with group value |
| { |
| upb_Status status; |
| upb_Status_Clear(&status); |
| upb::MtDataEncoder bad_enc; |
| bad_enc.EncodeMap(kUpb_FieldType_Int32, kUpb_FieldType_Group, 0, 0); |
| upb_MiniTable* bad_mt = upb_MiniTable_Build( |
| bad_enc.data().data(), bad_enc.data().size(), arena.ptr(), &status); |
| EXPECT_FALSE(upb_Status_IsOk(&status)); |
| EXPECT_EQ(bad_mt, nullptr); |
| } |
| } |
| |
| TEST(DecodeTest, DecodeMapInt32Int32SingleAndConsecutive) { |
| const upb_MiniTable* mt = &upb_0test__ModelWithMaps_msg_init; |
| |
| Arena enc_arena; |
| upb_test_ModelWithMaps* src_single = |
| upb_test_ModelWithMaps_new(enc_arena.ptr()); |
| upb_test_ModelWithMaps_map_ii_set(src_single, 10, 100, enc_arena.ptr()); |
| size_t single_size; |
| char* single_buf = upb_test_ModelWithMaps_serialize( |
| src_single, enc_arena.ptr(), &single_size); |
| ASSERT_NE(single_buf, nullptr); |
| absl::string_view single_payload(single_buf, single_size); |
| |
| upb_test_ModelWithMaps* src_multi = |
| upb_test_ModelWithMaps_new(enc_arena.ptr()); |
| upb_test_ModelWithMaps_map_ii_set(src_multi, 10, 100, enc_arena.ptr()); |
| upb_test_ModelWithMaps_map_ii_set(src_multi, 20, 200, enc_arena.ptr()); |
| upb_test_ModelWithMaps_map_ii_set(src_multi, 30, 300, enc_arena.ptr()); |
| size_t multi_size; |
| char* multi_buf = |
| upb_test_ModelWithMaps_serialize(src_multi, enc_arena.ptr(), &multi_size); |
| ASSERT_NE(multi_buf, nullptr); |
| absl::string_view multi_payload(multi_buf, multi_size); |
| |
| for (int options : GetDecodeOptionsToTest()) { |
| // Single entry test |
| { |
| Arena msg_arena; |
| char trace_buf[64] = {0}; |
| upb_test_ModelWithMaps* msg = upb_test_ModelWithMaps_new(msg_arena.ptr()); |
| upb_DecodeStatus result = upb_DecodeWithTrace( |
| single_payload.data(), single_payload.size(), UPB_UPCAST(msg), mt, |
| nullptr, options, msg_arena.ptr(), trace_buf, sizeof(trace_buf)); |
| ASSERT_EQ(result, kUpb_DecodeStatus_Ok) |
| << upb_DecodeStatus_String(result); |
| |
| const upb_Map* map = _upb_test_ModelWithMaps_map_ii_upb_map(msg); |
| ASSERT_NE(map, nullptr); |
| EXPECT_EQ(upb_Map_Size(map), 1u); |
| upb_MessageValue val; |
| EXPECT_TRUE(upb_Map_Get(map, MsgVal(10), &val)); |
| EXPECT_EQ(val.int32_val, 100); |
| |
| #ifndef NDEBUG |
| std::string expected_trace = "MMM"; |
| #if UPB_FASTTABLE |
| if (!(options & kUpb_DecodeOption_DisableFastTable)) { |
| expected_trace = "DF"; |
| } |
| #endif |
| EXPECT_EQ(FilteredTrace(absl::string_view(trace_buf)), expected_trace); |
| #endif |
| } |
| |
| // Consecutive entries test (loopback) |
| { |
| Arena msg_arena; |
| char trace_buf[64] = {0}; |
| upb_test_ModelWithMaps* msg = upb_test_ModelWithMaps_new(msg_arena.ptr()); |
| upb_DecodeStatus result = upb_DecodeWithTrace( |
| multi_payload.data(), multi_payload.size(), UPB_UPCAST(msg), mt, |
| nullptr, options, msg_arena.ptr(), trace_buf, sizeof(trace_buf)); |
| ASSERT_EQ(result, kUpb_DecodeStatus_Ok) |
| << upb_DecodeStatus_String(result); |
| |
| const upb_Map* map = _upb_test_ModelWithMaps_map_ii_upb_map(msg); |
| ASSERT_NE(map, nullptr); |
| EXPECT_EQ(upb_Map_Size(map), 3u); |
| upb_MessageValue val; |
| EXPECT_TRUE(upb_Map_Get(map, MsgVal(10), &val)); |
| EXPECT_EQ(val.int32_val, 100); |
| EXPECT_TRUE(upb_Map_Get(map, MsgVal(20), &val)); |
| EXPECT_EQ(val.int32_val, 200); |
| EXPECT_TRUE(upb_Map_Get(map, MsgVal(30), &val)); |
| EXPECT_EQ(val.int32_val, 300); |
| |
| #ifndef NDEBUG |
| std::string expected_trace = "MMMMMMMMM"; |
| #if UPB_FASTTABLE |
| if (!(options & kUpb_DecodeOption_DisableFastTable)) { |
| // First entry extends past limit_ptr into buffer slop, falling back to |
| // generic decoding (<MMM) which transitions stream to patch buffer. |
| // Subsequent entries decode in fasttable (DFF). |
| expected_trace = "D<MMMDFF"; |
| } |
| #endif |
| EXPECT_EQ(FilteredTrace(absl::string_view(trace_buf)), expected_trace); |
| #endif |
| } |
| } |
| } |
| |
| TEST(DecodeTest, DecodeMapStringStringSingleAndConsecutive) { |
| const upb_MiniTable* mt = &upb_0test__ModelWithMaps_msg_init; |
| |
| Arena enc_arena; |
| upb_test_ModelWithMaps* src_single = |
| upb_test_ModelWithMaps_new(enc_arena.ptr()); |
| upb_test_ModelWithMaps_map_ss_set(src_single, upb_StringView_FromString("k1"), |
| upb_StringView_FromString("v1"), |
| enc_arena.ptr()); |
| size_t single_size; |
| char* single_buf = upb_test_ModelWithMaps_serialize( |
| src_single, enc_arena.ptr(), &single_size); |
| ASSERT_NE(single_buf, nullptr); |
| absl::string_view single_payload(single_buf, single_size); |
| |
| upb_test_ModelWithMaps* src_multi = |
| upb_test_ModelWithMaps_new(enc_arena.ptr()); |
| upb_test_ModelWithMaps_map_ss_set(src_multi, upb_StringView_FromString("k1"), |
| upb_StringView_FromString("v1"), |
| enc_arena.ptr()); |
| upb_test_ModelWithMaps_map_ss_set(src_multi, upb_StringView_FromString("k2"), |
| upb_StringView_FromString("v2"), |
| enc_arena.ptr()); |
| size_t multi_size; |
| char* multi_buf = |
| upb_test_ModelWithMaps_serialize(src_multi, enc_arena.ptr(), &multi_size); |
| ASSERT_NE(multi_buf, nullptr); |
| absl::string_view multi_payload(multi_buf, multi_size); |
| |
| for (int options : GetDecodeOptionsToTest()) { |
| // Single entry test |
| { |
| Arena msg_arena; |
| char trace_buf[64] = {0}; |
| upb_test_ModelWithMaps* msg = upb_test_ModelWithMaps_new(msg_arena.ptr()); |
| upb_DecodeStatus result = upb_DecodeWithTrace( |
| single_payload.data(), single_payload.size(), UPB_UPCAST(msg), mt, |
| nullptr, options, msg_arena.ptr(), trace_buf, sizeof(trace_buf)); |
| ASSERT_EQ(result, kUpb_DecodeStatus_Ok) |
| << upb_DecodeStatus_String(result); |
| |
| const upb_Map* map = _upb_test_ModelWithMaps_map_ss_upb_map(msg); |
| ASSERT_NE(map, nullptr); |
| EXPECT_EQ(upb_Map_Size(map), 1u); |
| upb_MessageValue val; |
| EXPECT_TRUE( |
| upb_Map_Get(map, MsgVal(upb_StringView_FromString("k1")), &val)); |
| EXPECT_EQ(absl::string_view(val.str_val.data, val.str_val.size), "v1"); |
| |
| #ifndef NDEBUG |
| std::string expected_trace = "MMM"; |
| #if UPB_FASTTABLE |
| if (!(options & kUpb_DecodeOption_DisableFastTable)) { |
| expected_trace = "DF"; |
| } |
| #endif |
| EXPECT_EQ(FilteredTrace(absl::string_view(trace_buf)), expected_trace); |
| #endif |
| } |
| |
| // Consecutive entries test (loopback) |
| { |
| Arena msg_arena; |
| char trace_buf[64] = {0}; |
| upb_test_ModelWithMaps* msg = upb_test_ModelWithMaps_new(msg_arena.ptr()); |
| upb_DecodeStatus result = upb_DecodeWithTrace( |
| multi_payload.data(), multi_payload.size(), UPB_UPCAST(msg), mt, |
| nullptr, options, msg_arena.ptr(), trace_buf, sizeof(trace_buf)); |
| ASSERT_EQ(result, kUpb_DecodeStatus_Ok) |
| << upb_DecodeStatus_String(result); |
| |
| const upb_Map* map = _upb_test_ModelWithMaps_map_ss_upb_map(msg); |
| ASSERT_NE(map, nullptr); |
| EXPECT_EQ(upb_Map_Size(map), 2u); |
| upb_MessageValue val; |
| EXPECT_TRUE( |
| upb_Map_Get(map, MsgVal(upb_StringView_FromString("k1")), &val)); |
| EXPECT_EQ(absl::string_view(val.str_val.data, val.str_val.size), "v1"); |
| EXPECT_TRUE( |
| upb_Map_Get(map, MsgVal(upb_StringView_FromString("k2")), &val)); |
| EXPECT_EQ(absl::string_view(val.str_val.data, val.str_val.size), "v2"); |
| |
| #ifndef NDEBUG |
| std::string expected_trace = "MMMMMM"; |
| #if UPB_FASTTABLE |
| if (!(options & kUpb_DecodeOption_DisableFastTable)) { |
| expected_trace = "D<MMMDF"; |
| } |
| #endif |
| EXPECT_EQ(FilteredTrace(absl::string_view(trace_buf)), expected_trace); |
| #endif |
| } |
| } |
| } |
| |
| TEST(DecodeTest, DecodeMapInt32Message) { |
| const upb_MiniTable* mt = &upb_0test__ModelWithMaps_msg_init; |
| |
| Arena enc_arena; |
| upb_test_ModelWithMaps* src = upb_test_ModelWithMaps_new(enc_arena.ptr()); |
| upb_test_ModelWithExtensions* submsg = |
| upb_test_ModelWithExtensions_new(enc_arena.ptr()); |
| upb_test_ModelWithExtensions_set_random_int32(submsg, 123); |
| upb_test_ModelWithMaps_map_im_set(src, 10, submsg, enc_arena.ptr()); |
| |
| size_t size; |
| char* buf = upb_test_ModelWithMaps_serialize(src, enc_arena.ptr(), &size); |
| ASSERT_NE(buf, nullptr); |
| absl::string_view payload(buf, size); |
| |
| for (int options : GetDecodeOptionsToTest()) { |
| Arena msg_arena; |
| char trace_buf[64] = {0}; |
| upb_test_ModelWithMaps* msg = upb_test_ModelWithMaps_new(msg_arena.ptr()); |
| upb_DecodeStatus result = upb_DecodeWithTrace( |
| payload.data(), payload.size(), UPB_UPCAST(msg), mt, nullptr, options, |
| msg_arena.ptr(), trace_buf, sizeof(trace_buf)); |
| ASSERT_EQ(result, kUpb_DecodeStatus_Ok) << upb_DecodeStatus_String(result); |
| |
| const upb_Map* map = _upb_test_ModelWithMaps_map_im_upb_map(msg); |
| ASSERT_NE(map, nullptr); |
| EXPECT_EQ(upb_Map_Size(map), 1u); |
| upb_MessageValue val; |
| EXPECT_TRUE(upb_Map_Get(map, MsgVal(10), &val)); |
| ASSERT_NE(val.msg_val, nullptr); |
| const upb_test_ModelWithExtensions* decoded_sub = |
| (const upb_test_ModelWithExtensions*)val.msg_val; |
| EXPECT_EQ(upb_test_ModelWithExtensions_random_int32(decoded_sub), 123); |
| |
| #ifndef NDEBUG |
| std::string expected_trace = "MMMM"; |
| #if UPB_FASTTABLE |
| if (!(options & kUpb_DecodeOption_DisableFastTable)) { |
| expected_trace = "DDFF"; |
| } |
| #endif |
| EXPECT_EQ(FilteredTrace(absl::string_view(trace_buf)), expected_trace); |
| #endif |
| } |
| } |
| |
| TEST(DecodeTest, DecodeMapUnknownFieldFallback) { |
| const upb_MiniTable* mt = &upb_0test__ModelWithMaps_msg_init; |
| |
| // Field 4 (tag 0x22): |
| // Entry with key=10 (\x08\x0a), val=100 (\x10\x64), and unknown field 3 |
| // (\x18\x03) len 6 -> \x22\x06\x08\x0a\x10\x64\x18\x03 |
| constexpr absl::string_view payload("\x22\x06\x08\x0a\x10\x64\x18\x03", 8); |
| |
| for (int options : GetDecodeOptionsToTest()) { |
| Arena msg_arena; |
| char trace_buf[64] = {0}; |
| upb_test_ModelWithMaps* msg = upb_test_ModelWithMaps_new(msg_arena.ptr()); |
| upb_DecodeStatus result = upb_DecodeWithTrace( |
| payload.data(), payload.size(), UPB_UPCAST(msg), mt, nullptr, options, |
| msg_arena.ptr(), trace_buf, sizeof(trace_buf)); |
| ASSERT_EQ(result, kUpb_DecodeStatus_Ok) << upb_DecodeStatus_String(result); |
| |
| const upb_Map* map = _upb_test_ModelWithMaps_map_ii_upb_map(msg); |
| // When a map entry has unknown fields, the entry is not added to the map. |
| EXPECT_TRUE(map == nullptr || upb_Map_Size(map) == 0u); |
| |
| // The unknown field inside the map entry is retained in message unknowns. |
| EXPECT_TRUE(upb_Message_HasUnknown(UPB_UPCAST(msg))); |
| |
| #ifndef NDEBUG |
| std::string expected_trace = "MMMM"; |
| #if UPB_FASTTABLE |
| if (!(options & kUpb_DecodeOption_DisableFastTable)) { |
| expected_trace = "D<MMMM"; |
| } |
| #endif |
| EXPECT_EQ(FilteredTrace(absl::string_view(trace_buf)), expected_trace); |
| #endif |
| } |
| } |
| |
| static const upb_MiniTable* CreateDynamicMapTable( |
| int field_number, upb_FieldType key_type, upb_FieldType val_type, |
| upb_Arena* arena, const upb_MiniTable* val_submsg = nullptr) { |
| uint64_t key_mod = |
| (key_type == kUpb_FieldType_String) ? kUpb_FieldModifier_ValidateUtf8 : 0; |
| uint64_t val_mod = |
| (val_type == kUpb_FieldType_String) ? kUpb_FieldModifier_ValidateUtf8 : 0; |
| upb::MtDataEncoder entry_enc; |
| entry_enc.EncodeMap(key_type, val_type, key_mod, val_mod); |
| upb::Status status; |
| upb_MiniTable* entry_table = upb_MiniTable_Build( |
| entry_enc.data().data(), entry_enc.data().size(), arena, status.ptr()); |
| UPB_ASSERT(status.ok()); |
| if (val_submsg) { |
| upb_MiniTableField* val_f = const_cast<upb_MiniTableField*>( |
| upb_MiniTable_GetFieldByIndex(entry_table, 1)); |
| bool ok = upb_MiniTable_SetSubMessage(entry_table, val_f, val_submsg); |
| UPB_ASSERT(ok); |
| } |
| |
| upb::MtDataEncoder msg_enc; |
| msg_enc.StartMessage(0); |
| msg_enc.PutField(kUpb_FieldType_Message, field_number, |
| kUpb_FieldModifier_IsRepeated); |
| upb_MiniTable* msg_table = upb_MiniTable_Build( |
| msg_enc.data().data(), msg_enc.data().size(), arena, status.ptr()); |
| UPB_ASSERT(status.ok()); |
| |
| upb_MiniTableField* map_f = const_cast<upb_MiniTableField*>( |
| upb_MiniTable_GetFieldByIndex(msg_table, 0)); |
| bool ok = upb_MiniTable_SetSubMessage(msg_table, map_f, entry_table); |
| UPB_ASSERT(ok); |
| |
| return msg_table; |
| } |
| |
| template <typename K, typename V> |
| std::string SerializeDynamicMapMultiple( |
| const upb_MiniTable* mt, const std::vector<std::pair<K, V>>& entries, |
| upb_Arena* arena) { |
| upb_Message* src = upb_Message_New(mt, arena); |
| const upb_MiniTableField* f = upb_MiniTable_GetFieldByIndex(mt, 0); |
| const upb_MiniTable* entry_mt = upb_MiniTable_GetSubMessageTable(f); |
| upb_Map* map = upb_Message_GetOrCreateMutableMap(src, entry_mt, f, arena); |
| for (const auto& [key, val] : entries) { |
| upb_MessageValue kv{}, vv{}; |
| if constexpr (std::is_same_v<K, int32_t>) |
| kv.int32_val = key; |
| else if constexpr (std::is_same_v<K, int64_t>) |
| kv.int64_val = key; |
| else if constexpr (std::is_same_v<K, uint32_t>) |
| kv.uint32_val = key; |
| else if constexpr (std::is_same_v<K, uint64_t>) |
| kv.uint64_val = key; |
| else if constexpr (std::is_same_v<K, bool>) |
| kv.bool_val = key; |
| else if constexpr (std::is_same_v<K, upb_StringView>) |
| kv.str_val = key; |
| |
| if constexpr (std::is_same_v<V, int32_t>) |
| vv.int32_val = val; |
| else if constexpr (std::is_same_v<V, int64_t>) |
| vv.int64_val = val; |
| else if constexpr (std::is_same_v<V, uint32_t>) |
| vv.uint32_val = val; |
| else if constexpr (std::is_same_v<V, uint64_t>) |
| vv.uint64_val = val; |
| else if constexpr (std::is_same_v<V, float>) |
| vv.float_val = val; |
| else if constexpr (std::is_same_v<V, double>) |
| vv.double_val = val; |
| else if constexpr (std::is_same_v<V, bool>) |
| vv.bool_val = val; |
| else if constexpr (std::is_same_v<V, upb_StringView>) |
| vv.str_val = val; |
| else if constexpr (std::is_same_v<V, const upb_Message*> || |
| std::is_same_v<V, upb_Message*>) |
| vv.msg_val = val; |
| |
| ABSL_CHECK(upb_Map_Set(map, kv, vv, arena)); |
| } |
| |
| char* buf; |
| size_t size; |
| upb_EncodeStatus status = upb_Encode(src, mt, 0, arena, &buf, &size); |
| ABSL_CHECK_EQ(status, kUpb_EncodeStatus_Ok); |
| return std::string(buf, size); |
| } |
| |
| template <typename K, typename V> |
| std::string SerializeDynamicMap(const upb_MiniTable* mt, K key, V val, |
| upb_Arena* arena) { |
| return SerializeDynamicMapMultiple<K, V>(mt, {{key, val}}, arena); |
| } |
| |
| TEST(DecodeTest, DecodeMapTag2Byte) { |
| for (int options : GetDecodeOptionsToTest()) { |
| // IntMap Tag2Byte (field 16) |
| { |
| Arena mt_arena; |
| const upb_MiniTable* mt = CreateDynamicMapTable( |
| 16, kUpb_FieldType_Int32, kUpb_FieldType_Int32, mt_arena.ptr()); |
| std::string payload = |
| SerializeDynamicMap<int32_t, int32_t>(mt, 10, 100, mt_arena.ptr()); |
| Arena msg_arena; |
| char trace_buf[64] = {0}; |
| upb_Message* msg = upb_Message_New(mt, msg_arena.ptr()); |
| |
| 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); |
| |
| const upb_MiniTableField* f = upb_MiniTable_GetFieldByIndex(mt, 0); |
| const upb_Map* map = upb_Message_GetMap(msg, f); |
| ASSERT_NE(map, nullptr); |
| EXPECT_EQ(upb_Map_Size(map), 1u); |
| upb_MessageValue val; |
| EXPECT_TRUE(upb_Map_Get(map, MsgVal(10), &val)); |
| EXPECT_EQ(val.int32_val, 100); |
| |
| #ifndef NDEBUG |
| std::string expected_trace = "MMM"; |
| #if UPB_FASTTABLE |
| if (!(options & kUpb_DecodeOption_DisableFastTable)) { |
| expected_trace = "DF"; |
| } |
| #endif |
| EXPECT_EQ(FilteredTrace(absl::string_view(trace_buf)), expected_trace); |
| #endif |
| } |
| |
| // StrMap Tag2Byte (field 17) |
| { |
| Arena mt_arena; |
| const upb_MiniTable* mt = CreateDynamicMapTable( |
| 17, kUpb_FieldType_String, kUpb_FieldType_String, mt_arena.ptr()); |
| std::string payload = SerializeDynamicMap<upb_StringView, upb_StringView>( |
| mt, upb_StringView_FromString("k1"), upb_StringView_FromString("v1"), |
| mt_arena.ptr()); |
| Arena msg_arena; |
| char trace_buf[64] = {0}; |
| upb_Message* msg = upb_Message_New(mt, msg_arena.ptr()); |
| |
| 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); |
| |
| const upb_MiniTableField* f = upb_MiniTable_GetFieldByIndex(mt, 0); |
| const upb_Map* map = upb_Message_GetMap(msg, f); |
| ASSERT_NE(map, nullptr); |
| EXPECT_EQ(upb_Map_Size(map), 1u); |
| upb_MessageValue val; |
| EXPECT_TRUE( |
| upb_Map_Get(map, MsgVal(upb_StringView_FromString("k1")), &val)); |
| EXPECT_EQ(absl::string_view(val.str_val.data, val.str_val.size), "v1"); |
| |
| #ifndef NDEBUG |
| std::string expected_trace = "MMM"; |
| #if UPB_FASTTABLE |
| if (!(options & kUpb_DecodeOption_DisableFastTable)) { |
| expected_trace = "DF"; |
| } |
| #endif |
| EXPECT_EQ(FilteredTrace(absl::string_view(trace_buf)), expected_trace); |
| #endif |
| } |
| } |
| } |
| |
| TEST(DecodeTest, DecodeMapPresizeSkipScan) { |
| for (int options : GetDecodeOptionsToTest()) { |
| // 1. Multi-entry 1-byte tag (field 1) |
| { |
| Arena mt_arena; |
| const upb_MiniTable* mt = CreateDynamicMapTable( |
| 1, kUpb_FieldType_Int32, kUpb_FieldType_Int32, mt_arena.ptr()); |
| std::vector<std::pair<int32_t, int32_t>> entries; |
| for (int i = 1; i <= 25; ++i) { |
| entries.emplace_back(i, i * 10); |
| } |
| std::string payload = |
| SerializeDynamicMapMultiple(mt, entries, mt_arena.ptr()); |
| Arena msg_arena; |
| char trace_buf[256] = {0}; |
| upb_Message* msg = upb_Message_New(mt, msg_arena.ptr()); |
| |
| 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); |
| |
| const upb_MiniTableField* f = upb_MiniTable_GetFieldByIndex(mt, 0); |
| const upb_Map* map = upb_Message_GetMap(msg, f); |
| ASSERT_NE(map, nullptr); |
| EXPECT_EQ(upb_Map_Size(map), 25u); |
| for (int i = 1; i <= 25; ++i) { |
| upb_MessageValue val; |
| EXPECT_TRUE(upb_Map_Get(map, MsgVal(i), &val)); |
| EXPECT_EQ(val.int32_val, i * 10); |
| } |
| |
| #if UPB_FASTTABLE |
| if (!(options & kUpb_DecodeOption_DisableFastTable)) { |
| // Presizing allocated capacity 32 directly (_upb_entries_needed_for(25) |
| // = 29 -> log2ceil 5 -> 32). |
| EXPECT_EQ(_upb_Map_Capacity(map), 32); |
| #ifndef NDEBUG |
| EXPECT_EQ(FilteredTrace(absl::string_view(trace_buf)), |
| "D" + std::string(22, 'F') + "<MMMDFF"); |
| #endif |
| } |
| #endif |
| } |
| |
| // 2. Multi-entry 2-byte tag (field 16) |
| { |
| Arena mt_arena; |
| const upb_MiniTable* mt = CreateDynamicMapTable( |
| 16, kUpb_FieldType_Int32, kUpb_FieldType_Int32, mt_arena.ptr()); |
| std::vector<std::pair<int32_t, int32_t>> entries; |
| for (int i = 1; i <= 25; ++i) { |
| entries.emplace_back(i, i * 10); |
| } |
| std::string payload = |
| SerializeDynamicMapMultiple(mt, entries, mt_arena.ptr()); |
| Arena msg_arena; |
| char trace_buf[256] = {0}; |
| upb_Message* msg = upb_Message_New(mt, msg_arena.ptr()); |
| |
| 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); |
| |
| const upb_MiniTableField* f = upb_MiniTable_GetFieldByIndex(mt, 0); |
| const upb_Map* map = upb_Message_GetMap(msg, f); |
| ASSERT_NE(map, nullptr); |
| EXPECT_EQ(upb_Map_Size(map), 25u); |
| for (int i = 1; i <= 25; ++i) { |
| upb_MessageValue val; |
| EXPECT_TRUE(upb_Map_Get(map, MsgVal(i), &val)); |
| EXPECT_EQ(val.int32_val, i * 10); |
| } |
| |
| #if UPB_FASTTABLE |
| if (!(options & kUpb_DecodeOption_DisableFastTable)) { |
| EXPECT_EQ(_upb_Map_Capacity(map), 32); |
| #ifndef NDEBUG |
| EXPECT_EQ(FilteredTrace(absl::string_view(trace_buf)), |
| "D" + std::string(22, 'F') + "<MMMDFF"); |
| #endif |
| } |
| #endif |
| } |
| |
| // 3. Multibyte length entry (>= 128 bytes) stops skip-scan early |
| { |
| Arena mt_arena; |
| const upb_MiniTable* mt = CreateDynamicMapTable( |
| 1, kUpb_FieldType_String, kUpb_FieldType_String, mt_arena.ptr()); |
| std::string long_val(130, 'x'); |
| std::string payload; |
| payload += SerializeDynamicMap<upb_StringView, upb_StringView>( |
| mt, upb_StringView_FromString("k0"), upb_StringView_FromString("v0"), |
| mt_arena.ptr()); |
| payload += SerializeDynamicMap<upb_StringView, upb_StringView>( |
| mt, upb_StringView_FromString("k1"), upb_StringView_FromString("v1"), |
| mt_arena.ptr()); |
| payload += SerializeDynamicMap<upb_StringView, upb_StringView>( |
| mt, upb_StringView_FromString("k2"), upb_StringView_FromString("v2"), |
| mt_arena.ptr()); |
| payload += SerializeDynamicMap<upb_StringView, upb_StringView>( |
| mt, upb_StringView_FromString("k3"), |
| upb_StringView_FromString(long_val.c_str()), mt_arena.ptr()); |
| payload += SerializeDynamicMap<upb_StringView, upb_StringView>( |
| mt, upb_StringView_FromString("k4"), upb_StringView_FromString("v4"), |
| mt_arena.ptr()); |
| |
| Arena msg_arena; |
| char trace_buf[256] = {0}; |
| upb_Message* msg = upb_Message_New(mt, msg_arena.ptr()); |
| |
| 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); |
| |
| const upb_MiniTableField* f = upb_MiniTable_GetFieldByIndex(mt, 0); |
| const upb_Map* map = upb_Message_GetMap(msg, f); |
| ASSERT_NE(map, nullptr); |
| EXPECT_EQ(upb_Map_Size(map), 5u); |
| |
| upb_MessageValue val; |
| EXPECT_TRUE( |
| upb_Map_Get(map, MsgVal(upb_StringView_FromString("k3")), &val)); |
| EXPECT_EQ(absl::string_view(val.str_val.data, val.str_val.size), |
| long_val); |
| |
| #if UPB_FASTTABLE |
| if (!(options & kUpb_DecodeOption_DisableFastTable)) { |
| // Fast decoder decodes all entries successfully even when skip-scan |
| // stopped early at the multibyte length entry. |
| #ifndef NDEBUG |
| EXPECT_EQ(FilteredTrace(absl::string_view(trace_buf)), "DFFF<MMMF"); |
| #endif |
| } |
| #endif |
| } |
| } |
| } |
| |
| TEST(DecodeTest, DecodeMapZigZag) { |
| Arena mt_arena; |
| const upb_MiniTable* mt = CreateDynamicMapTable( |
| 1, kUpb_FieldType_SInt32, kUpb_FieldType_SInt32, mt_arena.ptr()); |
| std::string payload = |
| SerializeDynamicMap<int32_t, int32_t>(mt, -10, -100, mt_arena.ptr()); |
| |
| for (int options : GetDecodeOptionsToTest()) { |
| Arena msg_arena; |
| char trace_buf[64] = {0}; |
| upb_Message* msg = upb_Message_New(mt, msg_arena.ptr()); |
| |
| 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); |
| |
| const upb_MiniTableField* f = upb_MiniTable_GetFieldByIndex(mt, 0); |
| const upb_Map* map = upb_Message_GetMap(msg, f); |
| ASSERT_NE(map, nullptr); |
| EXPECT_EQ(upb_Map_Size(map), 1u); |
| upb_MessageValue val; |
| EXPECT_TRUE(upb_Map_Get(map, MsgVal(-10), &val)); |
| EXPECT_EQ(val.int32_val, -100); |
| |
| #ifndef NDEBUG |
| std::string expected_trace = "MMM"; |
| #if UPB_FASTTABLE |
| if (!(options & kUpb_DecodeOption_DisableFastTable)) { |
| expected_trace = "DF"; |
| } |
| #endif |
| EXPECT_EQ(FilteredTrace(absl::string_view(trace_buf)), expected_trace); |
| #endif |
| } |
| } |
| |
| TEST(DecodeTest, DecodeMapFixed32AndFloat) { |
| Arena mt_arena; |
| const upb_MiniTable* mt = CreateDynamicMapTable( |
| 1, kUpb_FieldType_Fixed32, kUpb_FieldType_Float, mt_arena.ptr()); |
| std::string payload = |
| SerializeDynamicMap<uint32_t, float>(mt, 123, 1.5f, mt_arena.ptr()); |
| |
| for (int options : GetDecodeOptionsToTest()) { |
| Arena msg_arena; |
| char trace_buf[64] = {0}; |
| upb_Message* msg = upb_Message_New(mt, msg_arena.ptr()); |
| |
| 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); |
| |
| const upb_MiniTableField* f = upb_MiniTable_GetFieldByIndex(mt, 0); |
| const upb_Map* map = upb_Message_GetMap(msg, f); |
| ASSERT_NE(map, nullptr); |
| EXPECT_EQ(upb_Map_Size(map), 1u); |
| upb_MessageValue val; |
| EXPECT_TRUE(upb_Map_Get(map, MsgVal(123u), &val)); |
| EXPECT_EQ(val.float_val, 1.5f); |
| |
| #ifndef NDEBUG |
| std::string expected_trace = "MMM"; |
| #if UPB_FASTTABLE |
| if (!(options & kUpb_DecodeOption_DisableFastTable)) { |
| expected_trace = "DF"; |
| } |
| #endif |
| EXPECT_EQ(FilteredTrace(absl::string_view(trace_buf)), expected_trace); |
| #endif |
| } |
| } |
| |
| TEST(DecodeTest, DecodeMapFixed64AndDouble) { |
| Arena mt_arena; |
| const upb_MiniTable* mt = CreateDynamicMapTable( |
| 1, kUpb_FieldType_Fixed64, kUpb_FieldType_Double, mt_arena.ptr()); |
| std::string payload = |
| SerializeDynamicMap<uint64_t, double>(mt, 456, 2.5, mt_arena.ptr()); |
| |
| for (int options : GetDecodeOptionsToTest()) { |
| Arena msg_arena; |
| char trace_buf[64] = {0}; |
| upb_Message* msg = upb_Message_New(mt, msg_arena.ptr()); |
| |
| 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); |
| |
| const upb_MiniTableField* f = upb_MiniTable_GetFieldByIndex(mt, 0); |
| const upb_Map* map = upb_Message_GetMap(msg, f); |
| ASSERT_NE(map, nullptr); |
| EXPECT_EQ(upb_Map_Size(map), 1u); |
| upb_MessageValue val; |
| EXPECT_TRUE(upb_Map_Get(map, MsgVal(uint64_t{456}), &val)); |
| EXPECT_EQ(val.double_val, 2.5); |
| |
| #ifndef NDEBUG |
| std::string expected_trace = "MMM"; |
| #if UPB_FASTTABLE |
| if (!(options & kUpb_DecodeOption_DisableFastTable)) { |
| // 20-byte entry extends past limit_ptr (4), falling back to generic |
| // decoder (<MMM). |
| expected_trace = "D<MMM"; |
| } |
| #endif |
| EXPECT_EQ(FilteredTrace(absl::string_view(trace_buf)), expected_trace); |
| #endif |
| } |
| } |
| |
| TEST(DecodeTest, DecodeMapInt64AndBool) { |
| // Int64 -> Int64 |
| { |
| Arena mt_arena; |
| const upb_MiniTable* mt = CreateDynamicMapTable( |
| 1, kUpb_FieldType_Int64, kUpb_FieldType_Int64, mt_arena.ptr()); |
| std::string payload = |
| SerializeDynamicMap<int64_t, int64_t>(mt, 10, 20, mt_arena.ptr()); |
| |
| for (int options : GetDecodeOptionsToTest()) { |
| Arena msg_arena; |
| char trace_buf[64] = {0}; |
| upb_Message* msg = upb_Message_New(mt, msg_arena.ptr()); |
| |
| 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); |
| |
| const upb_MiniTableField* f = upb_MiniTable_GetFieldByIndex(mt, 0); |
| const upb_Map* map = upb_Message_GetMap(msg, f); |
| ASSERT_NE(map, nullptr); |
| EXPECT_EQ(upb_Map_Size(map), 1u); |
| upb_MessageValue val; |
| EXPECT_TRUE(upb_Map_Get(map, MsgVal(int64_t{10}), &val)); |
| EXPECT_EQ(val.int64_val, 20); |
| |
| #ifndef NDEBUG |
| std::string expected_trace = "MMM"; |
| #if UPB_FASTTABLE |
| if (!(options & kUpb_DecodeOption_DisableFastTable)) { |
| expected_trace = "DF"; |
| } |
| #endif |
| EXPECT_EQ(FilteredTrace(absl::string_view(trace_buf)), expected_trace); |
| #endif |
| } |
| } |
| |
| // Int32 -> Bool |
| { |
| Arena mt_arena; |
| const upb_MiniTable* mt = CreateDynamicMapTable( |
| 1, kUpb_FieldType_Int32, kUpb_FieldType_Bool, mt_arena.ptr()); |
| std::string payload = |
| SerializeDynamicMap<int32_t, bool>(mt, 10, true, mt_arena.ptr()); |
| |
| for (int options : GetDecodeOptionsToTest()) { |
| Arena msg_arena; |
| char trace_buf[64] = {0}; |
| upb_Message* msg = upb_Message_New(mt, msg_arena.ptr()); |
| |
| 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); |
| |
| const upb_MiniTableField* f = upb_MiniTable_GetFieldByIndex(mt, 0); |
| const upb_Map* map = upb_Message_GetMap(msg, f); |
| ASSERT_NE(map, nullptr); |
| EXPECT_EQ(upb_Map_Size(map), 1u); |
| upb_MessageValue val; |
| EXPECT_TRUE(upb_Map_Get(map, MsgVal(10), &val)); |
| EXPECT_EQ(val.bool_val, true); |
| |
| #ifndef NDEBUG |
| std::string expected_trace = "MMM"; |
| #if UPB_FASTTABLE |
| if (!(options & kUpb_DecodeOption_DisableFastTable)) { |
| expected_trace = "DF"; |
| } |
| #endif |
| EXPECT_EQ(FilteredTrace(absl::string_view(trace_buf)), expected_trace); |
| #endif |
| } |
| } |
| } |
| |
| TEST(DecodeTest, DecodeMapBoolKeyCoercion) { |
| Arena mt_arena; |
| const upb_MiniTable* mt = CreateDynamicMapTable( |
| 1, kUpb_FieldType_Bool, kUpb_FieldType_Int32, mt_arena.ptr()); |
| std::string payload = |
| SerializeDynamicMap<bool, int32_t>(mt, true, 42, mt_arena.ptr()); |
| |
| for (int options : GetDecodeOptionsToTest()) { |
| // 1. Standard serialized entry (key = true) |
| { |
| 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()); |
| ASSERT_EQ(result, kUpb_DecodeStatus_Ok) |
| << upb_DecodeStatus_String(result); |
| |
| const upb_MiniTableField* f = upb_MiniTable_GetFieldByIndex(mt, 0); |
| const upb_Map* map = upb_Message_GetMap(msg, f); |
| ASSERT_NE(map, nullptr); |
| EXPECT_EQ(upb_Map_Size(map), 1u); |
| upb_MessageValue val; |
| EXPECT_TRUE(upb_Map_Get(map, MsgVal(true), &val)); |
| EXPECT_EQ(val.int32_val, 42); |
| } |
| |
| // 2. Wire payload containing multiple map entries with keys 1, 2, 3, 0. |
| // Wire values 1, 2, 3 must all coerce to true and collapse into a single |
| // map entry (with last write winning), while key 0 coerces to false. |
| // This ensures wire bytes of 0, 1, 2, 3 do not result in more than 2 |
| // entries. |
| { |
| Arena msg_arena; |
| upb_Message* msg = upb_Message_New(mt, msg_arena.ptr()); |
| |
| // Entry 1: key=1 (\x08\x01), val=10 (\x10\x0a) -> len 4 -> |
| // \x0a\x04\x08\x01\x10\x0a Entry 2: key=2 (\x08\x02), val=20 (\x10\x14) |
| // -> len 4 -> \x0a\x04\x08\x02\x10\x14 Entry 3: key=3 (\x08\x03), val=30 |
| // (\x10\x1e) -> len 4 -> \x0a\x04\x08\x03\x10\x1e Entry 4: key=0 |
| // (\x08\x00), val=100 (\x10\x64) -> len 4 -> \x0a\x04\x08\x00\x10\x64 |
| absl::string_view raw_payload( |
| "\x0a\x04\x08\x01\x10\x0a" |
| "\x0a\x04\x08\x02\x10\x14" |
| "\x0a\x04\x08\x03\x10\x1e" |
| "\x0a\x04\x08\x00\x10\x64", |
| 24); |
| upb_DecodeStatus result = |
| upb_Decode(raw_payload.data(), raw_payload.size(), msg, mt, nullptr, |
| options, msg_arena.ptr()); |
| ASSERT_EQ(result, kUpb_DecodeStatus_Ok) |
| << upb_DecodeStatus_String(result); |
| |
| const upb_MiniTableField* f = upb_MiniTable_GetFieldByIndex(mt, 0); |
| const upb_Map* map = upb_Message_GetMap(msg, f); |
| ASSERT_NE(map, nullptr); |
| // Keys 1, 2, 3 all coerce to `true` (overwriting each other), and 0 to |
| // `false`. Total map size must be exactly 2, not 4. |
| EXPECT_EQ(upb_Map_Size(map), 2u); |
| |
| upb_MessageValue val; |
| EXPECT_TRUE(upb_Map_Get(map, MsgVal(true), &val)); |
| EXPECT_EQ(val.int32_val, 30); |
| |
| EXPECT_TRUE(upb_Map_Get(map, MsgVal(false), &val)); |
| EXPECT_EQ(val.int32_val, 100); |
| } |
| } |
| } |
| |
| TEST(DecodeTest, DecodeMapStringBytes) { |
| Arena mt_arena; |
| const upb_MiniTable* mt = CreateDynamicMapTable( |
| 1, kUpb_FieldType_String, kUpb_FieldType_Bytes, mt_arena.ptr()); |
| std::string payload = SerializeDynamicMap<upb_StringView, upb_StringView>( |
| mt, upb_StringView_FromString("k1"), |
| upb_StringView_FromDataAndSize("\xff\xfe", 2), mt_arena.ptr()); |
| |
| for (int options : GetDecodeOptionsToTest()) { |
| Arena msg_arena; |
| char trace_buf[64] = {0}; |
| upb_Message* msg = upb_Message_New(mt, msg_arena.ptr()); |
| |
| 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); |
| |
| const upb_MiniTableField* f = upb_MiniTable_GetFieldByIndex(mt, 0); |
| const upb_Map* map = upb_Message_GetMap(msg, f); |
| ASSERT_NE(map, nullptr); |
| EXPECT_EQ(upb_Map_Size(map), 1u); |
| upb_MessageValue val; |
| EXPECT_TRUE( |
| upb_Map_Get(map, MsgVal(upb_StringView_FromString("k1")), &val)); |
| EXPECT_EQ(absl::string_view(val.str_val.data, val.str_val.size), |
| "\xff\xfe"); |
| |
| #ifndef NDEBUG |
| std::string expected_trace = "MMM"; |
| #if UPB_FASTTABLE |
| if (!(options & kUpb_DecodeOption_DisableFastTable)) { |
| expected_trace = "DF"; |
| } |
| #endif |
| EXPECT_EQ(FilteredTrace(absl::string_view(trace_buf)), expected_trace); |
| #endif |
| } |
| } |
| |
| TEST(DecodeTest, DecodeDynamicMapMessageValueTrace) { |
| Arena mt_arena; |
| auto [sub_mt, sub_field] = |
| test::MiniTable::MakeSingleFieldTable<test::field_types::Int32>( |
| 1, kUpb_DecodeFast_Scalar, mt_arena.ptr()); |
| |
| // 1. Int32 key -> Message value (Tag 1-byte) |
| { |
| const upb_MiniTable* mt = |
| CreateDynamicMapTable(1, kUpb_FieldType_Int32, kUpb_FieldType_Message, |
| mt_arena.ptr(), sub_mt); |
| |
| Arena enc_arena; |
| upb_Message* sub = upb_Message_New(sub_mt, enc_arena.ptr()); |
| ASSERT_TRUE(upb_Message_SetInt32(sub, sub_field, 42, enc_arena.ptr())); |
| std::string payload = SerializeDynamicMap<int32_t, upb_Message*>( |
| mt, 10, sub, enc_arena.ptr()); |
| |
| for (int options : GetDecodeOptionsToTest()) { |
| Arena msg_arena; |
| char trace_buf[64] = {0}; |
| upb_Message* msg = upb_Message_New(mt, msg_arena.ptr()); |
| |
| 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); |
| |
| const upb_MiniTableField* f = upb_MiniTable_GetFieldByIndex(mt, 0); |
| const upb_Map* map = upb_Message_GetMap(msg, f); |
| ASSERT_NE(map, nullptr); |
| EXPECT_EQ(upb_Map_Size(map), 1u); |
| upb_MessageValue val; |
| EXPECT_TRUE(upb_Map_Get(map, MsgVal(10), &val)); |
| ASSERT_NE(val.msg_val, nullptr); |
| EXPECT_EQ(upb_Message_GetInt32(val.msg_val, sub_field, 0), 42); |
| |
| #ifndef NDEBUG |
| std::string expected_trace = "MMMM"; |
| #if UPB_FASTTABLE |
| if (!(options & kUpb_DecodeOption_DisableFastTable)) { |
| expected_trace = "DDFF"; |
| } |
| #endif |
| EXPECT_EQ(FilteredTrace(absl::string_view(trace_buf)), expected_trace); |
| #endif |
| } |
| } |
| |
| // 2. String key -> Message value (Tag 1-byte) |
| { |
| const upb_MiniTable* mt = |
| CreateDynamicMapTable(1, kUpb_FieldType_String, kUpb_FieldType_Message, |
| mt_arena.ptr(), sub_mt); |
| |
| Arena enc_arena; |
| upb_Message* sub = upb_Message_New(sub_mt, enc_arena.ptr()); |
| ASSERT_TRUE(upb_Message_SetInt32(sub, sub_field, 99, enc_arena.ptr())); |
| std::string payload = SerializeDynamicMap<upb_StringView, upb_Message*>( |
| mt, upb_StringView_FromString("my_key"), sub, enc_arena.ptr()); |
| |
| for (int options : GetDecodeOptionsToTest()) { |
| Arena msg_arena; |
| char trace_buf[64] = {0}; |
| upb_Message* msg = upb_Message_New(mt, msg_arena.ptr()); |
| |
| 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); |
| |
| const upb_MiniTableField* f = upb_MiniTable_GetFieldByIndex(mt, 0); |
| const upb_Map* map = upb_Message_GetMap(msg, f); |
| ASSERT_NE(map, nullptr); |
| EXPECT_EQ(upb_Map_Size(map), 1u); |
| upb_MessageValue val; |
| EXPECT_TRUE( |
| upb_Map_Get(map, MsgVal(upb_StringView_FromString("my_key")), &val)); |
| ASSERT_NE(val.msg_val, nullptr); |
| EXPECT_EQ(upb_Message_GetInt32(val.msg_val, sub_field, 0), 99); |
| |
| #ifndef NDEBUG |
| std::string expected_trace = "MMMM"; |
| #if UPB_FASTTABLE |
| if (!(options & kUpb_DecodeOption_DisableFastTable)) { |
| expected_trace = "DDFF"; |
| } |
| #endif |
| EXPECT_EQ(FilteredTrace(absl::string_view(trace_buf)), expected_trace); |
| #endif |
| } |
| } |
| |
| // 3. Int32 key -> Message value (Tag 2-byte, e.g. field 73) |
| { |
| const upb_MiniTable* mt = |
| CreateDynamicMapTable(73, kUpb_FieldType_Int32, kUpb_FieldType_Message, |
| mt_arena.ptr(), sub_mt); |
| |
| Arena enc_arena; |
| upb_Message* sub = upb_Message_New(sub_mt, enc_arena.ptr()); |
| ASSERT_TRUE(upb_Message_SetInt32(sub, sub_field, 730, enc_arena.ptr())); |
| std::string payload = |
| SerializeDynamicMap<int32_t, upb_Message*>(mt, 7, sub, enc_arena.ptr()); |
| |
| for (int options : GetDecodeOptionsToTest()) { |
| Arena msg_arena; |
| char trace_buf[64] = {0}; |
| upb_Message* msg = upb_Message_New(mt, msg_arena.ptr()); |
| |
| 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); |
| |
| const upb_MiniTableField* f = upb_MiniTable_GetFieldByIndex(mt, 0); |
| const upb_Map* map = upb_Message_GetMap(msg, f); |
| ASSERT_NE(map, nullptr); |
| EXPECT_EQ(upb_Map_Size(map), 1u); |
| upb_MessageValue val; |
| EXPECT_TRUE(upb_Map_Get(map, MsgVal(7), &val)); |
| ASSERT_NE(val.msg_val, nullptr); |
| EXPECT_EQ(upb_Message_GetInt32(val.msg_val, sub_field, 0), 730); |
| |
| #ifndef NDEBUG |
| std::string expected_trace = "MMMM"; |
| #if UPB_FASTTABLE |
| if (!(options & kUpb_DecodeOption_DisableFastTable)) { |
| expected_trace = "DDFF"; |
| } |
| #endif |
| EXPECT_EQ(FilteredTrace(absl::string_view(trace_buf)), expected_trace); |
| #endif |
| } |
| } |
| } |
| |
| TEST(DecodeTest, DecodeMapBadUtf8KeyAndValue) { |
| Arena mt_arena; |
| const upb_MiniTable* mt = CreateDynamicMapTable( |
| 1, kUpb_FieldType_String, kUpb_FieldType_String, mt_arena.ptr()); |
| |
| for (int options : GetDecodeOptionsToTest()) { |
| // Bad UTF-8 key |
| { |
| Arena msg_arena; |
| char trace_buf[64] = {0}; |
| upb_Message* msg = upb_Message_New(mt, msg_arena.ptr()); |
| |
| // key = \xff\xfe, val = "v1" |
| constexpr absl::string_view payload("\x0a\x08\x0a\x02\xff\xfe\x12\x02v1", |
| 10); |
| upb_DecodeStatus result = upb_DecodeWithTrace( |
| payload.data(), payload.size(), msg, mt, nullptr, options, |
| msg_arena.ptr(), trace_buf, sizeof(trace_buf)); |
| EXPECT_EQ(result, kUpb_DecodeStatus_BadUtf8); |
| } |
| |
| // Bad UTF-8 value |
| { |
| Arena msg_arena; |
| char trace_buf[64] = {0}; |
| upb_Message* msg = upb_Message_New(mt, msg_arena.ptr()); |
| |
| // key = "k1", val = \xff\xfe |
| constexpr absl::string_view payload("\x0a\x08\x0a\x02k1\x12\x02\xff\xfe", |
| 10); |
| upb_DecodeStatus result = upb_DecodeWithTrace( |
| payload.data(), payload.size(), msg, mt, nullptr, options, |
| msg_arena.ptr(), trace_buf, sizeof(trace_buf)); |
| EXPECT_EQ(result, kUpb_DecodeStatus_BadUtf8); |
| } |
| } |
| } |
| |
| TEST(DecodeTest, DecodeMapOutOfOrderTagsAndFallback) { |
| const upb_MiniTable* mt = &upb_0test__ModelWithMaps_msg_init; |
| |
| for (int options : GetDecodeOptionsToTest()) { |
| // Out-of-order tags: value (tag 2) before key (tag 1) |
| { |
| Arena msg_arena; |
| char trace_buf[64] = {0}; |
| upb_test_ModelWithMaps* msg = upb_test_ModelWithMaps_new(msg_arena.ptr()); |
| |
| // Field 4 (tag 0x22): |
| // Entry: val=100 (\x10\x64), key=10 (\x08\x0a) -> len 4 -> |
| // \x22\x04\x10\x64\x08\x0a |
| constexpr absl::string_view payload("\x22\x04\x10\x64\x08\x0a", 6); |
| upb_DecodeStatus result = upb_DecodeWithTrace( |
| payload.data(), payload.size(), UPB_UPCAST(msg), mt, nullptr, options, |
| msg_arena.ptr(), trace_buf, sizeof(trace_buf)); |
| ASSERT_EQ(result, kUpb_DecodeStatus_Ok) |
| << upb_DecodeStatus_String(result); |
| |
| const upb_Map* map = _upb_test_ModelWithMaps_map_ii_upb_map(msg); |
| ASSERT_NE(map, nullptr); |
| EXPECT_EQ(upb_Map_Size(map), 1u); |
| upb_MessageValue val; |
| EXPECT_TRUE(upb_Map_Get(map, MsgVal(10), &val)); |
| EXPECT_EQ(val.int32_val, 100); |
| |
| #ifndef NDEBUG |
| std::string expected_trace = "MMM"; |
| #if UPB_FASTTABLE |
| if (!(options & kUpb_DecodeOption_DisableFastTable)) { |
| expected_trace = "D<MMM"; |
| } |
| #endif |
| EXPECT_EQ(FilteredTrace(absl::string_view(trace_buf)), expected_trace); |
| #endif |
| } |
| |
| // Empty map entry (len 0) |
| { |
| Arena msg_arena; |
| char trace_buf[64] = {0}; |
| upb_test_ModelWithMaps* msg = upb_test_ModelWithMaps_new(msg_arena.ptr()); |
| |
| constexpr absl::string_view payload("\x22\x00", 2); |
| upb_DecodeStatus result = upb_DecodeWithTrace( |
| payload.data(), payload.size(), UPB_UPCAST(msg), mt, nullptr, options, |
| msg_arena.ptr(), trace_buf, sizeof(trace_buf)); |
| ASSERT_EQ(result, kUpb_DecodeStatus_Ok) |
| << upb_DecodeStatus_String(result); |
| } |
| } |
| } |
| |
| TEST(DecodeTest, DecodeMapRequiredFieldsCheck) { |
| const upb_MiniTable* mt = &upb_0test__ModelWithMaps_msg_init; |
| |
| for (int extra_options : GetDecodeOptionsToTest()) { |
| int options = extra_options | kUpb_DecodeOption_CheckRequired; |
| // Valid submessage containing required field |
| { |
| Arena enc_arena; |
| upb_test_ModelWithMaps* src = upb_test_ModelWithMaps_new(enc_arena.ptr()); |
| upb_test_ModelWithRequiredFields* req = |
| upb_test_ModelWithRequiredFields_new(enc_arena.ptr()); |
| upb_test_ModelWithRequiredFields_set_id(req, 1); |
| upb_test_ModelWithMaps_map_im_required_set(src, 10, req, enc_arena.ptr()); |
| size_t size; |
| char* buf = upb_test_ModelWithMaps_serialize(src, enc_arena.ptr(), &size); |
| ASSERT_NE(buf, nullptr); |
| absl::string_view payload(buf, size); |
| |
| Arena msg_arena; |
| char trace_buf[64] = {0}; |
| upb_test_ModelWithMaps* msg = upb_test_ModelWithMaps_new(msg_arena.ptr()); |
| upb_DecodeStatus result = upb_DecodeWithTrace( |
| payload.data(), payload.size(), UPB_UPCAST(msg), mt, nullptr, options, |
| msg_arena.ptr(), trace_buf, sizeof(trace_buf)); |
| ASSERT_EQ(result, kUpb_DecodeStatus_Ok) |
| << upb_DecodeStatus_String(result); |
| |
| const upb_Map* map = _upb_test_ModelWithMaps_map_im_required_upb_map(msg); |
| ASSERT_NE(map, nullptr); |
| EXPECT_EQ(upb_Map_Size(map), 1u); |
| } |
| |
| // Invalid submessage missing required field |
| { |
| Arena enc_arena; |
| upb_test_ModelWithMaps* src = upb_test_ModelWithMaps_new(enc_arena.ptr()); |
| upb_test_ModelWithRequiredFields* req = |
| upb_test_ModelWithRequiredFields_new(enc_arena.ptr()); |
| // id is not set |
| upb_test_ModelWithMaps_map_im_required_set(src, 10, req, enc_arena.ptr()); |
| size_t size; |
| char* buf = upb_test_ModelWithMaps_serialize(src, enc_arena.ptr(), &size); |
| ASSERT_NE(buf, nullptr); |
| absl::string_view payload(buf, size); |
| |
| Arena msg_arena; |
| char trace_buf[64] = {0}; |
| upb_test_ModelWithMaps* msg = upb_test_ModelWithMaps_new(msg_arena.ptr()); |
| upb_DecodeStatus result = upb_DecodeWithTrace( |
| payload.data(), payload.size(), UPB_UPCAST(msg), mt, nullptr, options, |
| msg_arena.ptr(), trace_buf, sizeof(trace_buf)); |
| EXPECT_EQ(result, kUpb_DecodeStatus_MissingRequired); |
| } |
| } |
| } |
| |
| TEST(DecodeTest, DecodeMapMaxDepthExceeded) { |
| Arena mt_arena; |
| auto [sub_mt, sub_field] = |
| MiniTable::MakeSingleFieldTable<field_types::Message>( |
| 1, kUpb_DecodeFast_Scalar, mt_arena.ptr()); |
| bool linked = upb_MiniTable_SetSubMessage( |
| const_cast<upb_MiniTable*>(sub_mt), |
| const_cast<upb_MiniTableField*>(sub_field), sub_mt); |
| UPB_ASSERT(linked); |
| |
| const upb_MiniTable* mt = CreateDynamicMapTable( |
| 1, kUpb_FieldType_Int32, kUpb_FieldType_Message, mt_arena.ptr(), sub_mt); |
| |
| Arena enc_arena; |
| upb_Message* sub3 = upb_Message_New(sub_mt, enc_arena.ptr()); |
| upb_Message* sub2 = upb_Message_New(sub_mt, enc_arena.ptr()); |
| upb_Message_SetMessage(sub2, sub_field, sub3); |
| upb_Message* sub1 = upb_Message_New(sub_mt, enc_arena.ptr()); |
| upb_Message_SetMessage(sub1, sub_field, sub2); |
| |
| std::string payload = |
| SerializeDynamicMap<int32_t, upb_Message*>(mt, 10, sub1, enc_arena.ptr()); |
| |
| for (int options : GetDecodeOptionsToTest()) { |
| Arena msg_arena; |
| char trace_buf[64] = {0}; |
| upb_Message* msg = upb_Message_New(mt, msg_arena.ptr()); |
| |
| int decode_options = upb_Decode_LimitDepth(options, 2); |
| upb_DecodeStatus result = upb_DecodeWithTrace( |
| payload.data(), payload.size(), msg, mt, nullptr, decode_options, |
| msg_arena.ptr(), trace_buf, sizeof(trace_buf)); |
| EXPECT_EQ(result, kUpb_DecodeStatus_MaxDepthExceeded); |
| } |
| } |
| |
| TEST(DecodeTest, DecodeDynamicMapUnlinkedMessageValue) { |
| Arena mt_arena; |
| auto [sub_mt, sub_field] = |
| MiniTable::MakeSingleFieldTable<field_types::Int32>( |
| 1, kUpb_DecodeFast_Scalar, mt_arena.ptr()); |
| |
| // Linked table for encoding |
| const upb_MiniTable* mt_linked = CreateDynamicMapTable( |
| 1, kUpb_FieldType_Int32, kUpb_FieldType_Message, mt_arena.ptr(), sub_mt); |
| |
| // Unlinked table for decoding (sub_table for val_field is nullptr) |
| const upb_MiniTable* mt_unlinked = CreateDynamicMapTable( |
| 1, kUpb_FieldType_Int32, kUpb_FieldType_Message, mt_arena.ptr(), nullptr); |
| |
| Arena enc_arena; |
| upb_Message* sub = upb_Message_New(sub_mt, enc_arena.ptr()); |
| ASSERT_TRUE(upb_Message_SetInt32(sub, sub_field, 42, enc_arena.ptr())); |
| std::string payload = SerializeDynamicMap<int32_t, upb_Message*>( |
| mt_linked, 10, sub, enc_arena.ptr()); |
| |
| for (int options : GetDecodeOptionsToTest()) { |
| Arena msg_arena; |
| char trace_buf[64] = {0}; |
| upb_Message* msg = upb_Message_New(mt_unlinked, msg_arena.ptr()); |
| |
| upb_DecodeStatus result = upb_DecodeWithTrace( |
| payload.data(), payload.size(), msg, mt_unlinked, nullptr, options, |
| msg_arena.ptr(), trace_buf, sizeof(trace_buf)); |
| EXPECT_EQ(result, kUpb_DecodeStatus_Ok) << upb_DecodeStatus_String(result); |
| |
| // Since the value message is unlinked, it cannot be decoded into the map, |
| // and must be retained in unknown fields. |
| const upb_MiniTableField* f = upb_MiniTable_GetFieldByIndex(mt_unlinked, 0); |
| const upb_Map* map = upb_Message_GetMap(msg, f); |
| if (map != nullptr) { |
| EXPECT_EQ(upb_Map_Size(map), 0u); |
| } |
| EXPECT_TRUE(upb_Message_HasUnknown(msg)); |
| upb_FindUnknownRet2 unknown = upb_Message_FindUnknown2(msg, 1, 0); |
| EXPECT_EQ(unknown.status, kUpb_FindUnknown_Ok); |
| |
| #ifndef NDEBUG |
| std::string expected_trace = "MMM"; |
| #if UPB_FASTTABLE |
| if (!(options & kUpb_DecodeOption_DisableFastTable)) { |
| // D (fasttable map dispatch) -> < (fallback on unlinked submsg) -> MMM |
| // (slow path) |
| expected_trace = "D<MMM"; |
| } |
| #endif |
| EXPECT_EQ(FilteredTrace(absl::string_view(trace_buf)), expected_trace); |
| #endif |
| } |
| } |
| |
| TEST(DecodeTest, DecodeDynamicMapUnlinkedMapField) { |
| Arena mt_arena; |
| auto [sub_mt, sub_field] = |
| MiniTable::MakeSingleFieldTable<field_types::Int32>( |
| 1, kUpb_DecodeFast_Scalar, mt_arena.ptr()); |
| |
| // Linked table for encoding |
| const upb_MiniTable* mt_linked = CreateDynamicMapTable( |
| 1, kUpb_FieldType_Int32, kUpb_FieldType_Message, mt_arena.ptr(), sub_mt); |
| |
| // Unlinked table for decoding: the map field itself on the message has no |
| // submessage table |
| upb::MtDataEncoder msg_enc; |
| msg_enc.StartMessage(0); |
| msg_enc.PutField(kUpb_FieldType_Message, 1, kUpb_FieldModifier_IsRepeated); |
| upb::Status status; |
| const upb_MiniTable* mt_unlinked = |
| upb_MiniTable_Build(msg_enc.data().data(), msg_enc.data().size(), |
| mt_arena.ptr(), status.ptr()); |
| ASSERT_TRUE(status.ok()); |
| |
| Arena enc_arena; |
| upb_Message* sub = upb_Message_New(sub_mt, enc_arena.ptr()); |
| ASSERT_TRUE(upb_Message_SetInt32(sub, sub_field, 42, enc_arena.ptr())); |
| std::string payload = SerializeDynamicMap<int32_t, upb_Message*>( |
| mt_linked, 10, sub, enc_arena.ptr()); |
| |
| for (int options : GetDecodeOptionsToTest()) { |
| Arena msg_arena; |
| char trace_buf[64] = {0}; |
| upb_Message* msg = upb_Message_New(mt_unlinked, msg_arena.ptr()); |
| |
| upb_DecodeStatus result = upb_DecodeWithTrace( |
| payload.data(), payload.size(), msg, mt_unlinked, nullptr, options, |
| msg_arena.ptr(), trace_buf, sizeof(trace_buf)); |
| EXPECT_EQ(result, kUpb_DecodeStatus_Ok) << upb_DecodeStatus_String(result); |
| |
| EXPECT_TRUE(upb_Message_HasUnknown(msg)); |
| upb_FindUnknownRet2 unknown = upb_Message_FindUnknown2(msg, 1, 0); |
| EXPECT_EQ(unknown.status, kUpb_FindUnknown_Ok); |
| |
| #ifndef NDEBUG |
| std::string expected_trace = "M"; |
| #if UPB_FASTTABLE |
| if (!(options & kUpb_DecodeOption_DisableFastTable)) { |
| expected_trace = "D"; |
| } |
| #endif |
| EXPECT_EQ(FilteredTrace(absl::string_view(trace_buf)), expected_trace); |
| #endif |
| } |
| } |
| |
| TEST(DecodeTest, DecodeMapNearBufferEndBoundary) { |
| const upb_MiniTable* mt = &upb_0test__ModelWithMaps_msg_init; |
| |
| // Case 1: Valid map entry positioned such that the initial map field tag is |
| // before limit_ptr, but the map entry payload extends past limit_ptr into the |
| // slop buffer region. The fast decoder should safely fall back to the generic |
| // decoder without overrunning the slop buffer. |
| { |
| std::string payload; |
| payload += "\x08\x01"; // id = 1 (bytes 0..1) |
| payload += "\x22\x04"; // map_ii tag + len (bytes 2..3) |
| payload += "\x08\x01\x10\x2a"; // key=1, value=42 (bytes 4..7) |
| // Pad to 20 bytes with id=1 fields. |
| while (payload.size() < 20) { |
| payload += "\x08\x01"; |
| } |
| ASSERT_EQ(payload.size(), 20u); |
| |
| for (int options : GetDecodeOptionsToTest()) { |
| Arena msg_arena; |
| char trace_buf[64] = {0}; |
| upb_test_ModelWithMaps* msg = upb_test_ModelWithMaps_new(msg_arena.ptr()); |
| upb_DecodeStatus result = upb_DecodeWithTrace( |
| payload.data(), payload.size(), UPB_UPCAST(msg), mt, nullptr, options, |
| msg_arena.ptr(), trace_buf, sizeof(trace_buf)); |
| EXPECT_EQ(result, kUpb_DecodeStatus_Ok) |
| << upb_DecodeStatus_String(result); |
| |
| const upb_Map* map = _upb_test_ModelWithMaps_map_ii_upb_map(msg); |
| ASSERT_NE(map, nullptr); |
| EXPECT_EQ(upb_Map_Size(map), 1u); |
| upb_MessageValue val; |
| EXPECT_TRUE(upb_Map_Get(map, MsgVal(1), &val)); |
| EXPECT_EQ(val.int32_val, 42); |
| } |
| } |
| |
| // Case 2: Malformed varint value with continuation bit set at the very last |
| // byte of the buffer. The map tag is before limit_ptr, and the entry reaches |
| // the exact end of the buffer. If decoded by the fast decoder without the |
| // limit_ptr bounds check, ReadVarint would overrun the physical allocation. |
| // With the fix, it safely falls back to the generic decoder which handles the |
| // buffer transition to e->patch and returns Malformed. |
| { |
| // Buffer size = 18. limit_ptr is at offset 18 - 16 = 2. |
| // Bytes 0..1: map_ii tag (0x22) (before limit_ptr 2) + len 16 (0x10). |
| // Entry spans bytes 2..17 (reaching the exact last byte of the 18-byte |
| // buffer). Bytes 2..16: 15 bytes of key (varint tag 0x08 + 14-byte varint / |
| // fields) Byte 17: val tag 0x10 with no value bytes, or val byte 0x80 |
| // (continuation bit set). |
| std::string bad_payload; |
| bad_payload += "\x22\x10"; // map_ii tag (0x22) + len 16 (0x10) |
| bad_payload += "\x08\x01"; // key tag (0x08), key val (0x01) |
| // Fill with 12 bytes of id=1 |
| for (int i = 0; i < 6; ++i) { |
| bad_payload += "\x08\x01"; |
| } |
| bad_payload += '\x10'; // val tag (0x10) (byte 16) |
| bad_payload += |
| '\x80'; // val varint byte with MSB set at buffer end (byte 17) |
| ASSERT_EQ(bad_payload.size(), 18u); |
| |
| for (int options : GetDecodeOptionsToTest()) { |
| Arena msg_arena; |
| char trace_buf[64] = {0}; |
| upb_test_ModelWithMaps* msg = upb_test_ModelWithMaps_new(msg_arena.ptr()); |
| upb_DecodeStatus result = upb_DecodeWithTrace( |
| bad_payload.data(), bad_payload.size(), UPB_UPCAST(msg), mt, nullptr, |
| options, msg_arena.ptr(), trace_buf, sizeof(trace_buf)); |
| EXPECT_EQ(result, kUpb_DecodeStatus_Malformed); |
| } |
| } |
| } |
| |
| // Builds the serialization of a map field holding a single entry, so that we |
| // can construct entries that don't match the schema. |
| std::string MakeRawMapEntry(int field_number, int key_wire_type, |
| absl::string_view key_payload, int val_wire_type, |
| absl::string_view val_payload) { |
| std::string entry; |
| entry += static_cast<char>((1 << 3) | key_wire_type); |
| entry.append(key_payload.data(), key_payload.size()); |
| entry += static_cast<char>((2 << 3) | val_wire_type); |
| entry.append(val_payload.data(), val_payload.size()); |
| UPB_ASSERT(entry.size() < 128); |
| |
| std::string out; |
| out += static_cast<char>((field_number << 3) | kUpb_WireType_Delimited); |
| out += static_cast<char>(entry.size()); |
| out += entry; |
| return out; |
| } |
| |
| void ExpectFastMatchesMiniTable(const upb_MiniTable* mt, |
| absl::string_view payload, |
| int extra_options = 0) { |
| Arena fast_arena; |
| upb_Message* fast_msg = upb_Message_New(mt, fast_arena.ptr()); |
| upb_DecodeStatus fast_status = |
| upb_Decode(payload.data(), payload.size(), fast_msg, mt, nullptr, |
| extra_options, fast_arena.ptr()); |
| |
| Arena slow_arena; |
| upb_Message* slow_msg = upb_Message_New(mt, slow_arena.ptr()); |
| upb_DecodeStatus slow_status = upb_Decode( |
| payload.data(), payload.size(), slow_msg, mt, nullptr, |
| extra_options | kUpb_DecodeOption_DisableFastTable, slow_arena.ptr()); |
| |
| ASSERT_EQ(fast_status, slow_status) |
| << "fast=" << upb_DecodeStatus_String(fast_status) |
| << " minitable=" << upb_DecodeStatus_String(slow_status) |
| << " payload=" << absl::BytesToHexString(payload); |
| if (fast_status != kUpb_DecodeStatus_Ok) return; |
| |
| char* fast_buf; |
| size_t fast_size; |
| ASSERT_EQ(upb_Encode(fast_msg, mt, kUpb_EncodeOption_Deterministic, |
| fast_arena.ptr(), &fast_buf, &fast_size), |
| kUpb_EncodeStatus_Ok); |
| char* slow_buf; |
| size_t slow_size; |
| ASSERT_EQ(upb_Encode(slow_msg, mt, kUpb_EncodeOption_Deterministic, |
| slow_arena.ptr(), &slow_buf, &slow_size), |
| kUpb_EncodeStatus_Ok); |
| |
| EXPECT_EQ(absl::string_view(fast_buf, fast_size), |
| absl::string_view(slow_buf, slow_size)) |
| << "payload=" << absl::BytesToHexString(payload); |
| } |
| |
| TEST(DecodeTest, DecodeMapWireTypeMismatchMatchesMiniTableDecoder) { |
| struct WireTypeCase { |
| int wire_type; |
| absl::string_view payload; |
| }; |
| static constexpr WireTypeCase kWireTypeCases[] = { |
| {kUpb_WireType_Varint, absl::string_view("\x7f", 1)}, |
| {kUpb_WireType_64Bit, |
| absl::string_view("\x01\x02\x03\x04\x05\x06\x07\x08", 8)}, |
| {kUpb_WireType_32Bit, absl::string_view("\x01\x02\x03\x04", 4)}, |
| {kUpb_WireType_Delimited, absl::string_view("\x03" |
| "abc", |
| 4)}, |
| }; |
| static constexpr upb_FieldType kKeyTypes[] = { |
| kUpb_FieldType_Int32, kUpb_FieldType_Int64, kUpb_FieldType_UInt32, |
| kUpb_FieldType_UInt64, kUpb_FieldType_SInt32, kUpb_FieldType_SInt64, |
| kUpb_FieldType_Fixed32, kUpb_FieldType_Fixed64, kUpb_FieldType_SFixed32, |
| kUpb_FieldType_SFixed64, kUpb_FieldType_Bool, kUpb_FieldType_String, |
| }; |
| static constexpr upb_FieldType kValTypes[] = { |
| kUpb_FieldType_Int32, kUpb_FieldType_UInt64, kUpb_FieldType_SInt64, |
| kUpb_FieldType_Fixed32, kUpb_FieldType_Float, kUpb_FieldType_Double, |
| kUpb_FieldType_Bool, kUpb_FieldType_String, kUpb_FieldType_Bytes, |
| kUpb_FieldType_Message, |
| }; |
| |
| Arena mt_arena; |
| auto [sub_mt, sub_field] = |
| MiniTable::MakeSingleFieldTable<field_types::Int32>( |
| 1, kUpb_DecodeFast_Scalar, mt_arena.ptr()); |
| |
| for (upb_FieldType key_type : kKeyTypes) { |
| for (upb_FieldType val_type : kValTypes) { |
| const upb_MiniTable* mt = CreateDynamicMapTable( |
| 1, key_type, val_type, mt_arena.ptr(), |
| val_type == kUpb_FieldType_Message ? sub_mt : nullptr); |
| for (const WireTypeCase& key_case : kWireTypeCases) { |
| for (const WireTypeCase& val_case : kWireTypeCases) { |
| SCOPED_TRACE(absl::StrCat("key_type=", static_cast<int>(key_type), |
| " val_type=", static_cast<int>(val_type), |
| " key_wire_type=", key_case.wire_type, |
| " val_wire_type=", val_case.wire_type)); |
| ExpectFastMatchesMiniTable( |
| mt, MakeRawMapEntry(1, key_case.wire_type, key_case.payload, |
| val_case.wire_type, val_case.payload)); |
| } |
| } |
| } |
| } |
| } |
| |
| TEST(DecodeTest, DecodeMapDepthLimitMatchesMiniTableDecoder) { |
| Arena mt_arena; |
| auto [sub_mt, sub_field] = |
| MiniTable::MakeSingleFieldTable<field_types::Message>( |
| 1, kUpb_DecodeFast_Scalar, mt_arena.ptr()); |
| bool linked = upb_MiniTable_SetSubMessage( |
| const_cast<upb_MiniTable*>(sub_mt), |
| const_cast<upb_MiniTableField*>(sub_field), sub_mt); |
| UPB_ASSERT(linked); |
| |
| const upb_MiniTable* msg_val_mt = CreateDynamicMapTable( |
| 1, kUpb_FieldType_Int32, kUpb_FieldType_Message, mt_arena.ptr(), sub_mt); |
| const upb_MiniTable* scalar_val_mt = CreateDynamicMapTable( |
| 2, kUpb_FieldType_Int32, kUpb_FieldType_Int32, mt_arena.ptr()); |
| |
| // Need a wrapper message to enforce a nonzero recursion limit |
| auto [holder_mt, holder_field] = |
| MiniTable::MakeSingleFieldTable<field_types::Message>( |
| 1, kUpb_DecodeFast_Scalar, mt_arena.ptr()); |
| bool holder_linked = upb_MiniTable_SetSubMessage( |
| const_cast<upb_MiniTable*>(holder_mt), |
| const_cast<upb_MiniTableField*>(holder_field), scalar_val_mt); |
| UPB_ASSERT(holder_linked); |
| |
| Arena enc_arena; |
| upb_Message* sub = upb_Message_New(sub_mt, enc_arena.ptr()); |
| for (int i = 0; i < 3; ++i) { |
| upb_Message* outer = upb_Message_New(sub_mt, enc_arena.ptr()); |
| upb_Message_SetMessage(outer, sub_field, sub); |
| sub = outer; |
| } |
| std::string msg_val_payload = SerializeDynamicMap<int32_t, upb_Message*>( |
| msg_val_mt, 10, sub, enc_arena.ptr()); |
| std::string scalar_val_payload = SerializeDynamicMap<int32_t, int32_t>( |
| scalar_val_mt, 10, 42, enc_arena.ptr()); |
| |
| UPB_ASSERT(scalar_val_payload.size() < 128); |
| std::string nested_scalar_payload; |
| nested_scalar_payload += |
| static_cast<char>((1 << 3) | kUpb_WireType_Delimited); |
| nested_scalar_payload += static_cast<char>(scalar_val_payload.size()); |
| nested_scalar_payload += scalar_val_payload; |
| |
| // An entry that never recurses into a value, so the MiniTable decoder only |
| // charges one level for it. |
| static constexpr absl::string_view kKeyOnly("\x0a\x02\x08\x0a", 4); |
| |
| for (int max_depth = 1; max_depth <= 10; ++max_depth) { |
| SCOPED_TRACE(absl::StrCat("max_depth=", max_depth)); |
| ExpectFastMatchesMiniTable(msg_val_mt, msg_val_payload, |
| upb_Decode_LimitDepth(0, max_depth)); |
| ExpectFastMatchesMiniTable(scalar_val_mt, scalar_val_payload, |
| upb_Decode_LimitDepth(0, max_depth)); |
| ExpectFastMatchesMiniTable(holder_mt, nested_scalar_payload, |
| upb_Decode_LimitDepth(0, max_depth)); |
| ExpectFastMatchesMiniTable(msg_val_mt, kKeyOnly, |
| upb_Decode_LimitDepth(0, max_depth)); |
| } |
| } |
| |
| TEST(DecodeTest, DecodeMapTruncatedEntryMatchesMiniTableDecoder) { |
| Arena mt_arena; |
| const upb_MiniTable* mt = CreateDynamicMapTable( |
| 1, kUpb_FieldType_Int32, kUpb_FieldType_Int32, mt_arena.ptr()); |
| |
| // 3-byte entry (key tag 0x08, key 0x00, value tag 0x10 with missing value) |
| // padded by 16 bytes so that SizeFitsWithoutSwap is true. |
| std::string payload("\x0a\x03\x08\x00\x10", 5); |
| payload.append(16, '\0'); |
| ExpectFastMatchesMiniTable(mt, payload); |
| } |
| |
| TEST(DecodeTest, DecodeMapOverlongVarintMatchesMiniTableDecoder) { |
| Arena mt_arena; |
| for (upb_FieldType type : |
| {kUpb_FieldType_Int32, kUpb_FieldType_UInt32, kUpb_FieldType_SInt32}) { |
| const upb_MiniTable* mt = |
| CreateDynamicMapTable(1, type, type, mt_arena.ptr()); |
| std::string payload = MakeRawMapEntry( |
| 1, kUpb_WireType_Varint, absl::string_view("\x80\x80\x80\x80\x10", 5), |
| kUpb_WireType_Varint, absl::string_view("\x80\x80\x80\x80\x10", 5)); |
| for (int i = 0; i < 8; ++i) { |
| payload.append("\x0a\x00", 2); |
| } |
| ExpectFastMatchesMiniTable(mt, payload); |
| } |
| } |
| |
| TEST(DecodeTest, DecodeMapBadUtf8KeyWithMalformedValueMatchesMiniTableDecoder) { |
| // The MiniTable decoder validates a string key's UTF-8 as soon as it reads |
| // the key, before it looks at the value. The fast decoder must report the |
| // same error when the value is *also* bad (here: a message value whose bytes |
| // are a truncated varint), rather than surfacing the value's error first. |
| Arena mt_arena; |
| auto [sub_mt, sub_field] = |
| MiniTable::MakeSingleFieldTable<field_types::Int32>( |
| 1, kUpb_DecodeFast_Scalar, mt_arena.ptr()); |
| const upb_MiniTable* mt = CreateDynamicMapTable( |
| 1, kUpb_FieldType_String, kUpb_FieldType_Message, mt_arena.ptr(), sub_mt); |
| |
| // key = "\xff\xfe" (invalid UTF-8), value = submessage {08 80} (truncated |
| // varint for field 1). |
| std::string payload = MakeRawMapEntry( |
| 1, kUpb_WireType_Delimited, absl::string_view("\x02\xff\xfe", 3), |
| kUpb_WireType_Delimited, absl::string_view("\x02\x08\x80", 3)); |
| ExpectFastMatchesMiniTable(mt, payload); |
| } |
| |
| // Builds a message whose fields 1..N have the given types and are all members |
| // of a single oneof. |
| const upb_MiniTable* MakeOneofTable(std::initializer_list<upb_FieldType> types, |
| upb_Arena* arena) { |
| MtDataEncoder e; |
| e.StartMessage(kUpb_MessageModifier_ValidateUtf8); |
| int field_number = 1; |
| for (upb_FieldType type : types) { |
| e.PutField(type, field_number++, kUpb_FieldModifier_ValidateUtf8); |
| } |
| e.StartOneof(); |
| for (int i = 1; i < field_number; i++) e.PutOneofField(i); |
| Status status; |
| const upb_MiniTable* mt = upb_MiniTable_Build( |
| e.data().data(), e.data().size(), arena, status.ptr()); |
| ABSL_CHECK(mt) << upb_Status_ErrorMessage(status.ptr()); |
| return mt; |
| } |
| |
| std::vector<int> GetOneofDecodeOptionsToTest() { |
| std::vector<int> ret; |
| for (int options : GetDecodeOptionsToTest()) { |
| ret.push_back(options); |
| ret.push_back(options | kUpb_DecodeOption_AliasString); |
| } |
| return ret; |
| } |
| |
| // If decoding a oneof member fails, the oneof must still name the member that |
| // was set before, with its value intact. In particular we must never select the |
| // new member while its storage still holds the previous member's bytes (eg. a |
| // string's data pointer read back as an integer). |
| TEST(DecodeTest, FailedOneofScalarLeavesPreviousMember) { |
| Arena mt_arena; |
| const upb_MiniTable* mt = |
| MakeOneofTable({kUpb_FieldType_String, kUpb_FieldType_Bool, |
| kUpb_FieldType_Int32, kUpb_FieldType_Int64}, |
| mt_arena.ptr()); |
| const upb_MiniTableField* str_field = upb_MiniTable_FindFieldByNumber(mt, 1); |
| ASSERT_EQ(upb_MiniTableField_Type(str_field), kUpb_FieldType_String); |
| |
| for (int field_number : {2, 3, 4}) { |
| SCOPED_TRACE(field_number); |
| #if UPB_FASTTABLE |
| ASSERT_TRUE(MiniTable::HasFastTableEntry( |
| mt, upb_MiniTable_FindFieldByNumber(mt, field_number))); |
| #endif |
| // str_field = "abc", then the scalar with an overlong (malformed) varint. |
| std::string payload( |
| "\x0a\x03" |
| "abc"); |
| payload.push_back(static_cast<char>(field_number << 3)); |
| payload.append(10, '\x80'); |
| payload.push_back('\x01'); |
| |
| for (int options : GetOneofDecodeOptionsToTest()) { |
| SCOPED_TRACE(options); |
| Arena arena; |
| upb_Message* msg = upb_Message_New(mt, arena.ptr()); |
| EXPECT_EQ(upb_Decode(payload.data(), payload.size(), msg, mt, nullptr, |
| options, arena.ptr()), |
| kUpb_DecodeStatus_Malformed); |
| EXPECT_EQ(upb_Message_WhichOneofFieldNumber(msg, str_field), 1u); |
| upb_StringView sv = upb_Message_GetString( |
| msg, str_field, upb_StringView_FromDataAndSize(nullptr, 0)); |
| EXPECT_EQ(absl::string_view(sv.data, sv.size), "abc"); |
| } |
| } |
| } |
| |
| TEST(DecodeTest, FailedOneofStringLeavesPreviousMember) { |
| Arena mt_arena; |
| const upb_MiniTable* mt = MakeOneofTable( |
| {kUpb_FieldType_Int64, kUpb_FieldType_String, kUpb_FieldType_Bytes}, |
| mt_arena.ptr()); |
| const upb_MiniTableField* int_field = upb_MiniTable_FindFieldByNumber(mt, 1); |
| ASSERT_EQ(upb_MiniTableField_Type(upb_MiniTable_FindFieldByNumber(mt, 2)), |
| kUpb_FieldType_String); |
| #if UPB_FASTTABLE |
| ASSERT_TRUE( |
| MiniTable::HasFastTableEntry(mt, upb_MiniTable_FindFieldByNumber(mt, 2))); |
| ASSERT_TRUE( |
| MiniTable::HasFastTableEntry(mt, upb_MiniTable_FindFieldByNumber(mt, 3))); |
| #endif |
| |
| // int_field = 123, followed by a string/bytes member that fails to parse. |
| const std::string kPrefix = "\x08\x7b"; |
| const std::string kPayloads[] = { |
| // string = "\xff" (invalid UTF-8). |
| kPrefix + "\x12\x01\xff", |
| // bytes with length 5 but only 2 bytes of data. |
| kPrefix + |
| "\x1a\x05" |
| "ab", |
| }; |
| |
| for (const std::string& payload : kPayloads) { |
| SCOPED_TRACE(absl::CEscape(payload)); |
| for (int options : GetOneofDecodeOptionsToTest()) { |
| SCOPED_TRACE(options); |
| Arena arena; |
| upb_Message* msg = upb_Message_New(mt, arena.ptr()); |
| EXPECT_NE(upb_Decode(payload.data(), payload.size(), msg, mt, nullptr, |
| options, arena.ptr()), |
| kUpb_DecodeStatus_Ok); |
| EXPECT_EQ(upb_Message_WhichOneofFieldNumber(msg, int_field), 1u); |
| EXPECT_EQ(upb_Message_GetInt64(msg, int_field, 0), 123); |
| } |
| } |
| } |
| |
| } // namespace |
| |
| } // namespace test |
| } // namespace upb |