Merge pull request #4277 from BharatYadav007/bharat/libc-mktime-negative-one

fix(libc): normalize valid mktime minus-one results
This commit is contained in:
Dean 2026-10-13 01:30:26 +13:00 committed by GitHub
commit 0ef9dc9282
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 117 additions and 1 deletions

View File

@ -78,6 +78,16 @@ if(BUILD_TESTING)
set_tests_properties(guest_time_conversion PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
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:$<TARGET_FILE_DIR:libc>")
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)

View File

@ -309,8 +309,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<std::time_t>(-1)) return -1;
if (t == static_cast<std::time_t>(-1) && host.tm_wday == -1) return -1;
const int64_t time = static_cast<int64_t>(t);
localtime_s_nid_postfix(&time, timeptr);
return time;

View File

@ -0,0 +1,105 @@
#include "prx/libc/include/GuestTime.hpp"
#include "prx/libc/include/general/VabiMacros.hpp"
#include <array>
#include <cerrno>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <limits>
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<int>::max();
input.tm_mon = std::numeric_limits<int>::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<Converter, 2> converters{libc_mktime_nid_postfix, mktime_nid_postfix};
constexpr std::array<std::int64_t, 8> 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
}