Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add additional bounds checking in to_chars for floats #153

Merged
merged 27 commits into from
Feb 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
a98dccd
Add non-finite values test
mborland Feb 2, 2024
420646e
Add first approximation of required buffer length
mborland Feb 2, 2024
ffbd977
Add total length buffer check for fixed formatting
mborland Feb 2, 2024
02c6070
Add bounds checking to dragonbox
mborland Feb 2, 2024
f4171a6
Fix long double function call
mborland Feb 2, 2024
d6b37f7
Reorder checks to allow non-finite numbers in small buffers
mborland Feb 5, 2024
bd5c741
Add test for random number to fit in minimum size buffer
mborland Feb 5, 2024
ff22c4b
Add bounds checking to non-finite RYU handling
mborland Feb 5, 2024
514e61c
Add debug statement
mborland Feb 5, 2024
92415b8
Fix over counting in buffer sizing
mborland Feb 5, 2024
fe19e77
Fix __float128 sanity check
mborland Feb 5, 2024
1fb4a31
Ignore MSVC C4127
mborland Feb 5, 2024
eeddadf
Change range for MSVC
mborland Feb 5, 2024
ed59b7b
Improve debug-ability
mborland Feb 6, 2024
2961650
Fix comparison
mborland Feb 6, 2024
eec4f2c
Specialize __float128 test
mborland Feb 6, 2024
b717e88
Add min buffer size test set for __float128
mborland Feb 6, 2024
db41374
Add limits overload for __float128
mborland Feb 6, 2024
f76d388
Add boost.random to test CML
mborland Feb 6, 2024
04952ae
Change return val for error in RYU
mborland Feb 6, 2024
094a8a7
Move float128 testing into its own file
mborland Feb 6, 2024
4451f25
Remove boost random since it fails ASAN
mborland Feb 7, 2024
4c2c3cf
Increase precision of error message
mborland Feb 7, 2024
1501c5a
Add additional testing for MinGW failures
mborland Feb 7, 2024
75a78f0
Skip long double hex testing since there is related outstanding issue
mborland Feb 7, 2024
4d66615
Fix testing for platforms with 64-bit long double
mborland Feb 7, 2024
29be488
Ignore MSVC warning C4127
mborland Feb 7, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 68 additions & 0 deletions include/boost/charconv/detail/buffer_sizing.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
// Copyright 2024 Matt Borland
// Distributed under the Boost Software License, Version 1.0.
// https://www.boost.org/LICENSE_1_0.txt

#ifndef BOOST_CHARCONV_DETAIL_BUFFER_SIZING_HPP
#define BOOST_CHARCONV_DETAIL_BUFFER_SIZING_HPP

#include <boost/charconv/detail/config.hpp>
#include <boost/charconv/detail/integer_search_trees.hpp>
#include <type_traits>

namespace boost {
namespace charconv {
namespace detail {

#ifdef BOOST_MSVC
# pragma warning(push)
# pragma warning(disable: 4127) // Conditional expression for BOOST_IF_CONSTEXPR will be constant in not C++17
#endif

template <typename Real>
inline int get_real_precision(int precision = -1) noexcept
{
// If the user did not specify a precision than we use the maximum representable amount
// and remove trailing zeros at the end

int real_precision;
BOOST_IF_CONSTEXPR (std::is_same<Real, float>::value || std::is_same<Real, double>::value)
{
real_precision = precision == -1 ? std::numeric_limits<Real>::max_digits10 : precision;
}
else
{
#ifdef BOOST_CHARCONV_HAS_FLOAT128
BOOST_CHARCONV_IF_CONSTEXPR (std::is_same<Real, __float128>::value)
{
real_precision = 33;
}
else
#endif
{
#if BOOST_CHARCONV_LDBL_BITS == 128
real_precision = 33;
#else
real_precision = 18;
#endif
}
}

return real_precision;
}

template <typename Int>
inline int total_buffer_length(int real_precision, Int exp, bool signed_value)
{
// Sign + integer part + '.' + precision of fraction part + e+/e- or p+/p- + exponent digits
return static_cast<int>(signed_value) + 1 + real_precision + 2 + num_digits(exp);
}

#ifdef BOOST_MSVC
# pragma warning(pop)
#endif

} //namespace detail
} //namespace charconv
} //namespace boost

