Skip to content

Commit

Permalink
refactor userver: don't use boost::numeric_cast
Browse files Browse the repository at this point in the history
commit_hash:b1464d5bb4b4519ceecdd71a0b15a854dd80da41
  • Loading branch information
Anton3 committed Jan 24, 2025
1 parent 5962d49 commit a1114fc
Show file tree
Hide file tree
Showing 7 changed files with 76 additions and 47 deletions.
4 changes: 2 additions & 2 deletions core/src/utils/statistics/writer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
#include <algorithm>

#include <fmt/format.h>
#include <boost/numeric/conversion/cast.hpp>

#include <userver/utils/assert.hpp>
#include <userver/utils/numeric_cast.hpp>
#include <userver/utils/text_light.hpp>

#include <utils/statistics/writer_state.hpp>
Expand Down Expand Up @@ -136,7 +136,7 @@ Writer Writer::MakeChild() {
void Writer::Write(unsigned long long value) {
if (state_) {
ValidateUsage();
CheckAndWrite(*state_, MetricValue{boost::numeric_cast<std::int64_t>(value)});
CheckAndWrite(*state_, MetricValue{utils::numeric_cast<std::int64_t>(value)});
}
}

Expand Down
42 changes: 31 additions & 11 deletions universal/include/userver/utils/numeric_cast.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
/// @file userver/utils/numeric_cast.hpp
/// @brief @copybrief utils::numeric_cast

#include <limits>
#include <stdexcept>

#include <fmt/format.h>
Expand All @@ -26,21 +27,40 @@ using PrintableValue = std::conditional_t<(sizeof(T) > 1), T, int>;
/// ## Example usage:
///
/// @snippet utils/numeric_cast_test.cpp Sample utils::numeric_cast usage
template <typename U, typename T>
constexpr U numeric_cast(T input) {
static_assert(std::is_integral_v<T>);
static_assert(std::is_integral_v<U>);
template <typename To, typename From>
constexpr To numeric_cast(From input) {
static_assert(std::is_integral_v<From>);
static_assert(std::is_integral_v<To>);
using FromLimits = std::numeric_limits<From>;
using ToLimits = std::numeric_limits<To>;

U result = input;
if (static_cast<T>(result) != input || ((result < 0) != (input < 0))) {
std::string_view overflow_type{};

if constexpr (ToLimits::digits < FromLimits::digits) {
if (input > static_cast<From>(ToLimits::max())) {
overflow_type = "positive";
}
}

// signed -> signed: loss if narrowing
// signed -> unsigned: loss
if constexpr (FromLimits::is_signed && (!ToLimits::is_signed || ToLimits::digits < FromLimits::digits)) {
if (input < static_cast<From>(ToLimits::lowest())) {
overflow_type = "negative";
}
}

if (!overflow_type.empty()) {
throw std::runtime_error(fmt::format(
"Failed to convert {} {} into {} due to integer overflow",
compiler::GetTypeName<T>(),
static_cast<impl::PrintableValue<T>>(input),
compiler::GetTypeName<U>()
"Failed to convert {} {} into {} due to {} integer overflow",
compiler::GetTypeName<From>(),
static_cast<impl::PrintableValue<From>>(input),
compiler::GetTypeName<To>(),
overflow_type
));
}
return result;

return static_cast<To>(input);
}

} // namespace utils
Expand Down
7 changes: 3 additions & 4 deletions universal/src/crypto/public_key.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,10 @@
#include <openssl/rsa.h>
#include <openssl/x509.h>

#include <boost/numeric/conversion/cast.hpp>

#include <userver/crypto/exception.hpp>
#include <userver/crypto/hash.hpp>
#include <userver/crypto/openssl.hpp>
#include <userver/utils/numeric_cast.hpp>
#include <userver/utils/str_icase.hpp>
#include <userver/utils/text_light.hpp>

Expand All @@ -31,8 +30,8 @@ using Bignum = std::unique_ptr<BIGNUM, decltype(&::BN_clear_free)>;
Bignum LoadBignumFromBigEnd(const std::string_view raw) {
int size = 0;
try {
size = boost::numeric_cast<int>(raw.size());
} catch (const boost::bad_numeric_cast& ex) {
size = utils::numeric_cast<int>(raw.size());
} catch (const std::runtime_error& ex) {
throw KeyParseError{ex.what()};
}

Expand Down
28 changes: 19 additions & 9 deletions universal/src/formats/json/parser/int_parser.cpp
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
#include <userver/formats/json/parser/int_parser.hpp>

#include <limits>
#include <optional>
#include <type_traits>

#include <boost/numeric/conversion/cast.hpp>
#include <userver/utils/numeric_cast.hpp>

USERVER_NAMESPACE_BEGIN

Expand All @@ -12,30 +14,38 @@ namespace {

/* In JSON double with zero fractional part is a legal integer,
* e.g. "3.0" is an integer 3 */
template <class Int>
template <typename Int>
std::optional<Int> DoubleToInt(double value) {
auto i = std::round(value);
if (std::fabs(i - value) > std::max(std::fabs(value), std::fabs(i)) * std::numeric_limits<double>::epsilon()) {
return std::nullopt;
if (std::abs(i - value) > std::max(std::abs(value), std::abs(i)) * std::numeric_limits<double>::epsilon()) {
return std::nullopt; // rounding
}
return boost::numeric_cast<Int>(i);

// Taken from the implementation of boost::numeric_cast<Int>(double).
if (value <= static_cast<double>(std::numeric_limits<Int>::lowest()) - 1.0) {
throw std::runtime_error("numeric conversion: negative overflow");
}
if (value >= static_cast<double>(std::numeric_limits<Int>::max()) + 1.0) {
throw std::runtime_error("numeric conversion: positive overflow");
}
return value < 0.0 ? static_cast<Int>(std::ceil(value)) : static_cast<Int>(std::floor(value));
}

} // namespace

void IntegralParser<std::int32_t>::Int64(std::int64_t i) { this->SetResult(boost::numeric_cast<std::int32_t>(i)); }
void IntegralParser<std::int32_t>::Int64(std::int64_t i) { this->SetResult(utils::numeric_cast<std::int32_t>(i)); }

void IntegralParser<std::int32_t>::Uint64(std::uint64_t i) { this->SetResult(boost::numeric_cast<std::int32_t>(i)); }
void IntegralParser<std::int32_t>::Uint64(std::uint64_t i) { this->SetResult(utils::numeric_cast<std::int32_t>(i)); }

void IntegralParser<std::int32_t>::Double(double value) {
auto v = DoubleToInt<std::int32_t>(value);
if (!v) this->Throw("double");
this->SetResult(*std::move(v));
}

void IntegralParser<std::int64_t>::Int64(std::int64_t i) { this->SetResult(boost::numeric_cast<std::int64_t>(i)); }
void IntegralParser<std::int64_t>::Int64(std::int64_t i) { this->SetResult(utils::numeric_cast<std::int64_t>(i)); }

void IntegralParser<std::int64_t>::Uint64(std::uint64_t i) { this->SetResult(boost::numeric_cast<std::int64_t>(i)); }
void IntegralParser<std::int64_t>::Uint64(std::uint64_t i) { this->SetResult(utils::numeric_cast<std::int64_t>(i)); }

void IntegralParser<std::int64_t>::Double(double value) {
auto v = DoubleToInt<std::int64_t>(value);
Expand Down
21 changes: 8 additions & 13 deletions universal/src/formats/json/parser/parser_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,12 @@

#include <unordered_map>

#include <userver/compiler/demangle.hpp>
#include <userver/formats/json/parser/parser.hpp>
#include <userver/formats/json/serialize.hpp>

// TODO: move to utest/*
// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
#define EXPECT_THROW_TEXT(code, exception_type, exc_text) \
try { \
code; \
FAIL() << "expected exception " #exception_type ", but not thrown"; \
} catch (const exception_type& e) { \
EXPECT_EQ(e.what(), std::string{exc_text}) << "wrong exception message"; \
} catch (const std::exception& e) { \
FAIL() << "wrong exception type, expected " #exception_type ", but got " << typeid(e).name(); \
}
#define EXPECT_THROW_TEXT(code, exception_type, exc_text) UEXPECT_THROW_MSG(code, exception_type, exc_text)

USERVER_NAMESPACE_BEGIN
namespace fjp = formats::json::parser;
Expand Down Expand Up @@ -88,9 +80,12 @@ TEST(JsonStringParser, Int64Overflow) {
EXPECT_THROW_TEXT(
(fjp::ParseToType<int64_t, fjp::Int64Parser>(input)),
fjp::ParseError,
"Parse error at pos 20, path '': bad "
"numeric conversion: positive overflow, the latest token "
"was 18446744073709551615"
fmt::format(
"Parse error at pos 20, path '': Failed to convert {} 18446744073709551615 into {} "
"due to positive integer overflow, the latest token was 18446744073709551615",
compiler::GetTypeName<std::uint64_t>(),
compiler::GetTypeName<std::int64_t>()
)
);
}

Expand Down
10 changes: 5 additions & 5 deletions universal/src/utils/datetime.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@
#include <sys/param.h>

#include <cctz/time_zone.h>
#include <boost/lexical_cast.hpp>
#include <boost/numeric/conversion/cast.hpp>

#include <userver/utils/assert.hpp>
#include <userver/utils/from_string.hpp>
#include <userver/utils/mock_now.hpp>
#include <userver/utils/numeric_cast.hpp>

USERVER_NAMESPACE_BEGIN

Expand Down Expand Up @@ -186,10 +186,10 @@ std::uint32_t ParseDayTime(const std::string& str) {
std::uint8_t seconds = 0;

try {
hours = boost::numeric_cast<std::uint8_t>(boost::lexical_cast<int>(std::string(str, 0, 2)));
minutes = boost::numeric_cast<std::uint8_t>(boost::lexical_cast<int>(std::string(str, 3, 2)));
hours = utils::numeric_cast<std::uint8_t>(utils::FromString<int>(std::string_view{str}.substr(0, 2)));
minutes = utils::numeric_cast<std::uint8_t>(utils::FromString<int>(std::string_view{str}.substr(3, 2)));
if (str.size() == 8)
seconds = boost::numeric_cast<std::uint8_t>(boost::lexical_cast<int>(std::string(str, 6, 2)));
seconds = utils::numeric_cast<std::uint8_t>(utils::FromString<int>(std::string_view{str}.substr(6, 2)));

} catch (const std::exception& ex) {
throw std::invalid_argument(std::string("Failed to parse time from ") + str);
Expand Down
11 changes: 8 additions & 3 deletions universal/src/utils/from_string_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@
#include <random>
#include <type_traits>

#include <fmt/core.h>
#include <fmt/format.h>
#include <gtest/gtest.h>
#include <boost/lexical_cast.hpp>

#include <userver/compiler/demangle.hpp>

Expand All @@ -20,8 +21,12 @@ std::string ToString(T value) {
if constexpr (sizeof(value) == 1) {
// Prevent printing int8_t and uint8_t as a character
return std::to_string(static_cast<int>(value));
} else if constexpr (std::is_same_v<T, long double> && FMT_VERSION < 100100) {
// fmt before 10.1.0 formatted long double incorrectly
// https://github.com/fmtlib/fmt/issues/3564
return fmt::format("{:.{}g}", value, std::numeric_limits<long double>::max_digits10);
} else {
return boost::lexical_cast<std::string>(value);
return fmt::to_string(value);
}
}

Expand All @@ -42,7 +47,7 @@ auto CheckConverts(StringType input, T expectedResult) {

template <typename T>
auto TestConverts(const std::string& input, T expectedResult) {
CheckConverts(input.data(), expectedResult);
CheckConverts(input.c_str(), expectedResult);
CheckConverts(input, expectedResult);
CheckConverts(std::string_view{input}, expectedResult);
}
Expand Down

0 comments on commit a1114fc

Please sign in to comment.