Skip to content

Commit efba2fb

Browse files
authored
Fix off-by-one in StackTraceStringResolver (#3216)
Fixes #3194, ports #3212.
1 parent 81e1f06 commit efba2fb

File tree

3 files changed

+83
-13
lines changed

3 files changed

+83
-13
lines changed

log4j-layout-template-json-test/src/test/java/org/apache/logging/log4j/layout/template/json/resolver/StackTraceStringResolverTest.java

+63
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
*/
1717
package org.apache.logging.log4j.layout.template.json.resolver;
1818

19+
import static java.util.Collections.singletonList;
1920
import static org.apache.logging.log4j.layout.template.json.TestHelpers.CONFIGURATION;
2021
import static org.apache.logging.log4j.layout.template.json.TestHelpers.JAVA_BASE_PREFIX;
2122
import static org.apache.logging.log4j.layout.template.json.TestHelpers.asMap;
@@ -40,6 +41,7 @@
4041
import org.apache.logging.log4j.core.impl.Log4jLogEvent;
4142
import org.apache.logging.log4j.layout.template.json.JsonTemplateLayout;
4243
import org.apache.logging.log4j.layout.template.json.JsonTemplateLayoutDefaults;
44+
import org.apache.logging.log4j.layout.template.json.util.TruncatingBufferedPrintWriter;
4345
import org.apache.logging.log4j.util.Constants;
4446
import org.assertj.core.api.AbstractStringAssert;
4547
import org.junit.jupiter.api.Nested;
@@ -593,6 +595,67 @@ private String pointMatcherRegex(final Throwable exception) {
593595
private String matchingRegex(final String string) {
594596
return "[" + string.charAt(0) + "]" + Pattern.quote(string.substring(1));
595597
}
598+
599+
@Test
600+
void should_not_fail_on_truncated_output_not_ending_with_newline() {
601+
602+
// Try to find an exception whose truncated stack trace does not end with a newline
603+
final int maxStringLength = 100;
604+
final float maxByteCountPerChar =
605+
JsonTemplateLayoutDefaults.getCharset().newEncoder().maxBytesPerChar();
606+
final int maxStringByteCount =
607+
Math.toIntExact(Math.round(Math.ceil(maxByteCountPerChar * maxStringLength)));
608+
final TruncatingBufferedPrintWriter writer = TruncatingBufferedPrintWriter.ofCapacity(maxStringByteCount);
609+
Exception exception;
610+
String message = "m";
611+
do {
612+
exception = new Exception(message);
613+
exception.printStackTrace(writer);
614+
if (writer.truncated() && writer.buffer()[writer.length() - 1] != '\n') {
615+
break;
616+
}
617+
writer.close();
618+
message += "m";
619+
} while (true);
620+
621+
// Create the event template
622+
final String eventTemplate = writeJson(asMap(
623+
"ex",
624+
asMap(
625+
"$resolver",
626+
"exception",
627+
"field",
628+
"stackTrace",
629+
"stackTrace",
630+
asMap(
631+
"stringified",
632+
asMap(
633+
"truncation",
634+
asMap(
635+
"suffix",
636+
TRUNCATION_SUFFIX,
637+
"pointMatcherStrings",
638+
singletonList("this string shouldn't match with anything")))))));
639+
640+
// Create the layout
641+
final JsonTemplateLayout layout = JsonTemplateLayout.newBuilder()
642+
.setConfiguration(CONFIGURATION)
643+
.setEventTemplate(eventTemplate)
644+
.setMaxStringLength(maxStringLength)
645+
.setStackTraceEnabled(true)
646+
.build();
647+
648+
// Create the log event
649+
final LogEvent logEvent =
650+
Log4jLogEvent.newBuilder().setThrown(exception).build();
651+
652+
// Check the serialized event
653+
usingSerializedLogEventAccessor(layout, logEvent, accessor -> {
654+
final int expectedStackTraceLength = maxStringLength + TRUNCATION_SUFFIX.length();
655+
final String stackTrace = accessor.getString("ex");
656+
assertThat(stackTrace).hasSizeLessThan(expectedStackTraceLength);
657+
});
658+
}
596659
}
597660

598661
@Test

log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/StackTraceStringResolver.java

+12-13
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ private void truncate(
119119
for (; ; ) {
120120

121121
// Find the next label start, if present.
122-
final int labeledLineStartIndex = findLabeledLineStartIndex(srcWriter, startIndex, srcWriter.length());
122+
final int labeledLineStartIndex = findLabeledLineStartIndex(srcWriter, startIndex);
123123
final int endIndex = labeledLineStartIndex >= 0 ? labeledLineStartIndex : srcWriter.length();
124124

125125
// Copy up to the truncation point, if it matches.
@@ -195,26 +195,27 @@ private int findTruncationPointIndex(
195195
return -1;
196196
}
197197

198-
private static int findLabeledLineStartIndex(final CharSequence buffer, final int startIndex, final int endIndex) {
198+
private static int findLabeledLineStartIndex(final CharSequence buffer, final int startIndex) {
199199
// Note that the index arithmetic in this method is not guarded.
200200
// That is, there are no `Math.addExact()` or `Math.subtractExact()` usages.
201201
// Since we know a priori that we are already operating within buffer limits.
202-
for (int bufferIndex = startIndex; bufferIndex < endIndex; ) {
202+
final int bufferLength = buffer.length();
203+
for (int bufferIndex = startIndex; bufferIndex < bufferLength; ) {
203204

204205
// Find the next line start, if exists.
205-
final int lineStartIndex = findLineStartIndex(buffer, bufferIndex, endIndex);
206+
final int lineStartIndex = findLineStartIndex(buffer, bufferIndex);
206207
if (lineStartIndex < 0) {
207208
break;
208209
}
209210
bufferIndex = lineStartIndex;
210211

211212
// Skip tabs.
212-
while (bufferIndex < endIndex && '\t' == buffer.charAt(bufferIndex)) {
213+
while (bufferIndex < bufferLength && '\t' == buffer.charAt(bufferIndex)) {
213214
bufferIndex++;
214215
}
215216

216217
// Search for the `Caused by: ` occurrence.
217-
if (bufferIndex < (endIndex - 11)
218+
if (bufferIndex < (bufferLength - 11)
218219
&& buffer.charAt(bufferIndex) == 'C'
219220
&& buffer.charAt(bufferIndex + 1) == 'a'
220221
&& buffer.charAt(bufferIndex + 2) == 'u'
@@ -230,7 +231,7 @@ private static int findLabeledLineStartIndex(final CharSequence buffer, final in
230231
}
231232

232233
// Search for the `Suppressed: ` occurrence.
233-
else if (bufferIndex < (endIndex - 12)
234+
else if (bufferIndex < (bufferLength - 12)
234235
&& buffer.charAt(bufferIndex) == 'S'
235236
&& buffer.charAt(bufferIndex + 1) == 'u'
236237
&& buffer.charAt(bufferIndex + 2) == 'p'
@@ -249,13 +250,11 @@ else if (bufferIndex < (endIndex - 12)
249250
return -1;
250251
}
251252

252-
private static int findLineStartIndex(final CharSequence buffer, final int startIndex, final int endIndex) {
253-
char prevChar = '-';
254-
for (int i = startIndex; i <= endIndex; i++) {
255-
if (prevChar == '\n') {
256-
return i;
253+
private static int findLineStartIndex(final CharSequence buffer, final int startIndex) {
254+
for (int bufferIndex = startIndex; bufferIndex < buffer.length(); bufferIndex++) {
255+
if (buffer.charAt(bufferIndex) == '\n' && (bufferIndex + 1) < buffer.length()) {
256+
return bufferIndex + 1;
257257
}
258-
prevChar = buffer.charAt(i);
259258
}
260259
return -1;
261260
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<entry xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3+
xmlns="https://logging.apache.org/xml/ns"
4+
xsi:schemaLocation="https://logging.apache.org/xml/ns https://logging.apache.org/xml/ns/log4j-changelog-0.xsd"
5+
type="fixed">
6+
<issue id="3216" link="https://github.com/apache/logging-log4j2/pull/3216"/>
7+
<description format="asciidoc">Fix `ArrayIndexOutOfBoundsException` in JSON Template Layout truncated exception resolver</description>
8+
</entry>

0 commit comments

Comments
 (0)