From c2aea3f8e9fd2e62d0e7e1b2a51954958f94bcf8 Mon Sep 17 00:00:00 2001 From: pjh456 <147148383@qq.com> Date: Fri, 14 Aug 2026 10:44:23 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20split=20=E5=88=86=E9=9A=94=E7=AC=A6?= =?UTF-8?q?=E4=B8=BA=20RegExp=20=E5=AD=90=E7=B1=BB=E5=AF=B9=E8=B1=A1?= =?UTF-8?q?=E6=97=B6=E6=8C=89=E5=AD=97=E7=AC=A6=E4=B8=B2=E8=AF=AD=E4=B9=89?= =?UTF-8?q?=E5=88=87=E5=88=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/oxide_builtins/src/string.rs | 11 +- .../oxide_vm/tests/builtins_string_tests.rs | 128 ++++++++++++++++++ 2 files changed, 138 insertions(+), 1 deletion(-) diff --git a/crates/oxide_builtins/src/string.rs b/crates/oxide_builtins/src/string.rs index afefa0e..7085ea3 100644 --- a/crates/oxide_builtins/src/string.rs +++ b/crates/oxide_builtins/src/string.rs @@ -885,7 +885,16 @@ pub fn string_split(vm: &mut H, args: &[u8]) -> NativeResult { Some(v) if !v.is_undefined() => is_regexp_obj(v, vm), _ => false, }; - let sep = if is_undefined_sep || is_re { + // 类正则对象是否持有编译正则:native_fn 存在才是真 RegExp 实例,命中正则切分路径; + // 无编译正则的类正则对象(如 Object.create(RegExp.prototype))回退字符串路径, + // 须按 ToString 文本切分,且转换(&mut 路径)须先于 this 借用完成。 + let has_native_re = is_re && { + let re_ptr = sep_val.unwrap().as_js_object_ptr(); + // SAFETY: is_re 已保证 sep_val 为非空对象且 proto 恒等 RegExp.prototype。 + let re = unsafe { &*re_ptr }; + re.native_fn().is_some() + }; + let sep = if is_undefined_sep || has_native_re { String::new() } else { as_string(vm, sep_val.unwrap()).into_owned() diff --git a/crates/oxide_vm/tests/builtins_string_tests.rs b/crates/oxide_vm/tests/builtins_string_tests.rs index 0cca2a5..6390e91 100644 --- a/crates/oxide_vm/tests/builtins_string_tests.rs +++ b/crates/oxide_vm/tests/builtins_string_tests.rs @@ -348,6 +348,51 @@ fn string_split_string_separator() { assert_eq!(obj.prop_count(), 3); } +#[test] +fn string_split_regex_proto_subclass_fallback() { + // 类正则对象(proto 恒等 RegExp.prototype 但无编译正则)回退字符串路径, + // 按 ToString 文本("[object]")切分,不落入逐字符切分。 + let mut vm = Vm::new(); + let result = eval(&mut vm, "var sp = Object.create(RegExp.prototype); 'aXb'.split(sp)").unwrap(); + let obj = unsafe { &*result.as_js_object_ptr() }; + assert_eq!(obj.prop_count(), 1); + assert_eq!(to_str(&vm, obj.get_prop_at(0)), "aXb"); +} + +#[test] +fn string_split_empty_string_separator() { + let mut vm = Vm::new(); + let result = eval(&mut vm, "'abc'.split('')").unwrap(); + let obj = unsafe { &*result.as_js_object_ptr() }; + assert_eq!(obj.prop_count(), 3); + assert_eq!(to_str(&vm, obj.get_prop_at(0)), "a"); + assert_eq!(to_str(&vm, obj.get_prop_at(2)), "c"); + + let empty = eval(&mut vm, "''.split('')").unwrap(); + let obj = unsafe { &*empty.as_js_object_ptr() }; + assert_eq!(obj.prop_count(), 0); +} + +#[test] +fn string_split_undefined_separator_returns_single() { + let mut vm = Vm::new(); + let result = eval(&mut vm, "'a-b'.split(undefined)").unwrap(); + let obj = unsafe { &*result.as_js_object_ptr() }; + assert_eq!(obj.prop_count(), 1); + assert_eq!(to_str(&vm, obj.get_prop_at(0)), "a-b"); +} + +#[test] +fn string_split_real_regex_still_regex_path() { + // 真 RegExp 实例(native_fn 存在)仍走正则切分,含尾部空串。 + let mut vm = Vm::new(); + let result = eval(&mut vm, "'ab'.split(/b/)").unwrap(); + let obj = unsafe { &*result.as_js_object_ptr() }; + assert_eq!(obj.prop_count(), 2); + assert_eq!(to_str(&vm, obj.get_prop_at(0)), "a"); + assert_eq!(to_str(&vm, obj.get_prop_at(1)), ""); +} + // ── match 测试 ── #[test] @@ -523,3 +568,86 @@ fn string_last_index_of_empty_with_position() { let result = eval(&mut vm, "'hello'.lastIndexOf('', 3)").unwrap(); assert_eq!(result.as_int(), 3); } + +#[test] +fn string_pad_start_target_not_exceeding_fast_path() { + // targetLength 不大于原串长度时快速返回原串。 + let mut vm = Vm::new(); + let s = eval(&mut vm, "'abc'.padStart(2)").unwrap(); + assert_eq!(to_str(&vm, s), "abc"); + let s = eval(&mut vm, "'abc'.padStart(3, 'x')").unwrap(); + assert_eq!(to_str(&vm, s), "abc"); +} + +#[test] +fn string_pad_empty_pad_string_fast_path() { + // padString 为空串时快速返回原串。 + let mut vm = Vm::new(); + let s = eval(&mut vm, "'ab'.padStart(4, '')").unwrap(); + assert_eq!(to_str(&vm, s), "ab"); + let s = eval(&mut vm, "'ab'.padEnd(4, '')").unwrap(); + assert_eq!(to_str(&vm, s), "ab"); +} + +#[test] +fn string_to_well_formed_ascii() { + let mut vm = Vm::new(); + let s = eval(&mut vm, "'abc'.toWellFormed()").unwrap(); + assert_eq!(to_str(&vm, s), "abc"); + let b = eval(&mut vm, "'abc'.isWellFormed()").unwrap(); + assert!(b.as_bool()); +} + +#[test] +fn string_normalize_decomposed_and_composed() { + let mut vm = Vm::new(); + let s = eval(&mut vm, "'\\u00E9'.normalize('NFD')").unwrap(); + assert_eq!(to_str(&vm, s), "e\u{0301}"); + let s = eval(&mut vm, "'e\\u0301'.normalize('NFC')").unwrap(); + assert_eq!(to_str(&vm, s), "\u{00E9}"); +} + +#[test] +fn boxed_string_receiver_methods() { + // boxed String 对象作为 receiver 时取内部原始串执行方法。 + let mut vm = Vm::new(); + let result = eval(&mut vm, "new String('abc').split('')").unwrap(); + let obj = unsafe { &*result.as_js_object_ptr() }; + assert_eq!(obj.prop_count(), 3); + assert_eq!(to_str(&vm, obj.get_prop_at(0)), "a"); + let s = eval(&mut vm, "new String('abc').padStart(5, 'x')").unwrap(); + assert_eq!(to_str(&vm, s), "xxabc"); + let s = eval(&mut vm, "new String('abc').toWellFormed()").unwrap(); + assert_eq!(to_str(&vm, s), "abc"); +} + +#[test] +fn string_methods_null_receiver_throw_type_error() { + // null receiver 在构造类方法上统一抛 TypeError。 + let mut vm = Vm::new(); + for src in [ + "String.prototype.split.call(null, ',')", + "String.prototype.padStart.call(null, 5)", + "String.prototype.slice.call(null)", + "String.prototype.normalize.call(null)", + "String.prototype.toWellFormed.call(null)", + "String.prototype.repeat.call(null, 2)", + "String.prototype.trim.call(null)", + "String.prototype.codePointAt.call(null, 0)", + ] { + let err = eval(&mut vm, src).unwrap_err(); + assert!(err.contains("TypeError"), "{src} 应抛 TypeError,实际: {err}"); + } +} + +#[test] +fn string_slice_astral_character_indices() { + // 字符索引语义:astral 字符按单字符计位,切片不截断代理对。 + let mut vm = Vm::new(); + let s = eval(&mut vm, "'\\u{1F600}ab'.slice(1, 3)").unwrap(); + assert_eq!(to_str(&vm, s), "ab"); + let s = eval(&mut vm, "'\\u{1F600}ab'.slice(0, 1)").unwrap(); + assert_eq!(to_str(&vm, s), "\u{1F600}"); + let s = eval(&mut vm, "'\\u{1F600}'.padStart(3, 'x')").unwrap(); + assert_eq!(to_str(&vm, s), "xx\u{1F600}"); +}