Skip to content

Commit d671d7b

Browse files
srihithgclaude
andcommitted
[BugFix] Null-safe SparseRangeIterator::has_more() to fix physical-split empty-tablet CN crash (#75203)
PhysicalSplitMorselQueue::_try_get_split_from_single_tablet() evaluates _segment_range_iter.has_more() in its scan loop. On empty/edge tablets the iterator can still be default-constructed (_range == nullptr) when has_more() is reached; has_more() did an unconditional `_index < _range->_ranges.size()`, dereferencing the null _range -> std::vector::size() at address 0 -> CN SIGSEGV @0x0. This is exactly the reported crash stack (has_more() inlined into _try_get_split_from_single_tablet(), per the addr2line in #75203). #70281 hardened _cur_rowset()/_cur_segment() but not this iterator deref. A default-constructed iterator has no ranges, so has_more() reports false instead of dereferencing null. Verified under ASAN with a storage_primitive_test case: without the guard it SEGVs at range.h in std::vector<Range>::size() (matching the issue's addr2line); with the guard the test passes. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Signed-off-by: Srihith Garlapati <srihith.garlapati@gmail.com>
1 parent e5536d1 commit d671d7b

2 files changed

Lines changed: 24 additions & 1 deletion

File tree

be/src/storage_primitive/range.h

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -392,7 +392,12 @@ inline SparseRangeIterator<T>::SparseRangeIterator(const SparseRange<T>* r) : _r
392392

393393
template <typename T>
394394
inline bool SparseRangeIterator<T>::has_more() const {
395-
return _index < _range->_ranges.size();
395+
// A default-constructed iterator (_range == nullptr) has no ranges to traverse.
396+
// Guard the null before dereferencing: PhysicalSplitMorselQueue can reach
397+
// _segment_range_iter.has_more() while the iterator is still default-constructed
398+
// (empty/edge tablet, see issue #75203); without this check that dereferenced a
399+
// null _range -> std::vector::size() at address 0 -> CN SIGSEGV @0x0.
400+
return _range != nullptr && _index < _range->_ranges.size();
396401
}
397402

398403
template <typename T>

be/test/storage_primitive/range_test.cpp

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,4 +204,22 @@ TEST(SparseRangeIteratorTest, intersect_test) {
204204
}
205205
}
206206

207+
// Regression test for issue #75203: CN SIGSEGV @0x0 in
208+
// PhysicalSplitMorselQueue::_try_get_split_from_single_tablet().
209+
//
210+
// The physical-split loop evaluates `_segment_range_iter.has_more()` in its while
211+
// condition. On empty/edge tablets the iterator can still be default-constructed
212+
// (its _range pointer is null) when has_more() is reached. Before the fix has_more()
213+
// did an unconditional `_index < _range->_ranges.size()`, dereferencing the null
214+
// _range -> std::vector::size() read at address 0 -> SIGSEGV @0x0 (exactly the crash
215+
// stack in the issue: has_more() inlined into _try_get_split_from_single_tablet()).
216+
//
217+
// A default-constructed iterator has no ranges, so has_more() must report false
218+
// rather than dereference null. Without the fix this line is an ASAN
219+
// null/heap-buffer-overflow read; with it, it returns false.
220+
TEST(SparseRangeIteratorTest, has_more_on_default_constructed_is_null_safe) {
221+
SparseRangeIterator<> iter; // default-constructed: _range == nullptr
222+
EXPECT_FALSE(iter.has_more());
223+
}
224+
207225
} // namespace starrocks

0 commit comments

Comments
 (0)