feat: RegExp 五 Symbol 入口 live flag 直读与 lastIndex 替换收尾

This commit is contained in:
2026-09-21 07:02:56 +00:00
parent b1fade6afc
commit b4d281d109
4 changed files with 1146 additions and 277 deletions
File diff suppressed because it is too large Load Diff
@@ -59,14 +59,6 @@ impl<'a> MatchText<'a> {
}
}
/// 臂原生长度(Str 臂字节、Units 臂码元)——`slice` 的端点口径。
pub(crate) fn raw_len(&self) -> usize {
match self {
MatchText::Str(s) => s.len(),
MatchText::Units(u) => u.len(),
}
}
/// 借出单元序列(Str 臂一次性编码为 owned;Units 臂借用/浅拷)。
pub(crate) fn units(&self) -> Cow<'a, [u16]> {
match self {
+6 -6
View File
@@ -94,14 +94,14 @@ pub fn bind_regexp(core: &Arc<KernelCore>, session: &KernelSession, global: &mut
oxide_builtins::regexp::regexp_get_unicode_sets::<crate::vm::Vm> as *const (),
);
// Symbol.match 等 well-known symbol 方法按 Symbol 键安装,供 `re[Symbol.match]` 等读取。
// well-known symbol 方法按 Symbol 键安装,name 属性取 `[Symbol.match]` 式标签,供 `re[Symbol.match]` 等读取。
let world = session.builtin_world();
bind_well_known_method(
world,
core,
proto,
1,
"@@match",
"[Symbol.match]",
oxide_builtins::regexp::regexp_symbol_match::<crate::vm::Vm> as *const (),
1,
);
@@ -110,7 +110,7 @@ pub fn bind_regexp(core: &Arc<KernelCore>, session: &KernelSession, global: &mut
core,
proto,
2,
"@@replace",
"[Symbol.replace]",
oxide_builtins::regexp::regexp_symbol_replace::<crate::vm::Vm> as *const (),
2,
);
@@ -119,7 +119,7 @@ pub fn bind_regexp(core: &Arc<KernelCore>, session: &KernelSession, global: &mut
core,
proto,
3,
"@@search",
"[Symbol.search]",
oxide_builtins::regexp::regexp_symbol_search::<crate::vm::Vm> as *const (),
1,
);
@@ -128,7 +128,7 @@ pub fn bind_regexp(core: &Arc<KernelCore>, session: &KernelSession, global: &mut
core,
proto,
4,
"@@split",
"[Symbol.split]",
oxide_builtins::regexp::regexp_symbol_split::<crate::vm::Vm> as *const (),
2,
);
@@ -137,7 +137,7 @@ pub fn bind_regexp(core: &Arc<KernelCore>, session: &KernelSession, global: &mut
core,
proto,
7,
"@@matchAll",
"[Symbol.matchAll]",
oxide_builtins::regexp::regexp_symbol_match_all::<crate::vm::Vm> as *const (),
1,
);
@@ -584,3 +584,185 @@ fn regexp_flag_getter_non_regexp_this_throws_type_error() {
let result = eval(&mut vm, source).unwrap();
assert_eq!(to_str(&vm, result), "TypeError");
}
#[test]
fn symbol_match_live_global_shadow_and_exec_result() {
let mut vm = Vm::new();
// 影子 global 数据属性压过原型访问器(非 g 正则走 global 循环)。
let result = eval(
&mut vm,
"(() => { var r = /b/; var n = 0; r.exec = function() { n += 1; return n === 1 ? ['b'] : null; }; Object.defineProperty(r, 'global', { value: true, writable: true, configurable: true }); return r[Symbol.match]('abc').join(','); })()",
)
.unwrap();
assert_eq!(to_str(&vm, result), "b");
// 非 global 直接返回 exec 结果原值(对象恒等)。
let result = eval(
&mut vm,
"(() => { var r = /b/; var marker = { tag: 1 }; r.exec = function() { return marker; }; return r[Symbol.match]('abc') === marker; })()",
)
.unwrap();
assert!(result.as_bool());
}
#[test]
fn symbol_match_live_flag_reads_propagate() {
let mut vm = Vm::new();
// flags getter 抛错原样传播(unicode 读先于非 global 返回,读序不短路)。
let result = eval(
&mut vm,
"(() => { var r = /b/; Object.defineProperty(r, 'flags', { get() { throw new RangeError('flags-boom'); } }); try { r[Symbol.match]('abc'); return 'no-throw'; } catch (e) { return e.message; } })()",
)
.unwrap();
assert_eq!(to_str(&vm, result), "flags-boom");
let result = eval(
&mut vm,
"(() => { var r = /b/; Object.defineProperty(r, 'unicode', { get() { throw new RangeError('unicode-boom'); } }); try { r[Symbol.match]('abc'); return 'no-throw'; } catch (e) { return e.message; } })()",
)
.unwrap();
assert_eq!(to_str(&vm, result), "unicode-boom");
}
#[test]
fn symbol_entries_accept_plain_object_this() {
let mut vm = Vm::new();
// this 放宽:普通对象(带 exec)可作 match/replace/search 的 this。
let result = eval(
&mut vm,
"(() => { var o = { exec: function() { return ['x', { index: 0 }]; } }; return RegExp.prototype[Symbol.replace].call(o, 'abc', 'Y'); })()",
)
.unwrap();
assert_eq!(to_str(&vm, result), "Ybc");
let result = eval(
&mut vm,
"(() => { var o = { exec: function() { return { index: 2 }; } }; return RegExp.prototype[Symbol.search].call(o, 'abcd'); })()",
)
.unwrap();
// ToNumber 结果为浮点形态(2.0)。
assert_eq!(result.as_double(), 2.0);
}
#[test]
fn symbol_replace_functional_groups_tail_arg() {
let mut vm = Vm::new();
// groups 非 undefined 时作末参(5 参);undefined 时原串为末参(4 参)。
let result = eval(
&mut vm,
"(() => { var r = /b/; r.exec = function() { return { length: 1, 0: 'b', index: 1, groups: { g: 1 } }; }; return r[Symbol.replace]('abc', function() { return arguments.length; }); })()",
)
.unwrap();
assert_eq!(to_str(&vm, result), "a4c");
let result = eval(
&mut vm,
"(() => { var r = /b/; r.exec = function() { return { length: 1, 0: 'b', index: 1 }; }; return r[Symbol.replace]('abc', function() { return arguments.length; }); })()",
)
.unwrap();
assert_eq!(to_str(&vm, result), "a3c");
}
#[test]
fn symbol_replace_out_of_order_position_ignored() {
let mut vm = Vm::new();
// position 回退的替换被忽略(乱序结果)。
let result = eval(
&mut vm,
"(() => { var r = /./g; var n = 0; r.exec = function() { n += 1; if (n === 1) return { index: 1, length: 1, 0: 'x' }; if (n === 2) return { index: 1, length: 1, 0: 'y' }; return null; }; return r[Symbol.replace]('abcde', 'Z'); })()",
)
.unwrap();
assert_eq!(to_str(&vm, result), "aZcde");
}
#[test]
fn symbol_search_lastindex_init_and_restore() {
let mut vm = Vm::new();
// previous 与 +0 非 SameValue 时先 Set 0;exec 后按原值恢复。
let result = eval(
&mut vm,
"(() => { var r = /b/g; var seen = null; r.lastIndex = 3; r.exec = function() { seen = r.lastIndex; return { index: 1 }; }; r[Symbol.search]('abcd'); return seen + '/' + r.lastIndex; })()",
)
.unwrap();
assert_eq!(to_str(&vm, result), "0/3");
// 严格 SameValue 口径:-0 与 +0 不同值,初始化 Set 0 与恢复 Set -0
// 各触发一次写。
let result = eval(
&mut vm,
"(() => { var r = /b/; var sets = 0; var store = -0; Object.defineProperty(r, 'lastIndex', { configurable: true, set(v) { sets += 1; store = v; }, get() { return store; } }); r.exec = function() { return null; }; r[Symbol.search](''); return sets; })()",
)
.unwrap();
assert!(result.as_int() == 2);
}
#[test]
fn symbol_split_species_and_y_flags() {
let mut vm = Vm::new();
// species 构造收到补 y 的 flags;捕获组按原值推入(undefined 保留)。
let result = eval(
&mut vm,
"(() => { var flagsSeen = null; var o = { constructor: function() {}, flags: '' }; o.constructor[Symbol.species] = function(_, flags) { flagsSeen = flags; return { exec: function() { return null; }, lastIndex: 0 }; }; var out = RegExp.prototype[Symbol.split].call(o, 'ab'); return flagsSeen + '/' + out.length; })()",
)
.unwrap();
// exec 恒 null:逐单元推进后推整串尾段,结果单元素。
assert_eq!(to_str(&vm, result), "y/1");
let result = eval(
&mut vm,
"(() => { var o = { constructor: function() {}, flags: 'y' }; o.constructor[Symbol.species] = function(_, flags) { return { exec: function() { return [null, undefined]; }, set lastIndex(v) {}, get lastIndex() { return 1; } }; }; var out = RegExp.prototype[Symbol.split].call(o, 'ab'); return out.length + '/' + String(out[1]); })()",
)
.unwrap();
assert_eq!(to_str(&vm, result), "3/undefined");
}
#[test]
fn symbol_split_limit_zero_and_to_uint32() {
let mut vm = Vm::new();
// lim == 0 直接空数组;limit 经完整 ToNumber(对象转换异常传播)。
let result = eval(&mut vm, "RegExp.prototype[Symbol.split].call(/b/, 'ab', 0).length").unwrap();
assert!(result.as_int() == 0);
let result = eval(
&mut vm,
"(() => { var o = { valueOf: function() { throw new RangeError('limit-boom'); } }; try { RegExp.prototype[Symbol.split].call(/b/, 'ab', o); return 'no-throw'; } catch (e) { return e.message; } })()",
)
.unwrap();
assert_eq!(to_str(&vm, result), "limit-boom");
}
#[test]
fn symbol_match_all_species_matcher_and_cached_lastindex() {
let mut vm = Vm::new();
// matcher 经物种构造;lastIndex 只从 R 读一次并写入 matcher。
let result = eval(
&mut vm,
"(() => { var r = /b/g; r.lastIndex = 2; var ctorCalls = 0; r.constructor[Symbol.species] = function() { ctorCalls += 1; return /b/g; }; var it = r[Symbol.matchAll]('abc'); return ctorCalls + '/' + it.__mal_re__.lastIndex; })()",
)
.unwrap();
assert_eq!(to_str(&vm, result), "1/2");
// 非 RegExp this(@@match 布尔 false):matcher 直接 Construct(%RegExp%, «R, "g"»)。
let result = eval(
&mut vm,
"(() => { var o = { toString: function() { return 'b'; }, flags: 'x', get [Symbol.match]() { return false; } }; var it = RegExp.prototype[Symbol.matchAll].call(o, 'b'); return it.__mal_re__.source + '/' + it.__mal_re__.flags; })()",
)
.unwrap();
assert_eq!(to_str(&vm, result), "b/g");
}
#[test]
fn symbol_method_name_labels() {
let mut vm = Vm::new();
let result = eval(
&mut vm,
"[Symbol.match, Symbol.replace, Symbol.search, Symbol.split, Symbol.matchAll].map(function (s) { return RegExp.prototype[s].name; }).join('|')",
)
.unwrap();
assert_eq!(
to_str(&vm, result),
"[Symbol.match]|[Symbol.replace]|[Symbol.search]|[Symbol.split]|[Symbol.matchAll]"
);
}
#[test]
fn regexp_constructor_regexp_instance_form() {
let mut vm = Vm::new();
// (RegExp, flags):source 取实例 source,flags 参数优先;缺省取实例 flags。
let result = eval(&mut vm, "new RegExp(/ab/g, 'i').source + '/' + new RegExp(/ab/g, 'i').flags").unwrap();
assert_eq!(to_str(&vm, result), "ab/i");
let result = eval(&mut vm, "new RegExp(/ab/g).flags").unwrap();
assert_eq!(to_str(&vm, result), "g");
}