mirror of
https://github.com/fmtlib/fmt.git
synced 2026-10-29 02:17:28 +08:00
fix unchecked float to integral conversion in detail::duration_cast
This commit is contained in:
parent
b4ffa39b99
commit
7dc456208e
@ -448,12 +448,6 @@ template <> struct locale_writer<false> {
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T, typename U>
|
||||
using is_similar_arithmetic_type =
|
||||
bool_constant<(std::is_integral<T>::value && std::is_integral<U>::value) ||
|
||||
(std::is_floating_point<T>::value &&
|
||||
std::is_floating_point<U>::value)>;
|
||||
|
||||
FMT_NORETURN inline void throw_duration_error() {
|
||||
FMT_THROW(format_error("cannot format duration"));
|
||||
}
|
||||
@ -510,14 +504,37 @@ auto duration_cast(std::chrono::duration<FromRep, FromPeriod> from) -> To {
|
||||
#endif
|
||||
}
|
||||
|
||||
// Mixed integer <-> float casts are not supported by safe_duration_cast.
|
||||
// An integer to float conversion is always defined.
|
||||
template <typename To, typename FromRep, typename FromPeriod,
|
||||
FMT_ENABLE_IF(
|
||||
!is_similar_arithmetic_type<FromRep, typename To::rep>::value)>
|
||||
FMT_ENABLE_IF(std::is_integral<FromRep>::value&&
|
||||
std::is_floating_point<typename To::rep>::value)>
|
||||
auto duration_cast(std::chrono::duration<FromRep, FromPeriod> from) -> To {
|
||||
// Mixed integer <-> float cast is not supported by safe duration_cast.
|
||||
return std::chrono::duration_cast<To>(from);
|
||||
}
|
||||
|
||||
// Cast a floating-point duration to an integral one with a range check since
|
||||
// converting an out-of-range or NaN value to an integer is undefined behavior.
|
||||
template <typename To, typename FromRep, typename FromPeriod,
|
||||
FMT_ENABLE_IF(std::is_floating_point<FromRep>::value&&
|
||||
std::is_integral<typename To::rep>::value)>
|
||||
auto duration_cast(std::chrono::duration<FromRep, FromPeriod> from) -> To {
|
||||
#if FMT_SAFE_DURATION_CAST
|
||||
using to_rep = typename To::rep;
|
||||
auto count = detail::duration_cast<
|
||||
std::chrono::duration<FromRep, typename To::period>>(from)
|
||||
.count();
|
||||
// max + 1 is a power of 2 so the cast to FromRep is exact even when max
|
||||
// itself is rounded up by the conversion; the comparison is false for NaN.
|
||||
auto upper = static_cast<FromRep>(max_value<to_rep>()) + 1;
|
||||
auto lower = static_cast<FromRep>((std::numeric_limits<to_rep>::min)());
|
||||
if (!(count >= lower && count < upper)) throw_duration_error();
|
||||
return To(static_cast<to_rep>(count));
|
||||
#else
|
||||
return std::chrono::duration_cast<To>(from);
|
||||
#endif
|
||||
}
|
||||
|
||||
template <typename Duration>
|
||||
auto to_time_t(sys_time<Duration> time_point) -> std::time_t {
|
||||
// Cannot use std::chrono::system_clock::to_time_t since this would first
|
||||
|
||||
@ -995,6 +995,15 @@ TEST(chrono_test, out_of_range) {
|
||||
// A floating-point day count that doesn't fit in int.
|
||||
auto fd = std::chrono::duration<double>(1e300);
|
||||
EXPECT_THROW((void)fmt::format("{:%j}", fd), fmt::format_error);
|
||||
// A floating-point time point whose seconds don't fit in time_t.
|
||||
auto ftp = sys_time<std::chrono::duration<double>>(fd);
|
||||
EXPECT_THROW((void)fmt::format("{:%T}", ftp), fmt::format_error);
|
||||
auto fnan = sys_time<std::chrono::duration<double>>(
|
||||
std::chrono::duration<double>(std::numeric_limits<double>::quiet_NaN()));
|
||||
EXPECT_THROW((void)fmt::format("{:%T}", fnan), fmt::format_error);
|
||||
auto flocal = fmt::local_time<std::chrono::duration<double>>(
|
||||
std::chrono::duration<double>(-1e300));
|
||||
EXPECT_THROW((void)fmt::format("{:%F}", flocal), fmt::format_error);
|
||||
}
|
||||
|
||||
TEST(chrono_test, calendar_padding) {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user