From bd9b9813621e93f4a07740dcbfa94b3e735372ae Mon Sep 17 00:00:00 2001 From: pjh456 <147148383@qq.com> Date: Fri, 14 Aug 2026 12:54:27 +0800 Subject: [PATCH] =?UTF-8?q?perf:=20replace=20=E5=AD=97=E7=AC=A6=E4=B8=B2?= =?UTF-8?q?=20pattern=20=E8=B5=B0=E5=85=B1=E4=BA=AB=E5=80=9F=E7=94=A8?= =?UTF-8?q?=E9=9B=B6=E6=8B=B7=E8=B4=9D=E5=B9=B6=E4=BF=AE=20replace=20?= =?UTF-8?q?=E8=AF=AD=E4=B9=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/oxide_builtins/src/string.rs | 289 ++++++++++++------ .../oxide_vm/tests/builtins_string_tests.rs | 107 +++++++ 2 files changed, 296 insertions(+), 100 deletions(-) diff --git a/crates/oxide_builtins/src/string.rs b/crates/oxide_builtins/src/string.rs index 00363ca..9f2bff3 100644 --- a/crates/oxide_builtins/src/string.rs +++ b/crates/oxide_builtins/src/string.rs @@ -994,62 +994,198 @@ pub fn string_split(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(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` 指针。 + 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` 指针。 + 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` 指针。 + 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(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(vm: &mut H, value: JsValue) -> NativeResult /// (RegExp 或字符串模式)。 pub fn string_replace_all(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` 指针。 - 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) } diff --git a/crates/oxide_vm/tests/builtins_string_tests.rs b/crates/oxide_vm/tests/builtins_string_tests.rs index e5e0b35..a3dc53f 100644 --- a/crates/oxide_vm/tests/builtins_string_tests.rs +++ b/crates/oxide_vm/tests/builtins_string_tests.rs @@ -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]