8000 7FFF
Skip to content

Fix overflow in time_point formatting with large dates#3727

Merged
vitaut merged 3 commits intofmtlib:masterfrom
cschreib:master
Nov 25, 2023
Merged

Fix overflow in time_point formatting with large dates#3727
vitaut merged 3 commits intofmtlib:masterfrom
cschreib:master

Conversation

@cschreib
Copy link
Copy Markdown
Contributor
@cschreib cschreib commented Nov 24, 2023

This fixes #3725. I am not 100% sure the new implementation is what people would want; in particular, with FMT_SAFE_DURATION_CAST enabled, trying to format a date that is too large to fit in time_t will throw an exception. Without FMT_SAFE_DURATION_CAST I believe this will just invoke an undefined behavior (overflow of time_t, which is likely a signed integer type).

Copy link
Copy Markdown
Contributor
@vitaut vitaut left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the PR!

Comment on lines +444 to +471
template <typename Duration,
FMT_ENABLE_IF(std::is_integral<typename Duration::rep>::value)>
std::time_t to_time_t(
std::chrono::time_point<std::chrono::system_clock, Duration> time_point) {
#if FMT_SAFE_DURATION_CAST
return fmt_safe_duration_cast<std::chrono::duration<std::time_t>>(
time_point.time_since_epoch())
.count();
#else
return std::chrono::duration_cast<std::chrono::duration<std::time_t>>(
time_point.time_since_epoch())
.count();
#endif
}

template <typename Duration,
FMT_ENABLE_IF(std::is_floating_point<typename Duration::rep>::value)>
std::time_t to_time_t(
std::chrono::time_point<std::chrono::system_clock, Duration> time_point) {
return std::chrono::duration_cast<std::chrono::duration<std::time_t>>(
time_point.time_since_epoch())
.count();
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Considering that the two overloads of to_time_t are identical modulo fmt_safe_duration_cast I suggest merging them into one and moving the difference into fmt_safe_duration_cast which can just forward to duration_cast for floating point and when FMT_SAFE_DURATION_CAST is disabled.

Copy link
Copy Markdown
Contributor Author
10000

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you sure? I feel like fmt_safe_duration_cast should always offer the same safety guarantees, there being "safe" in the name. If we did this, perhaps this should be renamed fmt_duration_cast instead.

Regardless, I'd still need two overloads of that, since I can't use if constexpr to select a different path for floats. Would that be OK with you?

It also leaves open the actual issue, being that the safe casts don't seem to be available for mixed float<->integer casts. I don't immediately see a reason why, other than it just being more work.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overflow in float to int conversions are also undefined behaviour, ideally we should be able to catch these in safe mode.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Renaming to fmt_duration_cast sounds reasonable. We should handle the "safe" mode in it rather than having the callers deal with it. I understand that there will be two overloads, but the overall result should be simpler.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done and pushed

The function is now more generic and will handle all casts. It also
takes care of toggling safe vs unsafe casts using
FMT_SAFE_DURATION_CAST.
return write_encoded_tm_str(out, string_view(buf.data(), buf.size()), loc);
}

#if FMT_SAFE_DURATION_CAST
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe t 8000 his comment to others. Learn more.

This can be moved into fmt_duration_cast.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done, if I understood what you meant correctly!

Comment on lines +437 to +440
FMT_ENABLE_IF((std::is_integral<FromRep>::value &&
std::is_integral<typename To::rep>::value) ||
(std::is_floating_point<FromRep>::value &&
std::is_floating_point<typename To::rep>::value))>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's add a trait for this and reuse here and below (negated).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

@cschreib
Copy link
Copy Markdown
Contributor Author

I have pushed the requested changes and hopefully fixed the Windows CI. Let me know if you'd like me to rebase this into a single commit.

@vitaut vitaut merged commit 7f8d419 into fmtlib:master Nov 25, 2023
@vitaut
Copy link
Copy Markdown
Contributor
vitaut commented Nov 25, 2023

Thank you!

happymonkey1 pushed a commit to happymonkey1/fmt that referenced this pull request Apr 7, 2024
* Fix fmtlib#3725 and rename fmt_safe_duration_cast to fmt_duration_cast
The function is now more generic and will handle all casts. It also
takes care of toggling safe vs unsafe casts using
FMT_SAFE_DURATION_CAST.

* Refactor fmt_duration_cast to put #ifdef inside the function

* Fix compilation error with FMT_USE_LOCAL_TIME
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Date overflow with std::chrono::time_point

2 participants

0