From bb8a722cf752c80f4943cd272702a683632c9613 Mon Sep 17 00:00:00 2001 From: pjh456 <147148383@qq.com> Date: Tue, 22 Sep 2026 02:45:25 +0000 Subject: [PATCH] =?UTF-8?q?fix:=20matchAll=20=E5=85=A5=E5=8F=A3=20flags=20?= =?UTF-8?q?=E5=8D=95=E8=AF=BB=E4=B8=8E=E6=9E=84=E9=80=A0=E8=B7=AF=E5=BE=84?= =?UTF-8?q?=E6=94=B6=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/oxide_builtins/src/string/regex.rs | 275 +++++++++++----------- 1 file changed, 137 insertions(+), 138 deletions(-) diff --git a/crates/oxide_builtins/src/string/regex.rs b/crates/oxide_builtins/src/string/regex.rs index 63abc37c..17f4865e 100644 --- a/crates/oxide_builtins/src/string/regex.rs +++ b/crates/oxide_builtins/src/string/regex.rs @@ -662,9 +662,24 @@ pub(crate) const MALL_RE: &str = "__mal_re__"; /// `String.prototype.matchAll(pattern)`:返回带 `next` 的迭代器,逐步产出全部匹配 /// (要求 RegExp 带 global 标志;普通字符串会被转义成等效正则)。包装器 input /// 属性存原始字符串值(单元保真),index 属性为码元游标。 +/// +/// # 步骤 +/// 1. receiver 前置校验(RequireObjectCoercible,纯 is_* 读取)。 +/// 2. pattern 为 null/undefined:跳过对象分支,直接构造路径。 +/// 3. pattern 为对象:真 RegExp 先 flags 单读 g 判定;GetMethod 全链单读, +/// undefined/null 落构造路径,非 callable 非空值抛 TypeError,可调用则 +/// Call(matcher, pattern, « thisValue ») 原值返回。 +/// 4. 非对象 pattern:跳过对象分支,按字面文本编译载体正则。 +/// +/// # 边界与前提 +/// - ToString(thisValue) 延迟到构造路径实际消费(matcher 直调臂不触发)。 +/// - 构造路径(RegExpCreate(pattern, "g") + Invoke)复用 +/// `match_all_construct_invoke`,无 callable @@matchAll 时抛 TypeError。 pub fn string_match_all(vm: &mut H, args: &[u8]) -> NativeResult { builtins_debug!("String.prototype.matchAll called with {} args", args.len()); let this_val = vm.reg(args[0]); + // receiver 前置校验:null/undefined/symbol 抛 TypeError(规范第 1-2 步 + // RequireObjectCoercible,纯 is_* 读取无用户代码,先于一切分叉)。 if this_val.is_null() || this_val.is_undefined() { return NativeResult::Err(crate::error::create_type_error( vm, @@ -674,162 +689,80 @@ pub fn string_match_all(vm: &mut H, args: &[u8]) -> NativeResult { if this_val.is_symbol() { return NativeResult::Err(crate::error::create_type_error(vm, "Cannot convert a Symbol value to a string")); } - let input_val = match oxide_runtime_api::to_string_value_full(this_val, vm) { - Ok(v) => v, - Err(_) => { - if let Some(exc) = vm.take_uncaught_value() { - return NativeResult::Err(exc); - } - return NativeResult::Err(crate::error::create_type_error(vm, "Cannot convert value to a string")); - } - }; if args.len() < 2 { builtins_error!("String.prototype.matchAll: invalid receiver"); return NativeResult::Err(JsValue::undefined()); } let pattern_val = vm.reg(args[1]); - // 规范步骤 2-5:pattern 为 null/undefined 时跳过 IsRegExp 分支, - // 直接 RegExpCreate(this, "g") + Invoke(rx, @@matchAll, « S »)。 + // 对象分支条件(IsObject)为假:null/undefined pattern 跳过 flags/GetMethod + // 全部步骤,直接落构造路径。 if pattern_val.is_null() || pattern_val.is_undefined() { - // rx = new RegExp(ToString(this), "g")。 - let g_str = vm.new_string("g"); - let rx_val = match vm.construct_ctor( - JsValue::from_js_object(vm.session().builtin_world().regexp_constructor.as_ptr() as *mut JsObject), - &[input_val, g_str], - ) { - Ok(v) => v, - Err(e) => return NativeResult::Err(e), - }; - // Invoke(rx, @@matchAll, « S »)。 - let match_all_key = oxide_types::private_key::make_well_known_symbol_key(7); - let rx_ptr = rx_val.as_js_object_ptr(); - let proto_val = unsafe { &*rx_ptr }.proto(); - let rx_this = JsValue::from_js_object(rx_ptr); - let matcher = match vm.ordinary_get(unsafe { &*rx_ptr }, match_all_key, rx_this) { - Ok(v) => v, - Err(e) => return NativeResult::err(crate::iterator::engine_error(vm, &e)), - }; - if matcher.is_undefined() || matcher.is_null() { - // 查原型链。 - if proto_val.is_object() { - let p_ptr = proto_val.as_js_object_ptr(); - let p_this = proto_val; - let proto_matcher = match vm.ordinary_get(unsafe { &*p_ptr }, match_all_key, p_this) { - Ok(v) => v, - Err(e) => return NativeResult::err(crate::iterator::engine_error(vm, &e)), - }; - if crate::iterator::is_callable(proto_matcher) { - return match vm.call_function_sync(proto_matcher, rx_this, &[input_val]) { - Ok(v) => NativeResult::Ok(v), - Err(e) => NativeResult::err(crate::iterator::engine_error(vm, &e)), - }; - } - } - // 无 callable @@matchAll:构建包装器。 - return builder_wrapper(vm, input_val, rx_this); - } - if crate::iterator::is_callable(matcher) { - return match vm.call_function_sync(matcher, rx_this, &[input_val]) { - Ok(v) => NativeResult::Ok(v), - Err(e) => NativeResult::err(crate::iterator::engine_error(vm, &e)), - }; - } - return builder_wrapper(vm, input_val, rx_this); + return match_all_construct_invoke(vm, this_val, pattern_val); } - // 规范:IsRegExp(pattern) 时先检查 g 标志(Node.js 先行校验,早于 - // GetMethod 调用),不满足则抛 TypeError;满足后查 GetMethod(pattern, - // @@matchAll),可调用则直调,否则构建包装器。 - if is_regexp_obj(pattern_val, vm) { - let re_ptr = pattern_val.as_js_object_ptr(); - // 规范步骤 3.a-c:Get(rx, "flags") → RequireObjectCoercible → - // ToString 含 "g" 判定;getter 抛错传播。 - let flags = match crate::regexp::rx_get_flags(vm, re_ptr, pattern_val) { - Ok(f) => f, - Err(e) => return NativeResult::Err(e), - }; - // RequireObjectCoercible:flags 为 undefined/null 时抛 TypeError。 - // rx_get_flags 已将 undefined 转为 "undefined" 字符串,需额外检查。 - let flags_val = - match vm.ordinary_get(unsafe { &*re_ptr }, vm.kernel_core().perm_interner().intern("flags").0, pattern_val) - { + if pattern_val.is_object() { + // 真 RegExp:flags 单读原始值(getter 副作用/抛错只观察一次)→ + // RequireObjectCoercible → ToString 含 "g" 判定;g 判定先于 GetMethod。 + if is_regexp_obj(pattern_val, vm) { + let re_ptr = pattern_val.as_js_object_ptr(); + // SAFETY: pattern_val 已校验为非空对象值。 + let re_obj = unsafe { &*re_ptr }; + let flags_si = vm.kernel_core().perm_interner().intern("flags").0; + let flags_val = match vm.ordinary_get(re_obj, flags_si, pattern_val) { Ok(v) => v, Err(e) => return NativeResult::err(crate::iterator::engine_error(vm, &e)), }; - if flags_val.is_undefined() || flags_val.is_null() { - return NativeResult::Err(crate::error::create_type_error( - vm, - "String.prototype.matchAll: regex must have global flag", - )); - } - if !flags.contains('g') { - return NativeResult::Err(crate::error::create_type_error( - vm, - "String.prototype.matchAll: regex must have global flag", - )); - } - // 标志检查通过:查 GetMethod(pattern, @@matchAll)。 - let match_all_key = oxide_types::private_key::make_well_known_symbol_key(7); - let re_obj = unsafe { &*re_ptr }; - // 先查实例 own。 - let own_match_all = match vm.get_own_property_slot(re_obj, match_all_key) { - Some(_) => match vm.ordinary_get(re_obj, match_all_key, pattern_val) { - Ok(v) => v, - Err(e) => return NativeResult::err(crate::iterator::engine_error(vm, &e)), - }, - None => JsValue::undefined(), - }; - if crate::iterator::is_callable(own_match_all) { - return match vm.call_function_sync(own_match_all, pattern_val, &[input_val]) { - Ok(v) => NativeResult::Ok(v), - Err(e) => NativeResult::err(crate::iterator::engine_error(vm, &e)), - }; - } - // 实例 @@matchAll 不存在/undefined/null:回退原型链(GetMethod 对 null/undefined 均返 undefined)。 - if own_match_all.is_undefined() || own_match_all.is_null() { - let proto_val = re_obj.proto(); - if proto_val.is_object() { - let proto_ptr = proto_val.as_js_object_ptr(); - let proto_match_all = match vm.ordinary_get(unsafe { &*proto_ptr }, match_all_key, proto_val) { - Ok(v) => v, - Err(e) => return NativeResult::err(crate::iterator::engine_error(vm, &e)), - }; - if crate::iterator::is_callable(proto_match_all) { - return match vm.call_function_sync(proto_match_all, pattern_val, &[input_val]) { - Ok(v) => NativeResult::Ok(v), - Err(e) => NativeResult::err(crate::iterator::engine_error(vm, &e)), - }; - } - // 原型链也无 callable @@matchAll:规范 Invoke 抛 TypeError。 + if flags_val.is_undefined() || flags_val.is_null() { return NativeResult::Err(crate::error::create_type_error( vm, - "RegExp.prototype[Symbol.matchAll] is not a function", + "String.prototype.matchAll: regex must have global flag", + )); + } + let flags_sv = match oxide_runtime_api::to_string_value_full(flags_val, vm) { + Ok(v) => v, + Err(_) => { + if let Some(exc) = vm.take_uncaught_value() { + return NativeResult::Err(exc); + } + return NativeResult::Err(crate::error::create_type_error(vm, "Cannot convert value to a string")); + } + }; + if !String::from_utf16_lossy(&vm.string_units(flags_sv)).contains('g') { + return NativeResult::Err(crate::error::create_type_error( + vm, + "String.prototype.matchAll: regex must have global flag", )); } } - // 无 callable @@matchAll:规范 Invoke 抛 TypeError。 - NativeResult::Err(crate::error::create_type_error(vm, "RegExp.prototype[Symbol.matchAll] is not a function")) - } else { - // 非 RegExp:先查 @@matchAll(规范步骤 6-8,IsRegExp 为 false 时 - // 先 ToString(pattern) 转正则,但规范步骤 8 是 Invoke(rx, @@matchAll)。 - // 实际上,若 pattern 是对象且有 callable @@matchAll,应直调(类似 RegExp 分支)。 - if pattern_val.is_object() { - let match_all_key = oxide_types::private_key::make_well_known_symbol_key(7); - let p_ptr = pattern_val.as_js_object_ptr(); - let p_this = pattern_val; - let matcher = match vm.ordinary_get(unsafe { &*p_ptr }, match_all_key, p_this) { - Ok(v) => v, - Err(e) => return NativeResult::err(crate::iterator::engine_error(vm, &e)), - }; - if crate::iterator::is_callable(matcher) { - return match vm.call_function_sync(matcher, p_this, &[input_val]) { - Ok(v) => NativeResult::Ok(v), - Err(e) => NativeResult::err(crate::iterator::engine_error(vm, &e)), - }; - } + // GetMethod(pattern, @@matchAll) 全链单次 Get:own 命中 undefined/null + // 即停(Get 语义),不回退原型链。 + let match_all_key = oxide_types::private_key::make_well_known_symbol_key(7); + let matcher = match vm.ordinary_get(unsafe { &*pattern_val.as_js_object_ptr() }, match_all_key, pattern_val) { + Ok(v) => v, + Err(e) => return NativeResult::err(crate::iterator::engine_error(vm, &e)), + }; + // GetMethod 返 undefined/null:落构造路径(ToString(thisValue) → + // RegExpCreate(pattern, "g") → Invoke 产物 @@matchAll)。 + if matcher.is_undefined() || matcher.is_null() { + return match_all_construct_invoke(vm, this_val, pattern_val); } - // 按文本编译为 stub(非捕获组,免额外 capture)。 + if !crate::iterator::is_callable(matcher) { + return NativeResult::Err(crate::error::create_type_error( + vm, + "String.prototype.matchAll: matcher is not a function", + )); + } + // Call(matcher, pattern, « thisValue »):实参为原始 this 值。 + return match vm.call_function_sync(matcher, pattern_val, &[this_val]) { + Ok(v) => NativeResult::Ok(v), + Err(e) => NativeResult::err(crate::iterator::engine_error(vm, &e)), + }; + } + + // 非对象 pattern:对象分支整体跳过,按文本编译为 stub(非捕获组,免额外 + // capture)。 + { let pattern_units = try_string!(as_units(vm, pattern_val)).into_owned(); let pattern_str = String::from_utf16_lossy(&pattern_units); let escaped = regress::escape(&pattern_str); @@ -853,10 +786,76 @@ pub fn string_match_all(vm: &mut H, args: &[u8]) -> NativeResult { let raw = Box::into_raw(boxed) as *const u8; stub.set_native_fn(Some(unsafe { oxide_types::object::NativeFnPtr::from_raw(raw as *const ()) })); let stub_ptr = vm.alloc_object(stub); + // string = ToString(thisValue):构造路径消费点,仅此臂实际转换。 + let input_val = match oxide_runtime_api::to_string_value_full(this_val, vm) { + Ok(v) => v, + Err(_) => { + if let Some(exc) = vm.take_uncaught_value() { + return NativeResult::Err(exc); + } + return NativeResult::Err(crate::error::create_type_error(vm, "Cannot convert value to a string")); + } + }; builder_wrapper(vm, input_val, JsValue::from_js_object(stub_ptr)) } } +/// 构造路径(规范步骤 4-6):string = ToString(thisValue) → +/// regexp = RegExpCreate(pattern, "g") → Invoke(regexp, @@matchAll, « string »)。 +/// +/// # 边界与前提 +/// - pattern 为 null/undefined 时构造器内部映射(null → "null" 文本、 +/// undefined → 空模式),不在此处重复转换 +/// - 构造产物无 callable @@matchAll(含原型链删除)时 Invoke 抛 TypeError +/// - 对象 toString/valueOf 抛出的原始异常原样传播 +fn match_all_construct_invoke(vm: &mut H, this_val: JsValue, pattern_val: JsValue) -> NativeResult { + // string = ToString(thisValue)。 + let input_val = match oxide_runtime_api::to_string_value_full(this_val, vm) { + Ok(v) => v, + Err(_) => { + if let Some(exc) = vm.take_uncaught_value() { + return NativeResult::Err(exc); + } + return NativeResult::Err(crate::error::create_type_error(vm, "Cannot convert value to a string")); + } + }; + + // regexp = RegExpCreate(pattern, "g")。 + let g_str = vm.new_string("g"); + let rx_val = match vm.construct_ctor( + JsValue::from_js_object(vm.session().builtin_world().regexp_constructor.as_ptr() as *mut JsObject), + &[pattern_val, g_str], + ) { + Ok(v) => v, + Err(e) => return NativeResult::Err(e), + }; + if !rx_val.is_object() { + return NativeResult::Err(crate::error::create_type_error( + vm, + "match pattern constructor must return an object", + )); + } + + // Invoke(regexp, @@matchAll, « string »):产物全链 Get,无 callable 抛 TypeError。 + let match_all_key = oxide_types::private_key::make_well_known_symbol_key(7); + let rx_ptr = rx_val.as_js_object_ptr(); + let rx_this = JsValue::from_js_object(rx_ptr); + let matcher = match vm.ordinary_get(unsafe { &*rx_ptr }, match_all_key, rx_this) { + Ok(v) => v, + Err(e) => return NativeResult::err(crate::iterator::engine_error(vm, &e)), + }; + if matcher.is_undefined() || matcher.is_null() || !crate::iterator::is_callable(matcher) { + return NativeResult::Err(crate::error::create_type_error( + vm, + "RegExp.prototype[Symbol.matchAll] is not a function", + )); + } + match vm.call_function_sync(matcher, rx_this, &[input_val]) { + Ok(v) => NativeResult::Ok(v), + Err(e) => NativeResult::err(crate::iterator::engine_error(vm, &e)), + } +} + /// 构造 matchAll 包装对象:原型挂 `%RegExpStringIteratorPrototype%`, /// own 属性为 input/index(0)/re 三槽,next 由原型提供。 fn builder_wrapper(vm: &mut H, input_val: JsValue, re_obj: JsValue) -> NativeResult {