From fdd4783701226e04942ee9e69555a683f61ade52 Mon Sep 17 00:00:00 2001 From: pjh456 <147148383@qq.com> Date: Fri, 25 Sep 2026 14:17:21 +0000 Subject: [PATCH] =?UTF-8?q?fix:=20=E6=AD=A3=E5=88=99=20v=20=E6=A0=87?= =?UTF-8?q?=E5=BF=97=E9=9B=B6=E5=AE=BD=E6=8E=A8=E8=BF=9B=E6=8C=89=E7=A0=81?= =?UTF-8?q?=E7=82=B9=E5=8F=A3=E5=BE=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/oxide_builtins/src/regexp.rs | 24 +++++++++++++------ .../oxide_vm/tests/builtins_regexp_tests.rs | 20 ++++++++++++++++ 2 files changed, 37 insertions(+), 7 deletions(-) diff --git a/crates/oxide_builtins/src/regexp.rs b/crates/oxide_builtins/src/regexp.rs index 532cdd53..b9571102 100644 --- a/crates/oxide_builtins/src/regexp.rs +++ b/crates/oxide_builtins/src/regexp.rs @@ -1094,14 +1094,14 @@ pub(crate) fn replacement_units(vm: &mut H, val: JsValue) -> Result(vm: &mut H, args: &[u8]) -> NativeResult { Ok(g) => g, Err(err) => return NativeResult::Err(err), }; + // fullUnicode = u 或 v 标志任一为真(规范 "If flags contains 'u' or flags + // contains 'v'"),两属性 live 读取后或。 let full_unicode = match rx_get_bool_prop(vm, re_ptr, "unicode", this_val) { Ok(u) => u, Err(err) => return NativeResult::Err(err), }; + let full_unicode = full_unicode + || match rx_get_bool_prop(vm, re_ptr, "unicodeSets", this_val) { + Ok(v) => v, + Err(err) => return NativeResult::Err(err), + }; if !is_global { let result = match regexp_exec_call(vm, re_ptr, this_val, s_val) { @@ -1203,7 +1210,7 @@ pub fn regexp_symbol_match(vm: &mut H, args: &[u8]) -> NativeResult { /// # 步骤 /// 1. this 仅需对象(IsObject 门禁);S = ToString(string)。 /// 2. 非函数分支先 ToString(replacement)(Symbol 抛 TypeError)。 -/// 3. flags/global/unicode 经 Get live 直读;global 时 +/// 3. flags/global/unicode/unicodeSets 经 Get live 直读;global 时 /// ToIntegerOrInfinity(Get "lastIndex") 副作用读后 Set 0。 /// 4. 替换体(regexp_replace_results):exec 循环收集结果(零宽匹配按 /// fullUnicode 口径推进 lastIndex),按结果属性逐条展开 @@ -1211,7 +1218,7 @@ pub fn regexp_symbol_match(vm: &mut H, args: &[u8]) -> NativeResult { /// position 越上界钳到串长,乱序(position 回退)的替换被忽略。 /// /// # 边界与前提 -/// - 各属性读(flags/global/unicode/lastIndex/结果属性)均传播原异常。 +/// - 各属性读(flags/global/unicode/unicodeSets/lastIndex/结果属性)均传播原异常。 /// - exec 结果须为 null 或对象,否则 TypeError。 /// /// # 副作用 @@ -1290,7 +1297,10 @@ pub(crate) fn regexp_replace_results( is_global: bool, ) -> Result, JsValue> { let length_s = units.len(); - let full_unicode = rx_get_bool_prop(vm, re_ptr, "unicode", this_val)?; + // fullUnicode = u 或 v 标志任一为真(规范 "If flags contains 'u' or flags + // contains 'v'"),两属性 live 读取后或。 + let full_unicode = + rx_get_bool_prop(vm, re_ptr, "unicode", this_val)? || rx_get_bool_prop(vm, re_ptr, "unicodeSets", this_val)?; // exec 循环:global 收集到 null 为止,非 global 至多一条。 let mut results: Vec = Vec::new(); @@ -1647,7 +1657,7 @@ pub fn regexp_symbol_split(vm: &mut H, args: &[u8]) -> NativeResult { Ok(f) => f, Err(err) => return NativeResult::Err(err), }; - let unicode_matching = flags.contains('u'); + let unicode_matching = flags.contains('u') || flags.contains('v'); let new_flags = if flags.contains('y') { flags } else { format!("{flags}y") }; let new_flags_val = vm.new_string_owned(new_flags); let splitter_val = match vm.construct_ctor(c, &[this_val, new_flags_val]) { diff --git a/crates/oxide_vm/tests/builtins_regexp_tests.rs b/crates/oxide_vm/tests/builtins_regexp_tests.rs index 18289197..bb3c026c 100644 --- a/crates/oxide_vm/tests/builtins_regexp_tests.rs +++ b/crates/oxide_vm/tests/builtins_regexp_tests.rs @@ -1168,3 +1168,23 @@ fn regexp_replace_results_groups_to_object_and_receiver() { .unwrap(); assert_eq!(to_str(&vm, result), "E"); } + +#[test] +fn regexp_v_flag_zero_width_surrogate_pair_match_returns() { + // 回归钉:v 标志零宽正则对含代理对串必须返回匹配(零宽推进按码点口径, + // 与 exec 起点钳制自洽),不无限循环;replace/split 同族同口径。 + let mut vm = Vm::new(); + let result = eval(&mut vm, "var m = '𠮷a'.match(/(?:)/gv); m.length + ':' + m.join('|')").unwrap(); + assert_eq!(to_str(&vm, result), "3:||"); + let result = eval(&mut vm, "'𠮷a'.replace(/(?:)/gv, 'x')").unwrap(); + assert_eq!(to_str(&vm, result), "x𠮷xax"); + let result = eval(&mut vm, "var m = '𠮷a'.split(/(?:)/gv); m.length + ':' + m.join('|')").unwrap(); + assert_eq!(to_str(&vm, result), "2:𠮷|a"); + let result = eval(&mut vm, "var m = /(?:)/gv.exec('𠮷a'); m.length + ':' + m[0].length").unwrap(); + assert_eq!(to_str(&vm, result), "1:0"); + // u 标志与无标志面零回归(同串同零宽模式)。 + let result = eval(&mut vm, "var m = '𠮷a'.match(/(?:)/gu); m.length + ':' + m.join('|')").unwrap(); + assert_eq!(to_str(&vm, result), "3:||"); + let result = eval(&mut vm, "var m = '𠮷a'.match(/(?:)/g); m.length + ':' + m.join('|')").unwrap(); + assert_eq!(to_str(&vm, result), "4:|||"); +}