shinstev333 opened a new issue, #3933:
URL: https://github.com/apache/logging-log4j2/issues/3933

   
   ## Description
   
   The `ThrowableStackTraceRenderer` class in Log4j 2.25.1 throws 
`ArrayIndexOutOfBoundsException` when rendering exception chains containing 
multiple exceptions with custom `equals()` methods that compare by message 
content rather than object identity. This occurs because the renderer uses 
`Map<Throwable, Context.Metadata>` for storing stack trace metadata, and 
HashMap key collisions cause metadata calculated for one exception (with longer 
stack trace) to be incorrectly applied to another exception (with shorter stack 
trace).
   
   **Root Cause:** When exception classes override `equals()` to compare by 
message/type instead of object identity, different exception objects with 
identical messages but different stack trace lengths are treated as the same 
HashMap key. This causes the renderer to attempt accessing stack trace elements 
beyond the actual array bounds.
   
   **Affected Components:** This issue affects multiple throwable rendering 
classes:
   - `ThrowableStackTraceRenderer`
   - `ThrowableExtendedStackTraceRenderer`
   - `ThrowableInvertedStackTraceRenderer`
   - `ThrowableProxy`
   - `Throwables` utility class
   
   All these classes assume object identity for Throwable objects but break 
when custom `equals()` implementations are used.
   
   ## Configuration
   
   **Version:** 2.25.1
   
   **Operating system:** macOS
   
   **JDK:** JDK 17
   
   **Log4j Configuration:**
   ```xml
   <?xml version="1.0" encoding="UTF-8"?>
   <Configuration status="DEBUG" shutdownHook="disable">
       <Appenders>
           <Console name="STDOUT" target="SYSTEM_OUT">
               <PatternLayout pattern="%d{ISO8601} %highlight{[%p]} 
%X{RequestId} (%t) %c: %m%n" disableAnsi="false"/>
           </Console>
       </Appenders>
       <Loggers>
           <Root level="INFO">
               <AppenderRef ref="STDOUT"/>
           </Root>
       </Loggers>
   </Configuration>
   ```
   
   **System Properties:**
   ```
   systemProperty("java.util.logging.manager", 
"org.apache.logging.log4j.jul.LogManager")
   ```
   
   ## Logs
   
   ```
   2025-09-18T20:19:33.145604Z main DEBUG LoggerContext[name=42110406, 
org.apache.logging.log4j.core.LoggerContext@3a44431a] started OK.
   2025-09-18T20:19:33.148982Z main ERROR An exception occurred processing 
Appender STDOUT
   java.lang.ArrayIndexOutOfBoundsException: Index 2 out of bounds for length 2
       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.ThrowablePatternConverter.format(ThrowablePatternConverter.java:130)
       at 
org.apache.logging.log4j.core.layout.PatternLayout$NoFormatPatternSerializer.toSerializable(PatternLayout.java:354)
       at 
org.apache.logging.log4j.core.layout.PatternLayout.toText(PatternLayout.java:251)
       at 
org.apache.logging.log4j.core.layout.PatternLayout.encode(PatternLayout.java:237)
       at 
org.apache.logging.log4j.core.layout.PatternLayout.encode(PatternLayout.java:57)
       at 
org.apache.logging.log4j.core.appender.AbstractOutputStreamAppender.directEncodeEvent(AbstractOutputStreamAppender.java:227)
       at 
org.apache.logging.log4j.core.appender.AbstractOutputStreamAppender.tryAppend(AbstractOutputStreamAppender.java:220)
       at 
org.apache.logging.log4j.core.appender.AbstractOutputStreamAppender.append(AbstractOutputStreamAppender.java:211)
       at 
org.apache.logging.log4j.core.config.AppenderControl.tryCallAppender(AppenderControl.java:160)
       at 
org.apache.logging.log4j.core.config.AppenderControl.callAppender0(AppenderControl.java:133)
       at 
org.apache.logging.log4j.core.config.AppenderControl.callAppenderPreventRecursion(AppenderControl.java:124)
       at 
org.apache.logging.log4j.core.config.AppenderControl.callAppender(AppenderControl.java:88)
       at 
org.apache.logging.log4j.core.config.LoggerConfig.callAppenders(LoggerConfig.java:711)
       at 
org.apache.logging.log4j.core.config.LoggerConfig.processLogEvent(LoggerConfig.java:669)
       at 
org.apache.logging.log4j.core.config.LoggerConfig.log(LoggerConfig.java:645)
       at 
org.apache.logging.log4j.core.config.LoggerConfig.log(LoggerConfig.java:589)
       at 
org.apache.logging.log4j.core.config.AwaitCompletionReliabilityStrategy.log(AwaitCompletionReliabilityStrategy.java:92)
       at org.apache.logging.log4j.core.Logger.log(Logger.java:187)
       at 
org.apache.logging.log4j.spi.AbstractLogger.tryLogMessage(AbstractLogger.java:2970)
       at 
org.apache.logging.log4j.spi.AbstractLogger.logMessageTrackRecursion(AbstractLogger.java:2922)
       at 
org.apache.logging.log4j.spi.AbstractLogger.logMessageSafely(AbstractLogger.java:2904)
       at 
org.apache.logging.log4j.spi.AbstractLogger.logMessage(AbstractLogger.java:2648)
       at 
org.apache.logging.log4j.spi.AbstractLogger.logIfEnabled(AbstractLogger.java:2587)
       at 
org.apache.logging.log4j.spi.AbstractLogger.error(AbstractLogger.java:824)
       at 
com.Log4jArrayIndexOutOfBoundsException.logAndEmitErrorMetric(Log4jArrayIndexOutOfBoundsException.java:58)
       at 
com.Log4jArrayIndexOutOfBoundsException.simpleReproduction(Log4jArrayIndexOutOfBoundsException.java:19)
       at 
com.Log4jArrayIndexOutOfBoundsException.main(Log4jArrayIndexOutOfBoundsException.java:12)
   ```
   
   ## Reproduction
   
   **Complete standalone reproduction:**
   
   ```java
   package com;
   
   import java.util.Objects;
   import org.apache.logging.log4j.LogManager;
   import org.apache.logging.log4j.Logger;
   
   public class Log4jArrayIndexOutOfBoundsException {
       private static final Logger LOG = LogManager.getLogger();
   
       public static void main(String[] args) throws Exception {
           simpleReproduction();
       }
   
       private static void simpleReproduction() throws Exception {
           String message = "Processing failed";
           Exception deeperStackTrace = createDeepStackTrace(3, message);
           Exception lighterStackTrace = new CustomProcessingException(message, 
deeperStackTrace);
           logAndEmitErrorMetric(message, lighterStackTrace);
       }
   
       private static CustomProcessingException createDeepStackTrace(int depth, 
String message) {
           if (depth <= 0) {
               return new CustomProcessingException(message);
           }
           return createDeepStackTrace(depth - 1, message);  // Recursive to 
create deep stack
       }
   
       static class CustomProcessingException extends Exception {
           public CustomProcessingException(String message) {
               super(message);
           }
   
           public CustomProcessingException(String message, Throwable cause) {
               super(message, cause);
           }
   
           @Override
           public boolean equals(Object other) {
               if (this == other) {
                   return true;
               } else if (!(other instanceof CustomProcessingException)) {
                   return false;
               } else {
                   CustomProcessingException that = 
(CustomProcessingException)other;
                   return Objects.equals(this.getMessage(), that.getMessage());
               }
           }
   
           @Override
           public int hashCode() {
               return Objects.hash(getMessage());
           }
       }
   
       private static void logAndEmitErrorMetric(String message, Exception e) {
           LOG.error(message, e);
       }
   }
   ```
   
   **How the bug occurs:**
   1. `createDeepStackTrace(3, "Processing failed")` creates an exception with 
