//===-- 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; } inline int simple_strlen(const char *str) { int i = 0; for (; *str; ++str, ++i) { ; } return i; } enum class TestResult { Success, BufferSizeFailed, LengthsDiffer, StringsNotEqual, }; template 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; int prec = 0; int width = 0; LIBC_NAMESPACE::fputil::FPBits::StorageType 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 = LIBC_NAMESPACE::fputil::FPBits(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(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(fmt_arr[cur_fmt], ld_num, prec, width); } else { result = test_vals(fmt_arr[cur_fmt], num, prec, width); } if (result != TestResult::Success) { __builtin_trap(); } } return 0; }