fix: match/search 走 GetMethod 派发,flags 归序与 normalize 校验

This commit is contained in:
2026-09-25 06:23:11 +00:00
parent d4ed76855e
commit 01195ffb32
4 changed files with 147 additions and 50 deletions
+16 -2
View File
@@ -380,6 +380,18 @@ fn set_prop_by_si<H: VmHost>(obj: &mut JsObject, prop_name_si: u32, val: JsValue
obj.ensure_hash_props().push(val);
}
/// 规范化 flags 顺序为规范序(d g i m s u v y):同一组 flag 不论书写顺序
/// 恒得同一串,保证 `flags`/`toString` 输出与 V8 一致。
fn normalize_flags(flags: &str) -> String {
let mut out = String::new();
for c in ['d', 'g', 'i', 'm', 's', 'u', 'v', 'y'] {
if flags.contains(c) {
out.push(c);
}
}
out
}
/// `RegExp(pattern, flags)` 构造逻辑:用 regress 引擎编译模式(ECMAScript 语法,
/// 支持 backreference/lookaround/命名组/v-flag);非法模式抛 SyntaxError。
/// 编译结果存于对象的 native_fn 槽。
@@ -436,7 +448,8 @@ pub fn regexp_constructor<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
(Vec::<u16>::new(), Vec::<u16>::new())
};
let pattern = String::from_utf16_lossy(&pattern_units);
let flags = String::from_utf16_lossy(&flags_units);
let flags = normalize_flags(&String::from_utf16_lossy(&flags_units));
let flags_units = flags.encode_utf16().collect::<Vec<u16>>();
// u 与 v 互斥(regress 不校验此约束):编译前置抛 SyntaxError。
if flags.contains('u') && flags.contains('v') {
@@ -1528,7 +1541,8 @@ pub fn regexp_symbol_search<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult
Ok(n) => n,
Err(_) => return NativeResult::Err(crate::iterator::engine_error(vm, "cannot convert index")),
};
NativeResult::Ok(JsValue::float(n))
// 匹配位置恒为非负整数,与无匹配臂的 -1(int)同口径。
NativeResult::Ok(JsValue::int(n as i32))
}
/// SameValue(val, +0):严格口径,-0 与 +0 不同(规范步骤用 SameValue 非
+118 -35
View File
@@ -524,10 +524,27 @@ pub fn string_match_fn<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
Err(e) => NativeResult::Err(crate::iterator::engine_error(vm, &e)),
};
}
// 非正则模式:Construct %RegExp%(undefined → 空模式、其余经 ToString),
// 再 GetMethod(rx, @@match) + Invoke;转换抛错(含 Symbol)原样传播。
// string 值物化一次(text 借用至此结束),供下方两臂共用。
let s_units = text.units().into_owned();
let s_val = vm.new_string_units_owned(s_units);
// 对象模式:GetMethod(pattern, @@match) 先于构造(规范序);matcher 非
// undefined 则 Call(matcher, pattern, «string») 原值返回(自定义对象与
// 自置 @@match 的载体均走此臂)。
if pattern_val.is_object() {
let matcher = match rx_get_match_method_opt(vm, pattern_val.as_js_object_ptr(), pattern_val) {
Ok(m) => m,
Err(e) => return NativeResult::Err(e),
};
if let Some(matcher) = matcher {
return match vm.call_function_sync(matcher, pattern_val, &[s_val]) {
Ok(r) => NativeResult::Ok(r),
Err(e) => NativeResult::Err(crate::iterator::engine_error(vm, &e)),
};
}
}
// 非对象 / matcher undefined:Construct %RegExp%(undefined → 空模式、其余
// 经 ToString),再 GetMethod(rx, @@match) + Invoke;转换抛错(含 Symbol)
// 原样传播。
let regexp_ctor = vm.session().builtin_world().regexp_constructor.as_ptr() as *mut JsObject;
let rx_val = match vm.construct_ctor(JsValue::from_js_object(regexp_ctor), &[pattern_val]) {
Ok(v) => v,
@@ -563,46 +580,104 @@ fn rx_get_match_method<H: VmHost>(vm: &mut H, rx: *mut JsObject, this_val: JsVal
Ok(matcher)
}
/// GetMethod(rx, @@match) 的 Option 形态:undefined/null 返回 None(落构造
/// 路径),不可调用抛 TypeError,属性读抛错恢复原异常值。
fn rx_get_match_method_opt<H: VmHost>(
vm: &mut H, rx: *mut JsObject, this_val: JsValue,
) -> Result<Option<JsValue>, JsValue> {
let match_key = oxide_types::private_key::make_well_known_symbol_key(1);
let func = match vm.ordinary_get(unsafe { &*rx }, match_key, this_val) {
Ok(v) => v,
Err(e) => return Err(crate::iterator::engine_error(vm, &e)),
};
if func.is_undefined() || func.is_null() {
return Ok(None);
}
if !crate::iterator::is_callable(func) {
return Err(crate::error::create_type_error(vm, "Symbol.match is not callable"));
}
Ok(Some(func))
}
/// GetMethod(rx, @@search):undefined/null 返回 None(落构造路径),不可调用
/// 抛 TypeError,属性读抛错恢复原异常值。
fn rx_get_search_method<H: VmHost>(
vm: &mut H, rx: *mut JsObject, this_val: JsValue,
) -> Result<Option<JsValue>, JsValue> {
let search_key = oxide_types::private_key::make_well_known_symbol_key(3);
let func = match vm.ordinary_get(unsafe { &*rx }, search_key, this_val) {
Ok(v) => v,
Err(e) => return Err(crate::iterator::engine_error(vm, &e)),
};
if func.is_undefined() || func.is_null() {
return Ok(None);
}
if !crate::iterator::is_callable(func) {
return Err(crate::error::create_type_error(vm, "Symbol.search is not callable"));
}
Ok(Some(func))
}
/// `String.prototype.search(pattern)`:返回首个匹配位置(码元),无匹配返回 -1。
///
/// # 步骤
/// 1. string = ToString(this)(转换异常传播)。
/// 2. pattern 为对象时:GetMethod(pattern, @@search),searcher 非 undefined 则
/// Call(searcher, pattern, «string») 原值返回(真 RegExp 默认 @@search 与
/// 自定义对象均走此臂)。
/// 3. Construct %RegExp%(null/undefined 映射对应文本,其余经 ToString)→
/// GetMethod(rx, @@search) + Invoke(rx, «string»)。
///
/// # 边界与前提
/// - 无参 / null 模式走构造路径:空模式命中串头返回 0。
/// - 构造路径不预转换 pattern:构造器内部完成 ToString(异常原样传播)。
pub fn string_search<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
builtins_debug!("String.prototype.search called with {} args", args.len());
// 参数读取守卫先行:无参调用缺省 searchString 为 undefined(args 仅含 this 槽)。
let pattern_val = if args.len() >= 2 { vm.reg(args[1]) } else { JsValue::undefined() };
// 正则判定与参数转换先行(&mut 路径),后借 this 扫描。
let is_re = args.len() >= 2 && is_regexp_obj(pattern_val, vm);
let pattern: Vec<u16> = if args.len() >= 2 && !is_re {
try_string!(as_units(vm, pattern_val)).into_owned()
} else {
Vec::new()
};
let text = try_string!(this_text(vm, args));
if args.len() < 2 {
return NativeResult::Ok(JsValue::int(-1));
}
if is_re {
let re_ptr = pattern_val.as_js_object_ptr();
let re = unsafe { &*re_ptr };
let fn_ptr = match re.native_fn() {
Some(p) => p,
None => return NativeResult::Ok(JsValue::int(-1)),
// string 值物化(text 借用结束),供下方两臂共用。
let s_units = text.units().into_owned();
let s_val = vm.new_string_units_owned(s_units);
// 对象模式:GetMethod(pattern, @@search) 先于构造(规范序);searcher 非
// undefined 则 Call(searcher, pattern, «string») 原值返回。
if pattern_val.is_object() {
let searcher = match rx_get_search_method(vm, pattern_val.as_js_object_ptr(), pattern_val) {
Ok(m) => m,
Err(e) => return NativeResult::Err(e),
};
// SAFETY: fn_ptr 持有 regexp_constructor 存放的 `Box<regress::Regex>` 指针。
let regex = unsafe { &*(fn_ptr.as_ptr() as *const regress::Regex) };
if let Some(m) = text.find_from_units(regex, 0) {
return NativeResult::Ok(JsValue::int(text.unit_pos(m.range().start) as i32));
if let Some(searcher) = searcher {
return match vm.call_function_sync(searcher, pattern_val, &[s_val]) {
Ok(r) => NativeResult::Ok(r),
Err(e) => NativeResult::Err(crate::iterator::engine_error(vm, &e)),
};
}
return NativeResult::Ok(JsValue::int(-1));
}
// 空模式命中串头(规格口径)。
if pattern.is_empty() {
return NativeResult::Ok(JsValue::int(0));
// 非对象 / searcher undefined:Construct %RegExp% + GetMethod(rx, @@search)
// + Invoke(rx, «string»);转换抛错(含 Symbol)原样传播。
let regexp_ctor = vm.session().builtin_world().regexp_constructor.as_ptr() as *mut JsObject;
let rx_val = match vm.construct_ctor(JsValue::from_js_object(regexp_ctor), &[pattern_val]) {
Ok(v) => v,
Err(e) => return NativeResult::Err(e),
};
if !rx_val.is_object() {
return NativeResult::Err(crate::error::create_type_error(
vm,
"search pattern constructor must return an object",
));
}
let s = text.units();
if let Some(pos) = find_units(&s, &pattern, 0) {
return NativeResult::Ok(JsValue::int(pos as i32));
let searcher = match rx_get_search_method(vm, rx_val.as_js_object_ptr(), rx_val) {
Ok(m) => m,
Err(e) => return NativeResult::Err(e),
};
match searcher {
Some(searcher) => match vm.call_function_sync(searcher, rx_val, &[s_val]) {
Ok(r) => NativeResult::Ok(r),
Err(e) => NativeResult::Err(crate::iterator::engine_error(vm, &e)),
},
None => NativeResult::Err(crate::error::create_type_error(vm, "RegExp @@search is not callable")),
}
NativeResult::Ok(JsValue::int(-1))
}
/// `String.prototype.isWellFormed()`:字符串无孤立 surrogate(每个码元要么是
@@ -659,14 +734,22 @@ pub fn string_to_well_formed<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult
pub fn string_normalize<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
builtins_debug!("String.prototype.normalize called with {} args", args.len());
use unicode_normalization::UnicodeNormalization;
let form = if args.len() > 1 {
try_string!(as_units(vm, vm.reg(args[1]))).into_owned()
} else {
// form 缺省或 undefined 按 "NFC" 处理(规范第 3 步),其余经 ToString。
let form_val = if args.len() > 1 { vm.reg(args[1]) } else { JsValue::undefined() };
let form = if form_val.is_undefined() {
"NFC".encode_utf16().collect()
} else {
try_string!(as_units(vm, form_val)).into_owned()
};
// 规范化形式名按良形文本比对(NFC 等名无孤立 surrogate)。
let form_name = String::from_utf16_lossy(&form);
// this 转换先于 form 校验(规范第 2 步先于第 4 步)。
let s = try_string!(this_units(vm, args));
// form 非四合法值抛 RangeError。
match form_name.as_str() {
"NFC" | "NFD" | "NFKC" | "NFKD" => {}
_ => return NativeResult::Err(crate::error::create_range_error(vm, "normalize form is not valid")),
}
let out = map_well_formed_segments(&s, |seg| match form_name.as_str() {
"NFD" => seg.nfd().collect(),
"NFKC" => seg.nfkc().collect(),
@@ -702,8 +702,8 @@ fn symbol_entries_accept_plain_object_this() {
"(() => { 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);
// 匹配位置为整数形态(与无匹配臂的 -1 同口径)。
assert_eq!(result.as_int(), 2);
}
#[test]
+11 -11
View File
@@ -202,19 +202,19 @@ fn string_search_not_found() {
}
#[test]
fn string_search_no_args_returns_minus_one() {
// 无参调用缺省 searchString:args 仅含 this 槽,守卫返回 -1,不越界 panic。
fn string_search_no_args_returns_zero() {
// 无参调用缺省 searchString 为 undefined:空模式命中串头返回 0。
let mut vm = Vm::new();
let result = eval(&mut vm, "'abc'.search()").unwrap();
assert_eq!(result.as_int(), -1);
assert_eq!(result.as_int(), 0);
}
#[test]
fn string_search_undefined_arg_returns_minus_one() {
// 显式 undefined 参数:ToString(undefined)="undefined",在 "abc" 中无匹配返回 -1。
fn string_search_undefined_arg_returns_zero() {
// 显式 undefined 参数:空模式命中串头返回 0。
let mut vm = Vm::new();
let result = eval(&mut vm, "'abc'.search(undefined)").unwrap();
assert_eq!(result.as_int(), -1);
assert_eq!(result.as_int(), 0);
}
#[test]
@@ -227,13 +227,13 @@ fn string_search_empty_pattern_returns_zero() {
#[test]
fn string_no_arg_guard_family_consistent() {
// 缺参时 searchString 按 ToString(undefined)="undefined" 参与查找——'abc'
// 不含 "undefined",indexOf 得 -1、includes/startsWith/endsWith 得 false
// ("undefined" 命中的判别断言见 string_missing_arg_searches_undefined);
// search 缺参仍走旧守卫返回 -1。
// 缺参时 search 按空模式命中串头得 0;indexOf 按 ToString(undefined)=
// "undefined" 参与查找——'abc' 不含 "undefined" 得 -1、includes/startsWith/
// endsWith 得 false("undefined" 命中的判别断言见
// string_missing_arg_searches_undefined)。
let mut vm = Vm::new();
let result = eval(&mut vm, "'abc'.search()").unwrap();
assert_eq!(result.as_int(), -1);
assert_eq!(result.as_int(), 0);
let result = eval(&mut vm, "'abc'.indexOf()").unwrap();
assert_eq!(result.as_int(), -1);
let result = eval(&mut vm, "'abc'.includes()").unwrap();