Skip to content

ThrowableStackTraceRenderer can throw an ArrayIndexOutOfBoundsException when rendering a Throwable with a concurrently-mutated stacktrace #3940

@mosche

Description

@mosche

Description

We observed this in Elasticsearch when attempting to upgrade to log4j 2.25.1.
In rare cases we drop stacktraces we don't care about on async response listeners prior to sending them over the wire. If these exceptions are also logged, we might hit an ArrayIndexOutOfBoundsException such as shown below.

Configuration

Version: 2.25.1

Operating system: max OS, Linux 6.11.0-1017-gcp (amd64)

JDK: JDK 21, 24, 25

Logs

java.lang.ArrayIndexOutOfBoundsException: Index 0 out of bounds for length 0
	at org.apache.logging.log4j.core.pattern.ThrowableStackTraceRenderer.renderStackTraceElements(ThrowableStackTraceRenderer.java:169)
	at org.apache.logging.log4j.core.pattern.ThrowableStackTraceRenderer.renderThrowable(ThrowableStackTraceRenderer.java:110)
	at org.apache.logging.log4j.core.pattern.ThrowableStackTraceRenderer.renderThrowable(ThrowableStackTraceRenderer.java:87)
	at org.apache.logging.log4j.core.pattern.ThrowableStackTraceRenderer.renderThrowable(ThrowableStackTraceRenderer.java:59)
	at org.apache.logging.log4j.core.pattern.Main.lambda$main$0(Main.java:23)
	at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:545)
	at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:328)
	at java.base/java.util.concurrent.ThreadPerTaskExecutor$ThreadBoundFuture.run(ThreadPerTaskExecutor.java:323)
	at java.base/java.lang.VirtualThread.run(VirtualThread.java:456)

Reproduction

package org.apache.logging.log4j.core.pattern;

import org.apache.logging.log4j.core.impl.ThrowableFormatOptions;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executors;

import static java.util.concurrent.TimeUnit.SECONDS;

public class Main {
    public static void main(String[] args) throws Exception {
        var stackTraceRenderer = ThrowableStackTraceRendererFactory.INSTANCE.createStackTraceRenderer(ThrowableFormatOptions.newInstance(null));
        var executor = Executors.newVirtualThreadPerTaskExecutor();

        for (int i = 0; i < 10; i++) {
            var latch = new CountDownLatch(2);
            var exception = new RuntimeException("exception");

            executor.submit(() -> {
                try {
                    stackTraceRenderer.renderThrowable(new StringBuilder(), exception, System.lineSeparator());
                    latch.countDown();
                } catch (Throwable e) {
                    e.printStackTrace();
                }
            });
            executor.submit(() -> {
                exception.setStackTrace(new StackTraceElement[0]);
                latch.countDown();
            });
            if (latch.await(10, SECONDS) == false) {
                throw new IllegalStateException("timed out waiting for tasks to complete");
            }
        }
    }
}

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    Status

    To triage

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions