mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-23 18:21:13 +08:00
fix: split 委托 Symbol.split 并补零宽与 limit 口径
This commit is contained in:
@@ -1606,22 +1606,12 @@ pub fn regexp_symbol_split<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
let units = haystack.as_match_text().units().to_vec();
|
||||
let size = units.len();
|
||||
|
||||
// lim:缺省 2^32-1,否则 ToUint32(完整 ToNumber 传播转换异常;
|
||||
// NaN/±0 归零、±∞ 归 2^32-1、负数回绕)。
|
||||
let lim = if args.len() > 2 {
|
||||
let l = match oxide_runtime_api::to_number_full(vm.reg(args[2]), vm) {
|
||||
Ok(n) => n,
|
||||
Err(e) => return NativeResult::Err(crate::iterator::engine_error(vm, &e)),
|
||||
};
|
||||
if l.is_nan() || l == 0.0 {
|
||||
0usize
|
||||
} else if l.is_infinite() {
|
||||
u32::MAX as usize
|
||||
} else {
|
||||
l.trunc().rem_euclid(4_294_967_296.0) as usize
|
||||
}
|
||||
} else {
|
||||
u32::MAX as usize
|
||||
// lim:缺省或 undefined 为 2^32-1,否则 ToUint32(完整 ToNumber 传播
|
||||
// 转换异常;NaN/±0/±∞ 归零、负数回绕)。
|
||||
let lim_val = if args.len() > 2 { vm.reg(args[2]) } else { JsValue::undefined() };
|
||||
let lim = match crate::string::split_limit_to_uint32(vm, lim_val) {
|
||||
Ok(n) => n,
|
||||
Err(e) => return NativeResult::Err(e),
|
||||
};
|
||||
if lim == 0 {
|
||||
return NativeResult::Ok(make_units_array(vm, Vec::new()));
|
||||
|
||||
@@ -350,6 +350,26 @@ pub(crate) fn as_units<'a, H: VmHost>(vm: &'a mut H, val: JsValue) -> Result<Cow
|
||||
}
|
||||
}
|
||||
|
||||
// ── split limit 换算 ────────────────────────────────────────────────────
|
||||
|
||||
/// split 的 limit 参数 → ToUint32 口径的 usize:undefined 为 2^32-1(全量);
|
||||
/// 其余值经完整 ToNumber(对象走 ToPrimitive number hint,Symbol 抛 TypeError),
|
||||
/// NaN/±0/±∞ 归 0,有限值按 mod 2^32 回绕(负数回绕,-1 为 2^32-1)。
|
||||
/// 对象转换抛出的原生异常原样传播。
|
||||
pub(crate) fn split_limit_to_uint32<H: VmHost>(vm: &mut H, limit_val: JsValue) -> Result<usize, JsValue> {
|
||||
if limit_val.is_undefined() {
|
||||
return Ok(u32::MAX as usize);
|
||||
}
|
||||
let n = match oxide_runtime_api::to_number_full(limit_val, vm) {
|
||||
Ok(n) => n,
|
||||
Err(e) => return Err(crate::iterator::engine_error(vm, &e)),
|
||||
};
|
||||
if n.is_nan() || n == 0.0 || n.is_infinite() {
|
||||
return Ok(0);
|
||||
}
|
||||
Ok(n.trunc().rem_euclid(4_294_967_296.0) as usize)
|
||||
}
|
||||
|
||||
// ── 数组产出 ─────────────────────────────────────────────────────────────
|
||||
|
||||
/// 以已构造的字符串值构建字符串数组(元素零拷贝落地),供逐单元产出路径
|
||||
|
||||
@@ -11,7 +11,7 @@ use crate::regexp::build_groups_object;
|
||||
use super::common::{try_string, MatchText};
|
||||
use super::{
|
||||
as_units, byte_to_unit, find_units, make_string_array_values, make_units_array, map_well_formed_segments,
|
||||
this_text, this_units, unit_to_byte,
|
||||
split_limit_to_uint32, this_text, this_units, unit_to_byte,
|
||||
};
|
||||
|
||||
// ── 正则替换(单元口径) ────────────────────────────────────────────────
|
||||
@@ -162,90 +162,109 @@ fn string_arm_replace<H: VmHost>(
|
||||
|
||||
// ── 拆分 / 正则匹配 ─────────────────────────────────────────────────────
|
||||
|
||||
/// GetMethod(sep, @@split):undefined/null 返回 None(落字符串臂),不可调用
|
||||
/// 抛 TypeError,属性读抛错恢复原异常值。
|
||||
fn rx_get_split_method<H: VmHost>(vm: &mut H, sep: JsValue) -> Result<Option<JsValue>, JsValue> {
|
||||
let split_key = oxide_types::private_key::make_well_known_symbol_key(4);
|
||||
let ptr = sep.as_js_object_ptr();
|
||||
// SAFETY: 调用方已校验 sep 为非空对象值。
|
||||
let func = match vm.ordinary_get(unsafe { &*ptr }, split_key, sep) {
|
||||
Ok(v) => v,
|
||||
Err(_) => {
|
||||
if let Some(exc) = vm.take_uncaught_value() {
|
||||
return Err(exc);
|
||||
}
|
||||
return Err(crate::error::create_type_error(vm, "cannot read @@split"));
|
||||
}
|
||||
};
|
||||
if func.is_undefined() || func.is_null() {
|
||||
return Ok(None);
|
||||
}
|
||||
if !crate::iterator::is_callable(func) {
|
||||
return Err(crate::error::create_type_error(vm, "Symbol.split is not callable"));
|
||||
}
|
||||
Ok(Some(func))
|
||||
}
|
||||
|
||||
/// `String.prototype.split(separator, limit)`:按分隔符拆分为字符串数组;
|
||||
/// 分隔符可为 RegExp(含捕获组)或字符串。空分隔按单元逐个产出(规格口径,
|
||||
/// 孤立 surrogate 为 1 单元元素)。
|
||||
///
|
||||
/// # 步骤
|
||||
/// 1. receiver 前置校验(RequireObjectCoercible,纯 is_* 读取,先于一切分叉)。
|
||||
/// 2. separator 为对象时:GetMethod(separator, @@split),可调用则
|
||||
/// Call(splitter, separator, «thisValue, limit»)——传原始 this/limit
|
||||
/// 寄存器不预转换,结果原值返回(RegExp 分隔经此委托 Symbol.split)。
|
||||
/// 3. string = ToString(this)(转换异常传播)。
|
||||
/// 4. lim:limit 缺省或 undefined 为 2^32-1,否则 ToUint32(mod 2^32 回绕)。
|
||||
/// 5. separatorString = ToString(separator)(转换异常传播;先于 lim = 0 判定)。
|
||||
/// 6. lim = 0 → 空数组(先于 sep-undefined 判定)。
|
||||
/// 7. separator 为 undefined → [string]。
|
||||
/// 8. 空分隔逐码元产出(clamp(lim, 0, 串长));非空分隔搜索循环,尾段恒推。
|
||||
///
|
||||
/// # 边界与前提
|
||||
/// - 规范序位:GetMethod 派发先于 ToString(this),ToUint32(limit) 先于
|
||||
/// ToString(separator),ToString(separator) 先于 lim = 0 判定,
|
||||
/// lim = 0 判定先于 sep-undefined 判定。
|
||||
/// - 各转换(this/limit/separator)抛出的原生异常原样传播。
|
||||
pub fn string_split<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
builtins_debug!("String.prototype.split called with {} args", args.len());
|
||||
// 参数转换先行:separator 为对象时 ToString 可能触发用户代码(&mut 路径),
|
||||
// limit 仅纯函数计算;之后借 this 源串扫描切分。
|
||||
let sep_val = if args.len() >= 2 { Some(vm.reg(args[1])) } else { None };
|
||||
let is_undefined_sep = sep_val.map(|v| v.is_undefined()).unwrap_or(true);
|
||||
let is_re = match sep_val {
|
||||
Some(v) if !v.is_undefined() => is_regexp_obj(v, vm),
|
||||
_ => false,
|
||||
};
|
||||
// 类正则对象是否持有编译正则:native_fn 存在才是真 RegExp 实例,命中正则切分路径;
|
||||
// 无编译正则的类正则对象(如 Object.create(RegExp.prototype))回退字符串路径,
|
||||
// 须按 ToString 文本切分,且转换(&mut 路径)须先于 this 借用完成。
|
||||
let has_native_re = is_re && {
|
||||
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()
|
||||
};
|
||||
// ToUint32(limit),缺省为 2^32-1。
|
||||
let limit = if args.len() > 2 {
|
||||
let l = oxide_runtime_api::to_integer_or_infinity(vm.reg(args[2]));
|
||||
if l.is_infinite() {
|
||||
u32::MAX as usize
|
||||
} else {
|
||||
(l.max(0.0).trunc() as u64).min(u32::MAX as u64) as usize
|
||||
let this_val = vm.reg(args[0]);
|
||||
|
||||
// receiver 前置校验:null/undefined 抛 TypeError(RequireObjectCoercible,
|
||||
// 纯 is_* 读取无用户代码,先于一切分叉);symbol 的 ToString 必败,提前抛。
|
||||
if this_val.is_null() || this_val.is_undefined() {
|
||||
return NativeResult::Err(crate::error::create_type_error(
|
||||
vm,
|
||||
"String.prototype method called on null or undefined",
|
||||
));
|
||||
}
|
||||
if this_val.is_symbol() {
|
||||
return NativeResult::Err(crate::error::create_type_error(vm, "Cannot convert a Symbol value to a string"));
|
||||
}
|
||||
|
||||
let sep_val = if args.len() >= 2 { vm.reg(args[1]) } else { JsValue::undefined() };
|
||||
let limit_val = if args.len() > 2 { vm.reg(args[2]) } else { JsValue::undefined() };
|
||||
|
||||
// 对象分隔符经 GetMethod 派发:调用定义则 Call 后原值返回,须先于
|
||||
// ToString(this) 短路(实参为原始寄存器,不预转换)。
|
||||
if sep_val.is_object() {
|
||||
let splitter = try_string!(rx_get_split_method(vm, sep_val));
|
||||
if let Some(splitter) = splitter {
|
||||
return match vm.call_function_sync(splitter, sep_val, &[this_val, limit_val]) {
|
||||
Ok(r) => NativeResult::Ok(r),
|
||||
Err(_) => {
|
||||
if let Some(exc) = vm.take_uncaught_value() {
|
||||
return NativeResult::Err(exc);
|
||||
}
|
||||
NativeResult::Err(crate::error::create_type_error(vm, "split matcher call failed"))
|
||||
}
|
||||
};
|
||||
}
|
||||
} else {
|
||||
u32::MAX as usize
|
||||
};
|
||||
let sep_units: Vec<u16> = if is_undefined_sep || has_native_re {
|
||||
Vec::new()
|
||||
} else {
|
||||
let sep = sep_val.expect("separator present when not undefined and not native regexp");
|
||||
try_string!(as_units(vm, sep)).into_owned()
|
||||
};
|
||||
}
|
||||
|
||||
// string = ToString(this)。
|
||||
let s: Vec<u16> = try_string!(this_units(vm, args)).into_owned();
|
||||
// 规范:separator 为 undefined 时返回 [this]。
|
||||
if is_undefined_sep {
|
||||
|
||||
// lim = ToUint32(limit):undefined 为 2^32-1,对象经完整 ToNumber(异常传播)。
|
||||
let lim = try_string!(split_limit_to_uint32(vm, limit_val));
|
||||
|
||||
// separatorString = ToString(separator)(先于 lim = 0 判定:sep 转换
|
||||
// 抛错时 lim = 0 的早退不生效)。
|
||||
let sep_units: Vec<u16> = try_string!(as_units(vm, sep_val)).into_owned();
|
||||
|
||||
if lim == 0 {
|
||||
return NativeResult::Ok(make_units_array(vm, Vec::new()));
|
||||
}
|
||||
// separator 为 undefined 时返回单元素 [string]。
|
||||
if sep_val.is_undefined() {
|
||||
return NativeResult::Ok(make_units_array(vm, vec![s.to_vec()]));
|
||||
}
|
||||
if is_re {
|
||||
let re_ptr = sep_val.unwrap().as_js_object_ptr();
|
||||
let re = unsafe { &*re_ptr };
|
||||
if let Some(fn_ptr) = re.native_fn() {
|
||||
let regex = unsafe { &*(fn_ptr.as_ptr() as *const regress::Regex) };
|
||||
// 片段物化字符串值,未匹配捕获组推 undefined(结果数组按值混排)。
|
||||
let mut parts: Vec<JsValue> = Vec::new();
|
||||
let mut last_end = 0;
|
||||
for m in regex.find_from_utf16(&s, 0) {
|
||||
if parts.len() >= limit {
|
||||
break;
|
||||
}
|
||||
let range = m.range();
|
||||
parts.push(vm.new_string_units_owned(s[last_end..range.start].to_vec()));
|
||||
if parts.len() >= limit {
|
||||
break;
|
||||
}
|
||||
for i in 1..=m.captures.len() {
|
||||
if parts.len() >= limit {
|
||||
break;
|
||||
}
|
||||
match m.group(i) {
|
||||
Some(g) => parts.push(vm.new_string_units_owned(s[g.start..g.end].to_vec())),
|
||||
None => parts.push(JsValue::undefined()),
|
||||
}
|
||||
}
|
||||
last_end = range.end;
|
||||
}
|
||||
if last_end <= s.len() && parts.len() < limit {
|
||||
parts.push(vm.new_string_units(&s[last_end..]));
|
||||
}
|
||||
return NativeResult::Ok(make_string_array_values(vm, parts));
|
||||
}
|
||||
// 无原生正则的类正则对象回退到字符串路径。
|
||||
}
|
||||
|
||||
if sep_units.is_empty() {
|
||||
// 每单元一个元素(ASCII 走单字符缓存零分配,其余 1 单元串创建)。
|
||||
let mut values = Vec::with_capacity(s.len().min(limit));
|
||||
for &u in s.iter().take(limit) {
|
||||
let mut values = Vec::with_capacity(s.len().min(lim));
|
||||
for &u in s.iter().take(lim) {
|
||||
values.push(match vm.single_unit(u) {
|
||||
Some(v) => v,
|
||||
None => vm.new_string_units(&[u]),
|
||||
@@ -253,11 +272,12 @@ pub fn string_split<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
}
|
||||
return NativeResult::Ok(make_string_array_values(vm, values));
|
||||
}
|
||||
|
||||
// 非空字符串分隔:手动查找循环 + limit。
|
||||
let mut parts: Vec<Vec<u16>> = Vec::new();
|
||||
let mut start = 0;
|
||||
loop {
|
||||
if parts.len() >= limit {
|
||||
if parts.len() >= lim {
|
||||
break;
|
||||
}
|
||||
match find_units(&s, &sep_units, start) {
|
||||
|
||||
@@ -256,7 +256,7 @@ impl BuiltinWorld {
|
||||
("padEnd", methods.pad_end, 1),
|
||||
("startsWith", methods.starts_with, 1),
|
||||
("endsWith", methods.ends_with, 1),
|
||||
("split", methods.split, 1),
|
||||
("split", methods.split, 2),
|
||||
("replace", methods.replace, 2),
|
||||
("match", methods.match_fn, 1),
|
||||
("search", methods.search, 1),
|
||||
|
||||
@@ -789,6 +789,16 @@ fn symbol_split_limit_zero_and_to_uint32() {
|
||||
assert_eq!(to_str(&vm, result), "limit-boom");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn symbol_split_undefined_limit_full() {
|
||||
// limit 缺省与显式 undefined 均为 2^32-1 全量(非 ToUint32(NaN)=0)。
|
||||
let mut vm = Vm::new();
|
||||
let result = eval(&mut vm, "RegExp.prototype[Symbol.split].call(/,/, 'a,b', undefined).length").unwrap();
|
||||
assert!(result.as_int() == 2);
|
||||
let result = eval(&mut vm, "'a,b'.split(/,/, undefined).length").unwrap();
|
||||
assert!(result.as_int() == 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn symbol_match_all_species_matcher_and_cached_lastindex() {
|
||||
let mut vm = Vm::new();
|
||||
|
||||
@@ -702,6 +702,132 @@ fn string_split_real_regex_still_regex_path() {
|
||||
assert_eq!(to_str(&vm, obj.get_prop_at(1)), "");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn string_split_regexp_zero_width_skips_separator() {
|
||||
// 零宽匹配不推分隔段:"hello".split(new RegExp) 逐边界零宽,结果即逐码元。
|
||||
let mut vm = Vm::new();
|
||||
let result = eval(&mut vm, "'hello'.split(new RegExp).join('|')").unwrap();
|
||||
assert_eq!(to_str(&vm, result), "h|e|l|l|o");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn string_split_zero_width_stars_pattern() {
|
||||
// /l*/ 每边界零宽匹配:仅非零宽命中处分段,结果 ["h","e","o"]。
|
||||
let mut vm = Vm::new();
|
||||
let result = eval(&mut vm, "'hello'.split(/l*/g).join('|')").unwrap();
|
||||
assert_eq!(to_str(&vm, result), "h|e|o");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn string_split_zero_width_trailing_excluded() {
|
||||
// "x".split(/(?:)/) 尾段取自 lastMatchEnd,串尾零宽匹配被循环排除 → ["x"];
|
||||
// 非零宽末匹配尾段照推 → "x".split(/./) 得 ["",""]。
|
||||
let mut vm = Vm::new();
|
||||
let one = eval(&mut vm, "'x'.split(/(?:)/).join('|')").unwrap();
|
||||
assert_eq!(to_str(&vm, one), "x");
|
||||
let two = eval(&mut vm, "'x'.split(/./).join('|')").unwrap();
|
||||
assert_eq!(to_str(&vm, two), "|");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn string_split_empty_input_regexp() {
|
||||
// 空串输入:可命中正则单次 exec 得 [],不可命中得 [""]。
|
||||
let mut vm = Vm::new();
|
||||
let hit = eval(&mut vm, "''.split(/(?:)/).length").unwrap();
|
||||
assert_eq!(hit.as_int(), 0);
|
||||
let miss = eval(&mut vm, "''.split(/x/).length").unwrap();
|
||||
assert_eq!(miss.as_int(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn string_split_limit_to_uint32_wrap() {
|
||||
// limit 按 ToUint32 回绕:2^32 → 0 空数组、-1 → 2^32-1 全量、NaN → 0、
|
||||
// 缺省与 undefined 均全量。
|
||||
let mut vm = Vm::new();
|
||||
let wrap = eval(&mut vm, "'a,b,c'.split(',', 2 ** 32).length").unwrap();
|
||||
assert_eq!(wrap.as_int(), 0);
|
||||
let neg = eval(&mut vm, "'a,b,c'.split(',', -1).length").unwrap();
|
||||
assert_eq!(neg.as_int(), 3);
|
||||
let nan = eval(&mut vm, "'a,b,c'.split(',', NaN).length").unwrap();
|
||||
assert_eq!(nan.as_int(), 0);
|
||||
let undef = eval(&mut vm, "'a,b,c'.split(',', undefined).length").unwrap();
|
||||
assert_eq!(undef.as_int(), 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn string_split_limit_object_full_tonumber() {
|
||||
// limit 对象经完整 ToNumber:valueOf 直取、转换异常传播。
|
||||
let mut vm = Vm::new();
|
||||
let via_value_of = eval(&mut vm, "'a|b|c'.split('|', { valueOf: function () { return 2; } }).length").unwrap();
|
||||
assert_eq!(via_value_of.as_int(), 2);
|
||||
let err = eval(
|
||||
&mut vm,
|
||||
"'a|b'.split('|', { valueOf: function () { throw new RangeError('limit-boom'); } })",
|
||||
)
|
||||
.unwrap_err();
|
||||
assert!(err.contains("RangeError"), "limit 对象转换异常应传播,实际: {err}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn string_split_order_limit_before_separator_tostring() {
|
||||
// 序位:ToUint32(limit) 先于 ToString(separator)——limit 的 valueOf 抛错胜出。
|
||||
let mut vm = Vm::new();
|
||||
let err = eval(
|
||||
&mut vm,
|
||||
concat!(
|
||||
"'foo'.split(",
|
||||
"{ toString: function () { throw new RangeError('sep-boom'); } }, ",
|
||||
"{ valueOf: function () { throw new RangeError('limit-boom'); } })"
|
||||
),
|
||||
)
|
||||
.unwrap_err();
|
||||
assert!(err.contains("limit-boom"), "limit 转换应先于 separator 转换,实际: {err}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn string_split_getmethod_dispatch() {
|
||||
// 对象分隔符经 GetMethod 派发:Call(splitter, sep, «this, limit»)传原始
|
||||
// 寄存器(this 未 ToString、limit 未 ToUint32),结果原值返回。
|
||||
let mut vm = Vm::new();
|
||||
let result = eval(
|
||||
&mut vm,
|
||||
concat!(
|
||||
"var sep = {}; sep[Symbol.split] = function (s, l) { return [s, l]; }; ",
|
||||
"var o = Object.create(String.prototype); ",
|
||||
"var r = o.split(sep, 7); ",
|
||||
"(r[0] === o) + '|' + r[1]"
|
||||
),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(to_str(&vm, result), "true|7");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn string_split_getmethod_short_circuits_this_tostring() {
|
||||
// GetMethod 派发先于 ToString(this):this 的 toString 抛错不被触发。
|
||||
let mut vm = Vm::new();
|
||||
let result = eval(
|
||||
&mut vm,
|
||||
concat!(
|
||||
"var o = new String('x'); ",
|
||||
"o.toString = function () { throw new RangeError('this-boom'); }; ",
|
||||
"var sep = {}; sep[Symbol.split] = function () { return 42; }; ",
|
||||
"o.split(sep)"
|
||||
),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(result.as_int(), 42);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn string_split_zero_width_missing_capture_not_pushed() {
|
||||
// 零宽匹配不推分隔段与捕获组:串内仅零宽命中时缺席捕获组不占位,
|
||||
// 结果只剩尾段。
|
||||
let mut vm = Vm::new();
|
||||
let result = eval(&mut vm, "'a'.split(/(b)*/).join('|')").unwrap();
|
||||
assert_eq!(to_str(&vm, result), "a");
|
||||
}
|
||||
|
||||
// ── match 测试 ──
|
||||
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user