Skip to content

Commit 8f0814f

Browse files
[libc] Clarify printf percent conversion behavior.
Almost all printf conversions ending in '%' are undefined, but they're traditionally treated as if the complete conversion specifier is "%%". This patch modifies the parser to more closely match that behavior. Reviewed By: sivachandra Differential Revision: https://reviews.llvm.org/D144679
1 parent e981e6d commit 8f0814f

File tree

2 files changed

+34
-1
lines changed

2 files changed

+34
-1
lines changed

libc/src/stdio/printf_core/parser.cpp

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,9 @@ template <typename T> using int_type_of_v = typename int_type_of<T>::type;
4141
auto temp = get_arg_value<arg_type>(index); \
4242
if (!temp.has_value()) { \
4343
section.has_conv = false; \
44+
} else { \
45+
dst = cpp::bit_cast<int_type_of_v<arg_type>>(temp.value()); \
4446
} \
45-
dst = cpp::bit_cast<int_type_of_v<arg_type>>(temp.value()); \
4647
}
4748
#else
4849
#define WRITE_ARG_VAL_SIMPLEST(dst, arg_type, _) \
@@ -106,6 +107,13 @@ FormatSection Parser::get_next_section() {
106107
section.conv_name = str[cur_pos];
107108
switch (str[cur_pos]) {
108109
case ('%'):
110+
// Regardless of options, a % conversion is always safe. The standard says
111+
// that "The complete conversion specification shall be %%" but it also
112+
// says that "If a conversion specification is invalid, the behavior is
113+
// undefined." Based on that we define that any conversion specification
114+
// ending in '%' shall display as '%' regardless of any valid or invalid
115+
// options.
116+
section.has_conv = true;
109117
break;
110118
case ('c'):
111119
WRITE_ARG_VAL_SIMPLEST(section.conv_val_raw, int, conv_index);

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

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -523,4 +523,29 @@ TEST(LlvmLibcPrintfParserTest, IndexModeTrailingPercentCrash) {
523523
EXPECT_PFORMAT_EQ(expected1, format_arr[1]);
524524
}
525525

526+
TEST(LlvmLibcPrintfParserTest, DoublePercentIsAllowedInvalidIndex) {
527+
__llvm_libc::printf_core::FormatSection format_arr[10];
528+
529+
// Normally this conversion specifier would be raw (due to having a width
530+
// defined as an invalid argument) but since it's a % conversion it's allowed
531+
// by this specific parser. Any % conversion that is not just "%%" is
532+
// undefined, so this is implementation-specific behavior.
533+
// The goal is to be consistent. A conversion specifier of "%L%" is also
534+
// undefined, but is traditionally displayed as just "%". "%2%" is also
535+
// displayed as "%", even though if the width was respected it should be " %".
536+
// Finally, "%*%" traditionally is displayed as "%" but also traditionally
537+
// consumes an argument, since the * consumes an integer. Therefore, having
538+
// "%*2$%" display as "%" is consistent with other printf behavior.
539+
const char *str = "%*2$%";
540+
541+
evaluate(format_arr, str, 1, 2);
542+
543+
__llvm_libc::printf_core::FormatSection expected0;
544+
expected0.has_conv = true;
545+
546+
expected0.raw_string = str;
547+
expected0.conv_name = '%';
548+
EXPECT_PFORMAT_EQ(expected0, format_arr[0]);
549+
}
550+
526551
#endif // LIBC_COPT_PRINTF_DISABLE_INDEX_MODE

0 commit comments

Comments
 (0)