-
Notifications
You must be signed in to change notification settings - Fork 2.5k
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
Fix negative subsec for time_point #3261
Conversation
include/fmt/chrono.h
Outdated
val -= std::chrono::seconds(1); | ||
} | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: extra newline
test/chrono-test.cc
Outdated
EXPECT_EQ("59.000", fmt::format("{:%S}", epoch - 4 * d)); | ||
EXPECT_EQ("59.250", fmt::format("{:%S}", epoch - 3 * d)); | ||
EXPECT_EQ("59.500", fmt::format("{:%S}", epoch - 2 * d)); | ||
EXPECT_EQ("59.750", fmt::format("{:%S}", epoch - 1 * d)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure if we need all four of these checks because they are effectively testing the same thing. One is probably enough.
test/chrono-test.cc
Outdated
EXPECT_EQ("00.250", fmt::format("{:%S}", epoch + 1 * d)); | ||
EXPECT_EQ("00.500", fmt::format("{:%S}", epoch + 2 * d)); | ||
EXPECT_EQ("00.750", fmt::format("{:%S}", epoch + 3 * d)); | ||
EXPECT_EQ("01.000", fmt::format("{:%S}", epoch + 4 * d)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same here.
There was a problem hiding this 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! Overall looks good, just some minor comments inline.
Thank you! |
This change triggers a ubsan error on the following case: #include <fmt/chrono.h>
int main() {
auto d = std::chrono::system_clock::duration(-9223372036854767584);
fmt::print("{:}", std::chrono::system_clock::time_point(d));
}
Stack trace:
|
I attempted to resolve this ub by first converting the #include <fmt/chrono.h>
int main() {
auto val = -9223372036854767584 / 1'000'000 - 1; // -9223372036855
auto tp =
std::chrono::time_point<std::chrono::system_clock, std::chrono::seconds>(
std::chrono::seconds(val));
auto time = std::chrono::system_clock::to_time_t(tp);
fmt::print("{} {}", val, time);
// expected: -9223372036855 -9223372036855
// actual: -9223372036855 9223372036854
} The message from the sanitizer is as follows:
I don't know why the library would multiply the seconds by 1,000,000 and then convert it to |
An alternative solution is to subtract 1 from the resulting #include <fmt/chrono.h>
int main() {
auto val = -9223372036854; // -9223372036854767584 / 1'000'000
auto tp =
std::chrono::time_point<std::chrono::system_clock, std::chrono::seconds>(
std::chrono::seconds(val));
// This results in integer overflow since to_time_t does some multiplications.
auto t1 = std::chrono::system_clock::to_time_t(tp - std::chrono::seconds(1));
// This assumes time_t to represent seconds and is signed, which is not
// specified by the standard, but true for almost all platforms.
auto t2 = std::chrono::system_clock::to_time_t(tp) - 1;
fmt::print("t1: {}, t2: {}\n", t1, t2);
// t1: 9223372036854, t2: -9223372036855
} |
I turned the UB into an error in 240b728. If you have a better idea, a PR would be welcome. |
Fix #3117
The following piece of code (https://godbolt.org/z/4qr5389ns) used to print
59.000 00.750 00.500 00.250 00.000 00.250 00.500 00.750 01.000
, which is wrong. After this PR, it now correctly prints59.000 59.250 59.500 59.750 00.000 00.250 00.500 00.750 01.000