Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 16 additions & 1 deletion cpp/fory/serialization/any_serializer.h
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ template <> struct Serializer<std::any> {
return std::any();
}

return type_info->harness.any_read_fn(ctx);
return read_value(ctx, *type_info);
}

static inline std::any read_data(ReadContext &ctx) {
Expand All @@ -142,6 +142,21 @@ template <> struct Serializer<std::any> {
return std::any();
}

return read_value(ctx, type_info);
}

private:
static inline std::any read_value(ReadContext &ctx,
const TypeInfo &type_info) {
// std::any is a dynamic materialization boundary: its wire type can select
// another registered std::any-bearing object recursively. Keep concrete
// smart-pointer layouts out of this dynamic-depth policy.
auto depth_result = ctx.increase_dyn_depth();
if (FORY_PREDICT_FALSE(!depth_result.ok())) {
ctx.set_error(std::move(depth_result).error());
return std::any();
}
DynDepthGuard depth_guard(ctx);
return type_info.harness.any_read_fn(ctx);
}
};
Expand Down
32 changes: 32 additions & 0 deletions cpp/fory/serialization/any_serializer_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,13 @@ struct AnyHolderStruct {
FORY_STRUCT(AnyHolderStruct, first, second);
};

struct RecursiveAny {
int32_t value;
std::any next;

FORY_STRUCT(RecursiveAny, value, next);
};

TEST(AnySerializerTest, RoundTripStructFields) {
auto fory =
Fory::builder().xlang(true).compatible(false).track_ref(false).build();
Expand Down Expand Up @@ -93,6 +100,31 @@ TEST(AnySerializerTest, RoundTripStructFields) {
EXPECT_EQ(original, deserialized);
}

TEST(AnySerializerTest, RecursiveDepth) {
auto fory =
Fory::builder().xlang(true).track_ref(false).max_dyn_depth(2).build();
ASSERT_TRUE(fory.register_struct<RecursiveAny>(3).ok());
ASSERT_TRUE(register_any_type<RecursiveAny>(fory.type_resolver()).ok());
ASSERT_TRUE(register_any_type<int32_t>(fory.type_resolver()).ok());

RecursiveAny level3{3, int32_t{4}};
RecursiveAny level2{2, level3};
RecursiveAny level1{1, level2};

auto deep_bytes = fory.serialize(level1);
ASSERT_TRUE(deep_bytes.ok()) << deep_bytes.error().to_string();
auto deep_result = fory.deserialize<RecursiveAny>(deep_bytes.value());
ASSERT_FALSE(deep_result.ok());
EXPECT_EQ(deep_result.error().code(), ErrorCode::DepthExceed);

RecursiveAny shallow{1, int32_t{2}};
auto shallow_bytes = fory.serialize(shallow);
ASSERT_TRUE(shallow_bytes.ok()) << shallow_bytes.error().to_string();
auto shallow_result = fory.deserialize<RecursiveAny>(shallow_bytes.value());
ASSERT_TRUE(shallow_result.ok()) << shallow_result.error().to_string();
EXPECT_EQ(std::any_cast<int32_t>(shallow_result.value().next), 2);
}

} // namespace test
} // namespace serialization
} // namespace fory
4 changes: 2 additions & 2 deletions cpp/fory/serialization/basic_serializer.h
Original file line number Diff line number Diff line change
Expand Up @@ -806,7 +806,7 @@ template <> struct Serializer<char16_t> {
}

