mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-23 18:21:13 +08:00
fix: String 位置参数 BigInt 吞错改传播 TypeError
This commit is contained in:
@@ -2,6 +2,7 @@ use oxide_runtime_api::{NativeResult, VmHost};
|
||||
use oxide_types::object::JsObject;
|
||||
use oxide_types::value::JsValue;
|
||||
|
||||
use crate::array::to_integer_or_infinity_bounded;
|
||||
use crate::builtins_debug;
|
||||
use crate::builtins_error;
|
||||
|
||||
@@ -11,46 +12,6 @@ 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 位转为单元拼接
|
||||
@@ -208,14 +169,18 @@ pub fn string_index_of<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
// 单元序列落地为 owned:借用落地后与后续 &mut 调用无交叠。
|
||||
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 {
|
||||
f64_to_usafe_usize(vm.coerce_number_bounded(vm.reg(args[2])).unwrap_or(f64::NAN))
|
||||
let s: Vec<u16> = try_string!(this_units(vm, args)).into_owned();
|
||||
let n = s.len();
|
||||
// position:ToIntegerOrInfinity 传播式,负值归 0、+Inf 归 len。
|
||||
let pos = if args.len() > 2 {
|
||||
let p = match to_integer_or_infinity_bounded(vm, vm.reg(args[2])) {
|
||||
Ok(p) => p,
|
||||
Err(exc) => return NativeResult::Err(exc),
|
||||
};
|
||||
(p.max(0.0).min(n as f64)) as usize
|
||||
} else {
|
||||
0
|
||||
};
|
||||
let s: Vec<u16> = try_string!(this_units(vm, args)).into_owned();
|
||||
let n = s.len();
|
||||
let pos = pos_raw.min(n);
|
||||
|
||||
if search.is_empty() {
|
||||
return NativeResult::Ok(JsValue::int(pos as i32));
|
||||
@@ -232,13 +197,17 @@ pub fn string_includes<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
// 参数转换先行(&mut 路径):search 缺省为 undefined(经 ToString 得 "undefined")。
|
||||
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 {
|
||||
f64_to_usafe_usize(vm.coerce_number_bounded(vm.reg(args[2])).unwrap_or(f64::NAN))
|
||||
let s: Vec<u16> = try_string!(this_units(vm, args)).into_owned();
|
||||
// position:ToIntegerOrInfinity 传播式,负值归 0、+Inf 归 len。
|
||||
let pos = if args.len() > 2 {
|
||||
let p = match to_integer_or_infinity_bounded(vm, vm.reg(args[2])) {
|
||||
Ok(p) => p,
|
||||
Err(exc) => return NativeResult::Err(exc),
|
||||
};
|
||||
(p.max(0.0).min(s.len() as f64)) as usize
|
||||
} else {
|
||||
0
|
||||
};
|
||||
let s: Vec<u16> = try_string!(this_units(vm, args)).into_owned();
|
||||
let pos = pos_raw.min(s.len());
|
||||
|
||||
if search.is_empty() {
|
||||
return NativeResult::Ok(JsValue::bool(true));
|
||||
@@ -249,14 +218,18 @@ pub fn string_includes<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
/// `String.prototype.charAt(index)`:返回指定码元位置的 1 单元字符串;越界返回空串。
|
||||
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 路径),先行转换。
|
||||
// index 可能触发对象 ToNumber(&mut 路径),先行转换;ToIntegerOrInfinity
|
||||
// 传播式,负值/越界(含 ±Inf)返回空串。
|
||||
let idx = if args.len() >= 2 {
|
||||
f64_to_i32(vm.coerce_number_bounded(vm.reg(args[1])).unwrap_or(f64::NAN))
|
||||
match to_integer_or_infinity_bounded(vm, vm.reg(args[1])) {
|
||||
Ok(p) => p,
|
||||
Err(exc) => return NativeResult::Err(exc),
|
||||
}
|
||||
} else {
|
||||
0
|
||||
0.0
|
||||
};
|
||||
let s: Vec<u16> = try_string!(this_units(vm, args)).into_owned();
|
||||
if idx < 0 || idx as usize >= s.len() {
|
||||
if idx < 0.0 || idx >= s.len() as f64 {
|
||||
return NativeResult::Ok(vm.new_string(""));
|
||||
}
|
||||
let u = s[idx as usize];
|
||||
@@ -270,14 +243,18 @@ pub fn string_char_at<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
/// `String.prototype.charCodeAt(index)`:返回指定码元位置的 code unit;越界返回 NaN。
|
||||
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 路径),先行转换。
|
||||
// index 可能触发对象 ToNumber(&mut 路径),先行转换;ToIntegerOrInfinity
|
||||
// 传播式,负值/越界(含 ±Inf)返回 NaN。
|
||||
let idx = if args.len() >= 2 {
|
||||
f64_to_i32(vm.coerce_number_bounded(vm.reg(args[1])).unwrap_or(f64::NAN))
|
||||
match to_integer_or_infinity_bounded(vm, vm.reg(args[1])) {
|
||||
Ok(p) => p,
|
||||
Err(exc) => return NativeResult::Err(exc),
|
||||
}
|
||||
} else {
|
||||
0
|
||||
0.0
|
||||
};
|
||||
let s = try_string!(this_units(vm, args));
|
||||
if idx < 0 || idx as usize >= s.len() {
|
||||
if idx < 0.0 || idx >= s.len() as f64 {
|
||||
return NativeResult::Ok(JsValue::float(f64::NAN));
|
||||
}
|
||||
NativeResult::Ok(JsValue::int(s[idx as usize] as i32))
|
||||
@@ -333,21 +310,18 @@ pub fn string_last_index_of<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult
|
||||
// 参数转换先行(&mut 路径):search 缺省为 undefined(经 ToString 得 "undefined")。
|
||||
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 {
|
||||
let p = oxide_runtime_api::to_integer_or_infinity(vm.reg(args[2]));
|
||||
if p.is_nan() {
|
||||
None
|
||||
} else {
|
||||
Some(p as usize)
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let s: Vec<u16> = try_string!(this_units(vm, args)).into_owned();
|
||||
let n = s.len();
|
||||
let pos = match pos_raw {
|
||||
Some(p) => p.min(n),
|
||||
None => n,
|
||||
// position:ToIntegerOrInfinity 传播式(NaN → 0),负值归 0、+Inf 归 len;
|
||||
// 缺参 → len。
|
||||
let pos = if args.len() > 2 {
|
||||
let p = match to_integer_or_infinity_bounded(vm, vm.reg(args[2])) {
|
||||
Ok(p) => p,
|
||||
Err(exc) => return NativeResult::Err(exc),
|
||||
};
|
||||
(p.max(0.0).min(n as f64)) as usize
|
||||
} else {
|
||||
n
|
||||
};
|
||||
|
||||
if search.is_empty() {
|
||||
@@ -386,43 +360,45 @@ pub fn string_concat<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
/// `String.prototype.slice(start, end)`:按码元区间(支持负索引)取子串。
|
||||
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(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(f64_to_i32(vm.coerce_number_bounded(vm.reg(args[2])).unwrap_or(f64::NAN)))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
// this 单元序列先行(&mut 借用落地),后转两个位置参数。
|
||||
let s: Vec<u16> = try_string!(this_units(vm, args)).into_owned();
|
||||
let n = s.len() as i32;
|
||||
let start = match start_raw {
|
||||
Some(v) => {
|
||||
if v < 0 {
|
||||
(n + v).max(0)
|
||||
} else {
|
||||
v.min(n)
|
||||
}
|
||||
let n = s.len();
|
||||
// start:缺参 → 0;ToIntegerOrInfinity 传播式,相对折叠(负从尾数、+Inf 归 len)。
|
||||
let start = if args.len() > 1 {
|
||||
let p = match to_integer_or_infinity_bounded(vm, vm.reg(args[1])) {
|
||||
Ok(p) => p,
|
||||
Err(exc) => return NativeResult::Err(exc),
|
||||
};
|
||||
if p < 0.0 {
|
||||
(n as f64 + p).max(0.0) as usize
|
||||
} else {
|
||||
p.min(n as f64) as usize
|
||||
}
|
||||
None => 0,
|
||||
} else {
|
||||
0
|
||||
};
|
||||
let end = match end_raw {
|
||||
Some(v) => {
|
||||
if v < 0 {
|
||||
(n + v).max(0)
|
||||
// end:缺参/显式 undefined → len(规范 GetRelativeEnd),其余同相对折叠。
|
||||
let end = if args.len() > 2 {
|
||||
let end_val = vm.reg(args[2]);
|
||||
if end_val.is_undefined() {
|
||||
n
|
||||
} else {
|
||||
let p = match to_integer_or_infinity_bounded(vm, end_val) {
|
||||
Ok(p) => p,
|
||||
Err(exc) => return NativeResult::Err(exc),
|
||||
};
|
||||
if p < 0.0 {
|
||||
(n as f64 + p).max(0.0) as usize
|
||||
} else {
|
||||
v.min(n)
|
||||
p.min(n as f64) as usize
|
||||
}
|
||||
}
|
||||
None => n,
|
||||
} else {
|
||||
n
|
||||
};
|
||||
// 码元区间精确切片(孤立 surrogate 按单元保留,不吸附字符边界)。
|
||||
let out: Vec<u16> = if start < end {
|
||||
let (a, b) = (start as usize, end as usize);
|
||||
s[a.min(s.len())..b.min(s.len())].to_vec()
|
||||
s[start.min(s.len())..end.min(s.len())].to_vec()
|
||||
} else {
|
||||
Vec::new()
|
||||
};
|
||||
@@ -436,28 +412,34 @@ pub fn string_substring<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
let start_arg = if args.len() > 1 { Some(vm.reg(args[1])) } else { None };
|
||||
let end_arg = if args.len() > 2 { Some(vm.reg(args[2])) } else { None };
|
||||
let s: Vec<u16> = try_string!(this_units(vm, args)).into_owned();
|
||||
let n = s.len() as i32;
|
||||
// ToIntegerOrInfinity 传播式:NaN/负 → 0、+Inf → len,取 min。
|
||||
let mut start = match start_arg {
|
||||
Some(v) => {
|
||||
let v = oxide_runtime_api::to_integer_or_infinity(v);
|
||||
if v.is_nan() || v < 0.0 {
|
||||
0
|
||||
let p = match to_integer_or_infinity_bounded(vm, v) {
|
||||
Ok(p) => p,
|
||||
Err(exc) => return NativeResult::Err(exc),
|
||||
};
|
||||
if p < 0.0 {
|
||||
0.0
|
||||
} else {
|
||||
(v as i32).min(n)
|
||||
p.min(s.len() as f64)
|
||||
}
|
||||
}
|
||||
None => 0,
|
||||
None => 0.0,
|
||||
};
|
||||
let mut end = match end_arg {
|
||||
Some(v) => {
|
||||
let v = oxide_runtime_api::to_integer_or_infinity(v);
|
||||
if v.is_nan() || v < 0.0 {
|
||||
0
|
||||
let p = match to_integer_or_infinity_bounded(vm, v) {
|
||||
Ok(p) => p,
|
||||
Err(exc) => return NativeResult::Err(exc),
|
||||
};
|
||||
if p < 0.0 {
|
||||
0.0
|
||||
} else {
|
||||
(v as i32).min(n)
|
||||
p.min(s.len() as f64)
|
||||
}
|
||||
}
|
||||
None => n,
|
||||
None => s.len() as f64,
|
||||
};
|
||||
if start > end {
|
||||
std::mem::swap(&mut start, &mut end);
|
||||
@@ -474,23 +456,34 @@ pub fn string_substr<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
let start_arg = if args.len() > 1 { Some(vm.reg(args[1])) } else { None };
|
||||
let length_arg = if args.len() > 2 { Some(vm.reg(args[2])) } else { None };
|
||||
let s: Vec<u16> = try_string!(this_units(vm, args)).into_owned();
|
||||
let len = s.len() as isize;
|
||||
let len = s.len();
|
||||
// start:ToIntegerOrInfinity 传播式,相对折叠(负从尾数、+Inf 归 len)。
|
||||
let start = match start_arg {
|
||||
Some(v) => {
|
||||
let n = f64_to_isize(oxide_runtime_api::to_integer_or_infinity(v));
|
||||
if n < 0 {
|
||||
(len + n).max(0)
|
||||
let p = match to_integer_or_infinity_bounded(vm, v) {
|
||||
Ok(p) => p,
|
||||
Err(exc) => return NativeResult::Err(exc),
|
||||
};
|
||||
if p < 0.0 {
|
||||
(len as f64 + p).max(0.0) as usize
|
||||
} else {
|
||||
n.min(len)
|
||||
p.min(len as f64) as usize
|
||||
}
|
||||
}
|
||||
None => 0,
|
||||
} as usize;
|
||||
let length = match length_arg {
|
||||
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);
|
||||
// length:负 → 0;+Inf 取剩余全部。
|
||||
let length = match length_arg {
|
||||
Some(v) => {
|
||||
let p = match to_integer_or_infinity_bounded(vm, v) {
|
||||
Ok(p) => p,
|
||||
Err(exc) => return NativeResult::Err(exc),
|
||||
};
|
||||
p.max(0.0) as usize
|
||||
}
|
||||
None => len - start,
|
||||
};
|
||||
let count = length.min(len - start);
|
||||
let out: Vec<u16> = s[start..start + count].to_vec();
|
||||
NativeResult::Ok(vm.new_string_units_owned(out))
|
||||
}
|
||||
@@ -499,15 +492,19 @@ pub fn string_substr<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
/// 越界返回 undefined。
|
||||
pub fn string_at<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
builtins_debug!("String.prototype.at called with {} args", args.len());
|
||||
// index:ToIntegerOrInfinity 传播式,负索引从尾折算,越界(含 ±Inf)→ undefined。
|
||||
let idx = if args.len() > 1 {
|
||||
f64_to_i32(oxide_runtime_api::to_integer_or_infinity(vm.reg(args[1])))
|
||||
match to_integer_or_infinity_bounded(vm, vm.reg(args[1])) {
|
||||
Ok(p) => p,
|
||||
Err(exc) => return NativeResult::Err(exc),
|
||||
}
|
||||
} else {
|
||||
0
|
||||
0.0
|
||||
};
|
||||
let s = try_string!(this_units(vm, args));
|
||||
let len = s.len() as i32;
|
||||
let idx = if idx < 0 { len + idx } else { idx };
|
||||
if idx < 0 || idx >= len {
|
||||
let len = s.len() as f64;
|
||||
let idx = if idx < 0.0 { len + idx } else { idx };
|
||||
if idx < 0.0 || idx >= len {
|
||||
return NativeResult::Ok(JsValue::undefined());
|
||||
}
|
||||
let u = s[idx as usize];
|
||||
@@ -576,9 +573,17 @@ pub fn string_trim_end<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
/// `String.prototype.repeat(count)`:重复字符串 count 次(当前上限 10000 防滥用)。
|
||||
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 重复。
|
||||
// count 转换先行(&mut 路径):ToIntegerOrInfinity 传播式,负值与 +Inf
|
||||
// 抛 RangeError(规范步),后借 this 重复。
|
||||
let n = if args.len() > 1 {
|
||||
f64_to_usafe_usize(vm.coerce_number_bounded(vm.reg(args[1])).unwrap_or(f64::NAN)).min(10000)
|
||||
let p = match to_integer_or_infinity_bounded(vm, vm.reg(args[1])) {
|
||||
Ok(p) => p,
|
||||
Err(exc) => return NativeResult::Err(exc),
|
||||
};
|
||||
if p < 0.0 || p.is_infinite() {
|
||||
return NativeResult::Err(crate::error::create_range_error(vm, "Invalid count argument"));
|
||||
}
|
||||
(p as usize).min(10000)
|
||||
} else {
|
||||
1
|
||||
};
|
||||
@@ -595,8 +600,14 @@ pub fn string_repeat<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
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 均可能触发对象转换。
|
||||
// targetLength:ToIntegerOrInfinity 传播式,负值归 0;+Inf 经既有 10000
|
||||
// 上限检查自然落 RangeError。
|
||||
let target_arg = if args.len() > 1 {
|
||||
Some(f64_to_usafe_usize(vm.coerce_number_bounded(vm.reg(args[1])).unwrap_or(f64::NAN)))
|
||||
let p = match to_integer_or_infinity_bounded(vm, vm.reg(args[1])) {
|
||||
Ok(p) => p,
|
||||
Err(exc) => return NativeResult::Err(exc),
|
||||
};
|
||||
Some(p.max(0.0))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
@@ -609,7 +620,7 @@ pub fn string_pad_start<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
};
|
||||
let s: Vec<u16> = try_string!(this_units(vm, args)).into_owned();
|
||||
let s_len = code_point_count(&s);
|
||||
let target = target_arg.unwrap_or(s_len);
|
||||
let target = target_arg.map(|p| p as usize).unwrap_or(s_len);
|
||||
if target > 10000 {
|
||||
builtins_error!("String.prototype.padStart: invalid receiver");
|
||||
return NativeResult::Err(crate::error::create_range_error(vm, "Invalid string length"));
|
||||
@@ -636,8 +647,14 @@ pub fn string_pad_start<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
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 均可能触发对象转换。
|
||||
// targetLength:ToIntegerOrInfinity 传播式,负值归 0;+Inf 经既有 10000
|
||||
// 上限检查自然落 RangeError。
|
||||
let target_arg = if args.len() > 1 {
|
||||
Some(f64_to_usafe_usize(vm.coerce_number_bounded(vm.reg(args[1])).unwrap_or(f64::NAN)))
|
||||
let p = match to_integer_or_infinity_bounded(vm, vm.reg(args[1])) {
|
||||
Ok(p) => p,
|
||||
Err(exc) => return NativeResult::Err(exc),
|
||||
};
|
||||
Some(p.max(0.0))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
@@ -650,7 +667,7 @@ pub fn string_pad_end<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
};
|
||||
let s: Vec<u16> = try_string!(this_units(vm, args)).into_owned();
|
||||
let s_len = code_point_count(&s);
|
||||
let target = target_arg.unwrap_or(s_len);
|
||||
let target = target_arg.map(|p| p as usize).unwrap_or(s_len);
|
||||
if target > 10000 {
|
||||
builtins_error!("String.prototype.padEnd: invalid receiver");
|
||||
return NativeResult::Err(crate::error::create_range_error(vm, "Invalid string length"));
|
||||
@@ -679,13 +696,17 @@ pub fn string_starts_with<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
// 参数转换先行(&mut 路径):search 缺省为 undefined(经 ToString 得 "undefined")。
|
||||
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 {
|
||||
f64_to_usafe_usize(vm.coerce_number_bounded(vm.reg(args[2])).unwrap_or(f64::NAN))
|
||||
let s: Vec<u16> = try_string!(this_units(vm, args)).into_owned();
|
||||
// position:ToIntegerOrInfinity 传播式,负值归 0、+Inf 归 len。
|
||||
let pos = if args.len() > 2 {
|
||||
let p = match to_integer_or_infinity_bounded(vm, vm.reg(args[2])) {
|
||||
Ok(p) => p,
|
||||
Err(exc) => return NativeResult::Err(exc),
|
||||
};
|
||||
(p.max(0.0).min(s.len() as f64)) as usize
|
||||
} else {
|
||||
0
|
||||
};
|
||||
let s: Vec<u16> = try_string!(this_units(vm, args)).into_owned();
|
||||
let pos = pos_raw.min(s.len());
|
||||
let result = if search.is_empty() { true } else { s[pos..].starts_with(&search) };
|
||||
NativeResult::Ok(JsValue::bool(result))
|
||||
}
|
||||
@@ -697,13 +718,23 @@ pub fn string_ends_with<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
// 参数转换先行(&mut 路径):search 缺省为 undefined(经 ToString 得 "undefined")。
|
||||
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 {
|
||||
f64_to_usafe_usize(vm.coerce_number_bounded(vm.reg(args[2])).unwrap_or(f64::NAN))
|
||||
} else {
|
||||
usize::MAX
|
||||
};
|
||||
let s: Vec<u16> = try_string!(this_units(vm, args)).into_owned();
|
||||
let end_pos = end_pos_raw.min(s.len());
|
||||
// endPosition:缺参/显式 undefined → len(规范步),其余 ToIntegerOrInfinity
|
||||
// 传播式折叠,负值归 0、+Inf 归 len。
|
||||
let end_pos = if args.len() > 2 {
|
||||
let end_val = vm.reg(args[2]);
|
||||
if end_val.is_undefined() {
|
||||
s.len()
|
||||
} else {
|
||||
let p = match to_integer_or_infinity_bounded(vm, end_val) {
|
||||
Ok(p) => p,
|
||||
Err(exc) => return NativeResult::Err(exc),
|
||||
};
|
||||
(p.max(0.0).min(s.len() as f64)) as usize
|
||||
}
|
||||
} else {
|
||||
s.len()
|
||||
};
|
||||
let result = if search.is_empty() { true } else { s[..end_pos].ends_with(&search) };
|
||||
NativeResult::Ok(JsValue::bool(result))
|
||||
}
|
||||
|
||||
@@ -1624,3 +1624,51 @@ fn string_locale_methods_length_and_name() {
|
||||
.unwrap();
|
||||
assert!(r.as_bool());
|
||||
}
|
||||
|
||||
/// BigInt 位置参数钉:17 个方法的 ToIntegerOrInfinity 面对 BigInt 抛 TypeError。
|
||||
#[test]
|
||||
fn string_methods_bigint_position_type_error() {
|
||||
let mut vm = Vm::new();
|
||||
for src in [
|
||||
"'a'.indexOf('a', 0n)",
|
||||
"'abc'.includes('b', 1n)",
|
||||
"'abc'.charAt(0n)",
|
||||
"'abc'.charCodeAt(0n)",
|
||||
"'abc'.lastIndexOf('b', 1n)",
|
||||
"'abc'.slice(1n)",
|
||||
"'abc'.slice(0, 1n)",
|
||||
"'abc'.substring(1n)",
|
||||
"'abc'.substring(0, 1n)",
|
||||
"'abc'.substr(1n)",
|
||||
"'abc'.substr(0, 1n)",
|
||||
"'abc'.at(0n)",
|
||||
"'ab'.repeat(2n)",
|
||||
"'ab'.padStart(3n)",
|
||||
"'ab'.padEnd(3n)",
|
||||
"'abc'.startsWith('a', 1n)",
|
||||
"'abc'.endsWith('c', 2n)",
|
||||
] {
|
||||
let err = eval(&mut vm, src).unwrap_err();
|
||||
assert!(err.contains("TypeError"), "{}: {}", src, err);
|
||||
}
|
||||
}
|
||||
|
||||
/// valueOf 异常透传钉:A 形(indexOf)与 B 形(at)均保留原异常值。
|
||||
#[test]
|
||||
fn string_methods_value_of_abort_passes_through() {
|
||||
let mut vm = Vm::new();
|
||||
let v = eval(&mut vm, "try { 'a'.indexOf('a', {valueOf(){ throw 42 }}) } catch (e) { e }").unwrap();
|
||||
assert_eq!(v.as_int(), 42);
|
||||
let v = eval(&mut vm, "try { 'abc'.at({valueOf(){ throw 42 }}) } catch (e) { e }").unwrap();
|
||||
assert_eq!(v.as_int(), 42);
|
||||
}
|
||||
|
||||
/// repeat count 界判钉:负值与 +Infinity 均抛 RangeError。
|
||||
#[test]
|
||||
fn string_repeat_count_range_error() {
|
||||
let mut vm = Vm::new();
|
||||
let err = eval(&mut vm, "'ab'.repeat(-1)").unwrap_err();
|
||||
assert!(err.contains("RangeError"), "got: {}", err);
|
||||
let err = eval(&mut vm, "'ab'.repeat(Infinity)").unwrap_err();
|
||||
assert!(err.contains("RangeError"), "got: {}", err);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user