4 stack frames
   2. `new CustomProcessingException("Processing failed", deeperStackTrace)` 
creates an exception with 2 stack frames
   3. Both exceptions have identical messages → `equals()` returns `true` → 
HashMap collision
   4. Metadata from deeper exception (stackLength=4) gets applied to lighter 
exception (actual length=2)
   5. Renderer tries to access `stackTrace[2]`, `stackTrace[3]` → 
`ArrayIndexOutOfBoundsException`
   
   ## Related
   
   This issue is related to but distinct from #3929. Both issues stem from the 
`Map<Throwable, Context.Metadata>` design:
   
   - **Issue #3929:** NullPointerException due to concurrent mutation of 
suppressed exceptions
   - **This issue:** ArrayIndexOutOfBoundsException due to HashMap key 
collisions with custom `equals()` methods
   
   ## Suggested Fix
   
   Replace `HashMap<Throwable, Context.Metadata>` and `HashSet<Throwable>` with 
identity-based collections:
   - Use `IdentityHashMap` instead of `HashMap`
   - Use identity-based Set implementation or `System.identityHashCode()`
   - Ensure object identity is used consistently across all throwable rendering 
components
   
   **Additionally, review all use cases where Throwable objects are used as 
HashMap/HashSet keys throughout the Log4j codebase and consider updating them 
to identity-based collections as well.** ex. 
https://github.com/apache/logging-log4j2/blob/7209b27ba4b2a9a9ce7be99b3a8af9bd74de5ad0/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/ThrowableStackTraceRenderer.java#L284C42-L284C59
   
   
   This comprehensive approach would prevent custom `equals()` implementations 
from causing key collisions and other unexpected behaviors in Log4j's internal 
data structures across all exception handling components.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to