static inline char16_t read_data(ReadContext &ctx) {
char16_t value;
char16_t value{};
ctx.read_bytes(reinterpret_cast<uint8_t *>(&value), sizeof(char16_t),
ctx.error());
return value;
Expand Down Expand Up @@ -880,7 +880,7 @@ template <> struct Serializer<char32_t> {
}

static inline char32_t read_data(ReadContext &ctx) {
char32_t value;
char32_t value{};
ctx.read_bytes(reinterpret_cast<uint8_t *>(&value), sizeof(char32_t),
ctx.error());
return value;
Expand Down
32 changes: 21 additions & 11 deletions cpp/fory/serialization/context.cc
Original file line number Diff line number Diff line change
Expand Up @@ -482,7 +482,8 @@ ReadContext::read_enum_type_info(uint32_t base_type_id) {
return Unexpected(Error::type_mismatch(type_id, base_type_id));
}

static constexpr size_t k_min_remote_type_meta_limit = 8192;
static constexpr uint64_t k_min_remote_type_meta_limit = 8192;
static constexpr uint64_t k_max_remote_type_meta_keys = 8192;

Result<std::string, Error>
ReadContext::check_remote_type_meta_limit(const TypeMeta &type_meta) {
Expand All @@ -499,6 +500,14 @@ ReadContext::check_remote_type_meta_limit(const TypeMeta &type_meta) {
}

auto *entry = remote_schema_versions_by_type_.find(key);
if (FORY_PREDICT_FALSE(
entry == nullptr &&
static_cast<uint64_t>(remote_schema_versions_by_type_.size()) >=
k_max_remote_type_meta_keys)) {
return Unexpected(Error::invalid_data(
"Remote TypeMeta logical type limit 8192 exceeded"));
}

const uint32_t versions_for_type = entry == nullptr ? 0 : entry->second;
if (FORY_PREDICT_FALSE(versions_for_type >=
config_->max_schema_versions_per_type)) {
Expand All @@ -509,13 +518,14 @@ ReadContext::check_remote_type_meta_limit(const TypeMeta &type_meta) {
std::to_string(config_->max_schema_versions_per_type)));
}

const size_t accepted_type_count =
remote_schema_versions_by_type_.size() + (entry == nullptr ? 1 : 0);
const size_t global_limit = std::max(
k_min_remote_type_meta_limit,
accepted_type_count *
static_cast<size_t>(config_->max_average_schema_versions_per_type));
if (FORY_PREDICT_FALSE(total_accepted_schema_versions_ >= global_limit)) {
const uint64_t accepted_type_count =
static_cast<uint64_t>(remote_schema_versions_by_type_.size()) +
(entry == nullptr ? 1 : 0);
const uint64_t max_average = config_->max_average_schema_versions_per_type;
if (FORY_PREDICT_FALSE(
total_accepted_schema_versions_ >= k_min_remote_type_meta_limit &&
total_accepted_schema_versions_ / accepted_type_count >=
max_average)) {
return Unexpected(Error::invalid_data(
"Remote schema version limit exceeded globally. The data may be "
"malicious. If the data is not malicious, please increase "
Expand Down Expand Up @@ -753,9 +763,9 @@ bool ReadContext::set_graph_memory_exceeded(size_t bytes, size_t remaining) {
void ReadContext::reset() {
// Clear error state first
error_ = Error();
if (config_->track_ref) {
ref_reader_.reset();
}
// Wire-level skip paths can reserve reference slots even when local
// reference tracking is disabled, so every root must clear this state.
ref_reader_.reset();
reading_type_infos_.clear();
current_dyn_depth_ = 0;
// Root deserialization overwrites the remaining graph budget before any
Expand Down
2 changes: 1 addition & 1 deletion cpp/fory/serialization/context.h
Original file line number Diff line number Diff line change
Expand Up @@ -701,7 +701,7 @@ class ReadContext {
// Dynamic meta strings used for named type/class info.
meta::MetaStringTable meta_string_table_;
fory::flat_hash_map<std::string, uint32_t> remote_schema_versions_by_type_;
size_t total_accepted_schema_versions_ = 0;
uint64_t total_accepted_schema_versions_ = 0;
};

/// Implementation of DynDepthGuard destructor
Expand Down
40 changes: 40 additions & 0 deletions cpp/fory/serialization/graph_memory_budget_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,46 @@ TEST(GraphMemoryBudgetTest, SmartPointerStructOwners) {
EXPECT_EQ(*unique_exact.value(), *unique_value);
}

TEST(GraphMemoryBudgetTest, SharedWeakStructOwner) {
auto strong = std::make_shared<BudgetItem>();
strong->id = 11;
strong->name = "weak";
SharedWeak<BudgetItem> value = SharedWeak<BudgetItem>::from(strong);

auto writer = Fory::builder()
.xlang(true)
.compatible(false)
.track_ref(true)
.max_graph_memory_bytes(kDefaultGraphMemoryBytes)
.build();
writer.register_struct<BudgetItem>(1);
auto bytes = writer.serialize(value);
ASSERT_TRUE(bytes.ok()) << bytes.error().to_string();

constexpr size_t required = sizeof(BudgetItem);
auto small_fory =
Fory::builder()
.xlang(true)
.compatible(false)
.track_ref(true)
.max_graph_memory_bytes(static_cast<int64_t>(required - 1))
.build();
small_fory.register_struct<BudgetItem>(1);
auto small = small_fory.deserialize<SharedWeak<BudgetItem>>(bytes.value());
ASSERT_FALSE(small.ok());
EXPECT_EQ(small.error().code(), ErrorCode::InvalidData);

auto exact_fory = Fory::builder()
.xlang(true)
.compatible(false)
.track_ref(true)
.max_graph_memory_bytes(static_cast<int64_t>(required))
.build();
exact_fory.register_struct<BudgetItem>(1);
auto exact = exact_fory.deserialize<SharedWeak<BudgetItem>>(bytes.value());
ASSERT_TRUE(exact.ok()) << exact.error().to_string();
}

TEST(GraphMemoryBudgetTest, SmartPointerVectorOwner) {
auto value = std::make_shared<std::vector<BudgetItem>>(3);
auto bytes = serialize_value(value);
Expand Down
Loading
Loading