IllegalArgumentException when processing exceptions with null StackTraceElements (original) (raw)
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); }