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();
+}

Reply via email to