mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-24 02:31:13 +08:00
fix: Duration add/subtract 范围判据按存储分量加权和(对齐 IsValidDuration)
逐分量尾检误接受加权和落在 [2^53 s, 2^53 s + 86400 s) 的输入(各分量 均低于 2^53 秒上限而总和越界);按输入精确和校验又漏掉精确和低于上限、 而存储分量经 f64 舍入后达上限的情形。改按平衡后存储时间分量(含 days) 的纳秒加权和与 2^53 秒上限比较——校验对象与内部槽位的 float64 可表示 整数存储语义一致,判据与构造器/from 的 validate_duration_values 同式。 补加权和边界钉(恰 = 2^53 s 抛 RangeError、2^53 s − 1 s 通过并平衡到 day 级)。
This commit is contained in:
@@ -3850,7 +3850,7 @@ pub fn duration_add<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
/// 1. 最大单位取两侧最大的非零时间单位(days=3 最大,ns=9 最小)。
|
||||
/// 2. 分量在 i128 上按纳秒精确求和(f64 分量是精确整数,数学值不受 2^53 截断)。
|
||||
/// 3. 从总纳秒向下按同号截断分解,进位到最大单位为止。
|
||||
/// 4. 逐分量按纳秒刻度校验 2^53 秒上限。
|
||||
/// 4. 平衡后存储时间分量(含 days,𝔽 舍入)的纳秒加权和校验 2^53 秒上限。
|
||||
///
|
||||
/// # 边界与前提
|
||||
/// - 任一侧含日历单位抛 RangeError(本批无 relativeTo 支持)。
|
||||
@@ -3907,21 +3907,16 @@ fn add_duration_values<H: VmHost>(vm: &mut H, receiver: &[f64; 10], other: &[f64
|
||||
rem /= base;
|
||||
unit -= 1;
|
||||
}
|
||||
// 范围校验:对 𝔽 舍入后的每个分量按其纳秒刻度检查是否达到 2^53 秒上限。
|
||||
const MAX_TIME_NANOSECONDS: f64 = (1_i128 << 53) as f64 * 1_000_000_000.0;
|
||||
const UNIT_SCALES: [f64; 7] = [
|
||||
86_400_000_000_000.0,
|
||||
3_600_000_000_000.0,
|
||||
60_000_000_000.0,
|
||||
1_000_000_000.0,
|
||||
1_000_000.0,
|
||||
1_000.0,
|
||||
1.0,
|
||||
];
|
||||
for (index, scale) in (3..10).zip(UNIT_SCALES) {
|
||||
if values[index] != 0.0 && values[index].abs() * scale >= MAX_TIME_NANOSECONDS {
|
||||
return Err(crate::error::create_range_error(vm, "duration time fields are out of range"));
|
||||
}
|
||||
// 范围校验:判据作用在存储分量(平衡后 𝔽 舍入结果)的纳秒加权和上,而非输入
|
||||
// 精确和——内部槽位按 float64 可表示整数存储,转换可有损,舍入后分量可能越界;
|
||||
// 加权和同时覆盖逐分量检查的盲区(总和落在 [2^53 s, 2^53 s + 86400 s) 而各分量
|
||||
// 均低于上限的缝隙带)。
|
||||
const MAX_TIME_NANOSECONDS: i128 = (1_i128 << 53) * 1_000_000_000;
|
||||
let Some(stored_ns) = duration_time_nanoseconds(&values) else {
|
||||
return Err(crate::error::create_range_error(vm, "invalid duration"));
|
||||
};
|
||||
if stored_ns.abs() >= MAX_TIME_NANOSECONDS {
|
||||
return Err(crate::error::create_range_error(vm, "duration time fields are out of range"));
|
||||
}
|
||||
Ok(values)
|
||||
}
|
||||
|
||||
@@ -2521,6 +2521,30 @@ fn duration_subtract_equals_add_negated() {
|
||||
assert_eq!(str_val(&vm, r), "RangeError");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn duration_add_weighted_sum_range_boundary() {
|
||||
let mut vm = Vm::new();
|
||||
// 范围判据按平衡后时间分量加权和(spec IsValidDuration),非逐分量:
|
||||
// days 和 104249991374 + 时间 27392 s 恰 = 2^53 s → RangeError(逐分量均低于上限);
|
||||
// 同输入减 1 s(总和 2^53 s − 1 s)→ 通过并平衡到 day/hour/minute/second。
|
||||
let r = eval(
|
||||
&mut vm,
|
||||
"(() => {
|
||||
const a = new Temporal.Duration(0, 0, 0, 52083464098, 3, 53, 20);
|
||||
const b = {days: 52166527276, hours: 3, minutes: 43, seconds: 12};
|
||||
const kind = (fn) => { try { fn(); return 'no'; } catch (e) { return e.constructor.name; } };
|
||||
return [
|
||||
kind(() => a.add(b)),
|
||||
kind(() => new Temporal.Duration(0, 0, 0, -52083464098, -3, -53, -20).subtract(b)),
|
||||
kind(() => new Temporal.Duration(0, 0, 0, 52083464098, 3, 53, 19).add(b)),
|
||||
new Temporal.Duration(0, 0, 0, 52083464098, 3, 53, 19).add(b).toString(),
|
||||
].join('|');
|
||||
})()",
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(str_val(&vm, r), "RangeError|RangeError|no|P104249991374DT7H36M31S");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn duration_equals_compares_all_components() {
|
||||
let mut vm = Vm::new();
|
||||
|
||||
Reference in New Issue
Block a user