#endif //BOOST_CHARCONV_DETAIL_BUFFER_SIZING_HPP
83 changes: 61 additions & 22 deletions include/boost/charconv/detail/dragonbox/dragonbox.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
#include <boost/charconv/detail/dragonbox/dragonbox_common.hpp>
#include <boost/charconv/detail/bit_layouts.hpp>
#include <boost/charconv/detail/emulated128.hpp>
#include <boost/charconv/detail/buffer_sizing.hpp>
#include <boost/charconv/detail/to_chars_result.hpp>
#include <boost/charconv/chars_format.hpp>
#include <boost/core/bit.hpp>
#include <type_traits>
Expand Down Expand Up @@ -2560,15 +2562,18 @@ BOOST_FORCEINLINE BOOST_CHARCONV_SAFEBUFFERS auto to_decimal(Float x, Policies..

namespace to_chars_detail {
template <class Float, class FloatTraits>
extern char* to_chars(typename FloatTraits::carrier_uint significand, int exponent, char* buffer, chars_format fmt) noexcept;
extern to_chars_result dragon_box_print_chars(typename FloatTraits::carrier_uint significand, int exponent, char* first, char* last, chars_format fmt) noexcept;

// Avoid needless ABI overhead incurred by tag dispatch.
template <class PolicyHolder, class Float, class FloatTraits>
char* to_chars_n_impl(dragonbox_float_bits<Float, FloatTraits> br, char* buffer, chars_format fmt) noexcept
to_chars_result to_chars_n_impl(dragonbox_float_bits<Float, FloatTraits> br, char* first, char* last, chars_format fmt) noexcept
{
const auto exponent_bits = br.extract_exponent_bits();
const auto s = br.remove_exponent_bits(exponent_bits);

auto buffer = first;
const auto buffer_size = last - first;

if (br.is_finite(exponent_bits))
{
if (s.is_negative())
Expand All @@ -2583,33 +2588,48 @@ namespace to_chars_detail {
typename PolicyHolder::decimal_to_binary_rounding_policy{},
typename PolicyHolder::binary_to_decimal_rounding_policy{},
typename PolicyHolder::cache_policy{});
return to_chars_detail::to_chars<Float, FloatTraits>(result.significand,
result.exponent, buffer, fmt);
return to_chars_detail::dragon_box_print_chars<Float, FloatTraits>(result.significand, result.exponent, buffer, last, fmt);
}
else
{
if (fmt != chars_format::scientific)
{
std::memcpy(buffer, "0", 1); // NOLINT: Specifically not null-terminated
return buffer + 1;
return {buffer + 1, std::errc()};
}

std::memcpy(buffer, "0e+00", 5); // NOLINT: Specifically not null-terminated
return buffer + 5;
if (buffer_size >= 5)
{
std::memcpy(buffer, "0e+00", 5); // NOLINT: Specifically not null-terminated
return {buffer + 5, std::errc()};
}
else
{
return {last, std::errc::result_out_of_range};
}
}
}
else
{
if (s.is_negative())
bool is_negative = false;
if (s.is_negative())
{
*buffer = '-';
++buffer;
is_negative = true;
}

if (s.has_all_zero_significand_bits())
{
std::memcpy(buffer, "inf", 3); // NOLINT: Specifically not null-terminated
return buffer + 3;
if (buffer_size >= 3 + static_cast<std::ptrdiff_t>(is_negative))
{
std::memcpy(buffer, "inf", 3); // NOLINT: Specifically not null-terminated
return {buffer + 3, std::errc()};
}
else
{
return {last, std::errc::result_out_of_range};
}
}
else
{
Expand Down Expand Up @@ -2650,19 +2670,40 @@ namespace to_chars_detail {
{
if (!s.is_negative())
{
std::memcpy(buffer, "nan", 3); // NOLINT: Specifically not null-terminated
return buffer + 3;
if (buffer_size >= 3 + static_cast<std::ptrdiff_t>(is_negative))
{
std::memcpy(buffer, "nan", 3); // NOLINT: Specifically not null-terminated
return {buffer + 3, std::errc()};
}
else
{
return {last, std::errc::result_out_of_range};
}
}
else
{
std::memcpy(buffer, "nan(ind)", 8); // NOLINT: Specifically not null-terminated
return buffer + 8;
if (buffer_size >= 8 + static_cast<std::ptrdiff_t>(is_negative))
{
std::memcpy(buffer, "nan(ind)", 8); // NOLINT: Specifically not null-terminated
return {buffer + 8, std::errc()};
}
else
{
return {last, std::errc::result_out_of_range};
}
}
}
else
{
std::memcpy(buffer, "nan(snan)", 9); // NOLINT: Specifically not null-terminated
return buffer + 9;
if (buffer_size >= 9 + static_cast<std::ptrdiff_t>(is_negative))
{
std::memcpy(buffer, "nan(snan)", 9); // NOLINT: Specifically not null-terminated
return {buffer + 9, std::errc()};
}
else
{
return {last, std::errc::result_out_of_range};
}
}
}
}
Expand All @@ -2671,7 +2712,7 @@ namespace to_chars_detail {

// Returns the next-to-end position
template <typename Float, typename FloatTraits = dragonbox_float_traits<Float>, typename... Policies>
char* to_chars_n(Float x, char* buffer, chars_format fmt, BOOST_ATTRIBUTE_UNUSED Policies... policies) noexcept
to_chars_result to_chars_n(Float x, char* first, char* last, chars_format fmt, BOOST_ATTRIBUTE_UNUSED Policies... policies) noexcept
{
using namespace policy_impl;

Expand All @@ -2693,16 +2734,14 @@ char* to_chars_n(Float x, char* buffer, chars_format fmt, BOOST_ATTRIBUTE_UNUSED

#endif

return to_chars_detail::to_chars_n_impl<policy_holder>(dragonbox_float_bits<Float, FloatTraits>(x), buffer, fmt);
return to_chars_detail::to_chars_n_impl<policy_holder>(dragonbox_float_bits<Float, FloatTraits>(x), first, last, fmt);
}

// Null-terminate and bypass the return value of fp_to_chars_n
template <typename Float, typename FloatTraits = dragonbox_float_traits<Float>, typename... Policies>
char* to_chars(Float x, char* buffer, chars_format fmt, Policies... policies) noexcept
to_chars_result dragonbox_to_chars(Float x, char* first, char* last, chars_format fmt, Policies... policies) noexcept
{
auto ptr = to_chars_n<Float, FloatTraits>(x, buffer, fmt, policies...);
*ptr = '\0';
return ptr;
return to_chars_n<Float, FloatTraits>(x, first, last, fmt, policies...);
}

}}} // Namespaces
Expand Down
61 changes: 47 additions & 14 deletions include/boost/charconv/detail/ryu/ryu_generic_128.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -282,7 +282,7 @@ static inline struct floating_decimal_128 generic_binary_to_decimal(
return {output, exp, ieeeSign};
}

static inline int copy_special_str(char* result, const struct floating_decimal_128 fd) noexcept
static inline int copy_special_str(char* result, const std::ptrdiff_t result_size, const struct floating_decimal_128 fd) noexcept
{
if (fd.sign)
{
Expand All @@ -298,13 +298,27 @@ static inline int copy_special_str(char* result, const struct floating_decimal_1
fd.mantissa == static_cast<unsigned_128_type>(6917529027641081856) ||
fd.mantissa == static_cast<unsigned_128_type>(1) << 110) // 2^110
{
std::memcpy(result, "nan(snan)", 9);
return 10;
if (result_size >= 10)
{
std::memcpy(result, "nan(snan)", 9);
return 10;
}
else
{
return -1;
}
}
else
{
std::memcpy(result, "nan(ind)", 8);
return 9;
if (result_size >= 9)
{
std::memcpy(result, "nan(ind)", 8);
return 9;
}
else
{
return -1;
}
}
}
else
Expand All @@ -313,26 +327,45 @@ static inline int copy_special_str(char* result, const struct floating_decimal_1
fd.mantissa == static_cast<unsigned_128_type>(6917529027641081856) ||
fd.mantissa == static_cast<unsigned_128_type>(1) << 110) // 2^110
{
std::memcpy(result, "nan(snan)", 9);
return 9;
if (result_size >= 9)
{
std::memcpy(result, "nan(snan)", 9);
return 9;
}
else
{
return -1;
}
}
else
{
std::memcpy(result, "nan", 3);
return 3;
if (result_size >= 3)
{
std::memcpy(result, "nan", 3);
return 3;
}
else
{
return -1;
}
}
}
}

memcpy(result, "inf", 3);
return static_cast<int>(fd.sign) + 3;
if (result_size >= 3 + static_cast<std::ptrdiff_t>(fd.sign))
{
memcpy(result, "inf", 3);
return static_cast<int>(fd.sign) + 3;
}

return -1;
}

