Skip to content

Commit

Permalink
[libc] Add simple long double to printf float fuzz (#68449)
Browse files Browse the repository at this point in the history
Recent testing has uncovered some hard-to-find bugs in printf's long
double support. This patch adds an extra long double path to the fuzzer
with minimal extra effort. While a more thorough long double fuzzer
would be useful, it would need to handle the non-standard cases of 80
bit long doubles such as unnormal and pseudo-denormal numbers. For that
reason, a standalone long double fuzzer is left for future development.
  • Loading branch information
michaelrj-google committed Oct 16, 2023
1 parent 119b0f3 commit 8a47ad4
Show file tree
Hide file tree
Showing 3 changed files with 30 additions and 8 deletions.
30 changes: 24 additions & 6 deletions libc/fuzzing/stdio/printf_float_conv_fuzz.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,23 @@ inline bool simple_streq(char *first, char *second, int length) {
return true;
}

inline int simple_strlen(const char *str) {
int i = 0;
for (; *str; ++str, ++i) {
;
}
return i;
}

enum class TestResult {
Success,
BufferSizeFailed,
LengthsDiffer,
StringsNotEqual,
};

inline TestResult test_vals(const char *fmt, double num, int prec, int width) {
template <typename F>
inline TestResult test_vals(const char *fmt, F num, int prec, int width) {
// Call snprintf on a nullptr to get the buffer size.
int buffer_size = LIBC_NAMESPACE::snprintf(nullptr, 0, fmt, width, prec, num);

Expand Down Expand Up @@ -70,10 +79,7 @@ inline TestResult test_vals(const char *fmt, double num, int prec, int width) {
}

constexpr char const *fmt_arr[] = {
"%*.*f",
"%*.*e",
"%*.*g",
"%*.*a",
"%*.*f", "%*.*e", "%*.*g", "%*.*a", "%*.*Lf", "%*.*Le", "%*.*Lg", "%*.*La",
};

extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
Expand All @@ -100,6 +106,12 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {

num = LIBC_NAMESPACE::fputil::FPBits<double>(raw_num).get_val();

// While we could create a "ld_raw_num" from additional bytes, it's much
// easier to stick with simply casting num to long double. This avoids the
// issues around 80 bit long doubles, especially unnormal and pseudo-denormal
// numbers, which MPFR doesn't handle well.
long double ld_num = static_cast<long double>(num);

if (width > MAX_SIZE) {
width = MAX_SIZE;
} else if (width < -MAX_SIZE) {
Expand All @@ -114,7 +126,13 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t 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);
int fmt_len = simple_strlen(fmt_arr[cur_fmt]);
TestResult result;
if (fmt_arr[cur_fmt][fmt_len - 2] == 'L') {
result = test_vals<long double>(fmt_arr[cur_fmt], ld_num, prec, width);
} else {
result = test_vals<double>(fmt_arr[cur_fmt], num, prec, width);
}
if (result != TestResult::Success) {
__builtin_trap();
}
Expand Down
5 changes: 3 additions & 2 deletions libc/src/stdio/printf_core/float_hex_converter.h
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,9 @@ LIBC_INLINE int convert_float_hex_exp(Writer *writer,

// This is to handle situations where the mantissa isn't an even number of hex
// digits. This is primarily relevant for x86 80 bit long doubles, which have
// 63 bit mantissas.
if (mantissa_width % BITS_IN_HEX_DIGIT != 0) {
// 63 bit mantissas. In the case where the mantissa is 0, however, the
// exponent should stay as 0.
if (mantissa_width % BITS_IN_HEX_DIGIT != 0 && mantissa > 0) {
exponent -= mantissa_width % BITS_IN_HEX_DIGIT;
}

Expand Down
3 changes: 3 additions & 0 deletions libc/test/src/stdio/sprintf_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -748,6 +748,9 @@ TEST_F(LlvmLibcSPrintfTest, FloatHexExpConv) {
written = LIBC_NAMESPACE::sprintf(buff, "%.5a", nan);
ASSERT_STREQ_LEN(written, buff, "nan");

written = LIBC_NAMESPACE::sprintf(buff, "%La", 0.0L);
ASSERT_STREQ_LEN(written, buff, "0x0p+0");

written = LIBC_NAMESPACE::sprintf(buff, "%.1La", 0.1L);
#if defined(SPECIAL_X86_LONG_DOUBLE)
ASSERT_STREQ_LEN(written, buff, "0xc.dp-7");
Expand Down

0 comments on commit 8a47ad4

Please sign in to comment.