Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

IJPL 173473: fix ArrayIndexOutOfBoundsException when using ContainerUtil.concat() #2893

Open
wants to merge 2 commits into
base: 243.22562
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.Future;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.IntStream;

public class ContainerUtilTest extends TestCase {
Expand Down Expand Up @@ -94,6 +95,67 @@ public void testConcatedListsAfterModificationMustThrowCME() {
}
}

private static Future<?> modifyListUntilStopped(List<Integer> list, AtomicBoolean stopped) {

return AppExecutorUtil.getAppExecutorService().submit(()-> {

// simple random generator (may overflow)
for (int seed = 13; !stopped.get(); seed += 907) {
// random [0-31] (sign clipped)
int rand = (seed^(seed>>5)) & 0x1f;

// grow or shrink the list randomly.
if (rand < list.size()) {
list.remove(rand);
}
else {
list.add(seed);
}
}
});
}

private static List<Integer> createFilledList(int size) {
List<Integer> list = ContainerUtil.createLockFreeCopyOnWriteList();

for(int i=0; i<size; ++i) {
list.add(i);
}

return list;
}

public void testConcatenatedDynamicListsAreIterable() throws Exception {
List<Integer> list1 = createFilledList(32);
List<Integer> list2 = createFilledList(32);
List<Integer> values = ContainerUtil.concat(list1, list2);

AtomicBoolean stop = new AtomicBoolean(false);
Future<?> future1 = modifyListUntilStopped(list1, stop);
Future<?> future2 = modifyListUntilStopped(list2, stop);

try {
long count = 0;
int n=0;
long until = System.currentTimeMillis() + 1000;
while (System.currentTimeMillis() < until) {

for (Integer value : values) {
count += value;
}

// must work on streams (even parallel), too.
count += values.parallelStream().count();
++n;
}
stop.set(true);
} finally {
stop.set(true); // finally stop even in case of an error
future1.get();
future2.get();
}
}

public void testIterateWithCondition() {
Condition<Integer> cond = integer -> integer > 2;

Expand Down
18 changes: 18 additions & 0 deletions platform/util/src/com/intellij/util/containers/ContainerUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -1441,6 +1441,24 @@ public T get(int index) {
public int size() {
return size;
}

/**
* Returns an iterator over the <em>actual elements</em> in this list based on the underlying lists.
*
* @implNote
* This implementation replaces the straightforward implementation based on index operations.
* Those fail badly, if the underlying lists change since creation of this concatenated list:
* either by an {@link IndexOutOfBoundsException} if any list shrinks unexpectedly,
* or by missing all elements added later on.
*
* @return Returns an iterator over the <em>actual elements</em> of both lists.
*/
@Override
public Iterator<T> iterator() {
final Iterable<? extends T> it1 = list1;
final Iterable<? extends T> it2 = list2;
return concat(it1, it2).iterator();
}
};
}

Expand Down