You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@logging.apache.org by pk...@apache.org on 2024/11/21 22:42:58 UTC

(logging-log4j2) 04/06: Fix off-by-one in `StackTraceStringResolver` (#3216)

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

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

commit 34ccf4c0a82a0cad4cf4fda936608eb93c084b3f
Author: Volkan Yazıcı <vo...@yazi.ci>
AuthorDate: Mon Nov 18 20:51:33 2024 +0100

    Fix off-by-one in `StackTraceStringResolver` (#3216)
    
    Fixes #3194, ports #3212.
---
 .../resolver/StackTraceStringResolverTest.java     | 63 ++++++++++++++++++++++
 .../json/resolver/StackTraceStringResolver.java    | 25 +++++----
 .../3216_fix_JTL_StackTraceStringResolver.xml      |  8 +++
 3 files changed, 83 insertions(+), 13 deletions(-)

diff --git a/log4j-layout-template-json-test/src/test/java/org/apache/logging/log4j/layout/template/json/resolver/StackTraceStringResolverTest.java b/log4j-layout-template-json-test/src/test/java/org/apache/logging/log4j/layout/template/json/resolver/StackTraceStringResolverTest.java
index 430d6de646..dd3b371e10 100644
--- a/log4j-layout-template-json-test/src/test/java/org/apache/logging/log4j/layout/template/json/resolver/StackTraceStringResolverTest.java
+++ b/log4j-layout-template-json-test/src/test/java/org/apache/logging/log4j/layout/template/json/resolver/StackTraceStringResolverTest.java
@@ -16,6 +16,7 @@
  */
 package org.apache.logging.log4j.layout.template.json.resolver;
 
+import static java.util.Collections.singletonList;
 import static org.apache.logging.log4j.layout.template.json.TestHelpers.CONFIGURATION;
 import static org.apache.logging.log4j.layout.template.json.TestHelpers.JAVA_BASE_PREFIX;
 import static org.apache.logging.log4j.layout.template.json.TestHelpers.asMap;
@@ -40,6 +41,7 @@ import org.apache.logging.log4j.core.LogEvent;
 import org.apache.logging.log4j.core.impl.Log4jLogEvent;
 import org.apache.logging.log4j.layout.template.json.JsonTemplateLayout;
 import org.apache.logging.log4j.layout.template.json.JsonTemplateLayoutDefaults;
+import org.apache.logging.log4j.layout.template.json.util.TruncatingBufferedPrintWriter;
 import org.apache.logging.log4j.util.Constants;
 import org.assertj.core.api.AbstractStringAssert;
 import org.junit.jupiter.api.Nested;
@@ -593,6 +595,67 @@ class StackTraceStringResolverTest {
         private String matchingRegex(final String string) {
             return "[" + string.charAt(0) + "]" + Pattern.quote(string.substring(1));
         }
+
+        @Test
+        void should_not_fail_on_truncated_output_not_ending_with_newline() {
+
+            // Try to find an exception whose truncated stack trace does not end with a newline
+            final int maxStringLength = 100;
+            final float maxByteCountPerChar =
+                    JsonTemplateLayoutDefaults.getCharset().newEncoder().maxBytesPerChar();
+            final int maxStringByteCount =
+                    Math.toIntExact(Math.round(Math.ceil(maxByteCountPerChar * maxStringLength)));
+            final TruncatingBufferedPrintWriter writer = TruncatingBufferedPrintWriter.ofCapacity(maxStringByteCount);
+            Exception exception;
+            String message = "m";
+            do {
+                exception = new Exception(message);
+                exception.printStackTrace(writer);
+                if (writer.truncated() && writer.buffer()[writer.length() - 1] != '\n') {
+                    break;
+                }
+                writer.close();
+                message += "m";
+            } while (true);
+
+            // Create the event template
+            final String eventTemplate = writeJson(asMap(
+                    "ex",
+                    asMap(
+                            "$resolver",
+                            "exception",
+                            "field",
+                            "stackTrace",
+                            "stackTrace",
+                            asMap(
+                                    "stringified",
+                                    asMap(
+                                            "truncation",
+                                            asMap(
+                                                    "suffix",
+                                                    TRUNCATION_SUFFIX,
+                                                    "pointMatcherStrings",
+                                                    singletonList("this string shouldn't match with anything")))))));
+
+            // Create the layout
+            final JsonTemplateLayout layout = JsonTemplateLayout.newBuilder()
+                    .setConfiguration(CONFIGURATION)
+                    .setEventTemplate(eventTemplate)
+                    .setMaxStringLength(maxStringLength)
+                    .setStackTraceEnabled(true)
+                    .build();
+
+            // Create the log event
+            final LogEvent logEvent =
+                    Log4jLogEvent.newBuilder().setThrown(exception).build();
+
+            // Check the serialized event
+            usingSerializedLogEventAccessor(layout, logEvent, accessor -> {
+                final int expectedStackTraceLength = maxStringLength + TRUNCATION_SUFFIX.length();
+                final String stackTrace = accessor.getString("ex");
+                assertThat(stackTrace).hasSizeLessThan(expectedStackTraceLength);
+            });
+        }
     }
 
     @Test
diff --git a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/StackTraceStringResolver.java b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/StackTraceStringResolver.java
index ebd34a3a47..7a4634a86f 100644
--- a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/StackTraceStringResolver.java
+++ b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/StackTraceStringResolver.java
@@ -119,7 +119,7 @@ final class StackTraceStringResolver implements StackTraceResolver {
         for (; ; ) {
 
             // Find the next label start, if present.
-            final int labeledLineStartIndex = findLabeledLineStartIndex(srcWriter, startIndex, srcWriter.length());
+            final int labeledLineStartIndex = findLabeledLineStartIndex(srcWriter, startIndex);
             final int endIndex = labeledLineStartIndex >= 0 ? labeledLineStartIndex : srcWriter.length();
 
             // Copy up to the truncation point, if it matches.
@@ -195,26 +195,27 @@ final class StackTraceStringResolver implements StackTraceResolver {
         return -1;
     }
 
-    private static int findLabeledLineStartIndex(final CharSequence buffer, final int startIndex, final int endIndex) {
+    private static int findLabeledLineStartIndex(final CharSequence buffer, final int startIndex) {
         // Note that the index arithmetic in this method is not guarded.
         // That is, there are no `Math.addExact()` or `Math.subtractExact()` usages.
         // Since we know a priori that we are already operating within buffer limits.
-        for (int bufferIndex = startIndex; bufferIndex < endIndex; ) {
+        final int bufferLength = buffer.length();
+        for (int bufferIndex = startIndex; bufferIndex < bufferLength; ) {
 
             // Find the next line start, if exists.
-            final int lineStartIndex = findLineStartIndex(buffer, bufferIndex, endIndex);
+            final int lineStartIndex = findLineStartIndex(buffer, bufferIndex);
             if (lineStartIndex < 0) {
                 break;
             }
             bufferIndex = lineStartIndex;
 
             // Skip tabs.
-            while (bufferIndex < endIndex && '\t' == buffer.charAt(bufferIndex)) {
+            while (bufferIndex < bufferLength && '\t' == buffer.charAt(bufferIndex)) {
                 bufferIndex++;
             }
 
             // Search for the `Caused by: ` occurrence.
-            if (bufferIndex < (endIndex - 11)
+            if (bufferIndex < (bufferLength - 11)
                     && buffer.charAt(bufferIndex) == 'C'
                     && buffer.charAt(bufferIndex + 1) == 'a'
                     && buffer.charAt(bufferIndex + 2) == 'u'
@@ -230,7 +231,7 @@ final class StackTraceStringResolver implements StackTraceResolver {
             }
 
             // Search for the `Suppressed: ` occurrence.
-            else if (bufferIndex < (endIndex - 12)
+            else if (bufferIndex < (bufferLength - 12)
                     && buffer.charAt(bufferIndex) == 'S'
                     && buffer.charAt(bufferIndex + 1) == 'u'
                     && buffer.charAt(bufferIndex + 2) == 'p'
@@ -249,13 +250,11 @@ final class StackTraceStringResolver implements StackTraceResolver {
         return -1;
     }
 
-    private static int findLineStartIndex(final CharSequence buffer, final int startIndex, final int endIndex) {
-        char prevChar = '-';
-        for (int i = startIndex; i <= endIndex; i++) {
-            if (prevChar == '\n') {
-                return i;
+    private static int findLineStartIndex(final CharSequence buffer, final int startIndex) {
+        for (int bufferIndex = startIndex; bufferIndex < buffer.length(); bufferIndex++) {
+            if (buffer.charAt(bufferIndex) == '\n' && (bufferIndex + 1) < buffer.length()) {
+                return bufferIndex + 1;
             }
-            prevChar = buffer.charAt(i);
         }
         return -1;
     }
diff --git a/src/changelog/.2.x.x/3216_fix_JTL_StackTraceStringResolver.xml b/src/changelog/.2.x.x/3216_fix_JTL_StackTraceStringResolver.xml
new file mode 100644
index 0000000000..55e62f9fad
--- /dev/null
+++ b/src/changelog/.2.x.x/3216_fix_JTL_StackTraceStringResolver.xml
@@ -0,0 +1,8 @@
+<?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="3216" link="https://github.com/apache/logging-log4j2/pull/3216"/>
+  <description format="asciidoc">Fix `ArrayIndexOutOfBoundsException` in JSON Template Layout truncated exception resolver</description>
+</entry>