static inline int generic_to_chars_fixed(const struct floating_decimal_128 v, char* result, const ptrdiff_t result_size, int precision) noexcept
{
if (v.exponent == fd128_exceptional_exponent)
{
return copy_special_str(result, v);
return copy_special_str(result, result_size, v);
}

// Step 5: Print the decimal representation.
Expand Down Expand Up @@ -416,7 +449,7 @@ static inline int generic_to_chars(const struct floating_decimal_128 v, char* re
{
if (v.exponent == fd128_exceptional_exponent)
{
return copy_special_str(result, v);
return copy_special_str(result, result_size, v);
}

unsigned_128_type output = v.mantissa;
Expand Down Expand Up @@ -451,7 +484,7 @@ static inline int generic_to_chars(const struct floating_decimal_128 v, char* re
}
else if (olength == 0)
{
return -1; // Something has gone horribly wrong
return -2; // Something has gone horribly wrong
}

for (uint32_t i = 0; i < olength - 1; ++i)
Expand Down
12 changes: 11 additions & 1 deletion include/boost/charconv/limits.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
#ifndef BOOST_CHARCONV_LIMITS_HPP
#define BOOST_CHARCONV_LIMITS_HPP

#include <boost/config.hpp>
#include <boost/charconv/detail/config.hpp>
#include <limits>
#include <type_traits>

Expand Down Expand Up @@ -72,6 +72,16 @@ template<typename T> struct limits
std::numeric_limits<T>::max_digits10 + 3 + 2 + detail::exp_digits( std::numeric_limits<T>::max_exponent10 ); // as above
};

#ifdef BOOST_CHARCONV_HAS_FLOAT128

template <> struct limits<__float128>
{
BOOST_ATTRIBUTE_UNUSED static constexpr int max_chars10 = 33 + 3 + 2 + 5;
BOOST_ATTRIBUTE_UNUSED static constexpr int max_chars = max_chars10;
};

#endif

#if defined(BOOST_NO_CXX17_INLINE_VARIABLES)

// Definitions of in-class constexpr members are allowed but deprecated in C++17
Expand Down
Loading
Loading