Files
OxideJS-New/crates/oxide_vm/tests/builtins_regexp_tests.rs
T
pjh123 0e32d663ce fix: matchAll 空匹配推进至少一个字符,RegExp 补 GC 搬移深拷贝
matchAll 迭代器在空匹配(range.start == range.end)后把游标停在原位置,
同一位置反复空匹配导致死循环;改为按 UTF-8 字符边界推进(与 regress
find_iter 的 next_right_pos 一致),串尾空匹配把游标推到串长之后保证下轮
耗尽。

另修 RegExp 在 GC sweep 与 epoch 晋升中的指针别名:克隆只复制 native_fn 槽
的 Box 指针,源对象释放后克隆持悬垂指针,teardown 双重释放(heap 损坏)。
新增 clone_regexp_native 深拷贝已编译正则,sweep/promote 两处挂接,与
drop 配对保证单次释放。
2026-08-15 20:31:51 +08:00

250 lines
6.8 KiB
Rust

use oxide_compiler::compiler::Compiler;
use oxide_types::value::JsValue;
use oxide_vm::vm::Vm;
fn eval(vm: &mut Vm, source: &str) -> Result<JsValue, String> {
let allocator = oxide_parser::Allocator::default();
let program = oxide_parser::parse(&allocator, source).map_err(|e| format!("Parse error: {:?}", e))?;
let module = Compiler::new().compile(&program).map_err(|e| format!("Compile error: {}", e))?;
vm.run(&module)
}
fn to_str(vm: &Vm, val: JsValue) -> String {
vm.lookup_str(val).unwrap_or_default()
}
#[test]
fn regexp_survives_gc_and_still_matches() {
// 回归:正则存入全局对象触发晋升/GC 搬移后,native_fn 槽的已编译 Box
// 必须深拷贝到新对象(而非共享指针),否则 epoch 释放与 teardown 双重释放。
let mut vm = Vm::new();
let result = eval(&mut vm, "var r = /ab+c/g; globalThis.r = r; r.test('xxabbcx')").unwrap();
assert!(result.as_bool());
// 触发完整收集:存活正则克隆进新 arena,旧 Box 由 sweep 释放。
vm.reset();
let result = eval(&mut vm, "globalThis.r.test('xxabbcx')").unwrap();
assert!(result.as_bool(), "GC 后正则应仍可匹配");
let result = eval(&mut vm, "globalThis.r.exec('xabbbcx')[0]").unwrap();
assert_eq!(to_str(&vm, result), "abbbc");
}
// --- RegExp constructor ---
#[test]
fn regexp_constructor_creates_object() {
let mut vm = Vm::new();
let result = eval(&mut vm, "typeof new RegExp('abc')").unwrap();
assert_eq!(to_str(&vm, result), "object");
}
#[test]
fn regexp_constructor_with_flags() {
let mut vm = Vm::new();
let result = eval(&mut vm, "new RegExp('abc', 'gi').ignoreCase").unwrap();
assert!(result.as_bool());
}
#[test]
fn regexp_constructor_invalid_pattern_syntax_error() {
let mut vm = Vm::new();
let result = eval(&mut vm, "new RegExp('[')");
assert!(result.is_err() || (result.is_ok() && result.unwrap().is_string()));
}
#[test]
fn regexp_source_property() {
let mut vm = Vm::new();
let result = eval(&mut vm, "new RegExp('hello', 'g').source").unwrap();
assert_eq!(to_str(&vm, result), "hello");
}
#[test]
fn regexp_flags_property() {
let mut vm = Vm::new();
let result = eval(&mut vm, "new RegExp('abc', 'gi').flags").unwrap();
assert_eq!(to_str(&vm, result), "gi");
}
#[test]
fn regexp_global_property() {
let mut vm = Vm::new();
let result = eval(&mut vm, "new RegExp('abc', 'g').global").unwrap();
assert!(result.as_bool());
}
#[test]
fn regexp_multiline_property() {
let mut vm = Vm::new();
let result = eval(&mut vm, "new RegExp('abc', 'm').multiline").unwrap();
assert!(result.as_bool());
}
#[test]
fn regexp_last_index_property() {
let mut vm = Vm::new();
let result = eval(&mut vm, "new RegExp('abc', 'g').lastIndex").unwrap();
assert_eq!(result.as_int(), 0);
}
#[test]
fn regexp_dotall_sticky_unicode_default_false() {
let mut vm = Vm::new();
let result = eval(&mut vm, "new RegExp('abc').dotAll").unwrap();
assert!(!result.as_bool());
}
// --- RegExp.prototype.test ---
#[test]
fn regexp_test_basic() {
let mut vm = Vm::new();
let result = eval(&mut vm, "new RegExp('abc').test('abc')").unwrap();
assert!(result.as_bool());
}
#[test]
fn regexp_test_no_match() {
let mut vm = Vm::new();
let result = eval(&mut vm, "new RegExp('abc').test('xyz')").unwrap();
assert!(!result.as_bool());
}
#[test]
fn regexp_test_case_insensitive() {
let mut vm = Vm::new();
let result = eval(&mut vm, "new RegExp('abc', 'i').test('ABC')").unwrap();
assert!(result.as_bool());
}
#[test]
fn regexp_test_global_flag_iteration() {
let mut vm = Vm::new();
let result = eval(&mut vm, "var re = /a/g; re.test('a')").unwrap();
assert!(result.as_bool());
}
// --- RegExp.prototype.exec ---
#[test]
fn regexp_exec_basic() {
let mut vm = Vm::new();
let result = eval(&mut vm, "/hello/.exec('hello world') !== null").unwrap();
assert!(result.as_bool());
}
#[test]
fn regexp_exec_no_match_returns_null() {
let mut vm = Vm::new();
let result = eval(&mut vm, "/xyz/.exec('hello')").unwrap();
assert!(result.is_null());
}
#[test]
fn regexp_exec_returns_index() {
let mut vm = Vm::new();
let result = eval(&mut vm, "/ell/.exec('hello')").unwrap();
assert!(result.is_object());
}
#[test]
fn regexp_exec_returns_input() {
let mut vm = Vm::new();
let m = eval(&mut vm, "/ell/.exec('hello')").unwrap();
assert!(m.is_object());
}
#[test]
fn regexp_exec_global_last_index_advances() {
let mut vm = Vm::new();
let re = eval(&mut vm, "var re = /a/g; re.exec('aba'); re.lastIndex").unwrap();
assert_eq!(re.as_int(), 1);
}
#[test]
fn regexp_exec_global_resets_on_exhaustion() {
let mut vm = Vm::new();
let result = eval(&mut vm, "var re = /a/g; re.exec('a'); re.exec('a') === null").unwrap();
assert!(result.as_bool());
}
// --- RegExp literal compilation ---
#[test]
fn regexp_literal_basic() {
let mut vm = Vm::new();
let result = eval(&mut vm, "/hello/").unwrap();
assert!(result.is_object());
}
#[test]
fn regexp_literal_with_flags() {
let mut vm = Vm::new();
let result = eval(&mut vm, "/abc/i.test('ABC')").unwrap();
assert!(result.as_bool());
}
#[test]
fn regexp_literal_global_exec() {
let mut vm = Vm::new();
let result = eval(&mut vm, "var re = /a/g; re.exec('aba'); re.lastIndex").unwrap();
assert_eq!(result.as_int(), 1);
}
// --- String integration ---
#[test]
fn string_match_with_regexp() {
let mut vm = Vm::new();
let result = eval(&mut vm, "'hello world'.match(/ell/)").unwrap();
assert!(result.is_object());
}
#[test]
fn string_replace_with_regexp() {
let mut vm = Vm::new();
let result = eval(&mut vm, "'hello'.replace(/ell/, 'ipp')").unwrap();
assert_eq!(to_str(&vm, result), "hippo");
}
#[test]
fn string_replace_with_regexp_global() {
let mut vm = Vm::new();
let result = eval(&mut vm, "'aba'.replace(/a/g, 'c')").unwrap();
assert_eq!(to_str(&vm, result), "cbc");
}
#[test]
fn string_search_with_regexp() {
let mut vm = Vm::new();
let result = eval(&mut vm, "'hello'.search(/ell/)").unwrap();
assert_eq!(result.as_int(), 1);
}
#[test]
fn string_search_not_found() {
let mut vm = Vm::new();
let result = eval(&mut vm, "'hello'.search(/xyz/)").unwrap();
assert_eq!(result.as_int(), -1);
}
#[test]
fn string_split_with_regexp() {
let mut vm = Vm::new();
let result = eval(&mut vm, "'a,b,c'.split(/,/)").unwrap();
assert!(result.is_object());
let obj = unsafe { &*result.as_js_object_ptr() };
assert_eq!(obj.prop_count(), 3);
assert_eq!(to_str(&vm, obj.get_prop_at(1)), "b");
}
// --- toString ---
#[test]
fn regexp_to_string() {
let mut vm = Vm::new();
let result = eval(&mut vm, "/abc/gi.toString()").unwrap();
assert_eq!(to_str(&vm, result), "/abc/gi");
}