This is an automated email from the ASF dual-hosted git repository. rmaucher pushed a commit to branch 11.0.x in repository https://gitbox.apache.org/repos/asf/tomcat.git
commit 55d5e00d89b9881e49176884df5a466a0e8b746b Author: opencode <[email protected]> AuthorDate: Tue Oct 20 08:53:00 2026 +0200 Fix infinite loop in JsonFormatter for cyclic cause chains When a LogRecord throwable has a cyclic cause chain, the do/while loop in format() that walks getCause() never terminates, appending to the StringBuilder until the JVM runs out of memory, or hanging the writing thread indefinitely on a large heap. Throwable.initCause() only rejects a direct self-reference, so a cycle of length two or more is legal, plain application code. With AsyncFileHandler the single writer thread would be permanently lost, taking down all async logging. The walk of the chain now tracks the visited exceptions in an identity-based set and stops at the first repeated throwable, emitting it as a circular reference entry, in the manner of the output of Throwable.printStackTrace(). Non-cyclic chains produce byte-identical output. Added a regression test which, without the fix, fails with an OutOfMemoryError. --- java/org/apache/juli/JsonFormatter.java | 12 ++++++++++++ test/org/apache/juli/TestJsonFormatter.java | 25 +++++++++++++++++++++++++ 2 files changed, 37 insertions(+) diff --git a/java/org/apache/juli/JsonFormatter.java b/java/org/apache/juli/JsonFormatter.java index 51a803888d..bf0246a9f1 100644 --- a/java/org/apache/juli/JsonFormatter.java +++ b/java/org/apache/juli/JsonFormatter.java @@ -16,6 +16,9 @@ */ package org.apache.juli; +import java.util.Collections; +import java.util.IdentityHashMap; +import java.util.Set; import java.util.logging.LogManager; import java.util.logging.LogRecord; @@ -93,12 +96,21 @@ public class JsonFormatter extends OneLineFormatter { // Stack trace sb.append("\"throwable\": ["); boolean first = true; + // A cause chain may legally contain cycles (Throwable.initCause() only + // rejects a direct self-reference) so track the visited exceptions and + // stop the walk at the first repeated throwable, reporting it as a + // circular reference in the manner of Throwable.printStackTrace(). + Set<Throwable> seen = Collections.newSetFromMap(new IdentityHashMap<>()); do { if (!first) { sb.append(','); } else { first = false; } + if (!seen.add(t)) { + sb.append("\"[Circular reference] ").append(JSONFilter.escape(t.toString())).append('\"'); + break; + } sb.append('\"').append(JSONFilter.escape(t.toString())).append('\"'); for (StackTraceElement element : t.getStackTrace()) { sb.append(',').append('\"').append(' ').append(JSONFilter.escape(element.toString())).append('\"'); diff --git a/test/org/apache/juli/TestJsonFormatter.java b/test/org/apache/juli/TestJsonFormatter.java index cf8938bd52..9640b4a005 100644 --- a/test/org/apache/juli/TestJsonFormatter.java +++ b/test/org/apache/juli/TestJsonFormatter.java @@ -55,6 +55,31 @@ public class TestJsonFormatter { Assert.assertEquals(trace.get(0), "java.lang.IllegalStateException: Bad state"); } + @Test + public void testCircularCause() throws Exception { + + Exception a = new Exception("A"); + Exception b = new Exception("B"); + a.initCause(b); + b.initCause(a); + + LogRecord logRecord = new LogRecord(Level.FINE, "Test log message"); + logRecord.setThrown(a); + + Formatter formatter = new JsonFormatter(); + + // Without cycle detection this call would never complete + String result = formatter.format(logRecord); + + // Verify JSON content + Assert.assertTrue(result.startsWith("{")); + JSONParser parser = new JSONParser(new StringReader(result)); + LinkedHashMap<String,Object> json = parser.object(); + @SuppressWarnings("unchecked") + ArrayList<Object> trace = (ArrayList<Object>) json.get("throwable"); + Assert.assertTrue(String.valueOf(trace.get(trace.size() - 1)).startsWith("[Circular reference]")); + } + @Test public void testEscaping() { String[][] tests = new String[][] { --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
