mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-26 03:31:12 +08:00
builtins/vm: matchAll 编译正则载体专型标签,释放/深拷贝经 RegExp 同一守卫,止逐调用 Box 泄漏
This commit is contained in:
Generated
+1
@@ -1351,6 +1351,7 @@ dependencies = [
|
|||||||
"oxide_types",
|
"oxide_types",
|
||||||
"proptest",
|
"proptest",
|
||||||
"regex",
|
"regex",
|
||||||
|
"regress",
|
||||||
"rustc-hash",
|
"rustc-hash",
|
||||||
"ryu",
|
"ryu",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
|||||||
@@ -129,9 +129,9 @@ pub fn regexp_constructor<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
|||||||
NativeResult::Ok(JsValue::from_js_object(obj_ptr))
|
NativeResult::Ok(JsValue::from_js_object(obj_ptr))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 只读核算 RegExp 编译正则字节(不释放)。
|
/// 只读核算持有编译正则对象(RegExp / matchAll 载体)的正则字节(不释放)。
|
||||||
pub fn regexp_native_size(obj: &JsObject) -> u64 {
|
pub fn regexp_native_size(obj: &JsObject) -> u64 {
|
||||||
if !obj.is_regexp_obj() {
|
if !obj.holds_compiled_regex() {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
let Some(ptr) = obj.native_fn() else {
|
let Some(ptr) = obj.native_fn() else {
|
||||||
@@ -144,7 +144,8 @@ pub fn regexp_native_size(obj: &JsObject) -> u64 {
|
|||||||
std::mem::size_of::<regress::Regex>() as u64
|
std::mem::size_of::<regress::Regex>() as u64
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 释放 RegExp 对象 native_fn 槽中编译的 `regress::Regex`,返回释放字节数。
|
/// 释放持有编译正则对象(RegExp / matchAll 载体)native_fn 槽中的
|
||||||
|
/// `regress::Regex`,返回释放字节数。
|
||||||
pub fn drop_regexp_native(obj: &mut JsObject) -> u64 {
|
pub fn drop_regexp_native(obj: &mut JsObject) -> u64 {
|
||||||
let bytes = regexp_native_size(obj);
|
let bytes = regexp_native_size(obj);
|
||||||
if bytes == 0 {
|
if bytes == 0 {
|
||||||
@@ -164,8 +165,9 @@ pub fn drop_regexp_native(obj: &mut JsObject) -> u64 {
|
|||||||
///
|
///
|
||||||
/// 与 drop 配对:新对象获得独立的 `Box<regress::Regex>`,源对象保留自己的指针,
|
/// 与 drop 配对:新对象获得独立的 `Box<regress::Regex>`,源对象保留自己的指针,
|
||||||
/// 两侧各自释放恰好一次,杜绝跨 arena 克隆后的指针别名双释放。
|
/// 两侧各自释放恰好一次,杜绝跨 arena 克隆后的指针别名双释放。
|
||||||
|
/// 作用于全部持编译正则的对象形态(RegExp / matchAll 载体)。
|
||||||
pub fn clone_regexp_native(old_obj: &JsObject, new_obj: &mut JsObject) {
|
pub fn clone_regexp_native(old_obj: &JsObject, new_obj: &mut JsObject) {
|
||||||
if !old_obj.is_regexp_obj() {
|
if !old_obj.holds_compiled_regex() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let Some(ptr) = old_obj.native_fn() else {
|
let Some(ptr) = old_obj.native_fn() else {
|
||||||
@@ -555,6 +557,9 @@ pub fn regexp_symbol_match_all<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResu
|
|||||||
};
|
};
|
||||||
let object_proto = vm.session().builtin_world().object_proto.as_ptr() as *mut JsObject;
|
let object_proto = vm.session().builtin_world().object_proto.as_ptr() as *mut JsObject;
|
||||||
let mut stub = JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(object_proto));
|
let mut stub = JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(object_proto));
|
||||||
|
// 载体专型标签:native_fn 槽的 Box 经 RegExp 同一守卫释放/深拷贝,
|
||||||
|
// 避免每次 matchAll 泄漏一个已编译正则。
|
||||||
|
stub.type_tag = JsObject::OBJ_TYPE_REGEX_STUB;
|
||||||
let raw = Box::into_raw(Box::new(compiled)) as *const u8;
|
let raw = Box::into_raw(Box::new(compiled)) as *const u8;
|
||||||
stub.set_native_fn(Some(unsafe { NativeFnPtr::from_raw(raw as *const ()) }));
|
stub.set_native_fn(Some(unsafe { NativeFnPtr::from_raw(raw as *const ()) }));
|
||||||
JsValue::from_js_object(vm.alloc_object(stub))
|
JsValue::from_js_object(vm.alloc_object(stub))
|
||||||
|
|||||||
@@ -1507,6 +1507,9 @@ pub fn string_match_all<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
|||||||
};
|
};
|
||||||
let object_proto = vm.session().builtin_world().object_proto.as_ptr() as *mut JsObject;
|
let object_proto = vm.session().builtin_world().object_proto.as_ptr() as *mut JsObject;
|
||||||
let mut stub = JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(object_proto));
|
let mut stub = JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(object_proto));
|
||||||
|
// 载体专型标签:native_fn 槽的 Box 经 RegExp 同一守卫释放/深拷贝,
|
||||||
|
// 避免每次 matchAll 泄漏一个已编译正则。
|
||||||
|
stub.type_tag = oxide_types::object::JsObject::OBJ_TYPE_REGEX_STUB;
|
||||||
let boxed = Box::new(compiled);
|
let boxed = Box::new(compiled);
|
||||||
let raw = Box::into_raw(boxed) as *const u8;
|
let raw = Box::into_raw(boxed) as *const u8;
|
||||||
stub.set_native_fn(Some(unsafe { oxide_types::object::NativeFnPtr::from_raw(raw as *const ()) }));
|
stub.set_native_fn(Some(unsafe { oxide_types::object::NativeFnPtr::from_raw(raw as *const ()) }));
|
||||||
|
|||||||
@@ -669,6 +669,10 @@ impl JsObject {
|
|||||||
pub const OBJ_TYPE_PLAIN_MONTH_DAY: u8 = 26;
|
pub const OBJ_TYPE_PLAIN_MONTH_DAY: u8 = 26;
|
||||||
/// Temporal.PlainYearMonth 对象:年/月/参考日/日历 ID 存于 prop 0-3。
|
/// Temporal.PlainYearMonth 对象:年/月/参考日/日历 ID 存于 prop 0-3。
|
||||||
pub const OBJ_TYPE_PLAIN_YEAR_MONTH: u8 = 27;
|
pub const OBJ_TYPE_PLAIN_YEAR_MONTH: u8 = 27;
|
||||||
|
/// matchAll 迭代器内部载体对象:仅持有编译后的 `regress::Regex`(存于
|
||||||
|
/// `native_fn` 槽),无 RegExp 可见属性面;`native_fn` 的释放与深拷贝
|
||||||
|
/// 守卫与 RegExp 对象一致。
|
||||||
|
pub const OBJ_TYPE_REGEX_STUB: u8 = 28;
|
||||||
/// `is_session_epoch` 字段中的 session 标记位。
|
/// `is_session_epoch` 字段中的 session 标记位。
|
||||||
pub const SESSION_EPOCH_BIT: u8 = 0x01;
|
pub const SESSION_EPOCH_BIT: u8 = 0x01;
|
||||||
/// `is_session_epoch` 字段中的 GC 标记位。
|
/// `is_session_epoch` 字段中的 GC 标记位。
|
||||||
@@ -686,6 +690,17 @@ impl JsObject {
|
|||||||
pub fn is_regexp_obj(&self) -> bool {
|
pub fn is_regexp_obj(&self) -> bool {
|
||||||
self.type_tag == Self::OBJ_TYPE_REGEXP
|
self.type_tag == Self::OBJ_TYPE_REGEXP
|
||||||
}
|
}
|
||||||
|
/// 是否 matchAll 迭代器内部载体(`native_fn` 槽存编译后的 `regress::Regex`)。
|
||||||
|
#[inline]
|
||||||
|
pub fn is_regex_stub_obj(&self) -> bool {
|
||||||
|
self.type_tag == Self::OBJ_TYPE_REGEX_STUB
|
||||||
|
}
|
||||||
|
/// 是否持有编译正则:`native_fn` 槽存 `Box<regress::Regex>` 的两种对象形态
|
||||||
|
/// (RegExp 对象 / matchAll 载体)——其释放、深拷贝、字节核算守卫统一走此谓词。
|
||||||
|
#[inline]
|
||||||
|
pub fn holds_compiled_regex(&self) -> bool {
|
||||||
|
matches!(self.type_tag, Self::OBJ_TYPE_REGEXP | Self::OBJ_TYPE_REGEX_STUB)
|
||||||
|
}
|
||||||
/// 是否装箱 Boolean 对象。
|
/// 是否装箱 Boolean 对象。
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn is_boolean_obj(&self) -> bool {
|
pub fn is_boolean_obj(&self) -> bool {
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ num-traits = "0.2"
|
|||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
proptest.workspace = true
|
proptest.workspace = true
|
||||||
|
regress.workspace = true
|
||||||
oxide_ir = { path = "../oxide_ir" }
|
oxide_ir = { path = "../oxide_ir" }
|
||||||
oxide_emit = { path = "../oxide_emit" }
|
oxide_emit = { path = "../oxide_emit" }
|
||||||
criterion = { version = "0.5", features = ["html_reports"] }
|
criterion = { version = "0.5", features = ["html_reports"] }
|
||||||
|
|||||||
@@ -96,7 +96,7 @@ impl Vm {
|
|||||||
crate::async_generator::clone_async_generator_native_with_rewrite(src_ref, dst_ref, |value| {
|
crate::async_generator::clone_async_generator_native_with_rewrite(src_ref, dst_ref, |value| {
|
||||||
self.promote_value_if_epoch_object(value, forwarding)
|
self.promote_value_if_epoch_object(value, forwarding)
|
||||||
});
|
});
|
||||||
} else if src_ref.is_regexp_obj() {
|
} else if src_ref.holds_compiled_regex() {
|
||||||
// 已编译正则是 Box 深拷贝到新对象:源盒随 epoch 释放,互不共享。
|
// 已编译正则是 Box 深拷贝到新对象:源盒随 epoch 释放,互不共享。
|
||||||
regexp::clone_regexp_native(src_ref, dst_ref);
|
regexp::clone_regexp_native(src_ref, dst_ref);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -586,7 +586,7 @@ impl SessionGc {
|
|||||||
crate::async_generator::clone_async_generator_native_with_rewrite(old_ref, new_ref, |value| value);
|
crate::async_generator::clone_async_generator_native_with_rewrite(old_ref, new_ref, |value| value);
|
||||||
} else if old_ref.is_generator_obj() {
|
} else if old_ref.is_generator_obj() {
|
||||||
crate::generator::clone_generator_native_with_rewrite(old_ref, new_ref, |value| value);
|
crate::generator::clone_generator_native_with_rewrite(old_ref, new_ref, |value| value);
|
||||||
} else if old_ref.is_regexp_obj() {
|
} else if old_ref.holds_compiled_regex() {
|
||||||
// 已编译正则是 Box 深拷贝到新对象:源 Box 由 drop 释放,互不共享。
|
// 已编译正则是 Box 深拷贝到新对象:源 Box 由 drop 释放,互不共享。
|
||||||
regexp::clone_regexp_native(old_ref, new_ref);
|
regexp::clone_regexp_native(old_ref, new_ref);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,92 @@
|
|||||||
|
//! matchAll 已编译正则载体的生命周期钉:字符串模式 / 非 global 正则两条建载体
|
||||||
|
//! 路径的 `native_fn` 槽持有 `Box<regress::Regex>`,死载体须经 sweep 释放
|
||||||
|
//! (释放字节计入收集统计)、存活载体跨完整收集深拷贝后迭代器仍正确推进;
|
||||||
|
//! 载体无 RegExp 可见属性面(toString 保持普通对象标签)。
|
||||||
|
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use oxide_compiler::compiler::Compiler;
|
||||||
|
use oxide_types::value::JsValue;
|
||||||
|
use oxide_vm::vm::Vm;
|
||||||
|
use regress::Regex;
|
||||||
|
|
||||||
|
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(&Arc::new(module))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn to_str(vm: &Vm, val: JsValue) -> String {
|
||||||
|
vm.lookup_str(val).unwrap_or_default()
|
||||||
|
}
|
||||||
|
|
||||||
|
const ROUNDS: u64 = 256;
|
||||||
|
|
||||||
|
/// 字符串模式路径:N 个死载体(包装器 + 载体)经完整收集走 sweep 死分支,
|
||||||
|
/// 释放字节必须含 N 份编译正则——守卫未命中时该部分恒不计入。
|
||||||
|
#[test]
|
||||||
|
fn string_pattern_match_all_boxes_freed_by_sweep() {
|
||||||
|
let mut vm = Vm::new();
|
||||||
|
eval(&mut vm, "for (let i = 0; i < 256; i++) { 'aaaa'.matchAll('a'); }").unwrap();
|
||||||
|
let freed_before = vm.session_gc_stats().total_bytes_freed;
|
||||||
|
vm.reset();
|
||||||
|
let freed_delta = vm.session_gc_stats().total_bytes_freed - freed_before;
|
||||||
|
let min_regex_bytes = ROUNDS * std::mem::size_of::<Regex>() as u64;
|
||||||
|
assert!(
|
||||||
|
freed_delta >= min_regex_bytes,
|
||||||
|
"sweep 释放 {freed_delta} 字节,应含 {ROUNDS} 份编译正则(下界 {min_regex_bytes})"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 非 global 正则路径:`RegExp.prototype.matchAll` 复制补 g 建载体,同口径断言。
|
||||||
|
#[test]
|
||||||
|
fn non_global_regexp_match_all_boxes_freed_by_sweep() {
|
||||||
|
let mut vm = Vm::new();
|
||||||
|
// 真则常驻(存活根),循环内只产死载体:释放下界唯一来源是载体 Box。
|
||||||
|
eval(&mut vm, "var r = /ab/; for (let i = 0; i < 256; i++) { r[Symbol.matchAll]('abab'); }").unwrap();
|
||||||
|
let freed_before = vm.session_gc_stats().total_bytes_freed;
|
||||||
|
vm.reset();
|
||||||
|
let freed_delta = vm.session_gc_stats().total_bytes_freed - freed_before;
|
||||||
|
let min_regex_bytes = ROUNDS * std::mem::size_of::<Regex>() as u64;
|
||||||
|
assert!(
|
||||||
|
freed_delta >= min_regex_bytes,
|
||||||
|
"sweep 释放 {freed_delta} 字节,应含 {ROUNDS} 份编译正则(下界 {min_regex_bytes})"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 存活载体跨完整收集:克隆臂深拷贝 Box 后迭代器仍按原语义耗尽。
|
||||||
|
#[test]
|
||||||
|
fn string_pattern_match_all_iterator_survives_full_collect() {
|
||||||
|
let mut vm = Vm::new();
|
||||||
|
eval(&mut vm, "var it = 'abab'.matchAll('ab');").unwrap();
|
||||||
|
vm.reset();
|
||||||
|
let result = eval(
|
||||||
|
&mut vm,
|
||||||
|
"var out = []; var st = it.next(); while (!st.done) { out.push(String(st.value[0])); st = it.next(); } out.join(',')",
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(to_str(&vm, result), "ab,ab");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 非 global 正则路径的存活载体同样跨收集保持有效。
|
||||||
|
#[test]
|
||||||
|
fn non_global_regexp_match_all_iterator_survives_full_collect() {
|
||||||
|
let mut vm = Vm::new();
|
||||||
|
eval(&mut vm, "var r = /ab/; var it = r[Symbol.matchAll]('abab');").unwrap();
|
||||||
|
vm.reset();
|
||||||
|
let result = eval(
|
||||||
|
&mut vm,
|
||||||
|
"var out = []; var st = it.next(); while (!st.done) { out.push(String(st.value[0])); st = it.next(); } out.join(',')",
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(to_str(&vm, result), "ab,ab");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 载体经内部属性可达,但无 RegExp 属性面:toString 保持普通对象标签。
|
||||||
|
#[test]
|
||||||
|
fn match_all_carrier_has_no_regexp_surface() {
|
||||||
|
let mut vm = Vm::new();
|
||||||
|
let result = eval(&mut vm, "Object.prototype.toString.call('a'.matchAll('a').__mal_re__)").unwrap();
|
||||||
|
assert_eq!(to_str(&vm, result), "[object Object]");
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user