This is an automated email from the ASF dual-hosted git repository.

vy pushed a commit to branch 2.x
in repository https://gitbox.apache.org/repos/asf/logging-log4j2.git


The following commit(s) were added to refs/heads/2.x by this push:
     new 2bc484c30d Handle missing stack traces in `ExtendedThreadInformation` 
(#3655)
2bc484c30d is described below

commit 2bc484c30d7ca52606ccd185584acf513417a755
Author: Andreas "PAX" Lück <[email protected]>
AuthorDate: Mon May 19 22:49:55 2025 +0200

    Handle missing stack traces in `ExtendedThreadInformation` (#3655)
    
    Fixes `ArrayIndexOutOfBoundsException` on invocation of
    `Message#getFormattedMessage()` when any thread has no
    stack trace, which occurs on some JVM implementations.
---
 .../message/ExtendedThreadInformationTest.java     | 45 ++++++++++++++++++++++
 .../core/message/ExtendedThreadInformation.java    | 29 ++++++++------
 ...g_stack_traces_in_ExtendedThreadInformation.xml | 10 +++++
 3 files changed, 73 insertions(+), 11 deletions(-)

diff --git 
a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/message/ExtendedThreadInformationTest.java
 
b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/message/ExtendedThreadInformationTest.java
index 3dca41c498..95351959f8 100644
--- 
a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/message/ExtendedThreadInformationTest.java
+++ 
b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/message/ExtendedThreadInformationTest.java
@@ -16,10 +16,17 @@
  */
 package org.apache.logging.log4j.core.message;
 
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
 
+import java.lang.management.ThreadInfo;
 import org.apache.logging.log4j.message.ThreadDumpMessage;
 import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.EnumSource;
 
 /**
  * Tests that ThreadDumpMessage uses ExtendedThreadInformation when available.
@@ -33,4 +40,42 @@ class ExtendedThreadInformationTest {
         // System.out.print(message);
         assertTrue(message.contains(" Id="), "No header");
     }
+
+    @ParameterizedTest
+    @EnumSource(Thread.State.class)
+    void testMessageWithNullStackTrace(final Thread.State state) {
+        obtainMessageWithMissingStackTrace(state, null);
+    }
+
+    @ParameterizedTest
+    @EnumSource(Thread.State.class)
+    void testMessageWithEmptyStackTrace(final Thread.State state) {
+        obtainMessageWithMissingStackTrace(state, new StackTraceElement[0]);
+    }
+
+    private void obtainMessageWithMissingStackTrace(final Thread.State state, 
final StackTraceElement[] stackTrace) {
+        // setup
+        final String threadName = "the thread name";
+        final long threadId = 23523L;
+
+        final ThreadInfo threadInfo = mock(ThreadInfo.class);
+        when(threadInfo.getStackTrace()).thenReturn(stackTrace);
+        when(threadInfo.getThreadName()).thenReturn(threadName);
+        when(threadInfo.getThreadId()).thenReturn(threadId);
+        when(threadInfo.isSuspended()).thenReturn(true);
+        when(threadInfo.isInNative()).thenReturn(true);
+        when(threadInfo.getThreadState()).thenReturn(state);
+
+        // given
+        final ExtendedThreadInformation sut = new 
ExtendedThreadInformation(threadInfo);
+
+        // when
+        final StringBuilder result = new StringBuilder();
+        sut.printThreadInfo(result);
+
+        // then
+        assertThat(result.toString(), containsString(threadName));
+        assertThat(result.toString(), containsString(state.name()));
+        assertThat(result.toString(), 
containsString(String.valueOf(threadId)));
+    }
 }
diff --git 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/message/ExtendedThreadInformation.java
 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/message/ExtendedThreadInformation.java
index c6ea59d1c6..d3ea79e7ff 100644
--- 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/message/ExtendedThreadInformation.java
+++ 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/message/ExtendedThreadInformation.java
@@ -23,6 +23,7 @@ import java.lang.management.MonitorInfo;
 import java.lang.management.ThreadInfo;
 import org.apache.logging.log4j.message.ThreadInformation;
 import org.apache.logging.log4j.util.StringBuilders;
+import org.jspecify.annotations.Nullable;
 
 /**
  * Provides information on locks and monitors in the thread dump. This class 
requires Java 1.6 to compile and
@@ -119,17 +120,17 @@ class ExtendedThreadInformation implements 
ThreadInformation {
                 break;
             }
             case WAITING: {
-                final StackTraceElement element = info.getStackTrace()[0];
-                final String className = element.getClassName();
-                final String method = element.getMethodName();
-                if (className.equals("java.lang.Object") && 
method.equals("wait")) {
+                final StackTraceElement element = 
getFirstStackTraceElement(info);
+                final String className = element != null ? 
element.getClassName() : null;
+                final String method = element != null ? 
element.getMethodName() : null;
+                if ("java.lang.Object".equals(className) && 
"wait".equals(method)) {
                     sb.append(" (on object monitor");
                     if (info.getLockOwnerName() != null) {
                         sb.append(" owned by \"");
                         sb.append(info.getLockOwnerName()).append("\" 
Id=").append(info.getLockOwnerId());
                     }
                     sb.append(')');
-                } else if (className.equals("java.lang.Thread") && 
method.equals("join")) {
+                } else if ("java.lang.Thread".equals(className) && 
"join".equals(method)) {
                     sb.append(" (on completion of thread ")
                             .append(info.getLockOwnerId())
                             .append(')');
@@ -144,19 +145,19 @@ class ExtendedThreadInformation implements 
ThreadInformation {
                 break;
             }
             case TIMED_WAITING: {
-                final StackTraceElement element = info.getStackTrace()[0];
-                final String className = element.getClassName();
-                final String method = element.getMethodName();
-                if (className.equals("java.lang.Object") && 
method.equals("wait")) {
+                final StackTraceElement element = 
getFirstStackTraceElement(info);
+                final String className = element != null ? 
element.getClassName() : null;
+                final String method = element != null ? 
element.getMethodName() : null;
+                if ("java.lang.Object".equals(className) && 
"wait".equals(method)) {
                     sb.append(" (on object monitor");
                     if (info.getLockOwnerName() != null) {
                         sb.append(" owned by \"");
                         sb.append(info.getLockOwnerName()).append("\" 
Id=").append(info.getLockOwnerId());
                     }
                     sb.append(')');
-                } else if (className.equals("java.lang.Thread") && 
method.equals("sleep")) {
+                } else if ("java.lang.Thread".equals(className) && 
"sleep".equals(method)) {
                     sb.append(" (sleeping)");
-                } else if (className.equals("java.lang.Thread") && 
method.equals("join")) {
+                } else if ("java.lang.Thread".equals(className) && 
"join".equals(method)) {
                     sb.append(" (on completion of thread ")
                             .append(info.getLockOwnerId())
                             .append(')');
@@ -174,4 +175,10 @@ class ExtendedThreadInformation implements 
ThreadInformation {
                 break;
         }
     }
+
+    @Nullable
+    private static StackTraceElement getFirstStackTraceElement(final 
ThreadInfo info) {
+        final StackTraceElement[] stackTrace = info.getStackTrace();
+        return stackTrace != null && stackTrace.length > 0 ? stackTrace[0] : 
null;
+    }
 }
diff --git 
a/src/changelog/.2.x.x/3655_handle_missing_stack_traces_in_ExtendedThreadInformation.xml
 
b/src/changelog/.2.x.x/3655_handle_missing_stack_traces_in_ExtendedThreadInformation.xml
new file mode 100644
index 0000000000..738ae8bef5
--- /dev/null
+++ 
b/src/changelog/.2.x.x/3655_handle_missing_stack_traces_in_ExtendedThreadInformation.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<entry xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";
+       xmlns="https://logging.apache.org/xml/ns";
+       xsi:schemaLocation="https://logging.apache.org/xml/ns 
https://logging.apache.org/xml/ns/log4j-changelog-0.xsd";
+       type="fixed">
+  <issue id="3655" link="https://github.com/apache/logging-log4j2/pull/3655"/>
+  <description format="asciidoc">
+    Fix `ArrayIndexOutOfBoundsException` on invocation of 
`Message.getFormattedMessage()` when any thread has no stack trace, which 
occurs on some JVM implementations.
+  </description>
+</entry>

Reply via email to