This is an automated email from the ASF dual-hosted git repository.

yiguolei pushed a commit to branch branch-4.1
in repository https://gitbox.apache.org/repos/asf/doris.git


The following commit(s) were added to refs/heads/branch-4.1 by this push:
     new 865c640a291 branch-4.1: [fix](be) fix BE core due to explode_bitmap 
size exceeding INT_MAX #66034 (#67072)
865c640a291 is described below

commit 865c640a29178c04998513ac84be7aa90794e210
Author: github-actions[bot] 
<41898282+github-actions[bot]@users.noreply.github.com>
AuthorDate: Tue Aug 25 09:51:39 2026 +0800

    branch-4.1: [fix](be) fix BE core due to explode_bitmap size exceeding 
INT_MAX #66034 (#67072)
    
    Cherry-picked from #66034
    
    Co-authored-by: York Cao <[email protected]>
---
 be/src/exprs/table_function/vexplode_bitmap.cpp |  4 +-
 be/test/exprs/function/table_function_test.cpp  | 64 +++++++++++++++++++++++++
 2 files changed, 67 insertions(+), 1 deletion(-)

diff --git a/be/src/exprs/table_function/vexplode_bitmap.cpp 
b/be/src/exprs/table_function/vexplode_bitmap.cpp
index 0956c6223a4..48575d97918 100644
--- a/be/src/exprs/table_function/vexplode_bitmap.cpp
+++ b/be/src/exprs/table_function/vexplode_bitmap.cpp
@@ -105,7 +105,9 @@ void VExplodeBitmapTableFunction::process_close() {
 }
 
 int VExplodeBitmapTableFunction::get_value(MutableColumnPtr& column, int 
max_step) {
-    max_step = std::min(max_step, (int)(_cur_size - _cur_offset));
+    max_step =
+            static_cast<int>(std::min(static_cast<int64_t>(max_step), 
(_cur_size - _cur_offset)));
+    DCHECK_GE(max_step, 0);
     // should dispose the empty status, forward one step
     if (current_empty()) {
         column->insert_default();
diff --git a/be/test/exprs/function/table_function_test.cpp 
b/be/test/exprs/function/table_function_test.cpp
index 259620f1a2a..19e9b96034b 100644
--- a/be/test/exprs/function/table_function_test.cpp
+++ b/be/test/exprs/function/table_function_test.cpp
@@ -28,6 +28,7 @@
 #include "exprs/function/function_test_util.h"
 #include "exprs/mock_vexpr.h"
 #include "exprs/table_function/vexplode.h"
+#include "exprs/table_function/vexplode_bitmap.h"
 #include "exprs/table_function/vexplode_numbers.h"
 #include "exprs/table_function/vexplode_v2.h"
 #include "exprs/table_function/vjson_each.h"
@@ -1332,4 +1333,67 @@ TEST_F(TableFunctionTest, 
vjson_each_get_same_many_values_non_nullable) {
     fn.process_close();
 }
 
+// Regression test for a BE core crash when exploding a bitmap whose 
cardinality
+// exceeds INT_MAX. VExplodeBitmapTableFunction::get_value used to compute the
+// batch size as `std::min(max_step, (int)(_cur_size - _cur_offset))`. Both
+// `_cur_size` (the bitmap cardinality) and `_cur_offset` are int64_t, so when
+// the cardinality is above INT_MAX the C-style `(int)` cast overflows to a
+// NEGATIVE value, which is then fed into `target->resize(origin_size + 
max_step)`
+// and underflows -> crash. The overflow happens on the FIRST get_value call
+// (_cur_offset == 0), before any element is materialized, so we only need a
+// bitmap whose *cardinality* exceeds INT_MAX -- built cheaply here from a
+// Roaring range (a few KB, microseconds), not billions of individual inserts.
+TEST_F(TableFunctionTest, vexplode_bitmap_cardinality_exceeds_int_max) {
+    // Build a bitmap holding [0, 3'000'000'000) via a Roaring range. All 
values
+    // are < 2^32 so a plain 32-bit Roaring suffices; the range is stored as 
run
+    // containers (a few KB). Cardinality 3e9 > INT_MAX (2^31 - 1). Serialize 
to
+    // the Doris BITMAP wire format and deserialize into a BitmapValue, since
+    // BitmapValue has no public addRange.
+    roaring::Roaring inner;
+    inner.addRange(0, 3000000000ULL); // [0, 3e9)
+    detail::Roaring64Map r64(inner);
+    const int serialize_version = config::bitmap_serialize_version;
+    const size_t nbytes = r64.getSizeInBytes(serialize_version);
+    std::string buffer;
+    buffer.resize(nbytes);
+    r64.write(buffer.data(), serialize_version);
+    BitmapValue bv(buffer.data());
+    // Fail fast if the cheap build path did not actually exceed INT_MAX.
+    ASSERT_GT(bv.cardinality(), 
static_cast<uint64_t>(std::numeric_limits<int>::max()));
+
+    // One-column bitmap input block; the MockVExpr child returns column at 
pos 0.
+    init_expr_context(1);
+    auto bitmap_col = ColumnBitmap::create();
+    bitmap_col->insert_value(std::move(bv));
+    auto block = Block::create_unique();
+    block->insert({std::move(bitmap_col), std::make_shared<DataTypeBitMap>(), 
"bm"});
+
+    VExplodeBitmapTableFunction fn;
+    fn.set_expr_context(_ctx);
+
+    TQueryOptions q_opts;
+    TQueryGlobals q_globals;
+    RuntimeState rs(q_opts, q_globals);
+    ASSERT_TRUE(fn.process_init(block.get(), &rs).ok());
+    fn.process_row(0);
+    ASSERT_FALSE(fn.current_empty());
+
+    // Non-nullable path (_is_nullable == false): request exactly ONE batch.
+    // Pre-fix: (int)(_cur_size - _cur_offset) overflows to a negative max_step
+    //          -> target->resize underflow -> crash (caught by ASAN).
+    // Fixed:   the std::min is done in int64_t then cast back -> positive 
batch.
+    MutableColumnPtr out = ColumnInt64::create();
+    int ret = fn.get_value(out, 4096);
+    EXPECT_EQ(ret, 4096);
+    EXPECT_EQ(out->size(), 4096);
+
+    // The first batch materializes the smallest elements 0,1,2,... in order.
+    const auto& data = assert_cast<const ColumnInt64&>(*out).get_data();
+    for (int i = 0; i < 10; ++i) {
+        EXPECT_EQ(data[i], static_cast<int64_t>(i));
+    }
+
+    fn.process_close();
+}
+
 } // namespace doris


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to