diff --git a/libc/src/stdio/printf_core/parser.cpp b/libc/src/stdio/printf_core/parser.cpp --- a/libc/src/stdio/printf_core/parser.cpp +++ b/libc/src/stdio/printf_core/parser.cpp @@ -41,8 +41,9 @@ auto temp = get_arg_value(index); \ if (!temp.has_value()) { \ section.has_conv = false; \ + } else { \ + dst = cpp::bit_cast>(temp.value()); \ } \ - dst = cpp::bit_cast>(temp.value()); \ } #else #define WRITE_ARG_VAL_SIMPLEST(dst, arg_type, _) \ @@ -106,6 +107,13 @@ section.conv_name = str[cur_pos]; switch (str[cur_pos]) { case ('%'): + // Regardless of options, a % conversion is always safe. The standard says + // that "The complete conversion specification shall be %%" but it also + // says that "If a conversion specification is invalid, the behavior is + // undefined." Based on that we define that any conversion specification + // ending in '%' shall display as '%' regardless of any valid or invalid + // options. + section.has_conv = true; break; case ('c'): WRITE_ARG_VAL_SIMPLEST(section.conv_val_raw, int, conv_index); diff --git a/libc/test/src/stdio/printf_core/parser_test.cpp b/libc/test/src/stdio/printf_core/parser_test.cpp --- a/libc/test/src/stdio/printf_core/parser_test.cpp +++ b/libc/test/src/stdio/printf_core/parser_test.cpp @@ -523,4 +523,29 @@ EXPECT_PFORMAT_EQ(expected1, format_arr[1]); } +TEST(LlvmLibcPrintfParserTest, DoublePercentIsAllowedInvalidIndex) { + __llvm_libc::printf_core::FormatSection format_arr[10]; + + // Normally this conversion specifier would be raw (due to having a width + // defined as an invalid argument) but since it's a % conversion it's allowed + // by this specific parser. Any % conversion that is not just "%%" is + // undefined, so this is implementation-specific behavior. + // The goal is to be consistent. A conversion specifier of "%L%" is also + // undefined, but is traditionally displayed as just "%". "%2%" is also + // displayed as "%", even though if the width was respected it should be " %". + // Finally, "%*%" traditionally is displayed as "%" but also traditionally + // consumes an argument, since the * consumes an integer. Therefore, having + // "%*2$%" display as "%" is consistent with other printf behavior. + const char *str = "%*2$%"; + + evaluate(format_arr, str, 1, 2); + + __llvm_libc::printf_core::FormatSection expected0; + expected0.has_conv = true; + + expected0.raw_string = str; + expected0.conv_name = '%'; + EXPECT_PFORMAT_EQ(expected0, format_arr[0]); +} + #endif // LIBC_COPT_PRINTF_DISABLE_INDEX_MODE