Previously we only checked the long double value of the provided double. This meant we didn't check any values near the edge of the long double range, which meant we were missing several bugs. This patch adds a second long double value to check which is generated separately from the double value and can be anywhere in the range.
149 lines
4.5 KiB
C++
149 lines
4.5 KiB
C++
//===-- 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 <stddef.h>
|
|
#include <stdint.h>
|
|
|
|
#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;
|
|
}
|
|
|
|
inline int simple_strlen(const char *str) {
|
|
int i = 0;
|
|
for (; *str; ++str, ++i) {
|
|
;
|
|
}
|
|
return i;
|
|
}
|
|
|
|
enum class TestResult {
|
|
Success,
|
|
BufferSizeFailed,
|
|
LengthsDiffer,
|
|
StringsNotEqual,
|
|
};
|
|
|
|
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);
|
|
|
|
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 = LIBC_NAMESPACE::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", "%*.*Lf", "%*.*Le", "%*.*Lg", "%*.*La",
|
|
};
|
|
|
|
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;
|
|
long double ld_num = 0.0L;
|
|
int prec = 0;
|
|
int width = 0;
|
|
|
|
LIBC_NAMESPACE::fputil::FPBits<double>::StorageType raw_num = 0;
|
|
LIBC_NAMESPACE::fputil::FPBits<long double>::StorageType ld_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];
|
|
} else if (cur < sizeof(raw_num) + sizeof(prec) + sizeof(width) +
|
|
sizeof(ld_raw_num)) {
|
|
ld_raw_num = (ld_raw_num << 8) + data[cur];
|
|
}
|
|
}
|
|
|
|
num = LIBC_NAMESPACE::fputil::FPBits<double>(raw_num).get_val();
|
|
ld_num = LIBC_NAMESPACE::fputil::FPBits<long double>(ld_raw_num).get_val();
|
|
|
|
// checking the same value in double and long double could help find
|
|
// mismatches. It also ensures long doubles are being tested even before the
|
|
// input data is long enough. Mostly this is here to match previous behavior
|
|
// where this was the only long double value checked.
|
|
long double num_as_ld = static_cast<long double>(num);
|
|
|
|
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) {
|
|
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);
|
|
result = test_vals<long double>(fmt_arr[cur_fmt], num_as_ld, prec, width);
|
|
} else {
|
|
result = test_vals<double>(fmt_arr[cur_fmt], num, prec, width);
|
|
}
|
|
if (result != TestResult::Success) {
|
|
__builtin_trap();
|
|
}
|
|
}
|
|
return 0;
|
|
}
|