A very negative time point will stay negative when info.begin is
subtracted from it, which means that a negative value will be passed to
vector::reserve. That will convert to a huge size_t value, and the
vector will throw length_error. This commit clamps the time point
argument to be no less than the start of the zone's first info, so that
`tp - info.begin` will be non-negative.

This commit also changes it to chrono::abs(tp - info.begin).  This is
not strictly necessary now that tp is not less than info.begin, but it
makes the code more robust. Even if a later change makes it possible for
the value to be negative again, we will not pass a negative value to
vector::reserve.

libstdc++-v3/ChangeLog:

        PR libstdc++/127815
        * src/c++20/tzdb.cc (time_zone::_M_get_sys_info): Clamp
        parameter to start of first info. Use chrono::abs for argument
        to vector::reserve.
        * testsuite/std/time/time_zone/127815.cc: New test.
---

Tested x86_64-linux.

 libstdc++-v3/src/c++20/tzdb.cc                |  8 +++-
 .../testsuite/std/time/time_zone/127815.cc    | 40 +++++++++++++++++++
 2 files changed, 46 insertions(+), 2 deletions(-)
 create mode 100644 libstdc++-v3/testsuite/std/time/time_zone/127815.cc

diff --git a/libstdc++-v3/src/c++20/tzdb.cc b/libstdc++-v3/src/c++20/tzdb.cc
index fddfa38b32d7..67a80074381a 100644
--- a/libstdc++-v3/src/c++20/tzdb.cc
+++ b/libstdc++-v3/src/c++20/tzdb.cc
@@ -1085,7 +1085,11 @@ namespace std::chrono
     sys_info info;
 
     if (i == infos.begin())
-      info.begin = sys_days(year::min()/January/1);
+      {
+       info.begin = sys_days(year::min()/January/1);
+       if (tp < info.begin)
+         tp = info.begin;
+      }
     else
       info.begin = i[-1].until();
 
@@ -1106,7 +1110,7 @@ namespace std::chrono
       __throw_runtime_error("std::chrono::time_zone::get_info: invalid data");
 
     vector<pair<sys_info, string_view>> new_infos;
-    if (int n = ceil<years>(tp - info.begin).count())
+    if (int n = ceil<years>(chrono::abs(tp - info.begin)).count())
       new_infos.reserve(std::min(100, n * 2));
     long result_index = -1;
     int num_after = 4; // number of sys_info to generate past tp.
diff --git a/libstdc++-v3/testsuite/std/time/time_zone/127815.cc 
b/libstdc++-v3/testsuite/std/time/time_zone/127815.cc
new file mode 100644
index 000000000000..043b2affc15d
--- /dev/null
+++ b/libstdc++-v3/testsuite/std/time/time_zone/127815.cc
@@ -0,0 +1,40 @@
+// { dg-do run { target c++20 } }
+// { dg-require-effective-target tzdb }
+
+#include <chrono>
+#include <fstream>
+#include <testsuite_hooks.h>
+
+namespace __gnu_cxx
+{
+  // libstdc++ hook: read tzdata.zi from this directory instead of the 
system's.
+  const char* zoneinfo_dir_override() { return "."; }
+}
+
+static void
+test_pr127815()
+{
+  using namespace std::chrono;
+  const sys_seconds ymin{sys_days{year::min() / January / 1}};
+
+  static int version = 0;
+
+  for (const char* name : {"Plain", "Test"})
+    for (sys_seconds t : {sys_seconds::min(), ymin - 1s})
+    {
+      std::ofstream("tzdata.zi")
+       << "# version test" << ++version << "\n"
+          "R X 2000 ma - Mar lastSu 2 1 D\n"
+          "R X 2000 ma - O lastSu 2 0 S\n"
+          "Z Test -6 X T%sT\n"
+          "Z Plain -6 - PST\n";
+
+      sys_info info = locate_zone(name)->get_info(t);
+      VERIFY(info.abbrev == name[0] + std::string("ST"));
+    }
+}
+
+int main()
+{
+  test_pr127815();
+}
-- 
2.56.0

Reply via email to