Skip to content

Commit 0eb232f

Browse files
authored
Don't fail on insufficient parameters in ParameterFormatter (#2337, #2343)
1 parent 32075af commit 0eb232f

File tree

7 files changed

+131
-26
lines changed

7 files changed

+131
-26
lines changed

log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/UsingStatusLoggerMock.java

+2
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import java.lang.annotation.Retention;
2525
import java.lang.annotation.Target;
2626
import org.junit.jupiter.api.extension.ExtendWith;
27+
import org.junit.jupiter.api.parallel.ResourceLock;
2728

2829
/**
2930
* Shortcut to {@link StatusLoggerMockExtension}.
@@ -32,4 +33,5 @@
3233
@Target({TYPE, METHOD})
3334
@Documented
3435
@ExtendWith({ExtensionContextAnchor.class, StatusLoggerMockExtension.class})
36+
@ResourceLock("log4j2.StatusLogger")
3537
public @interface UsingStatusLoggerMock {}

log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/package-info.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
* limitations under the license.
1616
*/
1717
@Export
18-
@Version("2.23.0")
18+
@Version("2.23.1")
1919
package org.apache.logging.log4j.test.junit;
2020

2121
import org.osgi.annotation.bundle.Export;

log4j-api-test/src/test/java/org/apache/logging/log4j/message/ParameterFormatterTest.java

+18-7
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,14 @@
1717
package org.apache.logging.log4j.message;
1818

1919
import static org.assertj.core.api.Assertions.assertThat;
20+
import static org.assertj.core.api.Assertions.assertThatThrownBy;
2021

