Skip to content

Conversation

joepembe
Copy link
Contributor

@joepembe joepembe commented Sep 18, 2025

The ThrowableStackTraceRenderer class can throw a NullPointerException if the suppressed exceptions associated with the Throwable it is rendering are being concurrently mutated. This happens because ThrowableStackTraceRenderer invokes Throwable#getSuppressed() twice: once in ThrowableStackTraceRenderer.Context.Metadata#populateMetadata(), and a second time in ThrowableStackTraceRenderer#renderThrowable(), ahead of invoking ThrowableStackTraceRenderer#renderSuppressed(). If a racing thread manages to add a new suppressed exception to the being-logged exception between these two invocations, then the Map<Throwable, Context.Metadata> constructed by populateMetadata() will contain no mapping for the newly-added suppression, and as a result the dereference performed on line 168 explodes.

Note: the unit test I am adding here requires the ability to mock Throwable#getSuppressed(). Since this method is final, I had to add a dependency on mockito-inline, which then required that I fix up some unrelated unit tests that had been relying on the lack of support for mocking final methods.

This fixes #3929

The `ThrowableStackTraceRenderer` class can throw a `NullPointerException` if the suppressed
exceptions associated with the `Throwable` it is rendering are being concurrently mutated.
This happens because `ThrowableStackTraceRenderer` invokes `Throwable#getSuppressed()` twice:
once in `ThrowableStackTraceRenderer.Context.Metadata#populateMetadata()`, and a second time in
`ThrowableStackTraceRenderer#renderThrowable()`, ahead of invoking
`ThrowableStackTraceRenderer#renderSuppressed()`. If a racing thread manages to add a new
suppressed exception to the being-logged exception between these two invocations, then the
`Map<Throwable, Context.Metadata>` constructed by `populateMetadata()` will contain no mapping
for the newly-added suppression, and as a result the dereference performed on line 168 explodes.

Note: the unit test I am adding here requires the ability to mock `Throwable#getSuppressed()`.
Since this method is `final`, I had to add a dependency on `mockito-inline`, which then required
that I fix up some unrelated unit tests that had been relying on the lack of support for mocking
`final` methods.

This fixes apache#3929
Copy link

github-actions bot commented Sep 19, 2025

Job Requested goals Build Tool Version Build Outcome Build Scan®
build-macos-latest clean install 3.9.8 Build Scan PUBLISHED
build-ubuntu-latest clean install 3.9.8 Build Scan PUBLISHED
build-windows-latest clean install 3.9.8 Build Scan PUBLISHED
Generated by gradle/develocity-actions

Copy link
Member

@vy vy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks so much for the contribution! Would you mind also extending following tests to reproduce the issue, please?

  • ExtendedThrowablePatternConverterTest
  • RootThrowablePatternConverterTest
  • ThrowablePatternConverterTest

@vy vy self-assigned this Sep 26, 2025
@joepembe
Copy link
Contributor Author

Thanks so much for the contribution! Would you mind also extending following tests to reproduce the issue, please?

  • ExtendedThrowablePatternConverterTest
  • RootThrowablePatternConverterTest
  • ThrowablePatternConverterTest

Instead of modifying these tests, I added a new unit test specifically for ThrowableStackTraceRenderer. Is that sufficient?

@joepembe joepembe requested a review from vy October 2, 2025 20:23
Copy link
Member

@vy vy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would you mind removing ThrowableStackTraceRendererTest, the newly mockito-inline dependency, and changes necessitated due to that, please?

@joepembe joepembe requested a review from vy October 3, 2025 16:19
@vy vy enabled auto-merge (squash) October 3, 2025 16:53
@vy vy merged commit 7125d93 into apache:2.x Oct 3, 2025
7 checks passed
@github-project-automation github-project-automation bot moved this from To triage to Done in Log4j bug tracker Oct 3, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

ThrowableStackTraceRenderer throws NPE when rendering a Throwable with concurrently-mutated suppressions

2 participants