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>