Skip to content

Commit 630c572

Browse files
GH-49928: [C++][Parquet] Fix UB in UpdateLevelHistogram from nullptr std::span
1 parent 98ee71e commit 630c572

2 files changed

Lines changed: 39 additions & 1 deletion

File tree

cpp/src/parquet/column_writer.cc

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1809,7 +1809,8 @@ class TypedColumnWriterImpl : public ColumnWriterImpl,
18091809

18101810
auto add_levels = [](std::vector<int64_t>& level_histogram, const int16_t* levels,
18111811
int64_t num_levels, int16_t max_level) {
1812-
if (max_level == 0) {
1812+
ARROW_DCHECK(levels != nullptr || num_levels == 0 || max_level == 0);
1813+
if (max_level == 0 || levels == nullptr) {
18131814
return;
18141815
}
18151816
ARROW_DCHECK_EQ(static_cast<size_t>(max_level) + 1, level_histogram.size());

cpp/src/parquet/size_statistics_test.cc

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,43 @@ TEST(SizeStatistics, UpdateLevelHistogram) {
6060
UpdateLevelHistogram(std::vector<int16_t>{}, histogram);
6161
EXPECT_THAT(histogram, ::testing::ElementsAre(3, 3, 2));
6262
}
63+
{
64+
// Empty span should be a no-op.
65+
std::vector<int64_t> histogram(2, 0);
66+
UpdateLevelHistogram(std::span<const int16_t>{}, histogram);
67+
EXPECT_THAT(histogram, ::testing::ElementsAre(0, 0));
68+
}
69+
}
70+
71+
// Regression test for GH-49928: WriteBatch(0, nullptr, ...) on a nullable column
72+
// must not crash or DCHECK-fail, even though max_definition_level > 0.
73+
TEST(SizeStatistics, NullLevelsInColumnWriter) {
74+
auto node = schema::Int32("a", Repetition::OPTIONAL);
75+
auto schema_node = schema::GroupNode::Make("schema", Repetition::REQUIRED, {node});
76+
77+
auto props = WriterProperties::Builder()
78+
.enable_write_page_index()
79+
->enable_statistics()
80+
->set_size_statistics_level(SizeStatisticsLevel::PageAndColumnChunk)
81+
->build();
82+
83+
auto sink = CreateOutputStream();
84+
auto writer = ParquetFileWriter::Open(sink, std::dynamic_pointer_cast<schema::GroupNode>(schema_node), props);
85+
auto rg = writer->AppendRowGroup();
86+
auto col = static_cast<Int32Writer*>(rg->NextColumn());
87+
88+
// Empty write: num_values=0 with nullptr levels — must not crash.
89+
col->WriteBatch(/*num_values=*/0, /*def_levels=*/nullptr,
90+
/*rep_levels=*/nullptr, /*values=*/nullptr);
91+
92+
// Follow up with a real write so the file is valid.
93+
std::vector<int32_t> values = {42};
94+
std::vector<int16_t> def_levels = {1};
95+
col->WriteBatch(1, def_levels.data(), nullptr, values.data());
96+
97+
col->Close();
98+
rg->Close();
99+
writer->Close();
63100
}
64101

65102
TEST(SizeStatistics, ThriftSerDe) {

0 commit comments

Comments
 (0)