https://gcc.gnu.org/g:dad7575e1d2e24ee49f4caea69d67dbafdfbb974
commit r17-5617-gdad7575e1d2e24ee49f4caea69d67dbafdfbb974 Author: Jonathan Wakely <[email protected]> Date: Mon Oct 12 10:18:45 2026 +0100 libstdc++: Fix chrono::time_zone::get_info for very negative times [PR127815] 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 the vector::reserve call to use a size_t instead of int, so that if `tp - info.begin` somehow becomes negative again due to some future change, we get a very large size_t and then std::min<size_t>(100, n * 2) limits that to something which won't throw length_error. 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 unsigned type for argument to vector::reserve. * testsuite/std/time/time_zone/127815.cc: New test. Reviewed-by: Tomasz KamiĆski <[email protected]> Diff: --- libstdc++-v3/src/c++20/tzdb.cc | 17 +++++++-- .../testsuite/std/time/time_zone/127815.cc | 40 ++++++++++++++++++++++ 2 files changed, 54 insertions(+), 3 deletions(-) diff --git a/libstdc++-v3/src/c++20/tzdb.cc b/libstdc++-v3/src/c++20/tzdb.cc index fddfa38b32d7..2ebfb9a14d52 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(); @@ -1105,9 +1109,16 @@ namespace std::chrono if (rules.empty()) __throw_runtime_error("std::chrono::time_zone::get_info: invalid data"); + // This will hold the sys_info objects expanded from the named rules. vector<pair<sys_info, string_view>> new_infos; - if (int n = ceil<years>(tp - info.begin).count()) - new_infos.reserve(std::min(100, n * 2)); + + // Reserve space for two transitions per year, for each year between + // info.begin and tp, up to a maximum of 100. + // This is only an optimization to avoid too many reallocations + // and doesn't need to be exact. + if (size_t n = ceil<years>(tp - info.begin).count()) + new_infos.reserve(std::min<size_t>(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..2bd8ea850b0e --- /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 = reload_tzdb().locate_zone(name)->get_info(t); + VERIFY(info.abbrev == name[0] + std::string("ST")); + } +} + +int main() +{ + test_pr127815(); +}
