blob: af6dbd36952d1ce72da9a2be8ce31e5b0ffd0b96 [file]
// Protocol Buffers - Google's data interchange format
// Copyright 2025 Google LLC. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd
#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