From f0e852ae277cb9dfefe29d73b10b93755cae67bc Mon Sep 17 00:00:00 2001 From: BharatYadav007 Date: Sun, 11 Oct 2026 17:41:38 +0530 Subject: [PATCH] fix(libc): normalize valid mktime minus-one results --- core/libs/prx/libc/CMakeLists.txt | 10 +++ core/libs/prx/libc/src/Time.cpp | 3 +- core/libs/tests/GuestMktime.cpp | 105 ++++++++++++++++++++++++++++++ 3 files changed, 117 insertions(+), 1 deletion(-) create mode 100644 core/libs/tests/GuestMktime.cpp diff --git a/core/libs/prx/libc/CMakeLists.txt b/core/libs/prx/libc/CMakeLists.txt index b3830d4cc..a30a20b83 100644 --- a/core/libs/prx/libc/CMakeLists.txt +++ b/core/libs/prx/libc/CMakeLists.txt @@ -68,6 +68,16 @@ if(BUILD_TESTING) set_tests_properties(guest_time_conversion PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + add_test_executable(guest_mktime_tests ${LIBS_INCLUDE_DIR}/tests/GuestMktime.cpp) + target_include_directories(guest_mktime_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_mktime_tests PRIVATE libc) + configure_windows_unwind(guest_mktime_tests) + add_test(NAME guest_mktime COMMAND guest_mktime_tests) + set_tests_properties(guest_mktime PROPERTIES TIMEOUT 10) + if(WIN32) + set_tests_properties(guest_mktime PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() add_test_executable(precise_wait_tests ${LIBS_INCLUDE_DIR}/tests/PreciseWait.cpp) target_include_directories(precise_wait_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(precise_wait_tests PRIVATE libc) diff --git a/core/libs/prx/libc/src/Time.cpp b/core/libs/prx/libc/src/Time.cpp index 1da075245..4c8e794c6 100644 --- a/core/libs/prx/libc/src/Time.cpp +++ b/core/libs/prx/libc/src/Time.cpp @@ -305,8 +305,9 @@ GuestTm* APS5_VABI gmtime_nid_postfix(const int64_t* timer) { int64_t APS5_VABI libc_mktime_nid_postfix(GuestTm* timeptr) { std::tm host = toHostTm(*timeptr); + host.tm_wday = -1; const std::time_t t = std::mktime(&host); - if (t == static_cast(-1)) return -1; + if (t == static_cast(-1) && host.tm_wday == -1) return -1; const int64_t time = static_cast(t); localtime_s_nid_postfix(&time, timeptr); return time; diff --git a/core/libs/tests/GuestMktime.cpp b/core/libs/tests/GuestMktime.cpp new file mode 100644 index 000000000..0a81573f9 --- /dev/null +++ b/core/libs/tests/GuestMktime.cpp @@ -0,0 +1,105 @@ +#include "prx/libc/include/GuestTime.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include +#include +#include +#include + +extern "C" { +std::int64_t APS5_VABI libc_mktime_nid_postfix(GuestTm*); +std::int64_t APS5_VABI mktime_nid_postfix(GuestTm*); +GuestTm* APS5_VABI localtime_s_nid_postfix(const std::int64_t*, GuestTm*); +void APS5_VABI tzset_nid_postfix(); +} + +using Converter = std::int64_t (APS5_VABI *)(GuestTm*); + +static void Require(bool value, const char* message) { + if (!value) { + std::fprintf(stderr, "%s\n", message); + std::abort(); + } +} + +static bool Equal(const GuestTm& left, const GuestTm& right) { + return left.tm_sec == right.tm_sec && left.tm_min == right.tm_min && left.tm_hour == right.tm_hour + && left.tm_mday == right.tm_mday && left.tm_mon == right.tm_mon && left.tm_year == right.tm_year + && left.tm_wday == right.tm_wday && left.tm_yday == right.tm_yday && left.tm_isdst == right.tm_isdst + && left.tm_gmtoff == right.tm_gmtoff && left.tm_zone != nullptr && right.tm_zone != nullptr + && std::strcmp(left.tm_zone, right.tm_zone) == 0; +} + +static void CheckNormalization(Converter convert, std::int64_t timer) { + GuestTm expected{}; + Require(localtime_s_nid_postfix(&timer, &expected) == &expected, "Local conversion failed"); + GuestTm input = expected; + input.tm_sec += 60; + --input.tm_min; + input.tm_wday = -1; + input.tm_yday = -1; + input.tm_isdst = -1; + input.tm_gmtoff = -1; + input.tm_zone = nullptr; + errno = EOVERFLOW; + Require(convert(&input) == timer, "mktime returned the wrong timestamp"); + Require(Equal(input, expected), "mktime did not normalize a valid timestamp"); +} + +static void CheckFailure(Converter convert) { + GuestTm input{}; + input.tm_year = std::numeric_limits::max(); + input.tm_mon = std::numeric_limits::max(); + input.tm_mday = 1; + input.tm_wday = 4; + input.tm_yday = 12; + input.tm_isdst = -1; + input.tm_gmtoff = 123; + input.tm_zone = "unchanged"; + const GuestTm original = input; + Require(convert(&input) == -1, "mktime accepted an unrepresentable calendar"); + Require(Equal(input, original), "Failed mktime changed the guest calendar"); +#ifdef _WIN32 + input = {}; + input.tm_year = 69; + input.tm_mon = 11; + input.tm_mday = 31; + input.tm_hour = 23; + input.tm_min = 59; + input.tm_sec = 59; + input.tm_zone = "unchanged"; + const GuestTm beforeEpoch = input; + Require(convert(&input) == -1, "Windows mktime accepted a time before the epoch"); + Require(Equal(input, beforeEpoch), "Failed Windows mktime changed the guest calendar"); +#endif +} + +static void CheckZone(const char* zone) { +#ifdef _WIN32 + Require(_putenv_s("TZ", zone) == 0, "Could not set the time zone"); +#else + Require(setenv("TZ", zone, 1) == 0, "Could not set the time zone"); +#endif + tzset_nid_postfix(); + constexpr std::array converters{libc_mktime_nid_postfix, mktime_nid_postfix}; + constexpr std::array timers{-86401, -2, -1, 0, 1, 59, 60, 86400}; + for (auto convert : converters) { + for (const auto timer : timers) { +#ifdef _WIN32 + if (timer < 0) continue; +#endif + CheckNormalization(convert, timer); + } + CheckFailure(convert); + } +} + +int main() { + CheckZone("UTC0"); + CheckZone("UTC-2"); +#ifndef _WIN32 + CheckZone("UTC5"); +#endif +}