From 137a9906147e37e69c4fd071e1f9e0dbdcdc86e4 Mon Sep 17 00:00:00 2001 From: Vasiliy Mikhailov Date: Thu, 25 Jun 2026 11:36:48 +0400 Subject: [PATCH 1/2] Fix NullPointerException in AppendOnlyLinkedArrayList.forEachWhile on a full last chunk forEachWhile(S, BiPredicate) walks the linked chunks and, after iterating a chunk, follows the next-chunk pointer stored at a[capacity]. When the list size is exactly a multiple of the chunk capacity the last chunk is full but its next-chunk pointer has not been allocated yet (it is null), so `a` becomes null and the next iteration throws NullPointerException reading a[0]. Stop traversal when the next chunk is null. Adds a regression test that fails before this change (NPE) and passes after it. --- .../util/AppendOnlyLinkedArrayList.java | 3 +++ .../rxjava4/internal/util/MiscUtilTest.java | 22 +++++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/src/main/java/io/reactivex/rxjava4/internal/util/AppendOnlyLinkedArrayList.java b/src/main/java/io/reactivex/rxjava4/internal/util/AppendOnlyLinkedArrayList.java index 9a8ed49f69..24d2beae57 100644 --- a/src/main/java/io/reactivex/rxjava4/internal/util/AppendOnlyLinkedArrayList.java +++ b/src/main/java/io/reactivex/rxjava4/internal/util/AppendOnlyLinkedArrayList.java @@ -175,6 +175,9 @@ public void forEachWhile(S state, BiPredicate consumer } } a = (Object[])a[c]; + if (a == null) { + return; + } } } } diff --git a/src/test/java/io/reactivex/rxjava4/internal/util/MiscUtilTest.java b/src/test/java/io/reactivex/rxjava4/internal/util/MiscUtilTest.java index 535866d1dc..1eea87e97c 100644 --- a/src/test/java/io/reactivex/rxjava4/internal/util/MiscUtilTest.java +++ b/src/test/java/io/reactivex/rxjava4/internal/util/MiscUtilTest.java @@ -235,4 +235,26 @@ public void appendOnlyLinkedArrayListForEachWhileBiAll() throws Throwable { public void queueDrainHelperUtility() { TestHelper.checkUtilityClass(QueueDrainHelper.class); } + + @Test + public void appendOnlyLinkedArrayListForEachWhileBiFullChunkNoNext() throws Throwable { + // Fill exactly capacity elements in one chunk; no next chunk exists (a[c] == null). + // The predicate returns false for all elements so the loop must traverse past the full chunk. + AppendOnlyLinkedArrayList list = new AppendOnlyLinkedArrayList<>(2); + + list.add(1); + list.add(2); + + final List out = new ArrayList<>(); + + list.forEachWhile(null, new BiPredicate() { + @Override + public boolean test(Object state, Integer value) throws Throwable { + out.add(value); + return false; // never terminate early + } + }); + + assertEquals(Arrays.asList(1, 2), out); + } } From 771e743b4113c7baa5a2323d0097e35b2afa7154 Mon Sep 17 00:00:00 2001 From: David Karnok Date: Thu, 25 Jun 2026 18:45:19 +0200 Subject: [PATCH 2/2] Refactor forEachWhile to use lambda in test Refactor test method to use lambda expression. --- .../reactivex/rxjava4/internal/util/MiscUtilTest.java | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/test/java/io/reactivex/rxjava4/internal/util/MiscUtilTest.java b/src/test/java/io/reactivex/rxjava4/internal/util/MiscUtilTest.java index 1eea87e97c..7caf41a06a 100644 --- a/src/test/java/io/reactivex/rxjava4/internal/util/MiscUtilTest.java +++ b/src/test/java/io/reactivex/rxjava4/internal/util/MiscUtilTest.java @@ -236,7 +236,7 @@ public void queueDrainHelperUtility() { TestHelper.checkUtilityClass(QueueDrainHelper.class); } - @Test + @Test public void appendOnlyLinkedArrayListForEachWhileBiFullChunkNoNext() throws Throwable { // Fill exactly capacity elements in one chunk; no next chunk exists (a[c] == null). // The predicate returns false for all elements so the loop must traverse past the full chunk. @@ -247,12 +247,9 @@ public void appendOnlyLinkedArrayListForEachWhileBiFullChunkNoNext() throws Thro final List out = new ArrayList<>(); - list.forEachWhile(null, new BiPredicate() { - @Override - public boolean test(Object state, Integer value) throws Throwable { - out.add(value); - return false; // never terminate early - } + list.forEachWhile(null, (state, value) -> { + out.add(value); + return false; // never terminate early }); assertEquals(Arrays.asList(1, 2), out);