| // 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 <limits> |
| #include <optional> |
| #include <string> |
| #include <type_traits> |
| #include <vector> |
| |
| #include <gtest/gtest.h> |
| #include "absl/strings/str_cat.h" |
| #include "absl/strings/string_view.h" |
| #include "upb/mem/arena.hpp" |
| #include "upb/message/accessors.h" |
| #include "upb/message/accessors.hpp" |
| #include "upb/message/message.h" |
| #include "upb/mini_table/field.h" |
| #include "upb/mini_table/message.h" |
| #include "upb/wire/decode_fast/combinations.h" |
| #include "upb/wire/test_util/field_types.h" |
| #include "upb/wire/test_util/make_mini_table.h" |
| #include "upb/wire/test_util/wire_message.h" |
| |
| // Must be last. |
| #include "upb/port/def.inc" |
| |
| namespace upb { |
| namespace test { |
| |
| namespace { |
| |
| template <typename T> |
| struct TestValues { |
| static constexpr T kZero = 0; |
| static constexpr T kMin = std::numeric_limits<T>::min(); |
| static constexpr T kMax = std::numeric_limits<T>::max(); |
| }; |
| |
| template <> |
| struct TestValues<std::string> { |
| static constexpr absl::string_view kZero = ""; |
| static constexpr absl::string_view kMin = "a very minimum valued string!"; |
| static constexpr absl::string_view kMax = "a very maximum valued string!"; |
| }; |
| |
| 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 |
| } |
| |
| 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(TestValues<Value>::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), TestValues<Value>::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(TestValues<Value>::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), TestValues<Value>::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(TestValues<Value>::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), TestValues<Value>::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 = std::numeric_limits<Value>::max(); |
| } |
| 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(TestValues<Value>::kZero))}, |
| {1, TypeParam::WireValue(Value(TestValues<Value>::kMin))}, |
| {1, TypeParam::WireValue(Value(TestValues<Value>::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(TestValues<Value>::kZero), |
| Value(TestValues<Value>::kMin), |
| Value(TestValues<Value>::kMax)})); |
| EXPECT_EQ(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(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)})); |
| 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(0)) + |
| ToBinaryPayload(TypeParam::WireValue(1 << 10)) + |
| // 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(1 << 20), 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(absl::string_view(trace_buf), |
| ExpectedRepeatedFieldTrace(mt, field, 3)); |
| } |
| |
| 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. |
| } |
| |
| } // namespace |
| |
| } // namespace test |
| } // namespace upb |