From fea24b6d26d5c8cd0ae08c9b135cf6f4ca2a7c86 Mon Sep 17 00:00:00 2001 From: pjh456 <147148383@qq.com> Date: Mon, 21 Sep 2026 14:11:20 +0000 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=20oxide=5Fbuiltins=20?= =?UTF-8?q?=E6=95=B4=E6=95=B0=E6=BA=A2=E5=87=BA=20panic=20=E7=82=B9?= =?UTF-8?q?=E4=B8=8E=E5=AE=89=E5=85=A8=E8=BD=AC=E6=8D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/oxide_builtins/src/array/element.rs | 8 ++- crates/oxide_builtins/src/data_view.rs | 2 +- crates/oxide_builtins/src/date.rs | 24 ++++++- crates/oxide_builtins/src/global/annex_b.rs | 5 +- crates/oxide_builtins/src/global/uri.rs | 5 +- crates/oxide_builtins/src/json.rs | 2 +- crates/oxide_builtins/src/number.rs | 2 +- crates/oxide_builtins/src/object.rs | 2 +- crates/oxide_builtins/src/string/basic.rs | 68 +++++++++++++++---- crates/oxide_builtins/src/string/common.rs | 5 +- crates/oxide_builtins/src/string/locale.rs | 5 +- crates/oxide_builtins/src/string/regex.rs | 32 +++++++-- crates/oxide_builtins/src/temporal/common.rs | 32 +++++++-- .../oxide_builtins/src/temporal/plain_date.rs | 27 ++++++-- .../src/temporal/plain_date_time.rs | 30 +++++--- .../src/temporal/plain_month_day.rs | 32 +++++++-- .../oxide_builtins/src/temporal/plain_time.rs | 46 +++++++++---- .../src/temporal/plain_year_month.rs | 21 ++++-- crates/oxide_builtins/src/typed_array.rs | 34 ++++++---- 19 files changed, 291 insertions(+), 91 deletions(-) diff --git a/crates/oxide_builtins/src/array/element.rs b/crates/oxide_builtins/src/array/element.rs index e8f73a5a..0f310ecc 100644 --- a/crates/oxide_builtins/src/array/element.rs +++ b/crates/oxide_builtins/src/array/element.rs @@ -859,7 +859,9 @@ pub fn array_flat(vm: &mut H, args: &[u8]) -> NativeResult { } // HasProperty 门控:洞位不写目标(结果恒紧凑)。 if !arraylike_index_present(vm, src, i) { - stack.last_mut().unwrap().3 = i + 1; + if let Some(top) = stack.last_mut() { + top.3 = i + 1; + } continue; } let elem = arraylike_get_or_err!(vm, src, i); @@ -869,7 +871,9 @@ pub fn array_flat(vm: &mut H, args: &[u8]) -> NativeResult { && !e_ptr.is_null() && unsafe { &*e_ptr }.is_array() && !stack.iter().any(|f| std::ptr::eq(f.0, e_ptr)); - stack.last_mut().unwrap().3 = i + 1; + if let Some(top) = stack.last_mut() { + top.3 = i + 1; + } if descend { // elementLen = LengthOfArrayLike(element)。 let (_, elen, _) = match get_this_arraylike(vm, elem) { diff --git a/crates/oxide_builtins/src/data_view.rs b/crates/oxide_builtins/src/data_view.rs index 74913c8d..cdb1b41e 100644 --- a/crates/oxide_builtins/src/data_view.rs +++ b/crates/oxide_builtins/src/data_view.rs @@ -511,7 +511,7 @@ fn low64(vm: &mut H, val: JsValue) -> u64 { let v = vm.bigint_value(val); (v & (BigInt::from(u64::MAX))) .to_u64() - .expect("与 u64::MAX 掩码后恒在 u64 范围") + .unwrap_or(u64::MAX) } /// `DataView.prototype.toString`:校验 receiver 后返回 `[object DataView]`。 diff --git a/crates/oxide_builtins/src/date.rs b/crates/oxide_builtins/src/date.rs index 642bf2d9..2df70653 100644 --- a/crates/oxide_builtins/src/date.rs +++ b/crates/oxide_builtins/src/date.rs @@ -67,14 +67,25 @@ fn dt_from_ms(ms: f64) -> Option> { if !ms.is_finite() { return None; } - DateTime::from_timestamp_millis(ms as i64) + // f64 截断到 i64 可能溢出(超 ±9.22e18 ms),先做有界转换。 + let ms_i64 = if ms >= i64::MIN as f64 && ms <= i64::MAX as f64 { + ms as i64 + } else { + return None; + }; + DateTime::from_timestamp_millis(ms_i64) } fn dt_from_ms_local(ms: f64) -> Option> { if !ms.is_finite() { return None; } - DateTime::from_timestamp_millis(ms as i64).map(|dt| dt.with_timezone(&Local)) + let ms_i64 = if ms >= i64::MIN as f64 && ms <= i64::MAX as f64 { + ms as i64 + } else { + return None; + }; + DateTime::from_timestamp_millis(ms_i64).map(|dt| dt.with_timezone(&Local)) } fn naive_from_ms(ms: f64) -> Option { @@ -1028,7 +1039,14 @@ pub fn date_set_year(vm: &mut H, args: &[u8]) -> NativeResult { Some(d) => d, None => return NativeResult::Ok(JsValue::float(f64::NAN)), }; - let y = y_num.trunc() as i32; + let y_trunc = y_num.trunc(); + // f64 截断后可能超出 i32 范围,先做有界转换。 + let y = if y_trunc >= i32::MIN as f64 && y_trunc <= i32::MAX as f64 { + y_trunc as i32 + } else { + set_timestamp(obj, f64::NAN); + return NativeResult::Ok(JsValue::float(f64::NAN)); + }; let full_year = if (0..=99).contains(&y) { y + 1900 } else { y }; let nd = dt.with_year(full_year).unwrap_or(dt); let ts = nd.timestamp_millis() as f64; diff --git a/crates/oxide_builtins/src/global/annex_b.rs b/crates/oxide_builtins/src/global/annex_b.rs index 62955316..86baefaa 100644 --- a/crates/oxide_builtins/src/global/annex_b.rs +++ b/crates/oxide_builtins/src/global/annex_b.rs @@ -76,7 +76,10 @@ fn unescape_string(input: &str) -> String { } } - let ch = input[i..].chars().next().expect("valid utf-8 char boundary"); + let ch = match input[i..].chars().next() { + Some(c) => c, + None => break, + }; out.push(ch); i += ch.len_utf8(); } diff --git a/crates/oxide_builtins/src/global/uri.rs b/crates/oxide_builtins/src/global/uri.rs index 7154d615..3a90296a 100644 --- a/crates/oxide_builtins/src/global/uri.rs +++ b/crates/oxide_builtins/src/global/uri.rs @@ -77,7 +77,10 @@ fn decode_uri_units(input: &[u16], preserve_reserved: bool) -> Result, } let decoded = std::str::from_utf8(&encoded).map_err(|_| ())?; - let ch = decoded.chars().next().expect("单字节序解出恰一个码点"); + let ch = match decoded.chars().next() { + Some(c) => c, + None => continue, + }; if preserve_reserved && ch.is_ascii() && URI_RESERVED.contains(ch) { out.extend_from_slice(&input[raw_start..i]); continue; diff --git a/crates/oxide_builtins/src/json.rs b/crates/oxide_builtins/src/json.rs index 2f21b89c..1dc7197e 100644 --- a/crates/oxide_builtins/src/json.rs +++ b/crates/oxide_builtins/src/json.rs @@ -442,7 +442,7 @@ fn jsvalue_to_json( } else if val.is_bool() { out.push_str(if val.as_bool() { "true" } else { "false" }); } else if val.is_int() { - write!(out, "{}", val.as_int()).unwrap(); + let _ = write!(out, "{}", val.as_int()); } else if val.is_double() { let n = val.as_double(); if !n.is_finite() { diff --git a/crates/oxide_builtins/src/number.rs b/crates/oxide_builtins/src/number.rs index 24a34f13..271193ad 100644 --- a/crates/oxide_builtins/src/number.rs +++ b/crates/oxide_builtins/src/number.rs @@ -197,7 +197,7 @@ pub fn number_parse_int(vm: &mut H, args: &[u8]) -> NativeResult { } else { let mut acc = 0.0f64; for c in prefix.chars() { - acc = acc * radix as f64 + c.to_digit(radix).unwrap() as f64; + acc = acc * radix as f64 + c.to_digit(radix).unwrap_or(0) as f64; } acc }; diff --git a/crates/oxide_builtins/src/object.rs b/crates/oxide_builtins/src/object.rs index 43be0feb..796f439f 100644 --- a/crates/oxide_builtins/src/object.rs +++ b/crates/oxide_builtins/src/object.rs @@ -87,7 +87,7 @@ fn int_or_string_index(vm: &H, si: u32) -> Option { return Some(int_key_value(si)); } let key = vm.kernel_core().perm_interner().lookup(si)?; - is_integer_index(key).then(|| key.parse::().unwrap()) + is_integer_index(key).then(|| key.parse::().ok()).flatten() } /// 键 si 构造为 JS 可见字符串值(Object.keys / ownKeys / entries 族): diff --git a/crates/oxide_builtins/src/string/basic.rs b/crates/oxide_builtins/src/string/basic.rs index 1f91037d..1f631a6a 100644 --- a/crates/oxide_builtins/src/string/basic.rs +++ b/crates/oxide_builtins/src/string/basic.rs @@ -11,6 +11,46 @@ use super::{ this_units, }; +/// 安全地将 f64 转为 usize:NaN/±Inf/负值→0,超 u64 上限→usize::MAX。 +fn f64_to_usafe_usize(v: f64) -> usize { + if v.is_finite() && v >= 0.0 { + let u = v as u64; + if u <= usize::MAX as u64 { + u as usize + } else { + usize::MAX + } + } else { + 0 + } +} + +/// 安全地将 f64 转为 i32:NaN/±Inf→0,超 i32 范围→饱和。 +fn f64_to_i32(v: f64) -> i32 { + if v.is_finite() && v >= i32::MIN as f64 && v <= i32::MAX as f64 { + v as i32 + } else if v.is_nan() || v.is_infinite() { + 0 + } else if v > 0.0 { + i32::MAX + } else { + i32::MIN + } +} + +/// 安全地将 f64 转为 isize:NaN/±Inf→0,超 isize 范围→饱和。 +fn f64_to_isize(v: f64) -> isize { + if v.is_finite() && v >= isize::MIN as f64 && v <= isize::MAX as f64 { + v as isize + } else if v.is_nan() || v.is_infinite() { + 0 + } else if v > 0.0 { + isize::MAX + } else { + isize::MIN + } +} + // ── 静态方法 / 构造 ───────────────────────────────────────────────────── /// `String.fromCharCode(...codes)`:把各参数按 ToUint32 低 16 位转为单元拼接 @@ -168,7 +208,7 @@ pub fn string_index_of(vm: &mut H, args: &[u8]) -> NativeResult { let search_val = if args.len() >= 2 { vm.reg(args[1]) } else { JsValue::undefined() }; let search: Vec = try_string!(as_units(vm, search_val)).into_owned(); let pos_raw = if args.len() > 2 { - vm.coerce_number_bounded(vm.reg(args[2])).unwrap_or(f64::NAN) as usize + f64_to_usafe_usize(vm.coerce_number_bounded(vm.reg(args[2])).unwrap_or(f64::NAN)) } else { 0 }; @@ -192,7 +232,7 @@ pub fn string_includes(vm: &mut H, args: &[u8]) -> NativeResult { let search_val = if args.len() >= 2 { vm.reg(args[1]) } else { JsValue::undefined() }; let search: Vec = try_string!(as_units(vm, search_val)).into_owned(); let pos_raw = if args.len() > 2 { - vm.coerce_number_bounded(vm.reg(args[2])).unwrap_or(f64::NAN) as usize + f64_to_usafe_usize(vm.coerce_number_bounded(vm.reg(args[2])).unwrap_or(f64::NAN)) } else { 0 }; @@ -210,7 +250,7 @@ pub fn string_char_at(vm: &mut H, args: &[u8]) -> NativeResult { builtins_debug!("String.prototype.charAt called with {} args", args.len()); // index 可能触发对象 ToNumber(&mut 路径),先行转换。 let idx = if args.len() >= 2 { - vm.coerce_number_bounded(vm.reg(args[1])).unwrap_or(f64::NAN) as i32 + f64_to_i32(vm.coerce_number_bounded(vm.reg(args[1])).unwrap_or(f64::NAN)) } else { 0 }; @@ -231,7 +271,7 @@ pub fn string_char_code_at(vm: &mut H, args: &[u8]) -> NativeResult { builtins_debug!("String.prototype.charCodeAt called with {} args", args.len()); // index 可能触发对象 ToNumber(&mut 路径),先行转换。 let idx = if args.len() >= 2 { - vm.coerce_number_bounded(vm.reg(args[1])).unwrap_or(f64::NAN) as i32 + f64_to_i32(vm.coerce_number_bounded(vm.reg(args[1])).unwrap_or(f64::NAN)) } else { 0 }; @@ -347,12 +387,12 @@ pub fn string_slice(vm: &mut H, args: &[u8]) -> NativeResult { builtins_debug!("String.prototype.slice called with {} args", args.len()); // 位置参数先行(&mut 转换),后借 this 取子串。 let start_raw = if args.len() > 1 { - Some(vm.coerce_number_bounded(vm.reg(args[1])).unwrap_or(f64::NAN) as i32) + Some(f64_to_i32(vm.coerce_number_bounded(vm.reg(args[1])).unwrap_or(f64::NAN))) } else { None }; let end_raw = if args.len() > 2 { - Some(vm.coerce_number_bounded(vm.reg(args[2])).unwrap_or(f64::NAN) as i32) + Some(f64_to_i32(vm.coerce_number_bounded(vm.reg(args[2])).unwrap_or(f64::NAN))) } else { None }; @@ -436,7 +476,7 @@ pub fn string_substr(vm: &mut H, args: &[u8]) -> NativeResult { let len = s.len() as isize; let start = match start_arg { Some(v) => { - let n = oxide_runtime_api::to_integer_or_infinity(v) as isize; + let n = f64_to_isize(oxide_runtime_api::to_integer_or_infinity(v)); if n < 0 { (len + n).max(0) } else { @@ -446,7 +486,7 @@ pub fn string_substr(vm: &mut H, args: &[u8]) -> NativeResult { None => 0, } as usize; let length = match length_arg { - Some(v) => (oxide_runtime_api::to_integer_or_infinity(v) as isize).max(0) as usize, + Some(v) => f64_to_isize(oxide_runtime_api::to_integer_or_infinity(v)).max(0) as usize, None => len as usize - start, }; let count = length.min(len as usize - start); @@ -459,7 +499,7 @@ pub fn string_substr(vm: &mut H, args: &[u8]) -> NativeResult { pub fn string_at(vm: &mut H, args: &[u8]) -> NativeResult { builtins_debug!("String.prototype.at called with {} args", args.len()); let idx = if args.len() > 1 { - oxide_runtime_api::to_integer_or_infinity(vm.reg(args[1])) as i32 + f64_to_i32(oxide_runtime_api::to_integer_or_infinity(vm.reg(args[1]))) } else { 0 }; @@ -537,7 +577,7 @@ pub fn string_repeat(vm: &mut H, args: &[u8]) -> NativeResult { builtins_debug!("String.prototype.repeat called with {} args", args.len()); // count 转换先行(&mut 路径),后借 this 重复。 let n = if args.len() > 1 { - (vm.coerce_number_bounded(vm.reg(args[1])).unwrap_or(f64::NAN) as usize).min(10000) + f64_to_usafe_usize(vm.coerce_number_bounded(vm.reg(args[1])).unwrap_or(f64::NAN)).min(10000) } else { 1 }; @@ -555,7 +595,7 @@ pub fn string_pad_start(vm: &mut H, args: &[u8]) -> NativeResult { builtins_debug!("String.prototype.padStart called with {} args", args.len()); // 参数转换先行(&mut 路径):targetLength 与 padString 均可能触发对象转换。 let target_arg = if args.len() > 1 { - Some(vm.coerce_number_bounded(vm.reg(args[1])).unwrap_or(f64::NAN) as usize) + Some(f64_to_usafe_usize(vm.coerce_number_bounded(vm.reg(args[1])).unwrap_or(f64::NAN))) } else { None }; @@ -596,7 +636,7 @@ pub fn string_pad_end(vm: &mut H, args: &[u8]) -> NativeResult { builtins_debug!("String.prototype.padEnd called with {} args", args.len()); // 参数转换先行(&mut 路径):targetLength 与 padString 均可能触发对象转换。 let target_arg = if args.len() > 1 { - Some(vm.coerce_number_bounded(vm.reg(args[1])).unwrap_or(f64::NAN) as usize) + Some(f64_to_usafe_usize(vm.coerce_number_bounded(vm.reg(args[1])).unwrap_or(f64::NAN))) } else { None }; @@ -639,7 +679,7 @@ pub fn string_starts_with(vm: &mut H, args: &[u8]) -> NativeResult { let search_val = if args.len() >= 2 { vm.reg(args[1]) } else { JsValue::undefined() }; let search: Vec = try_string!(as_units(vm, search_val)).into_owned(); let pos_raw = if args.len() > 2 { - vm.coerce_number_bounded(vm.reg(args[2])).unwrap_or(f64::NAN) as usize + f64_to_usafe_usize(vm.coerce_number_bounded(vm.reg(args[2])).unwrap_or(f64::NAN)) } else { 0 }; @@ -657,7 +697,7 @@ pub fn string_ends_with(vm: &mut H, args: &[u8]) -> NativeResult { let search_val = if args.len() >= 2 { vm.reg(args[1]) } else { JsValue::undefined() }; let search: Vec = try_string!(as_units(vm, search_val)).into_owned(); let end_pos_raw = if args.len() > 2 { - vm.coerce_number_bounded(vm.reg(args[2])).unwrap_or(f64::NAN) as usize + f64_to_usafe_usize(vm.coerce_number_bounded(vm.reg(args[2])).unwrap_or(f64::NAN)) } else { usize::MAX }; diff --git a/crates/oxide_builtins/src/string/common.rs b/crates/oxide_builtins/src/string/common.rs index 1f1a7c51..42233fe4 100644 --- a/crates/oxide_builtins/src/string/common.rs +++ b/crates/oxide_builtins/src/string/common.rs @@ -268,8 +268,9 @@ pub(crate) fn map_well_formed_segments(units: &[u16], mut f: impl FnMut(&str) -> i += 1; continue; } - let s = String::from_utf16(&units[seg_start..i]).expect("良形段 from_utf16 必成功"); - out.extend(f(&s).encode_utf16()); + if let Ok(s) = String::from_utf16(&units[seg_start..i]) { + out.extend(f(&s).encode_utf16()); + } } out } diff --git a/crates/oxide_builtins/src/string/locale.rs b/crates/oxide_builtins/src/string/locale.rs index 1cc45ef5..edbfb281 100644 --- a/crates/oxide_builtins/src/string/locale.rs +++ b/crates/oxide_builtins/src/string/locale.rs @@ -428,8 +428,9 @@ fn final_sigma_lower(units: &[u16]) -> Vec { if (0xD800..=0xDBFF).contains(&u) && i + 1 < units.len() && (0xDC00..=0xDFFF).contains(&units[i + 1]) { // 代理对整体解码为码点小写后回写 UTF-16。 let cp = (((u as u32) - 0xD800) << 10 | (units[i + 1] as u32 - 0xDC00)) + 0x10000; - let c = char::from_u32(cp).expect("合法代理对必解码为合法码点"); - out.extend(c.to_lowercase().collect::().encode_utf16()); + if let Some(c) = char::from_u32(cp) { + out.extend(c.to_lowercase().collect::().encode_utf16()); + } i += 2; continue; } diff --git a/crates/oxide_builtins/src/string/regex.rs b/crates/oxide_builtins/src/string/regex.rs index 80b321bf..0b2fb532 100644 --- a/crates/oxide_builtins/src/string/regex.rs +++ b/crates/oxide_builtins/src/string/regex.rs @@ -279,7 +279,8 @@ pub fn string_split(vm: &mut H, args: &[u8]) -> NativeResult { // 无编译正则的类正则对象(如 Object.create(RegExp.prototype))回退字符串路径, // 须按 ToString 文本切分,且转换(&mut 路径)须先于 this 借用完成。 let has_native_re = is_re && { - let re_ptr = sep_val.unwrap().as_js_object_ptr(); + let sep = sep_val.expect("separator present when is_re is true"); + let re_ptr = sep.as_js_object_ptr(); // SAFETY: is_re 已保证 sep_val 为非空对象且 proto 恒等 RegExp.prototype。 let re = unsafe { &*re_ptr }; re.native_fn().is_some() @@ -298,7 +299,8 @@ pub fn string_split(vm: &mut H, args: &[u8]) -> NativeResult { let sep_units: Vec = if is_undefined_sep || has_native_re { Vec::new() } else { - try_string!(as_units(vm, sep_val.unwrap())).into_owned() + let sep = sep_val.expect("separator present when not undefined and not native regexp"); + try_string!(as_units(vm, sep)).into_owned() }; let s: Vec = try_string!(this_units(vm, args)).into_owned(); // 规范:separator 为 undefined 时返回 [this]。 @@ -461,7 +463,10 @@ fn string_replace_impl(vm: &mut H, args: &[u8], all: bool) -> NativeR if has_native_re { let re_ptr = pattern_val.as_js_object_ptr(); let re = unsafe { &*re_ptr }; - let fn_ptr = re.native_fn().unwrap(); + let fn_ptr = match re.native_fn() { + Some(p) => p, + None => return NativeResult::Err(crate::error::create_type_error(vm, "expected compiled regexp")), + }; // SAFETY: fn_ptr 持有 regexp_constructor 存放的 `Box` 指针。 let regex = unsafe { &*(fn_ptr.as_ptr() as *const regress::Regex) }; return regex_replace_fn(vm, regex, &s, replacer_val, is_global, text_arg); @@ -496,7 +501,10 @@ fn string_replace_impl(vm: &mut H, args: &[u8], all: bool) -> NativeR if this_val.is_string() && has_native_re && replacement_is_string { let re_ptr = pattern_val.as_js_object_ptr(); let re = unsafe { &*re_ptr }; - let fn_ptr = re.native_fn().unwrap(); + let fn_ptr = match re.native_fn() { + Some(p) => p, + None => return NativeResult::Err(crate::error::create_type_error(vm, "expected compiled regexp")), + }; // SAFETY: fn_ptr 持有 regexp_constructor 存放的 `Box` 指针。 let regex = unsafe { &*(fn_ptr.as_ptr() as *const regress::Regex) }; let h = &*vm; @@ -530,7 +538,10 @@ fn string_replace_impl(vm: &mut H, args: &[u8], all: bool) -> NativeR if has_native_re { let re_ptr = pattern_val.as_js_object_ptr(); let re = unsafe { &*re_ptr }; - let fn_ptr = re.native_fn().unwrap(); + let fn_ptr = match re.native_fn() { + Some(p) => p, + None => return NativeResult::Err(crate::error::create_type_error(vm, "expected compiled regexp")), + }; // SAFETY: fn_ptr 持有 regexp_constructor 存放的 `Box` 指针。 let regex = unsafe { &*(fn_ptr.as_ptr() as *const regress::Regex) }; let result = regex_replace_manual_units(regex, &s, &replacement, is_global); @@ -564,7 +575,11 @@ pub fn string_match_fn(vm: &mut H, args: &[u8]) -> NativeResult { }; // global 标志前置读:flags 串共享借用与下方 text 转换的 &mut 借用不可重叠。 let is_global = if is_re { - let re_ptr = pattern_val.unwrap().as_js_object_ptr(); + let re_val = match pattern_val { + Some(v) => v, + None => return NativeResult::Err(crate::error::create_type_error(vm, "expected regexp")), + }; + let re_ptr = re_val.as_js_object_ptr(); // SAFETY: is_re 已保证 pattern_val 为非空对象且 proto 恒等 RegExp.prototype。 let re = unsafe { &*re_ptr }; re.native_fn().is_some() && crate::regexp::regexp_has_flag(vm, re, 'g') @@ -575,7 +590,10 @@ pub fn string_match_fn(vm: &mut H, args: &[u8]) -> NativeResult { if args.len() < 2 { return NativeResult::Ok(JsValue::null()); } - let pattern_val = pattern_val.unwrap(); + let pattern_val = match pattern_val { + Some(v) => v, + None => return NativeResult::Err(crate::error::create_type_error(vm, "expected pattern")), + }; if is_re { let re_ptr = pattern_val.as_js_object_ptr(); let re = unsafe { &*re_ptr }; diff --git a/crates/oxide_builtins/src/temporal/common.rs b/crates/oxide_builtins/src/temporal/common.rs index 59b0f9c4..1bea5462 100644 --- a/crates/oxide_builtins/src/temporal/common.rs +++ b/crates/oxide_builtins/src/temporal/common.rs @@ -63,7 +63,12 @@ pub(crate) fn get_instant_epoch_ns(obj: &JsObject) -> Option { return Some(unsafe { oxide_runtime_api::bigint_data(value) }.to_i128().unwrap_or(i128::MAX)); } if value.is_int() || value.is_double() { - return Some(to_number(value) as i128); + let n = to_number(value); + // f64 转 i128:超大值饱和到 i128 边界。 + if n.is_finite() && n >= i128::MIN as f64 && n <= i128::MAX as f64 { + return Some(n as i128); + } + return Some(i128::MAX); } None } @@ -403,9 +408,28 @@ pub(crate) fn plain_date_time_object_parts( values[8] = values[8].clamp(0.0, 999.0); } let [year, month, day, hour, minute, second, millisecond, microsecond, nanosecond] = values; - let (year, month, day) = (year as i32, month as u32, day as u32); - let (hour, minute, second) = (hour as u32, minute as u32, second as u32); - let (millisecond, microsecond, nanosecond) = (millisecond as u32, microsecond as u32, nanosecond as u32); + // 分量值可能超出目标整型范围(f64 截断后仍超 i32/u32 上限),先做有界转换。 + // 超出范围后 valid_iso_date / valid_plain_time 会拒绝,此处兜住 debug 模式 panic。 + let year = if year.is_finite() && year >= i32::MIN as f64 && year <= i32::MAX as f64 { + year as i32 + } else { + return Err(crate::error::create_range_error(vm, "invalid date-time component")); + }; + let mut to_u32 = |v: f64| -> Result { + if v.is_finite() && v >= 0.0 && v <= u32::MAX as f64 { + Ok(v as u32) + } else { + Err(crate::error::create_range_error(vm, "invalid date-time component")) + } + }; + let month = to_u32(month)?; + let day = to_u32(day)?; + let hour = to_u32(hour)?; + let minute = to_u32(minute)?; + let second = to_u32(second)?; + let millisecond = to_u32(millisecond)?; + let microsecond = to_u32(microsecond)?; + let nanosecond = to_u32(nanosecond)?; if !valid_iso_date(year, month, day) || (!ignore_time && !valid_plain_time(hour, minute, second, millisecond, microsecond, nanosecond)) { diff --git a/crates/oxide_builtins/src/temporal/plain_date.rs b/crates/oxide_builtins/src/temporal/plain_date.rs index 49588c60..839ef18d 100644 --- a/crates/oxide_builtins/src/temporal/plain_date.rs +++ b/crates/oxide_builtins/src/temporal/plain_date.rs @@ -147,18 +147,33 @@ pub fn plain_date_constructor(vm: &mut H, args: &[u8]) -> NativeResul } // 分量转换序:year → month → day;缺参归 undefined 经 number-only 路径 // 统一落 RangeError(日期分量无缺省值,缺参不是合法分量)。 - let year = native_try!(temporal_number_component( + let year_raw = native_try!(temporal_number_component( vm, if args.len() > 1 { vm.reg(args[1]) } else { JsValue::undefined() }, - )) as i32; - let month = native_try!(temporal_number_component( + )); + let month_raw = native_try!(temporal_number_component( vm, if args.len() > 2 { vm.reg(args[2]) } else { JsValue::undefined() }, - )) as u32; - let day = native_try!(temporal_number_component( + )); + let day_raw = native_try!(temporal_number_component( vm, if args.len() > 3 { vm.reg(args[3]) } else { JsValue::undefined() }, - )) as u32; + )); + // f64 截断后可能超出目标整型范围,先做有界转换。 + let year = if year_raw >= i32::MIN as f64 && year_raw <= i32::MAX as f64 { + year_raw as i32 + } else { + return NativeResult::Err(crate::error::create_range_error(vm, "invalid year")); + }; + let mut to_u32 = |v: f64| -> Result { + if v >= 0.0 && v <= u32::MAX as f64 { + Ok(v as u32) + } else { + Err(crate::error::create_range_error(vm, "invalid date component")) + } + }; + let month = native_try!(to_u32(month_raw)); + let day = native_try!(to_u32(day_raw)); if !valid_iso_date(year, month, day) { return NativeResult::Err(crate::error::create_range_error(vm, "invalid ISO date")); } diff --git a/crates/oxide_builtins/src/temporal/plain_date_time.rs b/crates/oxide_builtins/src/temporal/plain_date_time.rs index 852c3e97..fb52b5d5 100644 --- a/crates/oxide_builtins/src/temporal/plain_date_time.rs +++ b/crates/oxide_builtins/src/temporal/plain_date_time.rs @@ -66,15 +66,27 @@ pub fn plain_date_time_constructor(vm: &mut H, args: &[u8]) -> Native return NativeResult::Err(crate::error::create_range_error(vm, "invalid date-time component")); } - let year = year_value as i32; - let month = month_value as u32; - let day = day_value as u32; - let hour = hour_value as u32; - let minute = minute_value as u32; - let second = second_value as u32; - let millisecond = millisecond_value as u32; - let microsecond = microsecond_value as u32; - let nanosecond = nanosecond_value as u32; + // f64 截断后可能超出目标整型范围,先做有界转换。 + let year = if year_value >= i32::MIN as f64 && year_value <= i32::MAX as f64 { + year_value as i32 + } else { + return NativeResult::Err(crate::error::create_range_error(vm, "invalid year")); + }; + let mut to_u32 = |v: f64| -> Result { + if v >= 0.0 && v <= u32::MAX as f64 { + Ok(v as u32) + } else { + Err(crate::error::create_range_error(vm, "invalid date-time component")) + } + }; + let month = native_try!(to_u32(month_value)); + let day = native_try!(to_u32(day_value)); + let hour = native_try!(to_u32(hour_value)); + let minute = native_try!(to_u32(minute_value)); + let second = native_try!(to_u32(second_value)); + let millisecond = native_try!(to_u32(millisecond_value)); + let microsecond = native_try!(to_u32(microsecond_value)); + let nanosecond = native_try!(to_u32(nanosecond_value)); let non_negative = components[1..].iter().all(|value| *value >= 0.0); if !non_negative || !valid_iso_date(year, month, day) diff --git a/crates/oxide_builtins/src/temporal/plain_month_day.rs b/crates/oxide_builtins/src/temporal/plain_month_day.rs index 47915d71..17c1b2d7 100644 --- a/crates/oxide_builtins/src/temporal/plain_month_day.rs +++ b/crates/oxide_builtins/src/temporal/plain_month_day.rs @@ -76,14 +76,29 @@ pub fn plain_month_day_constructor(vm: &mut H, args: &[u8]) -> Native vm, if args.len() > 2 { vm.reg(args[2]) } else { JsValue::undefined() }, )); + // f64 截断后可能超出 u32 范围,先做有界转换。 + let mut to_u32 = |v: f64| -> Result { + if v >= 0.0 && v <= u32::MAX as f64 { + Ok(v as u32) + } else { + Err(crate::error::create_range_error(vm, "invalid date component")) + } + }; + let month = native_try!(to_u32(month)); + let day = native_try!(to_u32(day)); let calendar_raw = if args.len() > 3 { vm.reg(args[3]) } else { JsValue::undefined() }; let calendar = native_try!(temporal_constructor_calendar_id(vm, calendar_raw)); - let ref_year = if args.len() > 4 && !vm.reg(args[4]).is_undefined() { - native_try!(temporal_number_component(vm, vm.reg(args[4]))) as i32 + let ref_year_raw = if args.len() > 4 && !vm.reg(args[4]).is_undefined() { + native_try!(temporal_number_component(vm, vm.reg(args[4]))) } else { - 1972 + 1972.0 }; - if !valid_iso_date(ref_year, month as u32, day as u32) { + let ref_year = if ref_year_raw >= i32::MIN as f64 && ref_year_raw <= i32::MAX as f64 { + ref_year_raw as i32 + } else { + return NativeResult::Err(crate::error::create_range_error(vm, "invalid year")); + }; + if !valid_iso_date(ref_year, month, day) { return NativeResult::Err(crate::error::create_range_error(vm, "invalid ISO date")); } // 表示范围(ISODateWithinLimits):-271821-04-19 … +275760-09-13。 @@ -97,8 +112,8 @@ pub fn plain_month_day_constructor(vm: &mut H, args: &[u8]) -> Native args, JsObject::OBJ_TYPE_PLAIN_MONTH_DAY, [ - JsValue::float(month), - JsValue::float(day), + JsValue::float(month as f64), + JsValue::float(day as f64), JsValue::float(ref_year as f64), calendar_value, ], @@ -516,7 +531,10 @@ pub fn plain_month_day_from(vm: &mut H, args: &[u8]) -> NativeResult } code } else { - let f = month_f.expect("month or monthCode is required checked above"); + let f = match month_f { + Some(v) => v, + None => return NativeResult::Err(crate::error::create_type_error(vm, "month is required")), + }; if f < 1.0 { return NativeResult::Err(crate::error::create_range_error(vm, "invalid month")); } diff --git a/crates/oxide_builtins/src/temporal/plain_time.rs b/crates/oxide_builtins/src/temporal/plain_time.rs index 4d18cd04..22e76f3e 100644 --- a/crates/oxide_builtins/src/temporal/plain_time.rs +++ b/crates/oxide_builtins/src/temporal/plain_time.rs @@ -43,15 +43,29 @@ fn is_ambiguous_date_string(s: &str) -> bool { }; match s.len() { 4 if all_digits(0..4) => { - let (month, day) = (two_digits(0).unwrap(), two_digits(2).unwrap()); - day_in_month(month, day) + match (two_digits(0), two_digits(2)) { + (Some(month), Some(day)) => day_in_month(month, day), + _ => false, + } + } + 6 if all_digits(0..6) => { + match two_digits(4) { + Some(m) => (1..=12).contains(&m), + None => false, + } } - 6 if all_digits(0..6) => (1..=12).contains(&two_digits(4).unwrap()), 5 if bytes[2] == b'-' && all_digits(0..2) && all_digits(3..5) => { - let (month, day) = (two_digits(0).unwrap(), two_digits(3).unwrap()); - day_in_month(month, day) + match (two_digits(0), two_digits(3)) { + (Some(month), Some(day)) => day_in_month(month, day), + _ => false, + } + } + 7 if bytes[4] == b'-' && all_digits(0..4) && all_digits(5..7) => { + match two_digits(5) { + Some(m) => (1..=12).contains(&m), + None => false, + } } - 7 if bytes[4] == b'-' && all_digits(0..4) && all_digits(5..7) => (1..=12).contains(&two_digits(5).unwrap()), _ => false, } } @@ -297,12 +311,20 @@ pub fn plain_time_constructor(vm: &mut H, args: &[u8]) -> NativeResul { return NativeResult::Err(crate::error::create_range_error(vm, "invalid time component")); } - let hour = hour_value as u32; - let minute = minute_value as u32; - let second = second_value as u32; - let ms = ms_value as u32; - let us = us_value as u32; - let ns = ns_value as u32; + // f64 截断后可能超出 u32 范围,先做有界转换。 + let mut to_u32 = |v: f64| -> Result { + if v >= 0.0 && v <= u32::MAX as f64 { + Ok(v as u32) + } else { + Err(crate::error::create_range_error(vm, "invalid time component")) + } + }; + let hour = native_try!(to_u32(hour_value)); + let minute = native_try!(to_u32(minute_value)); + let second = native_try!(to_u32(second_value)); + let ms = native_try!(to_u32(ms_value)); + let us = native_try!(to_u32(us_value)); + let ns = native_try!(to_u32(ns_value)); if !valid_plain_time(hour, minute, second, ms, us, ns) { return NativeResult::Err(crate::error::create_range_error(vm, "invalid time component")); } diff --git a/crates/oxide_builtins/src/temporal/plain_year_month.rs b/crates/oxide_builtins/src/temporal/plain_year_month.rs index 4ab562ae..eeee3836 100644 --- a/crates/oxide_builtins/src/temporal/plain_year_month.rs +++ b/crates/oxide_builtins/src/temporal/plain_year_month.rs @@ -398,7 +398,12 @@ pub fn plain_year_month_from(vm: &mut H, args: &[u8]) -> NativeResult None => (None, false), }; // year 转换(TypeError/RangeError)在 monthCode 语法之后。 - let year = native_try!(temporal_number_component(vm, year_raw)) as i32; + let year_raw = native_try!(temporal_number_component(vm, year_raw)); + let year = if year_raw >= i32::MIN as f64 && year_raw <= i32::MAX as f64 { + year_raw as i32 + } else { + return NativeResult::Err(crate::error::create_range_error(vm, "invalid year")); + }; let month_f = match month_raw.is_undefined() { true => None, false => Some(native_try!(temporal_number_component(vm, month_raw))), @@ -419,7 +424,10 @@ pub fn plain_year_month_from(vm: &mut H, args: &[u8]) -> NativeResult } code } else { - let f = month_f.expect("month or monthCode is required checked above"); + let f = match month_f { + Some(v) => v, + None => return NativeResult::Err(crate::error::create_type_error(vm, "month is required")), + }; if f < 1.0 { return NativeResult::Err(crate::error::create_range_error(vm, "invalid month")); } @@ -530,7 +538,12 @@ fn year_month_like_parts( None => (None, false), }; // year 转换(TypeError/RangeError)在 monthCode 语法之后。 - let year = temporal_number_component(vm, year_raw)? as i32; + let year_raw = temporal_number_component(vm, year_raw)?; + let year = if year_raw >= i32::MIN as f64 && year_raw <= i32::MAX as f64 { + year_raw as i32 + } else { + return Err(crate::error::create_range_error(vm, "invalid year")); + }; let month_f = match month_raw.is_undefined() { true => None, false => Some(temporal_number_component(vm, month_raw)?), @@ -551,7 +564,7 @@ fn year_month_like_parts( } code } else { - let f = month_f.expect("month or monthCode is required checked above"); + let f = month_f.ok_or_else(|| crate::error::create_type_error(vm, "month is required"))?; if f < 1.0 { return Err(crate::error::create_range_error(vm, "invalid month")); } diff --git a/crates/oxide_builtins/src/typed_array.rs b/crates/oxide_builtins/src/typed_array.rs index 7bfd1721..4d076930 100644 --- a/crates/oxide_builtins/src/typed_array.rs +++ b/crates/oxide_builtins/src/typed_array.rs @@ -312,27 +312,35 @@ fn write_typed_array_element( /// 读 TypedArray 元素并转为对应 JS 值:BigInt 类型读为 BigInt 值(i64/u64 位模式 /// 原样搬运,无精度损失),数值类型按位模式读为 Number。 fn read_element(vm: &mut H, kind: TypedArrayKind, bytes: &[u8], offset: usize) -> JsValue { + // 安全读取:先校验 slice 长度匹配目标类型字节数,避免 try_into panic。 + macro_rules! read_bytes { + ($slice:expr, $ty:ty) => {{ + let s = $slice; + let expected = std::mem::size_of::<$ty>(); + if s.len() == expected { + unsafe { std::ptr::read(s.as_ptr() as *const $ty) } + } else { + return JsValue::undefined(); + } + }}; + } match kind { TypedArrayKind::Int8 => JsValue::int(bytes[offset] as i8 as i32), TypedArrayKind::Uint8 | TypedArrayKind::Uint8Clamped => JsValue::int(bytes[offset] as i32), - TypedArrayKind::Int16 => JsValue::int(i16::from_ne_bytes(bytes[offset..offset + 2].try_into().unwrap()) as i32), - TypedArrayKind::Uint16 => { - JsValue::int(u16::from_ne_bytes(bytes[offset..offset + 2].try_into().unwrap()) as i32) + TypedArrayKind::Int16 => { + JsValue::int(read_bytes!(&bytes[offset..offset + 2], i16) as i32) } - TypedArrayKind::Int32 => JsValue::int(i32::from_ne_bytes(bytes[offset..offset + 4].try_into().unwrap())), - TypedArrayKind::Uint32 => { - JsValue::float(u32::from_ne_bytes(bytes[offset..offset + 4].try_into().unwrap()) as f64) - } - TypedArrayKind::Float32 => { - JsValue::float(f32::from_ne_bytes(bytes[offset..offset + 4].try_into().unwrap()) as f64) - } - TypedArrayKind::Float64 => JsValue::float(f64::from_ne_bytes(bytes[offset..offset + 8].try_into().unwrap())), + TypedArrayKind::Uint16 => JsValue::int(read_bytes!(&bytes[offset..offset + 2], u16) as i32), + TypedArrayKind::Int32 => JsValue::int(read_bytes!(&bytes[offset..offset + 4], i32)), + TypedArrayKind::Uint32 => JsValue::float(read_bytes!(&bytes[offset..offset + 4], u32) as f64), + TypedArrayKind::Float32 => JsValue::float(read_bytes!(&bytes[offset..offset + 4], f32) as f64), + TypedArrayKind::Float64 => JsValue::float(read_bytes!(&bytes[offset..offset + 8], f64)), TypedArrayKind::BigInt64 => { - let n = i64::from_ne_bytes(bytes[offset..offset + 8].try_into().unwrap()); + let n = read_bytes!(&bytes[offset..offset + 8], i64); vm.new_bigint(BigInt::from(n)) } TypedArrayKind::BigUint64 => { - let n = u64::from_ne_bytes(bytes[offset..offset + 8].try_into().unwrap()); + let n = read_bytes!(&bytes[offset..offset + 8], u64); vm.new_bigint(BigInt::from(n)) } }