Fix struct_time field overflow to raise OverflowError in time module (#7627)

* Fix struct_time field overflow to raise OverflowError in time module

* Address CodeRabbit review: cover tm_gmtoff and chain AssertionError

* Fix ruff format: single space before inline comment
This commit is contained in:
Changjoon authored and GitHub committed 2026-04-19 22:32:57 +09:00
1 parent 0b1b4f955e
commit ebb3253458
2 files changed
+64 -54

No files matched your search

+32 -54
View File
@@ -27,7 +27,7 @@ unsafe extern "C" {
mod decl {
use crate::{
AsObject, Py, PyObjectRef, PyResult, VirtualMachine,
builtins::{PyStrRef, PyTypeRef},
builtins::{PyBaseExceptionRef, PyStrRef, PyTypeRef},
function::{Either, FuncArgs, OptionalArg},
types::{PyStructSequence, struct_sequence_new},
};
@@ -354,6 +354,13 @@ mod decl {
) -> PyResult<CheckedTm> {
let invalid_tuple =
|| vm.new_type_error(format!("{func_name}(): illegal time tuple argument"));
let classify_err = |e: PyBaseExceptionRef| {
if e.class().is(vm.ctx.exceptions.overflow_error) {
vm.new_overflow_error(format!("{func_name} argument out of range"))
} else {
invalid_tuple()
}
};
let year: i64 = t.tm_year.clone().try_into_value(vm).map_err(|e| {
if e.class().is(vm.ctx.exceptions.overflow_error) {
@@ -370,46 +377,30 @@ mod decl {
.tm_mon
.clone()
.try_into_value::<i32>(vm)
.map_err(|_| invalid_tuple())?
.map_err(classify_err)?
- 1;
let tm_mday = t
.tm_mday
.clone()
.try_into_value(vm)
.map_err(|_| invalid_tuple())?;
let tm_hour = t
.tm_hour
.clone()
.try_into_value(vm)
.map_err(|_| invalid_tuple())?;
let tm_min = t
.tm_min
.clone()
.try_into_value(vm)
.map_err(|_| invalid_tuple())?;
let tm_sec = t
.tm_sec
.clone()
.try_into_value(vm)
.map_err(|_| invalid_tuple())?;
let tm_mday = t.tm_mday.clone().try_into_value(vm).map_err(classify_err)?;
let tm_hour = t.tm_hour.clone().try_into_value(vm).map_err(classify_err)?;
let tm_min = t.tm_min.clone().try_into_value(vm).map_err(classify_err)?;
let tm_sec = t.tm_sec.clone().try_into_value(vm).map_err(classify_err)?;
let tm_wday = (t
.tm_wday
.clone()
.try_into_value::<i32>(vm)
.map_err(|_| invalid_tuple())?
.map_err(classify_err)?
+ 1)
% 7;
let tm_yday = t
.tm_yday
.clone()
.try_into_value::<i32>(vm)
.map_err(|_| invalid_tuple())?
.map_err(classify_err)?
- 1;
let tm_isdst = t
.tm_isdst
.clone()
.try_into_value(vm)
.map_err(|_| invalid_tuple())?;
.map_err(classify_err)?;
let mut tm: libc::tm = unsafe { core::mem::zeroed() };
tm.tm_year = year - 1900;
@@ -474,7 +465,7 @@ mod decl {
.tm_gmtoff
.clone()
.try_into_value(vm)
.map_err(|_| invalid_tuple())?;
.map_err(classify_err)?;
tm.tm_gmtoff = gmtoff as _;
}
@@ -963,41 +954,28 @@ mod decl {
vm: &VirtualMachine,
) -> PyResult<libc::tm> {
let invalid_tuple = || vm.new_type_error("mktime(): illegal time tuple argument");
let year: i32 = t
.tm_year
.clone()
.try_into_value(vm)
.map_err(|_| invalid_tuple())?;
let classify_err = |e: PyBaseExceptionRef| {
if e.class().is(vm.ctx.exceptions.overflow_error) {
vm.new_overflow_error("mktime argument out of range")
} else {
invalid_tuple()
}
};
let year: i32 = t.tm_year.clone().try_into_value(vm).map_err(classify_err)?;
if year < i32::MIN + 1900 {
return Err(vm.new_overflow_error("year out of range"));
}
let mut tm: libc::tm = unsafe { core::mem::zeroed() };
tm.tm_sec = t
.tm_sec
.clone()
.try_into_value(vm)
.map_err(|_| invalid_tuple())?;
tm.tm_min = t
.tm_min
.clone()
.try_into_value(vm)
.map_err(|_| invalid_tuple())?;
tm.tm_hour = t
.tm_hour
.clone()
.try_into_value(vm)
.map_err(|_| invalid_tuple())?;
tm.tm_mday = t
.tm_mday
.clone()
.try_into_value(vm)
.map_err(|_| invalid_tuple())?;
tm.tm_sec = t.tm_sec.clone().try_into_value(vm).map_err(classify_err)?;
tm.tm_min = t.tm_min.clone().try_into_value(vm).map_err(classify_err)?;
tm.tm_hour = t.tm_hour.clone().try_into_value(vm).map_err(classify_err)?;
tm.tm_mday = t.tm_mday.clone().try_into_value(vm).map_err(classify_err)?;
tm.tm_mon = t
.tm_mon
.clone()
.try_into_value::<i32>(vm)
.map_err(|_| invalid_tuple())?
.map_err(classify_err)?
- 1;
tm.tm_year = year - 1900;
tm.tm_wday = -1;
@@ -1005,13 +983,13 @@ mod decl {
.tm_yday
.clone()
.try_into_value::<i32>(vm)
.map_err(|_| invalid_tuple())?
.map_err(classify_err)?
- 1;
tm.tm_isdst = t
.tm_isdst
.clone()
.try_into_value(vm)
.map_err(|_| invalid_tuple())?;
.map_err(classify_err)?;
Ok(tm)
}
+32
View File
@@ -17,3 +17,35 @@ assert x2.tm_min == 16
s = time.asctime(x)
# print(s)
assert s == "Thu Jan 1 00:16:40 1970"
# Regression test for RustPython issue #4938:
# struct_time field overflow should raise OverflowError (matching CPython),
# not TypeError. Covers mktime, asctime, and strftime.
I32_MAX_PLUS_1 = 2147483648
overflow_cases = [
(I32_MAX_PLUS_1, 1, 1, 0, 0, 0, 0, 0, 0), # i32 overflow in year
(2024, I32_MAX_PLUS_1, 1, 0, 0, 0, 0, 0, 0), # i32 overflow in month
(2024, 1, I32_MAX_PLUS_1, 0, 0, 0, 0, 0, 0), # i32 overflow in mday
(2024, 1, 1, 0, 0, I32_MAX_PLUS_1, 0, 0, 0), # i32 overflow in sec
(88888888888,) * 9, # multi-field i32 overflow
]
for case in overflow_cases:
for func_name, call in [
("mktime", lambda c=case: time.mktime(c)),
("asctime", lambda c=case: time.asctime(c)),
("strftime", lambda c=case: time.strftime("%Y", c)),
]:
try:
call()
except OverflowError:
pass # expected, matches CPython
except TypeError as e:
raise AssertionError(
f"{func_name}({case}) raised TypeError (should be OverflowError): {e}"
) from e
else:
raise AssertionError(
f"{func_name}({case}) did not raise — expected OverflowError"
)