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