diff --git a/libc/fuzzing/stdio/CMakeLists.txt b/libc/fuzzing/stdio/CMakeLists.txt --- a/libc/fuzzing/stdio/CMakeLists.txt +++ b/libc/fuzzing/stdio/CMakeLists.txt @@ -7,3 +7,13 @@ COMPILE_OPTIONS -DLIBC_COPT_MOCK_ARG_LIST ) + +add_libc_fuzzer( + printf_float_conv_fuzz + NEED_MPFR + SRCS + printf_float_conv_fuzz.cpp + DEPENDS + libc.src.stdio.snprintf + libc.src.__support.FPUtil.fp_bits +) diff --git a/libc/fuzzing/stdio/printf_float_conv_fuzz.cpp b/libc/fuzzing/stdio/printf_float_conv_fuzz.cpp new file mode 100644 --- /dev/null +++ b/libc/fuzzing/stdio/printf_float_conv_fuzz.cpp @@ -0,0 +1,123 @@ +//===-- printf_float_conv_fuzz.cpp ----------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +/// +/// Fuzzing test for llvm-libc printf %f/e/g/a implementations. +/// +//===----------------------------------------------------------------------===// +#include "src/stdio/snprintf.h" + +#include "src/__support/FPUtil/FPBits.h" + +#include +#include + +#include "utils/MPFRWrapper/mpfr_inc.h" + +constexpr int MAX_SIZE = 10000; + +inline bool simple_streq(char *first, char *second, int length) { + for (int i = 0; i < length; ++i) { + if (first[i] != second[i]) { + return false; + } + } + return true; +} + +enum class TestResult { + Success, + BufferSizeFailed, + LengthsDiffer, + StringsNotEqual, +}; + +inline TestResult test_vals(const char *fmt, double num, int prec, int width) { + // Call snprintf on a nullptr to get the buffer size. + int buffer_size = __llvm_libc::snprintf(nullptr, 0, fmt, width, prec, num); + + if (buffer_size < 0) { + return TestResult::BufferSizeFailed; + } + + char *test_buff = new char[buffer_size + 1]; + char *reference_buff = new char[buffer_size + 1]; + + int test_result = 0; + int reference_result = 0; + + test_result = + __llvm_libc::snprintf(test_buff, buffer_size + 1, fmt, width, prec, num); + reference_result = + mpfr_snprintf(reference_buff, buffer_size + 1, fmt, width, prec, num); + + // All of these calls should return that they wrote the same amount. + if (test_result != reference_result || test_result != buffer_size) { + return TestResult::LengthsDiffer; + } + + if (!simple_streq(test_buff, reference_buff, buffer_size)) { + return TestResult::StringsNotEqual; + } + + delete[] test_buff; + delete[] reference_buff; + return TestResult::Success; +} + +constexpr char const *fmt_arr[] = { + "%*.*f", + "%*.*e", + "%*.*g", + "%*.*a", +}; + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { + // const uint8_t raw_data[] = {0x30,0x27,0x1,0x0,0x0,0x0,0x0,0x0,0x24}; + // data = raw_data; + // size = sizeof(raw_data); + double num = 0.0; + int prec = 0; + int width = 0; + + __llvm_libc::fputil::FPBits::UIntType raw_num = 0; + + // Copy as many bytes of data as will fit into num, prec, and with. Any extras + // are ignored. + for (size_t cur = 0; cur < size; ++cur) { + if (cur < sizeof(raw_num)) { + raw_num = (raw_num << 8) + data[cur]; + } else if (cur < sizeof(raw_num) + sizeof(prec)) { + prec = (prec << 8) + data[cur]; + } else if (cur < sizeof(raw_num) + sizeof(prec) + sizeof(width)) { + width = (width << 8) + data[cur]; + } + } + + num = __llvm_libc::fputil::FPBits(raw_num).get_val(); + + if (width > MAX_SIZE) { + width = MAX_SIZE; + } else if (width < -MAX_SIZE) { + width = -MAX_SIZE; + } + + if (prec > MAX_SIZE) { + prec = MAX_SIZE; + } else if (prec < -MAX_SIZE) { + prec = -MAX_SIZE; + } + + for (size_t cur_fmt = 0; cur_fmt < sizeof(fmt_arr) / sizeof(char *); + ++cur_fmt) { + TestResult result = test_vals(fmt_arr[cur_fmt], num, prec, width); + if (result != TestResult::Success) { + __builtin_trap(); + } + } + return 0; +} diff --git a/libc/src/stdio/printf_core/float_dec_converter.h b/libc/src/stdio/printf_core/float_dec_converter.h --- a/libc/src/stdio/printf_core/float_dec_converter.h +++ b/libc/src/stdio/printf_core/float_dec_converter.h @@ -124,7 +124,9 @@ Writer *writer; // Writes to the final output. PaddingWriter padding_writer; // Handles prefixes/padding, uses total_digits. - int flush_buffer() { + int flush_buffer(bool round_up_max_blocks = false) { + const char MAX_BLOCK_DIGIT = (round_up_max_blocks ? '0' : '9'); + // Write the most recent buffered block, and mark has_written if (!has_written) { has_written = true; @@ -171,12 +173,12 @@ has_decimal_point) { size_t digits_to_write = digits_before_decimal - total_digits_written; if (digits_to_write > 0) { - RET_IF_RESULT_NEGATIVE(writer->write('9', digits_to_write)); + RET_IF_RESULT_NEGATIVE(writer->write(MAX_BLOCK_DIGIT, digits_to_write)); } RET_IF_RESULT_NEGATIVE(writer->write(DECIMAL_POINT)); if ((BLOCK_SIZE * max_block_count) - digits_to_write > 0) { RET_IF_RESULT_NEGATIVE(writer->write( - '9', (BLOCK_SIZE * max_block_count) - digits_to_write)); + MAX_BLOCK_DIGIT, (BLOCK_SIZE * max_block_count) - digits_to_write)); } // add 1 for the decimal point total_digits_written += BLOCK_SIZE * max_block_count + 1; @@ -186,7 +188,8 @@ // Clear the buffer of max blocks if (max_block_count > 0) { - RET_IF_RESULT_NEGATIVE(writer->write('9', max_block_count * BLOCK_SIZE)); + RET_IF_RESULT_NEGATIVE( + writer->write(MAX_BLOCK_DIGIT, max_block_count * BLOCK_SIZE)); total_digits_written += max_block_count * BLOCK_SIZE; max_block_count = 0; } @@ -294,19 +297,23 @@ end_buff[count] = int_to_str[count + 1 + (BLOCK_SIZE - block_digits)]; } - char low_digit; + char low_digit = '0'; if (block_digits > 0) { low_digit = end_buff[block_digits - 1]; } else if (max_block_count > 0) { low_digit = '9'; - } else { + } else if (buffered_digits > 0) { low_digit = block_buffer[buffered_digits - 1]; } + bool round_up_max_blocks = false; + // Round up if (round == RoundDirection::Up || (round == RoundDirection::Even && low_digit % 2 != 0)) { bool has_carry = true; + round_up_max_blocks = true; // if we're rounding up, we might need to + // round up the max blocks that are buffered. // handle the low block that we're adding for (int count = static_cast(block_digits) - 1; count >= 0 && has_carry; --count) { @@ -315,6 +322,8 @@ } else { end_buff[count] += 1; has_carry = false; + round_up_max_blocks = false; // If the low block isn't all nines, then + // the max blocks aren't rounded up. } } // handle the high block that's buffered @@ -359,7 +368,7 @@ // Either we intend to round down, or the rounding up is complete. Flush the // buffers. - RET_IF_RESULT_NEGATIVE(flush_buffer()); + RET_IF_RESULT_NEGATIVE(flush_buffer(round_up_max_blocks)); // And then write the final block. RET_IF_RESULT_NEGATIVE(writer->write({end_buff, block_digits})); @@ -383,19 +392,24 @@ } } - char low_digit; + char low_digit = '0'; if (block_digits > 0) { low_digit = end_buff[block_digits - 1]; } else if (max_block_count > 0) { low_digit = '9'; - } else { + } else if (buffered_digits > 0) { low_digit = block_buffer[buffered_digits - 1]; } + bool round_up_max_blocks = false; + // Round up if (round == RoundDirection::Up || (round == RoundDirection::Even && low_digit % 2 != 0)) { bool has_carry = true; + round_up_max_blocks = true; // if we're rounding up, we might need to + // round up the max blocks that are buffered. + // handle the low block that we're adding for (int count = static_cast(block_digits) - 1; count >= 0 && has_carry; --count) { @@ -404,6 +418,8 @@ } else { end_buff[count] += 1; has_carry = false; + round_up_max_blocks = false; // If the low block isn't all nines, then + // the max blocks aren't rounded up. } } // handle the high block that's buffered @@ -467,7 +483,7 @@ // Either we intend to round down, or the rounding up is complete. Flush the // buffers. - RET_IF_RESULT_NEGATIVE(flush_buffer()); + RET_IF_RESULT_NEGATIVE(flush_buffer(round_up_max_blocks)); // And then write the final block. It's written via the buffer so that if // this is also the first block, the decimal point will be placed correctly. @@ -758,10 +774,17 @@ last_block_size = int_to_str.size(); } + // This tracks if the number is truncated, that meaning that the digits after + // last_digit are non-zero. + bool truncated = false; + // This is the last block. const size_t maximum = precision + 1 - digits_written; uint32_t last_digit = 0; for (uint32_t k = 0; k < last_block_size - maximum; ++k) { + if (last_digit > 0) + truncated = true; + last_digit = digits % 10; digits /= 10; } @@ -771,35 +794,64 @@ if (maximum == last_block_size) { BlockInt extra_block = float_converter.get_block(cur_block - 1); last_digit = extra_block / ((MAX_BLOCK / 10) + 1); + if (extra_block % ((MAX_BLOCK / 10) + 1) > 0) { + truncated = true; + } } RoundDirection round; - // Is m * 10^(additionalDigits + 1) / 2^(-exponent) integer? - const int32_t requiredTwos = - -(exponent - MANT_WIDTH) - static_cast(precision) - 1; - const bool trailingZeros = - requiredTwos <= 0 || - (requiredTwos < 60 && - multiple_of_power_of_2(float_bits.get_explicit_mantissa(), - static_cast(requiredTwos))); + + // If we've already seen a truncated digit, then we don't need to check any + // more. + if (!truncated) { + // If the number is entirely above the decimal point + if (exponent - MANT_WIDTH >= 0) { + // Check every block until the decimal point for non-zero digits. + for (int cur_extra_block = cur_block - 1; cur_extra_block >= 0; + --cur_extra_block) { + BlockInt extra_block = float_converter.get_block(cur_extra_block); + if (extra_block > 0) { + truncated = true; + break; + } + } + } else { + // For all other numbers, use the formula from %f. + + // Is m * 10^(additionalDigits + 1) / 2^(-exponent) integer? + // We need to add positive_exponent, which is the final, positive, base 10 + // exponent. This is because the formula is set up for %f, where the + // precision is just the number of digits after the decimal point we + // print. + const int32_t requiredTwos = + -(exponent - MANT_WIDTH) - + static_cast(precision + positive_exponent) - 1; + const bool trailingZeros = + requiredTwos <= 0 || + (requiredTwos < 60 && + multiple_of_power_of_2(float_bits.get_explicit_mantissa(), + static_cast(requiredTwos))); + truncated = !trailingZeros; + } + } switch (fputil::quick_get_round()) { case FE_TONEAREST: // Round to nearest, if it's exactly halfway then round to even. if (last_digit != 5) { round = last_digit > 5 ? RoundDirection::Up : RoundDirection::Down; } else { - round = trailingZeros ? RoundDirection::Even : RoundDirection::Up; + round = !truncated ? RoundDirection::Even : RoundDirection::Up; } break; case FE_DOWNWARD: - if (is_negative && (!trailingZeros || last_digit > 0)) { + if (is_negative && (truncated || last_digit > 0)) { round = RoundDirection::Up; } else { round = RoundDirection::Down; } break; case FE_UPWARD: - if (!is_negative && (!trailingZeros || last_digit > 0)) { + if (!is_negative && (truncated || last_digit > 0)) { round = RoundDirection::Up; } else { round = RoundDirection::Down; @@ -1009,12 +1061,17 @@ } } + bool truncated = false; + // Find the digit after the lowest digit that we'll actually print to // determine the rounding. const uint32_t maximum = exp_precision + 1 - static_cast(digits_checked); uint32_t last_digit = 0; for (uint32_t k = 0; k < last_block_size - maximum; ++k) { + if (last_digit > 0) + truncated = true; + last_digit = digits % 10; digits /= 10; } @@ -1024,37 +1081,63 @@ if (maximum == last_block_size) { BlockInt extra_block = float_converter.get_block(cur_block - 1); last_digit = extra_block / ((MAX_BLOCK / 10) + 1); + + if (extra_block % ((MAX_BLOCK / 10) + 1) > 0) + truncated = true; } // TODO: unify this code across the three float conversions. RoundDirection round; - // Is m * 10^(additionalDigits + 1) / 2^(-exponent) integer? - const int32_t requiredTwos = - -(exponent - MANT_WIDTH) - static_cast(exp_precision) - 1; - // TODO: rename this variable to remove confusion with trailing_zeroes - const bool trailingZeros = - requiredTwos <= 0 || - (requiredTwos < 60 && - multiple_of_power_of_2(float_bits.get_explicit_mantissa(), - static_cast(requiredTwos))); + + // If we've already seen a truncated digit, then we don't need to check any + // more. + if (!truncated) { + // If the number is entirely above the decimal point + if (exponent - MANT_WIDTH >= 0) { + // Check every block until the decimal point for non-zero digits. + for (int cur_extra_block = cur_block - 1; cur_extra_block >= 0; + --cur_extra_block) { + BlockInt extra_block = float_converter.get_block(cur_extra_block); + if (extra_block > 0) { + truncated = true; + break; + } + } + } else { + // For all other numbers, use the formula from %f. + + // Is m * 10^(additionalDigits + 1) / 2^(-exponent) integer? + const int32_t requiredTwos = + -(exponent - MANT_WIDTH) - + static_cast(exp_precision - base_10_exp) - 1; + // TODO: rename this variable + const bool trailingZeros = + requiredTwos <= 0 || + (requiredTwos < 60 && + multiple_of_power_of_2(float_bits.get_explicit_mantissa(), + static_cast(requiredTwos))); + truncated = !trailingZeros; + } + } + switch (fputil::quick_get_round()) { case FE_TONEAREST: // Round to nearest, if it's exactly halfway then round to even. if (last_digit != 5) { round = last_digit > 5 ? RoundDirection::Up : RoundDirection::Down; } else { - round = trailingZeros ? RoundDirection::Even : RoundDirection::Up; + round = !truncated ? RoundDirection::Even : RoundDirection::Up; } break; case FE_DOWNWARD: - if (is_negative && (!trailingZeros || last_digit > 0)) { + if (is_negative && (truncated || last_digit > 0)) { round = RoundDirection::Up; } else { round = RoundDirection::Down; } break; case FE_UPWARD: - if (!is_negative && (!trailingZeros || last_digit > 0)) { + if (!is_negative && (truncated || last_digit > 0)) { round = RoundDirection::Up; } else { round = RoundDirection::Down; @@ -1072,8 +1155,32 @@ } else if (round == RoundDirection::Down) { round_up = false; } else { - // RoundDirection is even, so check the extra digit. - uint32_t low_digit = digits % 10; + // RoundDirection is even, so check the lowest digit that will be printed. + uint32_t low_digit; + + // maximum is the number of digits that will remain in digits after getting + // last_digit. If it's greater than zero, we can just check the lowest digit + // in digits. + if (maximum > 0) { + low_digit = digits % 10; + } else { + // Else if there are trailing nines, then the low digit is a nine, same + // with zeroes. + if (trailing_nines > 0) { + low_digit = 9; + } else if (trailing_zeroes > 0) { + low_digit = 0; + } else { + // If there are no trailing zeroes or nines, then the round direction + // doesn't actually matter here. Since this conversion passes off the + // value to another one for final conversion, rounding only matters to + // determine if the exponent is higher than expected (with an all nine + // number) or to determine the trailing zeroes to trim. In this case + // low_digit is set to 0, but it could be set to any number. + + low_digit = 0; + } + } round_up = (low_digit % 2) != 0; } diff --git a/libc/src/stdio/printf_core/float_hex_converter.h b/libc/src/stdio/printf_core/float_hex_converter.h --- a/libc/src/stdio/printf_core/float_hex_converter.h +++ b/libc/src/stdio/printf_core/float_hex_converter.h @@ -215,7 +215,7 @@ constexpr int EXP_SEPERATOR_LEN = 1; padding = static_cast(to_conv.min_width - (sign_char > 0 ? 1 : 0) - - PREFIX_LEN - mant_digits - + PREFIX_LEN - mant_digits - trailing_zeroes - static_cast(has_hexadecimal_point) - EXP_SEPERATOR_LEN - (EXP_LEN - exp_cur)); if (padding < 0) diff --git a/libc/test/src/stdio/sprintf_test.cpp b/libc/test/src/stdio/sprintf_test.cpp --- a/libc/test/src/stdio/sprintf_test.cpp +++ b/libc/test/src/stdio/sprintf_test.cpp @@ -859,6 +859,15 @@ written = __llvm_libc::sprintf(buff, "%+-#12.3a % 012.3a", 0.1256, 1256.0); ASSERT_STREQ_LEN(written, buff, "+0x1.014p-3 0x1.3a0p+10"); + + written = __llvm_libc::sprintf(buff, "%50.50a", 0x1.0p0); + ASSERT_STREQ_LEN(written, buff, + "0x1.00000000000000000000000000000000000000000000000000p+0"); + + written = __llvm_libc::sprintf(buff, "%58.50a", 0x1.0p0); + ASSERT_STREQ_LEN( + written, buff, + " 0x1.00000000000000000000000000000000000000000000000000p+0"); } TEST_F(LlvmLibcSPrintfTest, FloatDecimalConv) { @@ -1254,6 +1263,23 @@ written = __llvm_libc::sprintf(buff, "%.5f", 1.008e3); ASSERT_STREQ_LEN(written, buff, "1008.00000"); + // Found via fuzzing + + written = __llvm_libc::sprintf(buff, "%.9f", 1.9999999999999514); + ASSERT_STREQ_LEN(written, buff, "2.000000000"); + + written = __llvm_libc::sprintf(buff, "%.238f", 1.131959884853339E-72); + ASSERT_STREQ_LEN( + written, buff, + "0." + "000000000000000000000000000000000000000000000000000000000000000000000001" + "131959884853339045938639911360973972585316399767392273697826861241937664" + "824105639342441431495119762431744054912109728706985341609159156917030486" + "5110665559768676757812"); + + written = __llvm_libc::sprintf(buff, "%.36f", 9.9e-77); + ASSERT_STREQ_LEN(written, buff, "0.000000000000000000000000000000000000"); + // Found with the help of Fred Tydeman's tbin2dec test. written = __llvm_libc::sprintf(buff, "%.1f", 0x1.1000000000006p+3); ASSERT_STREQ_LEN(written, buff, "8.5"); @@ -1837,8 +1863,31 @@ written = __llvm_libc::sprintf(buff, "%.5e", 1.008e3); ASSERT_STREQ_LEN(written, buff, "1.00800e+03"); - // Subnormal Precision Tests + // Found Via Fuzzing + written = __llvm_libc::sprintf(buff, "%.0e", 2.5812229360061737E+200); + ASSERT_STREQ_LEN(written, buff, "3e+200"); + + written = __llvm_libc::sprintf(buff, "%.1e", 9.059E+200); + ASSERT_STREQ_LEN(written, buff, "9.1e+200"); + + written = __llvm_libc::sprintf(buff, "%.0e", 9.059E+200); + ASSERT_STREQ_LEN(written, buff, "9e+200"); + + written = __llvm_libc::sprintf(buff, "%.166e", 1.131959884853339E-72); + ASSERT_STREQ_LEN(written, buff, + "1." + "13195988485333904593863991136097397258531639976739227369782" + "68612419376648241056393424414314951197624317440549121097287" + "069853416091591569170304865110665559768676757812e-72"); + + written = __llvm_libc::sprintf(buff, "%.0e", 9.5); + ASSERT_STREQ_LEN(written, buff, "1e+01"); + + written = __llvm_libc::sprintf(buff, "%.10e", 1.9999999999890936); + ASSERT_STREQ_LEN(written, buff, "2.0000000000e+00"); + + // Subnormal Precision Tests written = __llvm_libc::sprintf(buff, "%.310e", 0x1.0p-1022); ASSERT_STREQ_LEN( written, buff, @@ -2441,8 +2490,17 @@ written = __llvm_libc::sprintf(buff, "%.15g", 22.25); ASSERT_STREQ_LEN(written, buff, "22.25"); - // Subnormal Precision Tests + // Found via fuzzing + written = __llvm_libc::sprintf(buff, "%.1g", 9.059E+200); + ASSERT_STREQ_LEN(written, buff, "9e+200"); + written = __llvm_libc::sprintf(buff, "%.2g", 9.059E+200); + ASSERT_STREQ_LEN(written, buff, "9.1e+200"); + + written = __llvm_libc::sprintf(buff, "%.0g", 9.5); + ASSERT_STREQ_LEN(written, buff, "1e+01"); + + // Subnormal Precision Tests written = __llvm_libc::sprintf(buff, "%.310g", 0x1.0p-1022); ASSERT_STREQ_LEN( written, buff,