Skip to content

IllegalArgumentException when processing exceptions with null StackTraceElements #1040

Description

@naotsugu

Affected Versions

All versions.

Description

When logging an exception, for example using logger.error("error", e), an IllegalArgumentException can be thrown from within Logback's internal processing.

Here is a typical stack trace:

java.lang.IllegalArgumentException: ste cannot be null
    at ch.qos.logback.classic.spi.StackTraceElementProxy.<init>(StackTraceElementProxy.java:30)
    at ch.qos.logback.classic.spi.ThrowableProxyUtil.steArrayToStepArray(ThrowableProxyUtil.java:49)
    at ch.qos.logback.classic.spi.ThrowableProxy.<init>(ThrowableProxy.java:55)
    at ch.qos.logback.classic.spi.ThrowableProxy.<init>(ThrowableProxy.java:60)
    at ch.qos.logback.classic.spi.ThrowableProxy.<init>(ThrowableProxy.java:60)
    at ch.qos.logback.classic.spi.ThrowableProxy.<init>(ThrowableProxy.java:60)
    at ch.qos.logback.classic.spi.LoggingEvent.<init>(LoggingEvent.java:119)
    at ch.qos.logback.classic.Logger.buildLoggingEventAndAppend(Logger.java:419)
    at ch.qos.logback.classic.Logger.filterAndLog_0_Or3Plus(Logger.java:383)
    at ch.qos.logback.classic.Logger.error(Logger.java:538)

Root Cause

The exception originates from the StackTraceElementProxy constructor, which validates that its StackTraceElement argument is not null.

// ch.qos.logback.classic.spi.StackTraceElementProxy
public StackTraceElementProxy(StackTraceElement ste) {
    if (ste == null) {
        throw new IllegalArgumentException("ste cannot be null");
    }
    this.ste = ste;
}

This constructor is called by ThrowableProxyUtil.steArrayToStepArray. The current implementation of this method iterates over an array of StackTraceElements but does not handle cases where elements in the array are null.

// ch.qos.logback.classic.spi.ThrowableProxyUtil (current implementation)
static StackTraceElementProxy[] steArrayToStepArray(StackTraceElement[] stea) {
    if (stea == null) {
        return new StackTraceElementProxy[0];
    }
    StackTraceElementProxy[] stepa = new StackTraceElementProxy[stea.length];
    for (int i = 0; i < stepa.length; i++) {
        // This line throws an exception if stea[i] is null
        stepa[i] = new StackTraceElementProxy(stea[i]);
    }
    return stepa;
}

Scenario

Although the JVM specification implies that Throwable.getStackTrace() should not return an array containing null elements, we have observed this behavior in a Jakarta EE environment.

The issue occurs intermittently when an exception is thrown from an EJB service running in a new transaction (i.e., with @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)). When the caller catches and logs this exception, the StackTraceElement[] array obtained from it sometimes contains null elements.

While the root of this problem may lie within the Jakarta EE platform, a logging framework should be robust enough to handle such real-world scenarios gracefully instead of crashing.

Proposed Solution

To prevent this crash, we propose modifying ThrowableProxyUtil.steArrayToStepArray to filter out null StackTraceElement objects. This ensures StackTraceElementProxy is never instantiated with a null argument.

Here is a suggested implementation that correctly handles null elements:

// ch.qos.logback.classic.spi.ThrowableProxyUtil
static StackTraceElementProxy[] steArrayToStepArray(StackTraceElement[] stea) {
    if (stea == null) {
        return new StackTraceElementProxy[0];
    }
    StackTraceElementProxy[] stepa = new StackTraceElementProxy[stea.length];
    int stepaSkips = 0;
    for (int i = 0; i < stepa.length; i++) {
        StackTraceElement ste = stea[i];
        if (ste == null) {
            stepaSkips++;
        } else {
            stepa[i] = new StackTraceElementProxy(ste);
        }
    }
    return (stepaSkips == 0) ? stepa : Arrays.copyOf(stepa, stepa.length - stepaSkips);
}

Metadata

Metadata

Assignees

Labels

Type

No type

Fields

No fields configured for issues without a type.

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions