mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-24 02:31:13 +08:00
fix: 修复 oxide_builtins 整数溢出 panic 点与安全转换
This commit is contained in:
@@ -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 };
|
||||
|
||||
Reference in New Issue
Block a user