On Mon, 18 May 2026 at 23:28, Jonathan Wakely <[email protected]> wrote:
>
> 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.
> ---
>
> Tested x86_64-linux.
>
> This should be backported to gcc-14/15/16.
>
>  libstdc++-v3/include/bits/chrono_io.h         | 17 +++--
>  .../testsuite/std/time/parse/125369.cc        | 65 +++++++++++++++++++
>  2 files changed, 78 insertions(+), 4 deletions(-)
>  create mode 100644 libstdc++-v3/testsuite/std/time/parse/125369.cc
>
> diff --git a/libstdc++-v3/include/bits/chrono_io.h 
> b/libstdc++-v3/include/bits/chrono_io.h
> index 1fe50ad79e05..f840f54e9112 100644
> --- a/libstdc++-v3/include/bits/chrono_io.h
> +++ b/libstdc++-v3/include/bits/chrono_io.h
> @@ -5234,8 +5234,13 @@ 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;
> +

Oops, this blank line isn't needed. Removed locally.

>                         }
>
>                       if (__is_failed(__err))
> @@ -5269,8 +5274,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();
> +}
> --
> 2.54.0
>

Reply via email to