Skip to content

Commit

Permalink
[libc] fix MPFR rounding problems in fuzz test
Browse files Browse the repository at this point in the history
The accuracy for the MPFR numbers in the strtofloat fuzz test was set
too high, causing rounding issues when rounding to a smaller final
result.

Reviewed By: lntue

Differential Revision: https://reviews.llvm.org/D154150
  • Loading branch information
michaelrj-google committed Jul 5, 2023
1 parent 8910cc2 commit cfbcbc8
Show file tree
Hide file tree
Showing 6 changed files with 58 additions and 32 deletions.
1 change: 1 addition & 0 deletions libc/fuzzing/stdlib/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ add_libc_fuzzer(
libc.src.stdlib.strtof
libc.src.stdlib.strtod
libc.src.stdlib.strtold
libc.src.__support.FPUtil.float_properties
)

add_libc_fuzzer(
Expand Down
54 changes: 47 additions & 7 deletions libc/fuzzing/stdlib/strtofloat_fuzz.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,16 @@
#include "src/stdlib/strtof.h"
#include "src/stdlib/strtold.h"

#include "src/__support/FPUtil/FloatProperties.h"

#include <math.h>
#include <stddef.h>
#include <stdint.h>

#include "utils/MPFRWrapper/mpfr_inc.h"

using __llvm_libc::fputil::FloatProperties;

extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
uint8_t *container = new uint8_t[size + 1];
if (!container)
Expand All @@ -40,6 +44,9 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {

char *out_ptr = nullptr;

size_t base = 0;

// This is just used to determine the base.
mpfr_t result;
mpfr_init2(result, 256);
mpfr_t bin_result;
Expand All @@ -59,8 +66,43 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
bin_result_ld == result_ld) {
mpfr_strtofr(result, str_ptr, &out_ptr, 10 /* base */, MPFR_RNDN);
result_strlen = out_ptr - str_ptr;
base = 10;
}

mpfr_clear(result);
mpfr_clear(bin_result);

// These must be calculated with the correct precision, and not any more, to
// prevent numbers like 66336650.00...01 (many zeroes) from causing an issue.
// 66336650 is exactly between two float values (66336652 and 66336648) so the
// correct float result for 66336650.00...01 is rounding up to 66336652. The
// correct double is instead 66336650, which when converted to float is
// rounded down to 66336648. This means we have to compare against the correct
// precision to get the correct result.
mpfr_t mpfr_float;
mpfr_init2(mpfr_float, FloatProperties<float>::MANTISSA_PRECISION);

mpfr_t mpfr_double;
mpfr_init2(mpfr_double, FloatProperties<double>::MANTISSA_PRECISION);

mpfr_t mpfr_long_double;
mpfr_init2(mpfr_long_double,
FloatProperties<long double>::MANTISSA_PRECISION);

// TODO: Add support for other rounding modes.
mpfr_strtofr(mpfr_float, str_ptr, &out_ptr, base, MPFR_RNDN);
mpfr_strtofr(mpfr_double, str_ptr, &out_ptr, base, MPFR_RNDN);
mpfr_strtofr(mpfr_long_double, str_ptr, &out_ptr, base, MPFR_RNDN);

float volatile float_result = mpfr_get_flt(mpfr_float, MPFR_RNDN);
double volatile double_result = mpfr_get_d(mpfr_double, MPFR_RNDN);
long double volatile long_double_result =
mpfr_get_ld(mpfr_long_double, MPFR_RNDN);

mpfr_clear(mpfr_float);
mpfr_clear(mpfr_double);
mpfr_clear(mpfr_long_double);

auto volatile atof_output = __llvm_libc::atof(str_ptr);
auto volatile strtof_output = __llvm_libc::strtof(str_ptr, &out_ptr);
ptrdiff_t strtof_strlen = out_ptr - str_ptr;
Expand All @@ -77,23 +119,21 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {

// If any result is NaN, all of them should be NaN. We can't use the usual
// comparisons because NaN != NaN.
if (isnan(result_ld)) {
if (isnan(float_result)) {
if (!(isnan(atof_output) && isnan(strtof_output) && isnan(strtod_output) &&
isnan(strtold_output)))
__builtin_trap();
} else {
if (mpfr_get_d(result, MPFR_RNDN) != atof_output)
if (double_result != atof_output)
__builtin_trap();
if (mpfr_get_flt(result, MPFR_RNDN) != strtof_output)
if (float_result != strtof_output)
__builtin_trap();
if (mpfr_get_d(result, MPFR_RNDN) != strtod_output)
if (double_result != strtod_output)
__builtin_trap();
if (mpfr_get_ld(result, MPFR_RNDN) != strtold_output)
if (long_double_result != strtold_output)
__builtin_trap();
}

mpfr_clear(result);
mpfr_clear(bin_result);
delete[] container;
return 0;
}
6 changes: 6 additions & 0 deletions libc/src/__support/FPUtil/FloatProperties.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ template <> struct FloatProperties<float> {
static constexpr uint32_t BIT_WIDTH = sizeof(BitsType) * 8;

static constexpr uint32_t MANTISSA_WIDTH = 23;
// The mantissa precision includes the implicit bit.
static constexpr uint32_t MANTISSA_PRECISION = MANTISSA_WIDTH + 1;
static constexpr uint32_t EXPONENT_WIDTH = 8;
static constexpr BitsType MANTISSA_MASK = (BitsType(1) << MANTISSA_WIDTH) - 1;
static constexpr BitsType SIGN_MASK = BitsType(1)
Expand All @@ -53,6 +55,7 @@ template <> struct FloatProperties<double> {
static constexpr uint32_t BIT_WIDTH = sizeof(BitsType) * 8;

static constexpr uint32_t MANTISSA_WIDTH = 52;
static constexpr uint32_t MANTISSA_PRECISION = MANTISSA_WIDTH + 1;
static constexpr uint32_t EXPONENT_WIDTH = 11;
static constexpr BitsType MANTISSA_MASK = (BitsType(1) << MANTISSA_WIDTH) - 1;
static constexpr BitsType SIGN_MASK = BitsType(1)
Expand Down Expand Up @@ -82,6 +85,7 @@ template <> struct FloatProperties<long double> {

static constexpr uint32_t MANTISSA_WIDTH =
FloatProperties<double>::MANTISSA_WIDTH;
static constexpr uint32_t MANTISSA_PRECISION = MANTISSA_WIDTH + 1;
static constexpr uint32_t EXPONENT_WIDTH =
FloatProperties<double>::EXPONENT_WIDTH;
static constexpr BitsType MANTISSA_MASK =
Expand Down Expand Up @@ -115,6 +119,7 @@ template <> struct FloatProperties<long double> {
static constexpr BitsType FULL_WIDTH_MASK = ((BitsType(1) << BIT_WIDTH) - 1);

static constexpr uint32_t MANTISSA_WIDTH = 63;
static constexpr uint32_t MANTISSA_PRECISION = MANTISSA_WIDTH + 1;
static constexpr uint32_t EXPONENT_WIDTH = 15;
static constexpr BitsType MANTISSA_MASK = (BitsType(1) << MANTISSA_WIDTH) - 1;

Expand Down Expand Up @@ -150,6 +155,7 @@ template <> struct FloatProperties<long double> {
static constexpr uint32_t BIT_WIDTH = sizeof(BitsType) << 3;

static constexpr uint32_t MANTISSA_WIDTH = 112;
static constexpr uint32_t MANTISSA_PRECISION = MANTISSA_WIDTH + 1;
static constexpr uint32_t EXPONENT_WIDTH = 15;
static constexpr BitsType MANTISSA_MASK = (BitsType(1) << MANTISSA_WIDTH) - 1;
static constexpr BitsType SIGN_MASK = BitsType(1)
Expand Down
1 change: 1 addition & 0 deletions libc/utils/MPFRWrapper/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ if(LIBC_TESTS_CAN_USE_MPFR)
libc.src.__support.CPP.string_view
libc.src.__support.CPP.type_traits
libc.src.__support.FPUtil.fp_bits
libc.src.__support.FPUtil.float_properties
libc.src.__support.FPUtil.fpbits_str
libc.src.__support.FPUtil.platform_defs
LibcTest.unit
Expand Down
27 changes: 2 additions & 25 deletions libc/utils/MPFRWrapper/MPFRUtils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
#include "src/__support/CPP/string.h"
#include "src/__support/CPP/string_view.h"
#include "src/__support/FPUtil/FPBits.h"
#include "src/__support/FPUtil/FloatProperties.h"
#include "src/__support/FPUtil/PlatformDefs.h"
#include "src/__support/FPUtil/fpbits_str.h"
#include "test/UnitTest/FPMatcher.h"
Expand All @@ -28,30 +29,6 @@ namespace __llvm_libc {
namespace testing {
namespace mpfr {

template <typename T> struct Precision;

template <> struct Precision<float> {
static constexpr unsigned int VALUE = 24;
};

template <> struct Precision<double> {
static constexpr unsigned int VALUE = 53;
};

#if defined(LONG_DOUBLE_IS_DOUBLE)
template <> struct Precision<long double> {
static constexpr unsigned int VALUE = 53;
};
#elif defined(SPECIAL_X86_LONG_DOUBLE)
template <> struct Precision<long double> {
static constexpr unsigned int VALUE = 64;
};
#else
template <> struct Precision<long double> {
static constexpr unsigned int VALUE = 113;
};
#endif

// A precision value which allows sufficiently large additional
// precision compared to the floating point precision.
template <typename T> struct ExtraPrecision;
Expand All @@ -74,7 +51,7 @@ template <> struct ExtraPrecision<long double> {
template <typename T>
static inline unsigned int get_precision(double ulp_tolerance) {
if (ulp_tolerance <= 0.5) {
return Precision<T>::VALUE;
return __llvm_libc::fputil::FloatProperties<T>::MANTISSA_PRECISION;
} else {
return ExtraPrecision<T>::VALUE;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ cc_library(
"//libc:__support_cpp_string",
"//libc:__support_cpp_string_view",
"//libc:__support_cpp_type_traits",
"//libc:__support_fputil_float_properties",
"//libc:__support_fputil_fp_bits",
"//libc:__support_fputil_fpbits_str",
"//libc:__support_fputil_platform_defs",
Expand Down

0 comments on commit cfbcbc8

Please sign in to comment.