From 29bad1e5822f465c8cf382e04dcff23962b35533 Mon Sep 17 00:00:00 2001 From: pjh456 <147148383@qq.com> Date: Tue, 22 Sep 2026 02:20:42 +0000 Subject: [PATCH] =?UTF-8?q?fix:=20=E5=88=A0=20exec=20=E5=B0=BE=E9=83=A8=20?= =?UTF-8?q?lastIndex=20=E9=87=8D=E5=A4=8D=E5=86=99=E5=9B=9E=E5=B9=B6?= =?UTF-8?q?=E4=BF=AE=E6=AD=A3=20flags=20=E6=B3=A8=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/oxide_builtins/src/regexp.rs | 22 +++++++------------ .../oxide_vm/tests/builtins_regexp_tests.rs | 9 ++++++++ 2 files changed, 17 insertions(+), 14 deletions(-) diff --git a/crates/oxide_builtins/src/regexp.rs b/crates/oxide_builtins/src/regexp.rs index 2dc4f917..a1092abb 100644 --- a/crates/oxide_builtins/src/regexp.rs +++ b/crates/oxide_builtins/src/regexp.rs @@ -541,8 +541,8 @@ pub fn clone_regexp_native(old_obj: &JsObject, new_obj: &mut JsObject) { /// /// # 步骤 /// 1. this 须为 RegExp 实例(内部槽门禁,非对象/非 RegExp 抛 TypeError)。 -/// 2. S = ToString(string);flags 串判 global/sticky(sticky 时 global 归 -/// false 的效果体现为两者只影响同一分支)。 +/// 2. S = ToString(string);flags 串判 global/sticky(g/y 字面量互斥,至多 +/// 其一,共用 tracks 分支)。 /// 3. 共享搜索核(与 exec 同一实现):lastIndex 读、越界短路、sticky 锚定 /// 过滤、lastIndex Set 写回。 /// 4. 命中返回 true,否则 false。 @@ -593,7 +593,7 @@ pub fn regexp_test(vm: &mut H, args: &[u8]) -> NativeResult { } /// exec/test 共享搜索核(规范 RegExpBuiltinExec 的搜索部分):lastIndex 经 -/// Get + ToLength 读取,搜索起点(global/sticky 取 lastIndex、其余取 0), +/// Get + ToLength 无条件读取(非 global/sticky 臂读到后丢弃、起点恒 0), /// 越界短路(先 Set 0),匹配与 sticky 后置锚定过滤,lastIndex Set 写回 /// (失败置 0、成功置匹配末尾,仅 global/sticky,非可写属性抛 TypeError)。 /// @@ -607,7 +607,8 @@ fn rx_search( vm: &mut H, re_ptr: *mut JsObject, this_val: JsValue, regex: ®ress::Regex, text: &MatchText, tracks_last_index: bool, is_sticky: bool, ) -> Result, JsValue> { - // lastIndex 读走 Get + ToLength:完整属性解析,读异常传播原异常。 + // lastIndex 读无条件执行(规范先读后在非 global/sticky 臂丢弃); + // 读走 Get + ToLength:完整属性解析,读异常传播原异常。 let last_index = { let li_si = vm.kernel_core().perm_interner().intern("lastIndex").0; match vm.ordinary_get(unsafe { &*re_ptr }, li_si, this_val) { @@ -636,7 +637,7 @@ fn rx_search( // 过滤——底层引擎对 y 不原生锚定,命中起点须恰在 lastIndex 才算有效。 let match_result = text.find_from_units(regex, start); let anchored = if is_sticky { - match_result.filter(|m| text.unit_pos(m.range().start) == last_index) + match_result.filter(|m| text.unit_pos(m.range().start) == start) } else { match_result }; @@ -662,8 +663,8 @@ fn rx_search( /// # 步骤 /// 1. this 仅需对象(IsObject 门禁);编译正则槽必须存在,非 RegExp 对象无 /// 可匹配模式,同抛 TypeError。 -/// 2. S = ToString(string);flags 串判 global/sticky(sticky 时 global 归 false -/// 的效果体现为两者只影响同一分支)。 +/// 2. S = ToString(string);flags 串判 global/sticky(g/y 字面量互斥,至多 +/// 其一,共用 tracks 分支)。 /// 3. 共享搜索核(与 test 同一实现):lastIndex 读(Get + ToLength)、越界 /// 短路(先 Set 0)、sticky 后置锚定过滤、lastIndex Set 写回。 /// 4. 无匹配回 null;命中构建结果数组(捕获组、index、input、groups)。 @@ -752,13 +753,6 @@ pub fn regexp_exec(vm: &mut H, args: &[u8]) -> NativeResult { vm.set_or_create_prop_value(unsafe { &mut *arr }, indices_si, indices_val); } - // 成功:lastIndex 推进到匹配末尾(仅 global/sticky),Set 语义。 - if tracks_last_index { - if let Err(err) = set_last_index(vm, re_ptr, this_val, text.unit_pos(range.end)) { - return NativeResult::Err(err); - } - } - NativeResult::Ok(JsValue::from_js_object(arr)) } diff --git a/crates/oxide_vm/tests/builtins_regexp_tests.rs b/crates/oxide_vm/tests/builtins_regexp_tests.rs index 495180f0..0315b93b 100644 --- a/crates/oxide_vm/tests/builtins_regexp_tests.rs +++ b/crates/oxide_vm/tests/builtins_regexp_tests.rs @@ -265,6 +265,15 @@ fn regexp_exec_negative_last_index_clamped_to_zero() { assert_eq!(to_str(&vm, result), "0:1"); } +#[test] +fn regexp_exec_last_index_set_once_on_success() { + // 成功路径 lastIndex 恰好 Set 一次(写回在核内单点):计数 setter 只被观察一次。 + let source = "(() => { var re = /a/g; var n = 0; Object.defineProperty(re, 'lastIndex', { configurable: true, set() { n += 1; }, get() { return 0; } }); re.exec('a'); return String(n); })()"; + let mut vm = Vm::new(); + let result = eval(&mut vm, source).unwrap(); + assert_eq!(to_str(&vm, result), "1"); +} + // --- test() lastIndex 规范语义(sticky 锚定 / 越界短路 / 不追踪不写回)--- #[test]