mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-23 18:21:13 +08:00
perf: replace 字符串 pattern 走共享借用零拷贝并修 replace 语义
This commit is contained in:
+189
-100
@@ -994,62 +994,198 @@ pub fn string_split<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
NativeResult::Ok(make_string_array(vm, parts))
|
||||
}
|
||||
|
||||
/// `replace`/`replaceAll` 共用的分叉实现:按 replacer 类型与参数形态选择路径,
|
||||
/// 全原始字符串场景零拷贝(共享借用三提取),函数 replacer 场景 receiver 走
|
||||
/// owned(回调跨 `&mut` 的硬约束)。
|
||||
///
|
||||
/// # 步骤
|
||||
/// 1. receiver 前置校验(RequireObjectCoercible,纯 is_* 读取)。
|
||||
/// 2. 纯对象头读取判定分叉:正则身份/编译正则/global 标志/函数 replacer。
|
||||
/// 3. 按分支执行替换。
|
||||
///
|
||||
/// # 边界与前提
|
||||
/// - 缺省 searchValue/replaceValue 均按 ToString(undefined)="undefined" 处理
|
||||
/// (`s.replace()` 全缺省等价于把 "undefined" 替换为 "undefined",结果与
|
||||
/// 原串一致;`s.replace("b")` 得到 "aundefinedc" 而非 "ac")。
|
||||
/// - replaceAll 遇非 global 正则抛 TypeError;类正则对象(proto 恒等
|
||||
/// RegExp.prototype 但无编译正则)replaceAll 返回原串、replace 按 ToString
|
||||
/// 文本走字符串路径。
|
||||
///
|
||||
/// # 注意事项
|
||||
/// - 分支 B 的 `&H` 共享借用与 `&mut` 互斥由编译器强制,三 `&str` 同源可共存
|
||||
/// (E0499 仅发生在 `&mut` 派生 Cow 并存)。
|
||||
/// - 函数 replacer 分支 receiver 必须 owned:`call_function_sync` 是 `&mut`,
|
||||
/// 文本若借自 vm 会 E0499。
|
||||
fn string_replace_impl<H: VmHost>(vm: &mut H, args: &[u8], all: bool) -> NativeResult {
|
||||
let this_val = vm.reg(args[0]);
|
||||
|
||||
// receiver 前置校验:null/undefined/symbol 抛 TypeError(规范第 1 步
|
||||
// RequireObjectCoercible,纯 is_* 读取无用户代码,先于一切分叉)。
|
||||
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 pattern_val = if args.len() >= 2 { vm.reg(args[1]) } else { JsValue::undefined() };
|
||||
let replacement_val = if args.len() > 2 { vm.reg(args[2]) } else { JsValue::undefined() };
|
||||
|
||||
// 分叉判定(全部纯读取/纯对象头读,无用户代码):正则对象身份、是否持有
|
||||
// 编译正则、global 标志、replacer 是否为函数。
|
||||
let is_re = is_regexp_obj(pattern_val, vm);
|
||||
let (has_native_re, is_global) = if is_re {
|
||||
let re_ptr = pattern_val.as_js_object_ptr();
|
||||
// SAFETY: is_re 已保证 pattern_val 为非空对象且 proto 恒等 RegExp.prototype。
|
||||
let re = unsafe { &*re_ptr };
|
||||
if re.native_fn().is_some() {
|
||||
let g = re.hash_props_vec().and_then(|v| v.get(3)).map(|v| v.as_bool()).unwrap_or(false);
|
||||
(true, g)
|
||||
} else {
|
||||
(false, false)
|
||||
}
|
||||
} else {
|
||||
(false, false)
|
||||
};
|
||||
let replacer_val = if replacement_val.is_object() {
|
||||
let o = unsafe { &*replacement_val.as_js_object_ptr() };
|
||||
if o.is_function() {
|
||||
Some(replacement_val)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// 类正则对象无编译正则:replaceAll 按现状返回原串(receiver 转换先行保错误次序)。
|
||||
if all && is_re && !has_native_re {
|
||||
let s = try_string!(this_string(vm, args)).into_owned();
|
||||
return NativeResult::Ok(vm.new_string_owned(s));
|
||||
}
|
||||
|
||||
// replaceAll 要求正则带 global:非 global 正则直接抛 TypeError(规范 flags 检查)。
|
||||
if all && has_native_re && !is_global {
|
||||
return NativeResult::Err(crate::error::create_type_error(
|
||||
vm,
|
||||
"String.prototype.replaceAll called with a non-global RegExp",
|
||||
));
|
||||
}
|
||||
|
||||
// 分支 A:函数 replacer。回调经 call_function_sync(&mut),文本若借自 vm
|
||||
// 会 E0499,receiver 必须 owned 本地 String(跨回调的硬约束)。
|
||||
if let Some(replacer_val) = replacer_val {
|
||||
let s = try_string!(this_string(vm, args)).into_owned();
|
||||
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();
|
||||
// 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);
|
||||
}
|
||||
let pattern = as_string(vm, pattern_val).into_owned();
|
||||
return string_replace_fn(vm, &s, &pattern, replacer_val, all);
|
||||
}
|
||||
|
||||
let replacement_is_string = args.len() <= 2 || replacement_val.is_string();
|
||||
|
||||
// 分支 B:全原始字符串快路径——共享借用三提取,receiver/pattern/replacement
|
||||
// 零拷贝(`&H` 共享借用派生多个 `&str` 可共存,E0499 只发生在 `&mut` 派生)。
|
||||
if this_val.is_string() && pattern_val.is_string() && replacement_is_string {
|
||||
let h = &*vm;
|
||||
let s = h.string_ref(this_val);
|
||||
let p = h.string_ref(pattern_val);
|
||||
let r: &str = if replacement_val.is_string() {
|
||||
h.string_ref(replacement_val)
|
||||
} else {
|
||||
"undefined"
|
||||
};
|
||||
let result = if all { s.replace(p, r) } else { s.replacen(p, r, 1) };
|
||||
return NativeResult::Ok(vm.new_string_owned(result));
|
||||
}
|
||||
|
||||
// 分支 B2:正则 pattern 快路径——regex 为对象内裸指针(不占 vm 借用),
|
||||
// receiver/replacement 共享借用零拷贝。
|
||||
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();
|
||||
// SAFETY: fn_ptr 持有 regexp_constructor 存放的 `Box<regress::Regex>` 指针。
|
||||
let regex = unsafe { &*(fn_ptr.as_ptr() as *const regress::Regex) };
|
||||
let h = &*vm;
|
||||
let s = h.string_ref(this_val);
|
||||
let r: &str = if replacement_val.is_string() {
|
||||
h.string_ref(replacement_val)
|
||||
} else {
|
||||
"undefined"
|
||||
};
|
||||
let result = regex_replace_manual(regex, s, r, is_global);
|
||||
return NativeResult::Ok(vm.new_string_owned(result));
|
||||
}
|
||||
|
||||
// 分支 C:一般路径(对象参与转换)。
|
||||
// 原始字符串 receiver:参数转换前置(&mut 路径)再借 receiver 零拷贝扫描;
|
||||
// 对象 receiver:先 ToString 得 owned(into_owned 即结束借用),转换顺序与
|
||||
// 改造前一致。
|
||||
let (s, pattern, replacement) = if this_val.is_string() {
|
||||
let pattern = if has_native_re {
|
||||
String::new()
|
||||
} else if args.len() < 2 {
|
||||
"undefined".to_string()
|
||||
} else {
|
||||
as_string(vm, pattern_val).into_owned()
|
||||
};
|
||||
let replacement = if args.len() > 2 {
|
||||
as_string(vm, replacement_val).into_owned()
|
||||
} else {
|
||||
"undefined".to_string()
|
||||
};
|
||||
let s = try_string!(this_string(vm, args));
|
||||
(s, pattern, replacement)
|
||||
} else {
|
||||
let s = try_string!(this_string(vm, args)).into_owned();
|
||||
let pattern = if has_native_re {
|
||||
String::new()
|
||||
} else if args.len() < 2 {
|
||||
"undefined".to_string()
|
||||
} else {
|
||||
as_string(vm, pattern_val).into_owned()
|
||||
};
|
||||
let replacement = if args.len() > 2 {
|
||||
as_string(vm, replacement_val).into_owned()
|
||||
} else {
|
||||
"undefined".to_string()
|
||||
};
|
||||
(Cow::Owned(s), pattern, replacement)
|
||||
};
|
||||
|
||||
// 统一扫描:命中编译正则走手动 $ 展开,否则字符串子串替换。
|
||||
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();
|
||||
// SAFETY: fn_ptr 持有 regexp_constructor 存放的 `Box<regress::Regex>` 指针。
|
||||
let regex = unsafe { &*(fn_ptr.as_ptr() as *const regress::Regex) };
|
||||
let result = regex_replace_manual(regex, &s, &replacement, is_global);
|
||||
return NativeResult::Ok(vm.new_string_owned(result));
|
||||
}
|
||||
let result = if all {
|
||||
s.replace(&pattern, &replacement)
|
||||
} else {
|
||||
s.replacen(&pattern, &replacement, 1)
|
||||
};
|
||||
NativeResult::Ok(vm.new_string_owned(result))
|
||||
}
|
||||
|
||||
/// `String.prototype.replace(pattern, replacement)`:替换首个匹配;
|
||||
/// 支持 RegExp(global 全替换)、字符串以及函数替换器。
|
||||
pub fn string_replace<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
builtins_debug!("String.prototype.replace called with {} args", args.len());
|
||||
let s = try_string!(this_string(vm, args)).into_owned();
|
||||
if args.len() < 2 {
|
||||
return NativeResult::Ok(vm.new_string_owned(s));
|
||||
}
|
||||
let pattern_val = vm.reg(args[1]);
|
||||
|
||||
if is_regexp_obj(pattern_val, vm) {
|
||||
let re_ptr = pattern_val.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) };
|
||||
let is_global = re.hash_props_vec().and_then(|v| v.get(3)).map(|v| v.as_bool()).unwrap_or(false);
|
||||
|
||||
if args.len() > 2 {
|
||||
let replacer_val = vm.reg(args[2]);
|
||||
if replacer_val.is_object() {
|
||||
let o = unsafe { &*replacer_val.as_js_object_ptr() };
|
||||
if o.is_function() {
|
||||
return regex_replace_fn(vm, regex, &s, replacer_val, is_global);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let replacement = if args.len() > 2 {
|
||||
as_string(vm, vm.reg(args[2])).into_owned()
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
// 手动展开 $ 引用(regress 的 replace 对 $n 展开为空)。
|
||||
let result = regex_replace_manual(regex, &s, &replacement, is_global);
|
||||
return NativeResult::Ok(vm.new_string_owned(result));
|
||||
}
|
||||
// 无原生正则的类正则对象回退到字符串路径。
|
||||
}
|
||||
|
||||
let pattern = as_string(vm, pattern_val).into_owned();
|
||||
if args.len() > 2 {
|
||||
let replacer_val = vm.reg(args[2]);
|
||||
if replacer_val.is_object() {
|
||||
let o = unsafe { &*replacer_val.as_js_object_ptr() };
|
||||
if o.is_function() {
|
||||
return string_replace_fn(vm, &s, &pattern, replacer_val, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
let replacement = if args.len() > 2 {
|
||||
as_string(vm, vm.reg(args[2])).into_owned()
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
let result = s.replacen(&pattern, &replacement, 1);
|
||||
NativeResult::Ok(vm.new_string_owned(result))
|
||||
string_replace_impl(vm, args, false)
|
||||
}
|
||||
|
||||
/// `String.prototype.match(pattern)`:按 RegExp 匹配;global 返回全部匹配数组,
|
||||
@@ -1411,52 +1547,5 @@ fn make_match_done_result<H: VmHost>(vm: &mut H, value: JsValue) -> NativeResult
|
||||
/// (RegExp 或字符串模式)。
|
||||
pub fn string_replace_all<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
builtins_debug!("String.prototype.replaceAll called with {} args", args.len());
|
||||
let s = try_string!(this_string(vm, args)).into_owned();
|
||||
if args.len() < 2 {
|
||||
return NativeResult::Ok(vm.new_string_owned(s));
|
||||
}
|
||||
let pattern_val = vm.reg(args[1]);
|
||||
if is_regexp_obj(pattern_val, vm) {
|
||||
let re_ptr = pattern_val.as_js_object_ptr();
|
||||
let re = unsafe { &*re_ptr };
|
||||
let fn_ptr = match re.native_fn() {
|
||||
Some(p) => p,
|
||||
None => return NativeResult::Ok(vm.new_string_owned(s)),
|
||||
};
|
||||
// SAFETY: fn_ptr 持有 regexp_constructor 存放的 `Box<regress::Regex>` 指针。
|
||||
let regex = unsafe { &*(fn_ptr.as_ptr() as *const regress::Regex) };
|
||||
if args.len() > 2 {
|
||||
let replacer_val = vm.reg(args[2]);
|
||||
if replacer_val.is_object() {
|
||||
let o = unsafe { &*replacer_val.as_js_object_ptr() };
|
||||
if o.is_function() {
|
||||
return regex_replace_fn(vm, regex, &s, replacer_val, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
let replacement = if args.len() > 2 {
|
||||
as_string(vm, vm.reg(args[2])).into_owned()
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
let result = regex_replace_manual(regex, &s, &replacement, true);
|
||||
return NativeResult::Ok(vm.new_string_owned(result));
|
||||
}
|
||||
let pattern = as_string(vm, pattern_val).into_owned();
|
||||
if args.len() > 2 {
|
||||
let replacer_val = vm.reg(args[2]);
|
||||
if replacer_val.is_object() {
|
||||
let o = unsafe { &*replacer_val.as_js_object_ptr() };
|
||||
if o.is_function() {
|
||||
return string_replace_fn(vm, &s, &pattern, replacer_val, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
let replacement = if args.len() > 2 {
|
||||
as_string(vm, vm.reg(args[2])).into_owned()
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
let result = s.replace(&pattern, &replacement);
|
||||
NativeResult::Ok(vm.new_string_owned(result))
|
||||
string_replace_impl(vm, args, true)
|
||||
}
|
||||
|
||||
@@ -305,6 +305,113 @@ fn string_replace_string_pattern() {
|
||||
assert_eq!(to_str(&vm, s), "heLlo");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn string_replace_string_pattern_only_first() {
|
||||
// 字符串 pattern 只替换首个匹配。
|
||||
let mut vm = Vm::new();
|
||||
let s = eval(&mut vm, "'aba'.replace('a', 'c')").unwrap();
|
||||
assert_eq!(to_str(&vm, s), "cba");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn string_replace_regex_global_all() {
|
||||
// 正则 global 全替换,非 global 只替换首个。
|
||||
let mut vm = Vm::new();
|
||||
let s = eval(&mut vm, "'hello'.replace(/l/g, 'L')").unwrap();
|
||||
assert_eq!(to_str(&vm, s), "heLLo");
|
||||
let s = eval(&mut vm, "'hello'.replace(/l/, 'L')").unwrap();
|
||||
assert_eq!(to_str(&vm, s), "heLlo");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn string_replace_function_replacer_string_pattern() {
|
||||
// 函数 replacer + 字符串 pattern:回调参数 (match, position, string)。
|
||||
let mut vm = Vm::new();
|
||||
let s = eval(&mut vm, "'ab'.replace('a', function(m){ return m.toUpperCase() })").unwrap();
|
||||
assert_eq!(to_str(&vm, s), "Ab");
|
||||
let s = eval(&mut vm, "'abc'.replace('b', function(m, o){ return o })").unwrap();
|
||||
assert_eq!(to_str(&vm, s), "a1c");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn string_replace_missing_replacement_is_undefined_string() {
|
||||
// 缺失 replaceValue 按 ToString(undefined)="undefined" 替换(非空串)。
|
||||
let mut vm = Vm::new();
|
||||
let s = eval(&mut vm, "'abc'.replace('b')").unwrap();
|
||||
assert_eq!(to_str(&vm, s), "aundefinedc");
|
||||
let s = eval(&mut vm, "'abc'.replaceAll('b')").unwrap();
|
||||
assert_eq!(to_str(&vm, s), "aundefinedc");
|
||||
let s = eval(&mut vm, "'abc'.replace('b', undefined)").unwrap();
|
||||
assert_eq!(to_str(&vm, s), "aundefinedc");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn string_replace_missing_args_replace_undefined_pattern() {
|
||||
// 缺失 searchValue 按 "undefined" 文本替换(找不到则原串不变)。
|
||||
let mut vm = Vm::new();
|
||||
let s = eval(&mut vm, "'xundefinedy'.replace()").unwrap();
|
||||
assert_eq!(to_str(&vm, s), "xundefinedy");
|
||||
let s = eval(&mut vm, "'xundefinedy'.replaceAll()").unwrap();
|
||||
assert_eq!(to_str(&vm, s), "xundefinedy");
|
||||
let s = eval(&mut vm, "'abc'.replace()").unwrap();
|
||||
assert_eq!(to_str(&vm, s), "abc");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn string_replace_all_nonglobal_regex_throws() {
|
||||
// replaceAll 遇非 global 正则抛 TypeError。
|
||||
let mut vm = Vm::new();
|
||||
for src in [
|
||||
"'abc'.replaceAll(/b/, 'X')",
|
||||
"'abc'.replaceAll(/b/, function(){ return 'X' })",
|
||||
"String.prototype.replaceAll.call('abc', /b/)",
|
||||
] {
|
||||
let err = eval(&mut vm, src).unwrap_err();
|
||||
assert!(err.contains("TypeError"), "{src} 应抛 TypeError,实际: {err}");
|
||||
}
|
||||
let s = eval(&mut vm, "'abc'.replaceAll(/b/g, 'X')").unwrap();
|
||||
assert_eq!(to_str(&vm, s), "aXc");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn string_replace_regex_proto_subclass_fallback() {
|
||||
// 类正则对象(proto 恒等 RegExp.prototype 但无编译正则):
|
||||
// replace 走字符串文本路径,replaceAll 返回原串。
|
||||
let mut vm = Vm::new();
|
||||
let s = eval(&mut vm, "var sp = Object.create(RegExp.prototype); 'aXb'.replace(sp, 'Y')").unwrap();
|
||||
assert_eq!(to_str(&vm, s), "aXb");
|
||||
let s = eval(&mut vm, "var sp = Object.create(RegExp.prototype); 'aXb'.replaceAll(sp, 'Y')").unwrap();
|
||||
assert_eq!(to_str(&vm, s), "aXb");
|
||||
let s = eval(
|
||||
&mut vm,
|
||||
"var sp = Object.create(RegExp.prototype); 'aXb'.replace(sp, function(m){ return 'Z' })",
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(to_str(&vm, s), "aXb");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn string_replace_boxed_receiver_and_replacement() {
|
||||
// boxed receiver 走对象 ToString 路径,boxed replacement 走 ToString 转换。
|
||||
let mut vm = Vm::new();
|
||||
let s = eval(&mut vm, "new String('abc').replace('b', 'X')").unwrap();
|
||||
assert_eq!(to_str(&vm, s), "aXc");
|
||||
let s = eval(&mut vm, "'abc'.replace('b', new String('X'))").unwrap();
|
||||
assert_eq!(to_str(&vm, s), "aXc");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn string_replace_astral_and_empty_pattern() {
|
||||
// astral 串走零拷贝快路径不破坏代理对;空 pattern 语义保持。
|
||||
let mut vm = Vm::new();
|
||||
let s = eval(&mut vm, "'\\u{1F600}x\\u{1F600}'.replace('x', 'Y')").unwrap();
|
||||
assert_eq!(to_str(&vm, s), "\u{1F600}Y\u{1F600}");
|
||||
let s = eval(&mut vm, "'abc'.replace('', '-')").unwrap();
|
||||
assert_eq!(to_str(&vm, s), "-abc");
|
||||
let s = eval(&mut vm, "'abc'.replaceAll('', '-')").unwrap();
|
||||
assert_eq!(to_str(&vm, s), "-a-b-c-");
|
||||
}
|
||||
|
||||
// ── split 测试 ──
|
||||
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user