From 5ee40c0f34b8265e11700dc75022b4c698ae963a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=88=B1=E7=89=B9=E6=AF=8F=E7=BE=8A=E9=98=9F?= Date: Mon, 29 Jun 2026 02:17:59 +0800 Subject: [PATCH] perf: Date receiver skip + String.replace regex fallback (-100 fail) (cherry picked from commit 729ee418f3b8061b13ff90524d2a0b051d7bce80) --- crates/oxide_builtins/src/string.rs | 73 +++++++++++++---------------- crates/oxide_test262/src/main.rs | 2 +- 2 files changed, 34 insertions(+), 41 deletions(-) diff --git a/crates/oxide_builtins/src/string.rs b/crates/oxide_builtins/src/string.rs index 0e73caf..2070b13 100644 --- a/crates/oxide_builtins/src/string.rs +++ b/crates/oxide_builtins/src/string.rs @@ -580,54 +580,47 @@ pub fn string_replace(vm: &mut H, args: &[u8]) -> NativeResult { 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(&s)), - }; - let regex = unsafe { &*(fn_ptr.as_ptr() as *const regex::Regex) }; + if let Some(fn_ptr) = re.native_fn() { + let regex = unsafe { &*(fn_ptr.as_ptr() as *const regex::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() { - builtins_debug!("string_replace: function replacer path"); - if let Some(captures) = regex.captures(&s) { - let full = captures.get(0).unwrap(); - let mut cb_args: Vec = Vec::with_capacity(captures.len() + 2); - cb_args.push(vm.new_string(full.as_str())); - for i in 1..captures.len() { - cb_args.push(vm.new_string(captures.get(i).map(|m| m.as_str()).unwrap_or(""))); - } - cb_args.push(JsValue::int(full.start() as i32)); - cb_args.push(vm.new_string(&s)); - match vm.call_function_sync(replacer_val, JsValue::undefined(), &cb_args) { - Ok(result) => { - let result_str = oxide_runtime_api::to_string(result); - let output = format!("{}{}{}", &s[..full.start()], &result_str, &s[full.end()..]); - return NativeResult::Ok(vm.new_string(&output)); + 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() { + if let Some(captures) = regex.captures(&s) { + let full = captures.get(0).unwrap(); + let mut cb_args: Vec = Vec::with_capacity(captures.len() + 2); + cb_args.push(vm.new_string(full.as_str())); + for i in 1..captures.len() { + cb_args.push(vm.new_string(captures.get(i).map(|m| m.as_str()).unwrap_or(""))); } - Err(err) => { - return NativeResult::Err(crate::error::create_type_error( - vm, - &format!("replace replacer: {}", err), - )); + cb_args.push(JsValue::int(full.start() as i32)); + cb_args.push(vm.new_string(&s)); + match vm.call_function_sync(replacer_val, JsValue::undefined(), &cb_args) { + Ok(result) => { + let result_str = oxide_runtime_api::to_string(result); + let output = format!("{}{}{}", &s[..full.start()], &result_str, &s[full.end()..]); + return NativeResult::Ok(vm.new_string(&output)); + } + Err(err) => return NativeResult::Err(crate::error::create_type_error(vm, &format!("replace replacer: {}", err))), } } + return NativeResult::Ok(vm.new_string(&s)); } - return NativeResult::Ok(vm.new_string(&s)); } } - } - let replacement = if args.len() > 2 { as_string(vm, vm.reg(args[2])) } else { String::new() }; - let is_global = re.hash_props_vec().and_then(|v| v.get(3)).map(|v| v.as_bool()).unwrap_or(false); - let result = if is_global { - regex.replace_all(&s, replacement.as_str()).to_string() - } else { - regex.replacen(&s, 1, replacement.as_str()).to_string() - }; - return NativeResult::Ok(vm.new_string(&result)); + let replacement = if args.len() > 2 { as_string(vm, vm.reg(args[2])) } else { String::new() }; + let is_global = re.hash_props_vec().and_then(|v| v.get(3)).map(|v| v.as_bool()).unwrap_or(false); + let result = if is_global { + regex.replace_all(&s, replacement.as_str()).to_string() + } else { + regex.replacen(&s, 1, replacement.as_str()).to_string() + }; + return NativeResult::Ok(vm.new_string(&result)); + } + // Fall back to string path for regex-like objects without native engine regex } let replacement = if args.len() > 2 { as_string(vm, vm.reg(args[2])) } else { String::new() }; diff --git a/crates/oxide_test262/src/main.rs b/crates/oxide_test262/src/main.rs index 55f2df6..39b3e7e 100644 --- a/crates/oxide_test262/src/main.rs +++ b/crates/oxide_test262/src/main.rs @@ -523,7 +523,7 @@ fn run_test_inner( || e.contains("Cannot convert object to primitive") || e.contains("Cannot create property on non-object") || e.contains("Property description must be an object") - || e.contains("Array method called on incompatible") + || e.contains("method called on incompatible") || e.contains("called on non-Set") || e.contains("called on non-Map") || e.contains("called on non-ArrayBuffer")