From 2b785839b1f6300211981088f6a22d631e639e3e Mon Sep 17 00:00:00 2001 From: Wleeaf <179125857+wleeaf@users.noreply.github.com> Date: Sun, 25 Oct 2026 11:55:30 +0000 Subject: [PATCH] Fix division by zero when packing coarse or sub-nanosecond time_points The pack and object_with_zone adaptors for std::chrono::time_point computed the nanosecond part as count % (1000000000 / nano_num) * nano_num. When the period is longer than one second (std::chrono::minutes, hours, C++20 sys_days), 1000000000 / nano_num is 0; when it is shorter than a nanosecond (std::pico), nano_num itself is 0. Either way packing divided by zero. Split the time since the epoch into whole seconds and the nanosecond remainder with duration_cast instead. Results for the periods that already worked are unchanged. --- include/msgpack/v1/adaptor/cpp11/chrono.hpp | 31 +++++-------- test/msgpack_cpp11.cpp | 48 +++++++++++++++++++++ test/object_with_zone.cpp | 16 +++++++ 3 files changed, 74 insertions(+), 21 deletions(-) diff --git a/include/msgpack/v1/adaptor/cpp11/chrono.hpp b/include/msgpack/v1/adaptor/cpp11/chrono.hpp index 85e5d7b31..9b0fee3e5 100644 --- a/include/msgpack/v1/adaptor/cpp11/chrono.hpp +++ b/include/msgpack/v1/adaptor/cpp11/chrono.hpp @@ -246,20 +246,15 @@ template struct pack> { template msgpack::packer& operator()(msgpack::packer& o, std::chrono::time_point const& v) const { - int64_t count = detail::integral_cast(v.time_since_epoch().count()); - int64_t nano_num = - Duration::period::ratio::num * - (1000000000L / Duration::period::ratio::den); - - int64_t nanosec = count % (1000000000L / nano_num) * nano_num; - int64_t sec = 0; + std::chrono::seconds whole_sec = + std::chrono::duration_cast(v.time_since_epoch()); + int64_t nanosec = std::chrono::duration_cast( + v.time_since_epoch() - whole_sec).count(); + int64_t sec = whole_sec.count(); if (nanosec < 0) { nanosec = 1000000000L + nanosec; --sec; } - sec += count - * Duration::period::ratio::num - / Duration::period::ratio::den; if ((sec >> 34) == 0) { uint64_t data64 = (detail::integral_cast(nanosec) << 34) | detail::integral_cast(sec); @@ -296,21 +291,15 @@ struct pack> { template struct object_with_zone> { void operator()(msgpack::object::with_zone& o, const std::chrono::time_point& v) const { - int64_t count = detail::integral_cast(v.time_since_epoch().count()); - - int64_t nano_num = - Duration::period::ratio::num * - (1000000000L / Duration::period::ratio::den); - - int64_t nanosec = count % (1000000000L / nano_num) * nano_num; - int64_t sec = 0; + std::chrono::seconds whole_sec = + std::chrono::duration_cast(v.time_since_epoch()); + int64_t nanosec = std::chrono::duration_cast( + v.time_since_epoch() - whole_sec).count(); + int64_t sec = whole_sec.count(); if (nanosec < 0) { nanosec = 1000000000L + nanosec; --sec; } - sec += count - * Duration::period::ratio::num - / Duration::period::ratio::den; if ((sec >> 34) == 0) { uint64_t data64 = (detail::integral_cast(nanosec) << 34) | detail::integral_cast(sec); if ((data64 & 0xffffffff00000000L) == 0) { diff --git a/test/msgpack_cpp11.cpp b/test/msgpack_cpp11.cpp index 2a42c9774..30e72c834 100644 --- a/test/msgpack_cpp11.cpp +++ b/test/msgpack_cpp11.cpp @@ -1026,6 +1026,54 @@ BOOST_AUTO_TEST_CASE(system_clock_96_zero_seconds) BOOST_CHECK_EQUAL(converted.time_since_epoch().count(), 0); } +BOOST_AUTO_TEST_CASE(system_clock_minutes) +{ + typedef std::chrono::time_point time_point; + time_point val1(std::chrono::minutes(2)); + msgpack::sbuffer sbuf; + msgpack::pack(sbuf, val1); + char packed[] = { + static_cast(0xd6u), + static_cast(-1), + 0, 0, 0, static_cast(0x78) // 120 seconds + }; + BOOST_CHECK_EQUAL(sbuf.size(), sizeof(packed)); + BOOST_CHECK_EQUAL(memcmp(sbuf.data(), packed, sizeof(packed)), 0); + + msgpack::object_handle oh = + msgpack::unpack(sbuf.data(), sbuf.size()); + std::chrono::system_clock::time_point val2 = oh.get().as(); + BOOST_CHECK(val1 == val2); +} + +BOOST_AUTO_TEST_CASE(system_clock_minutes_negative) +{ + typedef std::chrono::time_point time_point; + time_point val1(std::chrono::minutes(-2)); + msgpack::sbuffer sbuf; + msgpack::pack(sbuf, val1); + + msgpack::object_handle oh = + msgpack::unpack(sbuf.data(), sbuf.size()); + std::chrono::system_clock::time_point val2 = oh.get().as(); + BOOST_CHECK(val1 == val2); +} + +BOOST_AUTO_TEST_CASE(system_clock_picoseconds) +{ + typedef std::chrono::duration picoseconds; + typedef std::chrono::time_point time_point; + // 1.5 s, which system_clock can represent on every platform + time_point val1(picoseconds(1500000000000LL)); + msgpack::sbuffer sbuf; + msgpack::pack(sbuf, val1); + + msgpack::object_handle oh = + msgpack::unpack(sbuf.data(), sbuf.size()); + std::chrono::system_clock::time_point val2 = oh.get().as(); + BOOST_CHECK(val1 == val2); +} + BOOST_AUTO_TEST_CASE(system_clock_impl_min) { std::chrono::system_clock::time_point val1(std::chrono::system_clock::time_point::min()); diff --git a/test/object_with_zone.cpp b/test/object_with_zone.cpp index fbd2c7659..82e690e93 100644 --- a/test/object_with_zone.cpp +++ b/test/object_with_zone.cpp @@ -967,6 +967,22 @@ BOOST_AUTO_TEST_CASE(system_clock_64_max) BOOST_CHECK(obj.as() == v); } +BOOST_AUTO_TEST_CASE(system_clock_minutes) +{ + std::chrono::time_point v(std::chrono::minutes(2)); + msgpack::zone z; + msgpack::object obj(v, z); + BOOST_CHECK(obj.as() == v); +} + +BOOST_AUTO_TEST_CASE(system_clock_minutes_negative) +{ + std::chrono::time_point v(std::chrono::minutes(-2)); + msgpack::zone z; + msgpack::object obj(v, z); + BOOST_CHECK(obj.as() == v); +} + BOOST_AUTO_TEST_CASE(system_clock_impl_min) { std::chrono::system_clock::time_point v(std::chrono::system_clock::time_point::min());