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

JingsongLi pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/paimon.git


The following commit(s) were added to refs/heads/master by this push:
     new 19b7ee7073 [core] Prevent integer overflow in sort buffer size with 
isEmpty check (#8209)
19b7ee7073 is described below

commit 19b7ee70738a0fcb9d89bd228e54d2fc7784ac65
Author: Liurnly <[email protected]>
AuthorDate: Fri Jun 12 15:46:18 2026 +0800

    [core] Prevent integer overflow in sort buffer size with isEmpty check 
(#8209)
---
 .../org/apache/paimon/append/cluster/Sorter.java   |   2 +-
 .../paimon/crosspartition/GlobalIndexAssigner.java |   2 +-
 .../apache/paimon/mergetree/MergeTreeWriter.java   |   2 +-
 .../paimon/mergetree/SortBufferWriteBuffer.java    |   5 +
 .../org/apache/paimon/mergetree/WriteBuffer.java   |   2 +
 .../mergetree/localmerge/HashMapLocalMerger.java   |   5 +
 .../paimon/mergetree/localmerge/LocalMerger.java   |   2 +
 .../localmerge/SortBufferLocalMerger.java          |   5 +
 .../paimon/sort/BinaryExternalSortBuffer.java      |  15 ++-
 .../paimon/sort/BinaryInMemorySortBuffer.java      |   3 +-
 .../java/org/apache/paimon/sort/SortBuffer.java    |   2 +
 .../SortBufferWriteBufferOverflowTest.java         | 131 +++++++++++++++++++++
 .../paimon/sort/BinaryExternalSortBufferTest.java  |  29 +++++
 .../paimon/flink/sink/LocalMergeOperator.java      |   2 +-
 .../apache/paimon/flink/sorter/SortOperator.java   |   2 +-
 15 files changed, 201 insertions(+), 8 deletions(-)

diff --git 
a/paimon-core/src/main/java/org/apache/paimon/append/cluster/Sorter.java 
b/paimon-core/src/main/java/org/apache/paimon/append/cluster/Sorter.java
index 41786992de..e1cfe9fbe3 100644
--- a/paimon-core/src/main/java/org/apache/paimon/append/cluster/Sorter.java
+++ b/paimon-core/src/main/java/org/apache/paimon/append/cluster/Sorter.java
@@ -104,7 +104,7 @@ public abstract class Sorter {
             buffer.write(rowWithKey);
         }
 
-        if (buffer.size() > 0) {
+        if (!buffer.isEmpty()) {
             return buffer.sortedIterator();
         } else {
             throw new IllegalStateException("numRecords after sorting is 0.");
diff --git 
a/paimon-core/src/main/java/org/apache/paimon/crosspartition/GlobalIndexAssigner.java
 
b/paimon-core/src/main/java/org/apache/paimon/crosspartition/GlobalIndexAssigner.java
index 12f90cd447..cb7d45a648 100644
--- 
a/paimon-core/src/main/java/org/apache/paimon/crosspartition/GlobalIndexAssigner.java
+++ 
b/paimon-core/src/main/java/org/apache/paimon/crosspartition/GlobalIndexAssigner.java
@@ -207,7 +207,7 @@ public class GlobalIndexAssigner implements Serializable, 
Closeable {
             throws Exception {
         bootstrap = false;
         boolean isEmpty = true;
-        if (bootstrapKeys.size() > 0) {
+        if (!bootstrapKeys.isEmpty()) {
             RocksDBBulkLoader bulkLoader = keyIndex.createBulkLoader();
             MutableObjectIterator<BinaryRow> keyIterator = 
bootstrapKeys.sortedIterator();
             BinaryRow row = new BinaryRow(2);
diff --git 
a/paimon-core/src/main/java/org/apache/paimon/mergetree/MergeTreeWriter.java 
b/paimon-core/src/main/java/org/apache/paimon/mergetree/MergeTreeWriter.java
index 488b3992dd..beb2651f1f 100644
--- a/paimon-core/src/main/java/org/apache/paimon/mergetree/MergeTreeWriter.java
+++ b/paimon-core/src/main/java/org/apache/paimon/mergetree/MergeTreeWriter.java
@@ -208,7 +208,7 @@ public class MergeTreeWriter implements 
RecordWriter<KeyValue>, MemoryOwner {
 
     private void flushWriteBuffer(boolean waitForLatestCompaction, boolean 
forcedFullCompaction)
             throws Exception {
-        if (writeBuffer.size() > 0) {
+        if (!writeBuffer.isEmpty()) {
             if (compactManager.shouldWaitForLatestCompaction()) {
                 waitForLatestCompaction = true;
             }
diff --git 
a/paimon-core/src/main/java/org/apache/paimon/mergetree/SortBufferWriteBuffer.java
 
b/paimon-core/src/main/java/org/apache/paimon/mergetree/SortBufferWriteBuffer.java
index 17fc5af625..6f6d84c935 100644
--- 
a/paimon-core/src/main/java/org/apache/paimon/mergetree/SortBufferWriteBuffer.java
+++ 
b/paimon-core/src/main/java/org/apache/paimon/mergetree/SortBufferWriteBuffer.java
@@ -150,6 +150,11 @@ public class SortBufferWriteBuffer implements WriteBuffer {
         return buffer.size();
     }
 
+    @Override
+    public boolean isEmpty() {
+        return buffer.isEmpty();
+    }
+
     @Override
     public long memoryOccupancy() {
         return buffer.getOccupancy();
diff --git 
a/paimon-core/src/main/java/org/apache/paimon/mergetree/WriteBuffer.java 
b/paimon-core/src/main/java/org/apache/paimon/mergetree/WriteBuffer.java
index 9b2749fb7f..ddeb54286f 100644
--- a/paimon-core/src/main/java/org/apache/paimon/mergetree/WriteBuffer.java
+++ b/paimon-core/src/main/java/org/apache/paimon/mergetree/WriteBuffer.java
@@ -45,6 +45,8 @@ public interface WriteBuffer {
     /** Record size of this table. */
     int size();
 
+    boolean isEmpty();
+
     /** Memory occupancy size of this table. */
     long memoryOccupancy();
 
diff --git 
a/paimon-core/src/main/java/org/apache/paimon/mergetree/localmerge/HashMapLocalMerger.java
 
b/paimon-core/src/main/java/org/apache/paimon/mergetree/localmerge/HashMapLocalMerger.java
index 1a395fb36c..2db4576506 100644
--- 
a/paimon-core/src/main/java/org/apache/paimon/mergetree/localmerge/HashMapLocalMerger.java
+++ 
b/paimon-core/src/main/java/org/apache/paimon/mergetree/localmerge/HashMapLocalMerger.java
@@ -118,6 +118,11 @@ public class HashMapLocalMerger implements LocalMerger {
         return buffer.getNumElements();
     }
 
+    @Override
+    public boolean isEmpty() {
+        return buffer.getNumElements() == 0;
+    }
+
     @Override
     public void forEach(Consumer<InternalRow> consumer) throws IOException {
         KeyValueIterator<BinaryRow, BinaryRow> iterator = 
buffer.getEntryIterator(false);
diff --git 
a/paimon-core/src/main/java/org/apache/paimon/mergetree/localmerge/LocalMerger.java
 
b/paimon-core/src/main/java/org/apache/paimon/mergetree/localmerge/LocalMerger.java
index bec71808a7..8e0e47da80 100644
--- 
a/paimon-core/src/main/java/org/apache/paimon/mergetree/localmerge/LocalMerger.java
+++ 
b/paimon-core/src/main/java/org/apache/paimon/mergetree/localmerge/LocalMerger.java
@@ -32,6 +32,8 @@ public interface LocalMerger {
 
     int size();
 
+    boolean isEmpty();
+
     void forEach(Consumer<InternalRow> consumer) throws IOException;
 
     void clear();
diff --git 
a/paimon-core/src/main/java/org/apache/paimon/mergetree/localmerge/SortBufferLocalMerger.java
 
b/paimon-core/src/main/java/org/apache/paimon/mergetree/localmerge/SortBufferLocalMerger.java
index 198e6c67d3..d35f499c81 100644
--- 
a/paimon-core/src/main/java/org/apache/paimon/mergetree/localmerge/SortBufferLocalMerger.java
+++ 
b/paimon-core/src/main/java/org/apache/paimon/mergetree/localmerge/SortBufferLocalMerger.java
@@ -58,6 +58,11 @@ public class SortBufferLocalMerger implements LocalMerger {
         return sortBuffer.size();
     }
 
+    @Override
+    public boolean isEmpty() {
+        return sortBuffer.isEmpty();
+    }
+
     @Override
     public void forEach(Consumer<InternalRow> consumer) throws IOException {
         sortBuffer.forEach(
diff --git 
a/paimon-core/src/main/java/org/apache/paimon/sort/BinaryExternalSortBuffer.java
 
b/paimon-core/src/main/java/org/apache/paimon/sort/BinaryExternalSortBuffer.java
index 5521295ea3..960965c901 100644
--- 
a/paimon-core/src/main/java/org/apache/paimon/sort/BinaryExternalSortBuffer.java
+++ 
b/paimon-core/src/main/java/org/apache/paimon/sort/BinaryExternalSortBuffer.java
@@ -59,7 +59,7 @@ public class BinaryExternalSortBuffer implements SortBuffer {
     private final List<ChannelWithMeta> spillChannelIDs;
     private final MemorySize maxDiskSize;
 
-    private int numRecords = 0;
+    private long numRecords = 0;
 
     public BinaryExternalSortBuffer(
             BinaryRowSerializer serializer,
@@ -122,7 +122,18 @@ public class BinaryExternalSortBuffer implements 
SortBuffer {
 
     @Override
     public int size() {
-        return numRecords;
+        if (numRecords > Integer.MAX_VALUE) {
+            throw new RuntimeException(
+                    "numRecords "
+                            + numRecords
+                            + " exceeds Integer.MAX_VALUE, use isEmpty() 
instead of size().");
+        }
+        return (int) numRecords;
+    }
+
+    @Override
+    public boolean isEmpty() {
+        return numRecords == 0;
     }
 
     @Override
diff --git 
a/paimon-core/src/main/java/org/apache/paimon/sort/BinaryInMemorySortBuffer.java
 
b/paimon-core/src/main/java/org/apache/paimon/sort/BinaryInMemorySortBuffer.java
index 2fe3ebdbae..6f0af79a8d 100644
--- 
a/paimon-core/src/main/java/org/apache/paimon/sort/BinaryInMemorySortBuffer.java
+++ 
b/paimon-core/src/main/java/org/apache/paimon/sort/BinaryInMemorySortBuffer.java
@@ -148,7 +148,8 @@ public class BinaryInMemorySortBuffer extends 
BinaryIndexedSortable implements S
         return false;
     }
 
-    boolean isEmpty() {
+    @Override
+    public boolean isEmpty() {
         return this.numRecords == 0;
     }
 
diff --git a/paimon-core/src/main/java/org/apache/paimon/sort/SortBuffer.java 
b/paimon-core/src/main/java/org/apache/paimon/sort/SortBuffer.java
index 723ec21c01..127165c173 100644
--- a/paimon-core/src/main/java/org/apache/paimon/sort/SortBuffer.java
+++ b/paimon-core/src/main/java/org/apache/paimon/sort/SortBuffer.java
@@ -29,6 +29,8 @@ public interface SortBuffer {
 
     int size();
 
+    boolean isEmpty();
+
     void clear();
 
     long getOccupancy();
diff --git 
a/paimon-core/src/test/java/org/apache/paimon/mergetree/SortBufferWriteBufferOverflowTest.java
 
b/paimon-core/src/test/java/org/apache/paimon/mergetree/SortBufferWriteBufferOverflowTest.java
new file mode 100644
index 0000000000..f357b9a28c
--- /dev/null
+++ 
b/paimon-core/src/test/java/org/apache/paimon/mergetree/SortBufferWriteBufferOverflowTest.java
@@ -0,0 +1,131 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.paimon.mergetree;
+
+import org.apache.paimon.compression.CompressOptions;
+import org.apache.paimon.data.serializer.AbstractRowDataSerializer;
+import org.apache.paimon.data.serializer.BinaryRowSerializer;
+import org.apache.paimon.disk.IOManager;
+import org.apache.paimon.memory.HeapMemorySegmentPool;
+import org.apache.paimon.memory.MemorySegmentPool;
+import org.apache.paimon.options.MemorySize;
+import org.apache.paimon.sort.BinaryExternalSortBuffer;
+import org.apache.paimon.sort.BinaryInMemorySortBuffer;
+import org.apache.paimon.sort.IntNormalizedKeyComputer;
+import org.apache.paimon.sort.IntRecordComparator;
+
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+import sun.misc.Unsafe;
+
+import java.lang.reflect.Field;
+import java.nio.file.Path;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+/**
+ * Test that {@link SortBufferWriteBuffer} (used by {@link MergeTreeWriter}) 
handles int overflow in
+ * numRecords correctly by using isEmpty() instead of size().
+ */
+public class SortBufferWriteBufferOverflowTest {
+
+    @TempDir Path tempDir;
+
+    private IOManager ioManager;
+    private MemorySegmentPool memorySegmentPool;
+
+    @BeforeEach
+    public void setUp() {
+        ioManager = IOManager.create(tempDir.toString());
+        memorySegmentPool = new HeapMemorySegmentPool(32 * 1024 * 3L, 32 * 
1024);
+    }
+
+    @AfterEach
+    public void tearDown() throws Exception {
+        ioManager.close();
+    }
+
+    private BinaryExternalSortBuffer createSortBuffer() {
+        BinaryRowSerializer serializer = new BinaryRowSerializer(1);
+        @SuppressWarnings({"unchecked", "rawtypes"})
+        BinaryInMemorySortBuffer inMemorySortBuffer =
+                BinaryInMemorySortBuffer.createBuffer(
+                        IntNormalizedKeyComputer.INSTANCE,
+                        (AbstractRowDataSerializer) serializer,
+                        IntRecordComparator.INSTANCE,
+                        memorySegmentPool);
+        return new BinaryExternalSortBuffer(
+                serializer,
+                IntRecordComparator.INSTANCE,
+                memorySegmentPool.pageSize(),
+                inMemorySortBuffer,
+                ioManager,
+                128,
+                CompressOptions.defaultOptions(),
+                MemorySize.MAX_VALUE);
+    }
+
+    private static SortBufferWriteBuffer 
createWriteBuffer(BinaryExternalSortBuffer buffer)
+            throws Exception {
+        Field theUnsafeField = Unsafe.class.getDeclaredField("theUnsafe");
+        theUnsafeField.setAccessible(true);
+        Unsafe unsafe = (Unsafe) theUnsafeField.get(null);
+        SortBufferWriteBuffer writeBuffer =
+                (SortBufferWriteBuffer) 
unsafe.allocateInstance(SortBufferWriteBuffer.class);
+
+        Field bufferField = 
SortBufferWriteBuffer.class.getDeclaredField("buffer");
+        bufferField.setAccessible(true);
+        bufferField.set(writeBuffer, buffer);
+        return writeBuffer;
+    }
+
+    private static void setNumRecords(BinaryExternalSortBuffer buffer, long 
numRecords)
+            throws Exception {
+        Field numRecordsField = 
BinaryExternalSortBuffer.class.getDeclaredField("numRecords");
+        numRecordsField.setAccessible(true);
+        numRecordsField.setLong(buffer, numRecords);
+    }
+
+    @Test
+    public void testIsEmptyWorksWhenNumRecordsExceedsIntMax() throws Exception 
{
+        BinaryExternalSortBuffer buffer = createSortBuffer();
+        SortBufferWriteBuffer writeBuffer = createWriteBuffer(buffer);
+
+        assertThat(writeBuffer.size()).isEqualTo(0);
+        assertThat(writeBuffer.isEmpty()).isTrue();
+
+        setNumRecords(buffer, Integer.MAX_VALUE);
+        assertThat(writeBuffer.size()).isEqualTo(Integer.MAX_VALUE);
+        assertThat(writeBuffer.isEmpty()).isFalse();
+
+        setNumRecords(buffer, (long) Integer.MAX_VALUE + 1);
+
+        assertThat(writeBuffer.isEmpty()).isFalse();
+        assertThatThrownBy(writeBuffer::size)
+                .isInstanceOf(RuntimeException.class)
+                .hasMessageContaining("exceeds Integer.MAX_VALUE");
+
+        writeBuffer.clear();
+        assertThat(writeBuffer.isEmpty()).isTrue();
+        assertThat(writeBuffer.size()).isEqualTo(0);
+    }
+}
diff --git 
a/paimon-core/src/test/java/org/apache/paimon/sort/BinaryExternalSortBufferTest.java
 
b/paimon-core/src/test/java/org/apache/paimon/sort/BinaryExternalSortBufferTest.java
index 0ff35a0606..8aea0a76ce 100644
--- 
a/paimon-core/src/test/java/org/apache/paimon/sort/BinaryExternalSortBufferTest.java
+++ 
b/paimon-core/src/test/java/org/apache/paimon/sort/BinaryExternalSortBufferTest.java
@@ -37,6 +37,7 @@ import org.junit.jupiter.api.io.TempDir;
 
 import java.io.File;
 import java.io.IOException;
+import java.lang.reflect.Field;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.util.ArrayList;
@@ -46,6 +47,7 @@ import java.util.List;
 import java.util.stream.Collectors;
 
 import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
 
 /** Test for {@link BinaryExternalSortBuffer}. */
 public class BinaryExternalSortBufferTest {
@@ -96,6 +98,33 @@ public class BinaryExternalSortBufferTest {
         assertThat(files).isEmpty();
     }
 
+    private static void setNumRecords(BinaryExternalSortBuffer sorter, long 
numRecords)
+            throws Exception {
+        Field numRecordsField = 
BinaryExternalSortBuffer.class.getDeclaredField("numRecords");
+        numRecordsField.setAccessible(true);
+        numRecordsField.setLong(sorter, numRecords);
+    }
+
+    @Test
+    public void testSizeBoundary() throws Exception {
+        BinaryExternalSortBuffer sorter = createBuffer();
+
+        assertThat(sorter.size()).isEqualTo(0);
+        assertThat(sorter.isEmpty()).isTrue();
+
+        setNumRecords(sorter, Integer.MAX_VALUE);
+        assertThat(sorter.size()).isEqualTo(Integer.MAX_VALUE);
+        assertThat(sorter.isEmpty()).isFalse();
+
+        setNumRecords(sorter, (long) Integer.MAX_VALUE + 1);
+        assertThat(sorter.isEmpty()).isFalse();
+        assertThatThrownBy(sorter::size)
+                .isInstanceOf(RuntimeException.class)
+                .hasMessageContaining("exceeds Integer.MAX_VALUE");
+
+        sorter.clear();
+    }
+
     @Test
     public void testSortNoSpill() throws Exception {
         int size = 1_000_000;
diff --git 
a/paimon-flink/paimon-flink-common/src/main/java/org/apache/paimon/flink/sink/LocalMergeOperator.java
 
b/paimon-flink/paimon-flink-common/src/main/java/org/apache/paimon/flink/sink/LocalMergeOperator.java
index 401ca43682..4852171a64 100644
--- 
a/paimon-flink/paimon-flink-common/src/main/java/org/apache/paimon/flink/sink/LocalMergeOperator.java
+++ 
b/paimon-flink/paimon-flink-common/src/main/java/org/apache/paimon/flink/sink/LocalMergeOperator.java
@@ -201,7 +201,7 @@ public class LocalMergeOperator extends 
AbstractStreamOperator<InternalRow>
     }
 
     private void flushBuffer() throws Exception {
-        if (merger.size() == 0) {
+        if (merger.isEmpty()) {
             return;
         }
 
diff --git 
a/paimon-flink/paimon-flink-common/src/main/java/org/apache/paimon/flink/sorter/SortOperator.java
 
b/paimon-flink/paimon-flink-common/src/main/java/org/apache/paimon/flink/sorter/SortOperator.java
index dfa1e432a1..01c349ac83 100644
--- 
a/paimon-flink/paimon-flink-common/src/main/java/org/apache/paimon/flink/sorter/SortOperator.java
+++ 
b/paimon-flink/paimon-flink-common/src/main/java/org/apache/paimon/flink/sorter/SortOperator.java
@@ -107,7 +107,7 @@ public class SortOperator extends 
TableStreamOperator<InternalRow>
 
     @Override
     public void endInput() throws Exception {
-        if (buffer.size() > 0) {
+        if (!buffer.isEmpty()) {
             MutableObjectIterator<BinaryRow> iterator = 
buffer.sortedIterator();
             BinaryRow binaryRow = new BinaryRow(arity);
             while ((binaryRow = iterator.next(binaryRow)) != null) {

Reply via email to