Skip to content

Commit 8a47ad4

Browse files
[libc] Add simple long double to printf float fuzz (#68449)
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.
1 parent 119b0f3 commit 8a47ad4

File tree

3 files changed

+30
-8
lines changed

3 files changed

+30
-8
lines changed

libc/fuzzing/stdio/printf_float_conv_fuzz.cpp

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,14 +29,23 @@ inline bool simple_streq(char *first, char *second, int length) {
2929
return true;
3030
}
3131

32+
inline int simple_strlen(const char *str) {
33+
int i = 0;
34+
for (; *str; ++str, ++i) {
35+
;
36+
}
37+
return i;
38+
}
39+
3240
enum class TestResult {
3341
Success,
3442
BufferSizeFailed,
3543
LengthsDiffer,
3644
StringsNotEqual,
3745
};
3846

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

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

7281
constexpr char const *fmt_arr[] = {
73-
"%*.*f",
74-
"%*.*e",
75-
"%*.*g",
76-
"%*.*a",
82+
"%*.*f", "%*.*e", "%*.*g", "%*.*a", "%*.*Lf", "%*.*Le", "%*.*Lg", "%*.*La",
7783
};
7884

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

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

109+
// While we could create a "ld_raw_num" from additional bytes, it's much
110+
// easier to stick with simply casting num to long double. This avoids the
111+
// issues around 80 bit long doubles, especially unnormal and pseudo-denormal
112+
// numbers, which MPFR doesn't handle well.
113+
long double ld_num = static_cast<long double>(num);
114+
103115
if (width > MAX_SIZE) {
104116
width = MAX_SIZE;
105117
} else if (width < -MAX_SIZE) {
@@ -114,7 +126,13 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
114126

115127
for (size_t cur_fmt = 0; cur_fmt < sizeof(fmt_arr) / sizeof(char *);
116128
++cur_fmt) {
117-
TestResult result = test_vals(fmt_arr[cur_fmt], num, prec, width);
129+
int fmt_len = simple_strlen(fmt_arr[cur_fmt]);
130+
TestResult result;
131+
if (fmt_arr[cur_fmt][fmt_len - 2] == 'L') {
132+
result = test_vals<long double>(fmt_arr[cur_fmt], ld_num, prec, width);
133+
} else {
134+
result = test_vals<double>(fmt_arr[cur_fmt], num, prec, width);
135+
}
118136
if (result != TestResult::Success) {
119137
__builtin_trap();
120138
}

libc/src/stdio/printf_core/float_hex_converter.h

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,8 +75,9 @@ LIBC_INLINE int convert_float_hex_exp(Writer *writer,
7575

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

libc/test/src/stdio/sprintf_test.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -748,6 +748,9 @@ TEST_F(LlvmLibcSPrintfTest, FloatHexExpConv) {
748748
written = LIBC_NAMESPACE::sprintf(buff, "%.5a", nan);
749749
ASSERT_STREQ_LEN(written, buff, "nan");
750750

751+
written = LIBC_NAMESPACE::sprintf(buff, "%La", 0.0L);
752+
ASSERT_STREQ_LEN(written, buff, "0x0p+0");
753+
751754
written = LIBC_NAMESPACE::sprintf(buff, "%.1La", 0.1L);
752755
#if defined(SPECIAL_X86_LONG_DOUBLE)
753756
ASSERT_STREQ_LEN(written, buff, "0xc.dp-7");

0 commit comments

Comments
 (0)