2122
import java.util.ArrayList;
2223
import java.util.Arrays;
2324
import java.util.Collections;
2425
import java.util.List;
2526
import org.apache.logging.log4j.message.ParameterFormatter.MessagePatternAnalysis;
27+
import org.apache.logging.log4j.test.junit.UsingStatusLoggerMock;
2628
import org.junit.jupiter.api.Test;
2729
import org.junit.jupiter.params.ParameterizedTest;
2830
import org.junit.jupiter.params.provider.CsvSource;
@@ -31,7 +33,8 @@
3133
/**
3234
* Tests {@link ParameterFormatter}.
3335
*/
34-
public class ParameterFormatterTest {
36+
@UsingStatusLoggerMock
37+
class ParameterFormatterTest {
3538

3639
@ParameterizedTest
3740
@CsvSource({
@@ -49,7 +52,7 @@ public class ParameterFormatterTest {
4952
"4,0:2:4:10,false,{}{}{}a{]b{}",
5053
"5,0:2:4:7:10,false,{}{}{}a{}b{}"
5154
})
52-
public void test_pattern_analysis(
55+
void test_pattern_analysis(
5356
final int placeholderCount,
5457
final String placeholderCharIndicesString,
5558
final boolean escapedPlaceholderFound,
@@ -65,14 +68,22 @@ public void test_pattern_analysis(
6568
}
6669
}
6770

71+
@ParameterizedTest
72+
@CsvSource({"1,foo {}", "2,bar {}{}"})
73+
void format_should_fail_on_insufficient_args(final int placeholderCount, final String pattern) {
74+
final int argCount = placeholderCount - 1;
75+
assertThatThrownBy(() -> ParameterFormatter.format(pattern, new Object[argCount], argCount))
76+
.isInstanceOf(IllegalArgumentException.class)
77+
.hasMessage(
78+
"found %d argument placeholders, but provided %d for pattern `%s`",
79+
placeholderCount, argCount, pattern);
80+
}
81+
6882
@ParameterizedTest
6983
@MethodSource("messageFormattingTestCases")
70-
void assertMessageFormatting(
84+
void format_should_work(
7185
final String pattern, final Object[] args, final int argCount, final String expectedFormattedMessage) {
72-
MessagePatternAnalysis analysis = ParameterFormatter.analyzePattern(pattern, -1);
73-
final StringBuilder buffer = new StringBuilder();
74-
ParameterFormatter.formatMessage(buffer, pattern, args, argCount, analysis);
75-
String actualFormattedMessage = buffer.toString();
86+
final String actualFormattedMessage = ParameterFormatter.format(pattern, args, argCount);
7687
assertThat(actualFormattedMessage).isEqualTo(expectedFormattedMessage);
7788
}
7889

log4j-api-test/src/test/java/org/apache/logging/log4j/message/ParameterizedMessageTest.java

+74-13
Original file line numberDiff line numberDiff line change
@@ -19,17 +19,31 @@
1919
import static org.assertj.core.api.Assertions.assertThat;
2020

2121
import java.math.BigDecimal;
22+
import java.util.List;
23+
import java.util.function.Supplier;
24+
import java.util.stream.Collectors;
2225
import java.util.stream.Stream;
26+
import org.apache.logging.log4j.Level;
27+
import org.apache.logging.log4j.status.StatusData;
28+
import org.apache.logging.log4j.test.ListStatusListener;
2329
import org.apache.logging.log4j.test.junit.Mutable;
2430
import org.apache.logging.log4j.test.junit.SerialUtil;
31+
import org.apache.logging.log4j.test.junit.UsingStatusListener;
2532
import org.junit.jupiter.api.Test;
2633
import org.junit.jupiter.params.ParameterizedTest;
2734
import org.junit.jupiter.params.provider.MethodSource;
2835

29-
public class ParameterizedMessageTest {
36+
@UsingStatusListener
37+
class ParameterizedMessageTest {
38+
39+
final ListStatusListener statusListener;
40+
41+
ParameterizedMessageTest(ListStatusListener statusListener) {
42+
this.statusListener = statusListener;
43+
}
3044

3145
@Test
32-
public void testNoArgs() {
46+
void testNoArgs() {
3347
final String testMsg = "Test message {}";
3448
ParameterizedMessage msg = new ParameterizedMessage(testMsg, (Object[]) null);
3549
String result = msg.getFormattedMessage();
@@ -41,7 +55,7 @@ public void testNoArgs() {
4155
}
4256

4357
@Test
44-
public void testZeroLength() {
58+
void testZeroLength() {
4559
final String testMsg = "";
4660
ParameterizedMessage msg = new ParameterizedMessage(testMsg, new Object[] {"arg"});
4761
String result = msg.getFormattedMessage();
@@ -53,7 +67,7 @@ public void testZeroLength() {
5367
}
5468

5569
@Test
56-
public void testOneCharLength() {
70+
void testOneCharLength() {
5771
final String testMsg = "d";
5872
ParameterizedMessage msg = new ParameterizedMessage(testMsg, new Object[] {"arg"});
5973
String result = msg.getFormattedMessage();
@@ -65,71 +79,71 @@ public void testOneCharLength() {
6579
}
6680

6781
@Test
68-
public void testFormat3StringArgs() {
82+
void testFormat3StringArgs() {
6983
final String testMsg = "Test message {}{} {}";
7084
final String[] args = {"a", "b", "c"};
7185
final String result = ParameterizedMessage.format(testMsg, args);
7286
assertThat(result).isEqualTo("Test message ab c");
7387
}
7488

7589
@Test
76-
public void testFormatNullArgs() {
90+
void testFormatNullArgs() {
7791
final String testMsg = "Test message {} {} {} {} {} {}";
7892
final String[] args = {"a", null, "c", null, null, null};
7993
final String result = ParameterizedMessage.format(testMsg, args);
8094
assertThat(result).isEqualTo("Test message a null c null null null");
8195
}
8296

8397
@Test
84-
public void testFormatStringArgsIgnoresSuperfluousArgs() {
98+
void testFormatStringArgsIgnoresSuperfluousArgs() {
8599
final String testMsg = "Test message {}{} {}";
86100
final String[] args = {"a", "b", "c", "unnecessary", "superfluous"};
87101
final String result = ParameterizedMessage.format(testMsg, args);
88102
assertThat(result).isEqualTo("Test message ab c");
89103
}
90104

91105
@Test
92-
public void testFormatStringArgsWithEscape() {
106+
void testFormatStringArgsWithEscape() {
93107
final String testMsg = "Test message \\{}{} {}";
94108
final String[] args = {"a", "b", "c"};
95109
final String result = ParameterizedMessage.format(testMsg, args);
96110
assertThat(result).isEqualTo("Test message {}a b");
97111
}
98112

99113
@Test
100-
public void testFormatStringArgsWithTrailingEscape() {
114+
void testFormatStringArgsWithTrailingEscape() {
101115
final String testMsg = "Test message {}{} {}\\";
102116
final String[] args = {"a", "b", "c"};
103117
final String result = ParameterizedMessage.format(testMsg, args);
104118
assertThat(result).isEqualTo("Test message ab c\\");
105119
}
106120

107121
@Test
108-
public void testFormatStringArgsWithTrailingText() {
122+
void testFormatStringArgsWithTrailingText() {
109123
final String testMsg = "Test message {}{} {}Text";
110124
final String[] args = {"a", "b", "c"};
111125
final String result = ParameterizedMessage.format(testMsg, args);
112126
assertThat(result).isEqualTo("Test message ab cText");
113127
}
114128

115129
@Test
116-
public void testFormatStringArgsWithTrailingEscapedEscape() {
130+
void testFormatStringArgsWithTrailingEscapedEscape() {
117131
final String testMsg = "Test message {}{} {}\\\\";
118132
final String[] args = {"a", "b", "c"};
119133
final String result = ParameterizedMessage.format(testMsg, args);
120134
assertThat(result).isEqualTo("Test message ab c\\");
121135
}
122136

123137
@Test
124-
public void testFormatStringArgsWithEscapedEscape() {
138+
void testFormatStringArgsWithEscapedEscape() {
125139
final String testMsg = "Test message \\\\{}{} {}";
126140
final String[] args = {"a", "b", "c"};
127141
final String result = ParameterizedMessage.format(testMsg, args);
128142
assertThat(result).isEqualTo("Test message \\ab c");
129143
}
130144

131145
@Test
132-
public void testSafeWithMutableParams() { // LOG4J2-763
146+
void testSafeWithMutableParams() { // LOG4J2-763
133147
final String testMsg = "Test message {}";
134148
final Mutable param = new Mutable().set("abc");
135149
final ParameterizedMessage msg = new ParameterizedMessage(testMsg, param);
@@ -170,4 +184,51 @@ void testSerializable(final Object arg) {
170184
assertThat(actual).isInstanceOf(ParameterizedMessage.class);
171185
assertThat(actual.getFormattedMessage()).isEqualTo(expected.getFormattedMessage());
172186
}
187+
188+
static Stream<Object[]> testCasesForInsufficientFormatArgs() {
189+
return Stream.of(new Object[] {1, "foo {}"}, new Object[] {2, "bar {}{}"});
190+
}
191+
192+
@ParameterizedTest
193+
@MethodSource("testCasesForInsufficientFormatArgs")
194+
void formatTo_should_fail_on_insufficient_args(final int placeholderCount, final String pattern) {
195+
final int argCount = placeholderCount - 1;
196+
verifyFormattingFailureOnInsufficientArgs(placeholderCount, pattern, argCount, () -> {
197+
final ParameterizedMessage message = new ParameterizedMessage(pattern, new Object[argCount]);
198+
final StringBuilder buffer = new StringBuilder();
199+
message.formatTo(buffer);
200+
return buffer.toString();
201+
});
202+
}
203+
204+
@ParameterizedTest
205+
@MethodSource("testCasesForInsufficientFormatArgs")
206+
void format_should_fail_on_insufficient_args(final int placeholderCount, final String pattern) {
207+
final int argCount = placeholderCount - 1;
208+
verifyFormattingFailureOnInsufficientArgs(
209+
placeholderCount, pattern, argCount, () -> ParameterizedMessage.format(pattern, new Object[argCount]));
210+
}
211+
212+
private void verifyFormattingFailureOnInsufficientArgs(
213+
final int placeholderCount,
214+
final String pattern,
215+
final int argCount,
216+
final Supplier<String> formattedMessageSupplier) {
217+
218+
// Verify the formatted message
219+
final String formattedMessage = formattedMessageSupplier.get();
220+
assertThat(formattedMessage).isEqualTo(pattern);
221+
222+
// Verify the logged failure
223+
final List<StatusData> statusDataList = statusListener.getStatusData().collect(Collectors.toList());
224+
assertThat(statusDataList).hasSize(1);
225+
final StatusData statusData = statusDataList.get(0);
226+
assertThat(statusData.getLevel()).isEqualTo(Level.ERROR);
227+
assertThat(statusData.getMessage().getFormattedMessage()).isEqualTo("Unable to format msg: %s", pattern);
228+
assertThat(statusData.getThrowable())
229+
.isInstanceOf(IllegalArgumentException.class)
230+
.hasMessage(
231+
"found %d argument placeholders, but provided %d for pattern `%s`",
232+
placeholderCount, argCount, pattern);
233+
}
173234
}

log4j-api/src/main/java/org/apache/logging/log4j/message/ParameterFormatter.java

+12-3
Original file line numberDiff line numberDiff line change
@@ -205,11 +205,12 @@ else if (placeholderCount >= placeholderCharIndices.length) {
205205
}
206206

207207
/**
208-
* Format the following pattern using provided arguments.
208+
* Format the given pattern using provided arguments.
209209
*
210210
* @param pattern a formatting pattern
211211
* @param args arguments to be formatted
212212
* @return the formatted message
213+
* @throws IllegalArgumentException on invalid input
213214
*/
214215
static String format(final String pattern, final Object[] args, int argCount) {
215216
final StringBuilder result = new StringBuilder();
@@ -218,6 +219,14 @@ static String format(final String pattern, final Object[] args, int argCount) {
218219
return result.toString();
219220
}
220221

222+
/**
223+
* Format the given pattern using provided arguments into the buffer pointed.
224+
*
225+
* @param buffer a buffer the formatted output will be written to
226+
* @param pattern a formatting pattern
227+
* @param args arguments to be formatted
228+
* @throws IllegalArgumentException on invalid input
229+
*/
221230
static void formatMessage(
222231
final StringBuilder buffer,
223232
final String pattern,
@@ -250,7 +259,7 @@ static void formatMessage(
250259
}
251260
}
252261

253-
static void formatMessageContainingNoEscapes(
262+
private static void formatMessageContainingNoEscapes(
254263
final StringBuilder buffer,
255264
final String pattern,
256265
final Object[] args,
@@ -271,7 +280,7 @@ static void formatMessageContainingNoEscapes(
271280
buffer.append(pattern, precedingTextStartIndex, pattern.length());
272281
}
273282

274-
static void formatMessageContainingEscapes(
283+
private static void formatMessageContainingEscapes(
275284
final StringBuilder buffer,
276285
final String pattern,
277286
final Object[] args,

log4j-api/src/main/java/org/apache/logging/log4j/message/ParameterizedMessage.java

+16-2
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,9 @@
2626
import java.io.Serializable;
2727
import java.util.Arrays;
2828
import java.util.Objects;
29+
import org.apache.logging.log4j.Logger;
2930
import org.apache.logging.log4j.message.ParameterFormatter.MessagePatternAnalysis;
31+
import org.apache.logging.log4j.status.StatusLogger;
3032
import org.apache.logging.log4j.util.Constants;
3133
import org.apache.logging.log4j.util.StringBuilderFormattable;
3234
import org.apache.logging.log4j.util.internal.SerializationUtil;
@@ -79,6 +81,8 @@ public class ParameterizedMessage implements Message, StringBuilderFormattable {
7981

8082
private static final long serialVersionUID = -665975803997290697L;
8183

84+
private static final Logger STATUS_LOGGER = StatusLogger.getLogger();
85+
8286
private static final ThreadLocal<FormatBufferHolder> FORMAT_BUFFER_HOLDER_REF =
8387
Constants.ENABLE_THREADLOCALS ? ThreadLocal.withInitial(FormatBufferHolder::new) : null;
8488

@@ -274,7 +278,12 @@ public void formatTo(final StringBuilder buffer) {
274278
buffer.append(formattedMessage);
275279
} else {
276280
final int argCount = args != null ? args.length : 0;
277-
ParameterFormatter.formatMessage(buffer, pattern, args, argCount, patternAnalysis);
281+
try {
282+
ParameterFormatter.formatMessage(buffer, pattern, args, argCount, patternAnalysis);
283+
} catch (final Exception error) {
284+
STATUS_LOGGER.error("Unable to format msg: {}", pattern, error);
285+
buffer.append(pattern);
286+
}
278287
}
279288
}
280289

@@ -285,7 +294,12 @@ public void formatTo(final StringBuilder buffer) {
285294
*/
286295
public static String format(final String pattern, final Object[] args) {
287296
final int argCount = args != null ? args.length : 0;
288-
return ParameterFormatter.format(pattern, args, argCount);
297+
try {
298+
return ParameterFormatter.format(pattern, args, argCount);
299+
} catch (final Exception error) {
300+
STATUS_LOGGER.error("Unable to format msg: {}", pattern, error);
301+
return pattern;
302+
}
289303
}
290304

291305
@Override
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="http://logging.apache.org/log4j/changelog"
4+
xsi:schemaLocation="http://logging.apache.org/log4j/changelog https://logging.apache.org/log4j/changelog-0.1.3.xsd"
5+
type="fixed">
6+
<issue id="2343" link="https://github.com/apache/logging-log4j2/pull/2343"/>
7+
<description format="asciidoc">Fix that parameterized message formatting doesn't throw an exception when there are insufficient number of parameters</description>
8+
</entry>

0 commit comments

Comments
 (0)