From 59feec79d038d677581eae9979f3d47d89e44fb8 Mon Sep 17 00:00:00 2001 From: pjh456 <147148383@qq.com> Date: Sun, 20 Sep 2026 21:21:09 +0000 Subject: [PATCH] =?UTF-8?q?fix:=20=E6=97=A5=E6=9C=9F=E5=A4=9A=E5=8F=82?= =?UTF-8?q?=E7=BB=84=E5=90=88=E8=A1=A5=E5=85=A8=E5=88=86=E9=87=8F=E6=9C=89?= =?UTF-8?q?=E9=99=90=E6=80=A7=E5=AE=88=E5=8D=AB=E4=B8=8E=E6=9E=84=E9=80=A0?= =?UTF-8?q?=E5=99=A8=20TimeClip=20=E6=94=B6=E5=B0=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/oxide_builtins/src/date.rs | 45 ++++++++++++++----- .../tests/builtin_overflow_boundary_tests.rs | 18 ++++++++ 2 files changed, 52 insertions(+), 11 deletions(-) diff --git a/crates/oxide_builtins/src/date.rs b/crates/oxide_builtins/src/date.rs index 1cbcfa23..642bf2d9 100644 --- a/crates/oxide_builtins/src/date.rs +++ b/crates/oxide_builtins/src/date.rs @@ -164,14 +164,19 @@ fn local_components(ms: f64) -> Option<(i64, i64, i64, i64, i64, i64, i64)> { )) } -/// 本地 setter 收尾:组合出的时刻值过 TimeClip 包络,越界(含非有限)为 -/// Invalid Date(存 NaN 并返回 NaN);有效值写回并原样返回。 -fn finish_local_setter(obj: &mut JsObject, ts: f64) -> NativeResult { - let ts = if ts.is_finite() && (-MS_LIMIT as f64..=MS_LIMIT as f64).contains(&ts) { +/// TimeClip:|ts| > 8.64e15 或非有限的时刻值归 NaN(Invalid Date)。 +fn time_clip(ts: f64) -> f64 { + if ts.is_finite() && (-MS_LIMIT as f64..=MS_LIMIT as f64).contains(&ts) { ts } else { f64::NAN - }; + } +} + +/// 本地 setter 收尾:组合出的时刻值过 TimeClip 包络,越界(含非有限)为 +/// Invalid Date(存 NaN 并返回 NaN);有效值写回并原样返回。 +fn finish_local_setter(obj: &mut JsObject, ts: f64) -> NativeResult { + let ts = time_clip(ts); set_timestamp(obj, ts); NativeResult::Ok(JsValue::float(ts)) } @@ -205,17 +210,33 @@ macro_rules! apply_date_result { /// 再按本地时区映射到真实 UTC 时刻。 /// /// # 边界与前提 -/// - 调用方保证各分量已 ToNumber 且非 NaN(任一 NaN 由调用方短路)。 -/// - 分量按 ToIntegerOrInfinity 截断;超规范包络(|时刻| > 275760 年)的组合时刻由调用方 TimeClip 归 NaN。 +/// - 任一分量非有限(±Infinity 或 NaN)直接返回 NaN。 +/// - 分量按 ToIntegerOrInfinity 截断;年/月进位远超包络(日数公式乘积溢出带) +/// 直接返回 NaN。 +/// - naive 时刻超 TimeClip 包络(|naive_ms| > 8.64e15)返回 NaN,同时兜住 +/// i64 转换的饱和带;最终 UTC 时刻的 TimeClip 由调用方收尾(finish_local_setter +/// 或构造器 time_clip)。 fn make_local_timestamp(y: f64, m: f64, d: f64, h: f64, min: f64, sec: f64, ms: f64) -> f64 { - // 远超包络的分量直接拒绝(避免后续整型运算溢出)。 - if y.abs() > 2e15 { + // 任一非有限分量按规范无效,同时封死后续整型饱和带。 + if !y.is_finite() + || !m.is_finite() + || !d.is_finite() + || !h.is_finite() + || !min.is_finite() + || !sec.is_finite() + || !ms.is_finite() + { return f64::NAN; } // 月溢出归一到 [0, 12),商进位到年份(负月同样成立)。 let m_norm = m.trunc().rem_euclid(12.0); let y_carry = (m.trunc() / 12.0).floor(); + + // 年与月进位远超包络时直接拒绝,避免日数公式乘积溢出。 + if y.abs() > 2e15 || y_carry.abs() > 2e15 { + return f64::NAN; + } let y = (y.trunc() + y_carry) as i64; // 基准取当月 1 号零点,全部分量统一折算为 i64 毫秒, @@ -223,7 +244,8 @@ fn make_local_timestamp(y: f64, m: f64, d: f64, h: f64, min: f64, sec: f64, ms: let day_ms = civil_day_count(y, m_norm as i64 + 1, 1) as f64 * 86_400_000.0; let time_ms = h.trunc() * 3_600_000.0 + min.trunc() * 60_000.0 + sec.trunc() * 1_000.0 + ms.trunc(); let naive_ms = day_ms + (d.trunc() - 1.0) * 86_400_000.0 + time_ms; - if !naive_ms.is_finite() { + // 组合超 TimeClip 包络即无效,同时兜住 i64 转换的饱和带。 + if !naive_ms.is_finite() || naive_ms.abs() > MS_LIMIT as f64 { return f64::NAN; } let naive_ms = naive_ms as i64; @@ -363,7 +385,8 @@ pub fn date_constructor(vm: &mut H, args: &[u8]) -> NativeResult { { f64::NAN } else { - make_local_timestamp(y_val, m_val, d_val, h_val, min_val, sec_val, ms_val) + // 组合结果与 setter 同收尾:TimeClip 包络外(含非有限)归 NaN。 + time_clip(make_local_timestamp(y_val, m_val, d_val, h_val, min_val, sec_val, ms_val)) } } else { let val = vm.reg(args[1]); diff --git a/crates/oxide_builtins/tests/builtin_overflow_boundary_tests.rs b/crates/oxide_builtins/tests/builtin_overflow_boundary_tests.rs index 5cf551bf..6523c0aa 100644 --- a/crates/oxide_builtins/tests/builtin_overflow_boundary_tests.rs +++ b/crates/oxide_builtins/tests/builtin_overflow_boundary_tests.rs @@ -98,3 +98,21 @@ fn typed_array_fill_finite_negative_huge_start() { let s = eval_str("new Uint8Array(4).fill(0, -1e300).join(',')").unwrap(); assert_eq!(s, "0,0,0,0"); } + +// 月份分量 ±Infinity/有限巨值:构造器与 setter 均直接 NaN(debug 构建不得整型溢出)。 +#[test] +fn date_constructor_huge_month_is_nan() { + assert_nan("new Date(2020, Infinity, 1).getTime()"); + assert_nan("new Date(2020, -Infinity, 1).getTime()"); + assert_nan("new Date(2020, 1e300, 1).getTime()"); + assert_nan("new Date(2020, 0, 1).setMonth(1e300)"); +} + +// 年分量超 TimeClip 包络:构造器组合结果归 NaN;包络内邻近组合不损伤 +// (valueOf 直接读槽位,不经 chrono 年份上限,可钉超 262142 年存储值)。 +#[test] +fn date_constructor_out_of_envelope_year_is_nan() { + assert_nan("new Date(275760, 8, 13, 1).getTime()"); + let (_vm, result) = eval("new Date(275759, 11, 31).valueOf()").unwrap(); + assert!(result.is_double() && result.as_double().is_finite(), "包络内组合应得有限时间戳"); +}