Skip to content

Commit a21fc4c

Browse files
[libc] Fix printf handling of INT_MIN width (#101729)
Prevously, if INT_MIN was passed as a wildcard width to a printf conversion the parser would attempt to negate it to get the positive width (and set the left justify flag), but it would underflow and the width would be treated as 0. This patch corrects the issue by instead treating a width of INT_MIN as identical to -INT_MAX. Also includes docs changes to explain this behavior and adding b to the list of int conversions.
1 parent 3e4af61 commit a21fc4c

File tree

4 files changed

+54
-5
lines changed

4 files changed

+54
-5
lines changed

libc/docs/dev/printf_behavior.rst

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,7 @@ If a conversion specification is provided an invalid type modifier, that type
160160
modifier will be ignored, and the default type for that conversion will be used.
161161
In the case of the length modifier "L" and integer conversions, it will be
162162
treated as if it was "ll" (lowercase LL). For this purpose the list of integer
163-
conversions is d, i, u, o, x, X, n.
163+
conversions is d, i, u, o, x, X, b, B, n.
164164

165165
If a conversion specification ending in % has any options that consume arguments
166166
(e.g. "%*.*%") those arguments will be consumed as normal, but their values will
@@ -169,9 +169,13 @@ be ignored.
169169
If a conversion specification ends in a null byte ('\0') then it shall be
170170
treated as an invalid conversion followed by a null byte.
171171

172-
If a number passed as a min width or precision value is out of range for an int,
173-
then it will be treated as the largest or smallest value in the int range
174-
(e.g. "%-999999999999.999999999999s" is the same as "%-2147483648.2147483647s").
172+
If a number passed as a field width or precision value is out of range for an
173+
int, then it will be treated as the largest value in the int range
174+
(e.g. "%-999999999999.999999999999s" is the same as "%-2147483647.2147483647s").
175+
176+
If the field width is set to INT_MIN by using the '*' form,
177+
e.g. printf("%*d", INT_MIN, 1), it will be treated as INT_MAX, since -INT_MIN is
178+
not representable as an int.
175179

176180
If a number passed as a bit width is less than or equal to zero, the conversion
177181
is considered invalid. If the provided bit width is larger than the width of

libc/src/stdio/printf_core/parser.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,8 @@ template <typename ArgProvider> class Parser {
129129
cur_pos = cur_pos + result.parsed_len;
130130
}
131131
if (section.min_width < 0) {
132-
section.min_width = -section.min_width;
132+
section.min_width =
133+
(section.min_width == INT_MIN) ? INT_MAX : -section.min_width;
133134
section.flags = static_cast<FormatFlags>(section.flags |
134135
FormatFlags::LEFT_JUSTIFIED);
135136
}

libc/test/src/stdio/printf_core/parser_test.cpp

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -316,6 +316,47 @@ TEST(LlvmLibcPrintfParserTest, EvalThreeArgs) {
316316
ASSERT_PFORMAT_EQ(expected2, format_arr[2]);
317317
}
318318

319+
TEST(LlvmLibcPrintfParserTest, EvalOneArgWithOverflowingWidthAndPrecision) {
320+
LIBC_NAMESPACE::printf_core::FormatSection format_arr[10];
321+
const char *str = "%-999999999999.999999999999d";
322+
int arg1 = 12345;
323+
evaluate(format_arr, str, arg1);
324+
325+
LIBC_NAMESPACE::printf_core::FormatSection expected;
326+
expected.has_conv = true;
327+
328+
expected.raw_string = {str, 28};
329+
expected.flags = LIBC_NAMESPACE::printf_core::FormatFlags::LEFT_JUSTIFIED;
330+
expected.min_width = INT_MAX;
331+
expected.precision = INT_MAX;
332+
expected.conv_val_raw = arg1;
333+
expected.conv_name = 'd';
334+
335+
ASSERT_PFORMAT_EQ(expected, format_arr[0]);
336+
}
337+
338+
TEST(LlvmLibcPrintfParserTest,
339+
EvalOneArgWithOverflowingWidthAndPrecisionAsArgs) {
340+
LIBC_NAMESPACE::printf_core::FormatSection format_arr[10];
341+
const char *str = "%*.*d";
342+
int arg1 = INT_MIN; // INT_MIN = -2147483648 if int is 32 bits.
343+
int arg2 = INT_MIN;
344+
int arg3 = 12345;
345+
evaluate(format_arr, str, arg1, arg2, arg3);
346+
347+
LIBC_NAMESPACE::printf_core::FormatSection expected;
348+
expected.has_conv = true;
349+
350+
expected.raw_string = {str, 5};
351+
expected.flags = LIBC_NAMESPACE::printf_core::FormatFlags::LEFT_JUSTIFIED;
352+
expected.min_width = INT_MAX;
353+
expected.precision = arg2;
354+
expected.conv_val_raw = arg3;
355+
expected.conv_name = 'd';
356+
357+
ASSERT_PFORMAT_EQ(expected, format_arr[0]);
358+
}
359+
319360
#ifndef LIBC_COPT_PRINTF_DISABLE_INDEX_MODE
320361

321362
TEST(LlvmLibcPrintfParserTest, IndexModeOneArg) {

libc/test/src/stdio/snprintf_test.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,9 @@ TEST(LlvmLibcSNPrintfTest, CutOff) {
4141
// passing null as the output pointer is allowed as long as buffsz is 0.
4242
written = LIBC_NAMESPACE::snprintf(nullptr, 0, "%s and more", "1234567890");
4343
EXPECT_EQ(written, 19);
44+
45+
written = LIBC_NAMESPACE::snprintf(nullptr, 0, "%*s", INT_MIN, "nothing");
46+
EXPECT_EQ(written, INT_MAX);
4447
}
4548

4649
TEST(LlvmLibcSNPrintfTest, NoCutOff) {

0 commit comments

Comments
 (0)