https://gcc.gnu.org/g:46b74a7b248545312346b9a924d25ceb4bada80a
commit r14-12618-g46b74a7b248545312346b9a924d25ceb4bada80a Author: Jonathan Wakely <[email protected]> Date: Mon May 18 15:44:21 2026 +0100 libstdc++: Make chrono::parse fail for bad %z [PR125369] The chrono parsing code failed to check for errors when parsing input to match %z. The expected input is [+-]hh[mm] but if we read less than two valid digits for the hh or mm parts we didn't set failbit in the stream, and used the -1 error values returned for each bad digit in the offset value. This resulted in a "successful" parse that produced a value like -11h or -11min for the time zone offset. libstdc++-v3/ChangeLog: PR libstdc++/125369 * include/bits/chrono_io.h (__detail::_Parser::operator()): Check for errors when parsing digits for a %z format. * testsuite/std/time/parse/125369.cc: New test. Reviewed-by: Tomasz KamiĆski <[email protected]> (cherry picked from commit 11a1cf805aac7b7db21bd2ea6cc22a69f5aa92f9) Diff: --- libstdc++-v3/include/bits/chrono_io.h | 16 ++++-- libstdc++-v3/testsuite/std/time/parse/125369.cc | 65 +++++++++++++++++++++++++ 2 files changed, 77 insertions(+), 4 deletions(-) diff --git a/libstdc++-v3/include/bits/chrono_io.h b/libstdc++-v3/include/bits/chrono_io.h index 6b3f361f8294..6e398a4ad3dd 100644 --- a/libstdc++-v3/include/bits/chrono_io.h +++ b/libstdc++-v3/include/bits/chrono_io.h @@ -3943,8 +3943,12 @@ namespace __detail else { // Read hh - __hh = 10 * _S_try_read_digit(__is, __err); - __hh += _S_try_read_digit(__is, __err); + auto __d1 = _S_try_read_digit(__is, __err); + auto __d2 = _S_try_read_digit(__is, __err); + if (__d1 >= 0 && __d2 >= 0) [[likely]] + __hh = 10 * __d1 + __d2; + else + __err |= ios_base::failbit; } if (__is_failed(__err)) @@ -3978,8 +3982,12 @@ namespace __detail int_least32_t __mm = 0; if (__read_mm) { - __mm = 10 * _S_try_read_digit(__is, __err); - __mm += _S_try_read_digit(__is, __err); + auto __d1 = _S_try_read_digit(__is, __err); + auto __d2 = _S_try_read_digit(__is, __err); + if (__d1 >= 0 && __d2 >= 0) [[likely]] + __mm = 10 * __d1 + __d2; + else + __err |= ios_base::failbit; } if (!__is_failed(__err)) diff --git a/libstdc++-v3/testsuite/std/time/parse/125369.cc b/libstdc++-v3/testsuite/std/time/parse/125369.cc new file mode 100644 index 000000000000..719c56dc9282 --- /dev/null +++ b/libstdc++-v3/testsuite/std/time/parse/125369.cc @@ -0,0 +1,65 @@ +// { dg-do run { target c++20 } } + +#include <chrono> +#include <sstream> +#include <testsuite_hooks.h> + +using namespace std::chrono; + +const std::string input = "2026-05-18 15:26:48 "; +const std::string fmt = "%F %T %"; +sys_seconds ss; +minutes offset{}; +std::string abbrev; + +void +test_parse_z() +{ + for (auto suffix : {"Z", "+", "+Z", "-A", "+1Z", "+010", "+010Z"}) + { + std::istringstream in{input + suffix}; + from_stream(in, (fmt + "z").c_str(), ss, &abbrev, &offset); + + VERIFY( in.fail() ); + VERIFY( ss == sys_seconds(0s) ); + VERIFY( offset == 0min ); + VERIFY( abbrev.empty() ); + } +} + +void +test_parse_Ez() +{ + for (auto suffix : {"Z", "+", "-", "+01:", "+01:X"}) + { + std::istringstream in{input + suffix}; + from_stream(in, (fmt + "Ez").c_str(), ss, &abbrev, &offset); + + VERIFY( in.fail() ); + VERIFY( ss == sys_seconds(0s) ); + VERIFY( offset == 0min ); + VERIFY( abbrev.empty() ); + } +} + +void +test_parse_Oz() +{ + for (auto suffix : {"Z", "+", "-", "+01:", "+01:X"}) + { + std::istringstream in{input + suffix}; + from_stream(in, (fmt + "Oz").c_str(), ss, &abbrev, &offset); + + VERIFY( in.fail() ); + VERIFY( ss == sys_seconds(0s) ); + VERIFY( offset == 0min ); + VERIFY( abbrev.empty() ); + } +} + +int main() +{ + test_parse_z(); + test_parse_Ez(); + test_parse_Oz(); +}
