mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-23 18:21:13 +08:00
fix: 修复 oxide_builtins 整数溢出 panic 点与安全转换
This commit is contained in:
@@ -859,7 +859,9 @@ pub fn array_flat<H: VmHost>(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<H: VmHost>(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) {
|
||||
|
||||
@@ -511,7 +511,7 @@ fn low64<H: VmHost>(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]`。
|
||||
|
||||
@@ -67,14 +67,25 @@ fn dt_from_ms(ms: f64) -> Option<DateTime<Utc>> {
|
||||
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<DateTime<Local>> {
|
||||
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<NaiveDateTime> {
|
||||
@@ -1028,7 +1039,14 @@ pub fn date_set_year<H: VmHost>(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;
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -77,7 +77,10 @@ fn decode_uri_units(input: &[u16], preserve_reserved: bool) -> Result<Vec<u16>,
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
@@ -442,7 +442,7 @@ fn jsvalue_to_json<H: VmHost>(
|
||||
} 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() {
|
||||
|
||||
@@ -197,7 +197,7 @@ pub fn number_parse_int<H: VmHost>(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
|
||||
};
|
||||
|
||||
@@ -87,7 +87,7 @@ fn int_or_string_index<H: VmHost>(vm: &H, si: u32) -> Option<u32> {
|
||||
return Some(int_key_value(si));
|
||||
}
|
||||
let key = vm.kernel_core().perm_interner().lookup(si)?;
|
||||
is_integer_index(key).then(|| key.parse::<u32>().unwrap())
|
||||
is_integer_index(key).then(|| key.parse::<u32>().ok()).flatten()
|
||||
}
|
||||
|
||||
/// 键 si 构造为 JS 可见字符串值(Object.keys / ownKeys / entries 族):
|
||||
|
||||
@@ -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<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
let search_val = if args.len() >= 2 { vm.reg(args[1]) } else { JsValue::undefined() };
|
||||
let search: Vec<u16> = 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<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
let search_val = if args.len() >= 2 { vm.reg(args[1]) } else { JsValue::undefined() };
|
||||
let search: Vec<u16> = 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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
pub fn string_at<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
let search_val = if args.len() >= 2 { vm.reg(args[1]) } else { JsValue::undefined() };
|
||||
let search: Vec<u16> = 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<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
let search_val = if args.len() >= 2 { vm.reg(args[1]) } else { JsValue::undefined() };
|
||||
let search: Vec<u16> = 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
|
||||
};
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -428,8 +428,9 @@ fn final_sigma_lower(units: &[u16]) -> Vec<u16> {
|
||||
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::<String>().encode_utf16());
|
||||
if let Some(c) = char::from_u32(cp) {
|
||||
out.extend(c.to_lowercase().collect::<String>().encode_utf16());
|
||||
}
|
||||
i += 2;
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -279,7 +279,8 @@ pub fn string_split<H: VmHost>(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<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
let sep_units: Vec<u16> = 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<u16> = try_string!(this_units(vm, args)).into_owned();
|
||||
// 规范:separator 为 undefined 时返回 [this]。
|
||||
@@ -461,7 +463,10 @@ fn string_replace_impl<H: VmHost>(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<regress::Regex>` 指针。
|
||||
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<H: VmHost>(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<regress::Regex>` 指针。
|
||||
let regex = unsafe { &*(fn_ptr.as_ptr() as *const regress::Regex) };
|
||||
let h = &*vm;
|
||||
@@ -530,7 +538,10 @@ fn string_replace_impl<H: VmHost>(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<regress::Regex>` 指针。
|
||||
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<H: VmHost>(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<H: VmHost>(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 };
|
||||
|
||||
@@ -63,7 +63,12 @@ pub(crate) fn get_instant_epoch_ns(obj: &JsObject) -> Option<i128> {
|
||||
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<H: VmHost>(
|
||||
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<u32, JsValue> {
|
||||
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))
|
||||
{
|
||||
|
||||
@@ -147,18 +147,33 @@ pub fn plain_date_constructor<H: VmHost>(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<u32, JsValue> {
|
||||
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"));
|
||||
}
|
||||
|
||||
@@ -66,15 +66,27 @@ pub fn plain_date_time_constructor<H: VmHost>(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<u32, JsValue> {
|
||||
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)
|
||||
|
||||
@@ -76,14 +76,29 @@ pub fn plain_month_day_constructor<H: VmHost>(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<u32, JsValue> {
|
||||
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<H: VmHost>(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<H: VmHost>(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"));
|
||||
}
|
||||
|
||||
@@ -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<H: VmHost>(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<u32, JsValue> {
|
||||
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"));
|
||||
}
|
||||
|
||||
@@ -398,7 +398,12 @@ pub fn plain_year_month_from<H: VmHost>(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<H: VmHost>(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<H: VmHost>(
|
||||
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<H: VmHost>(
|
||||
}
|
||||
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"));
|
||||
}
|
||||
|
||||
@@ -312,27 +312,35 @@ fn write_typed_array_element<H: VmHost>(
|
||||
/// 读 TypedArray 元素并转为对应 JS 值:BigInt 类型读为 BigInt 值(i64/u64 位模式
|
||||
/// 原样搬运,无精度损失),数值类型按位模式读为 Number。
|
||||
fn read_element<H: VmHost>(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))
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user