mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-23 18:21:13 +08:00
feat: Weak 族 stub 构造器面补 prototype 与 name/length 属性
This commit is contained in:
@@ -3,30 +3,66 @@ use std::sync::Arc;
|
||||
use oxide_kernel::kernel::{KernelCore, KernelSession};
|
||||
use oxide_kernel::shape_forge::EMPTY_SHAPE_ID;
|
||||
use oxide_types::mem::P;
|
||||
use oxide_types::object::JsObject;
|
||||
use oxide_types::object::{JsObject, PropAttributes};
|
||||
use oxide_types::value::JsValue;
|
||||
|
||||
use crate::bindings::{bind_global_value, configure_native_constructor};
|
||||
|
||||
const STUBS: [(&str, *const (), u8); 5] = [
|
||||
("Proxy", oxide_builtins::stubs::proxy_stub::<crate::vm::Vm> as *const (), 2),
|
||||
("WeakMap", oxide_builtins::stubs::weakmap_stub::<crate::vm::Vm> as *const (), 0),
|
||||
("WeakSet", oxide_builtins::stubs::weakset_stub::<crate::vm::Vm> as *const (), 0),
|
||||
("WeakRef", oxide_builtins::stubs::weakref_stub::<crate::vm::Vm> as *const (), 1),
|
||||
/// 未实现内置的 stub 表:(全局名、native 函数项指针、形参个数、原型 @@toStringTag)。
|
||||
///
|
||||
/// `tag` 为 `None` 的条目(Proxy)原型对象不设 @@toStringTag;四弱族各取族名。
|
||||
const STUBS: [(&str, *const (), u8, Option<&str>); 5] = [
|
||||
("Proxy", oxide_builtins::stubs::proxy_stub::<crate::vm::Vm> as *const (), 2, None),
|
||||
(
|
||||
"WeakMap",
|
||||
oxide_builtins::stubs::weakmap_stub::<crate::vm::Vm> as *const (),
|
||||
0,
|
||||
Some("WeakMap"),
|
||||
),
|
||||
(
|
||||
"WeakSet",
|
||||
oxide_builtins::stubs::weakset_stub::<crate::vm::Vm> as *const (),
|
||||
0,
|
||||
Some("WeakSet"),
|
||||
),
|
||||
(
|
||||
"WeakRef",
|
||||
oxide_builtins::stubs::weakref_stub::<crate::vm::Vm> as *const (),
|
||||
1,
|
||||
Some("WeakRef"),
|
||||
),
|
||||
(
|
||||
"FinalizationRegistry",
|
||||
oxide_builtins::stubs::finalization_registry_stub::<crate::vm::Vm> as *const (),
|
||||
1,
|
||||
Some("FinalizationRegistry"),
|
||||
),
|
||||
];
|
||||
|
||||
/// 把未实现内置(Proxy/WeakMap/WeakSet/WeakRef/FinalizationRegistry)
|
||||
/// 的 stub 构造器绑定到 global,并登记到 `stub_objects` 供快照跟踪。
|
||||
/// 在 `obj` 上按键 `si` 安装数据属性,描述符为 `attributes`:
|
||||
/// 开 shape 槽,值写入命名属性区并置描述符元数据。
|
||||
fn set_data_property(core: &Arc<KernelCore>, obj: &mut JsObject, si: u32, value: JsValue, attributes: PropAttributes) {
|
||||
let shape = core.shape_forge().make_shape(obj.shape_id(), si);
|
||||
obj.set_shape_id(shape);
|
||||
let pos = obj.push_prop(value);
|
||||
obj.set_data_meta(pos, attributes);
|
||||
obj.bump_generation();
|
||||
}
|
||||
|
||||
/// 把未实现内置(Proxy/WeakMap/WeakSet/WeakRef/FinalizationRegistry)的 stub 构造器
|
||||
/// 绑定到 global,并登记到 `stub_objects` 供快照跟踪。
|
||||
///
|
||||
/// # 副作用
|
||||
/// 每个 stub 按规范补装构造器面属性:`prototype`(新建 plain 对象,原型链落
|
||||
/// Object.prototype,其上带指回 stub 的 `constructor`,四弱族另带 @@toStringTag)、
|
||||
/// `length`、`name`;描述符均不可枚举,`prototype` 另不可写、不可配置。
|
||||
/// 原型对象登记进 world 释放登记表,与 stub 对象同生死,session 收尾统一释放。
|
||||
pub fn bind_stubs(core: &Arc<KernelCore>, session: &mut KernelSession, global: &mut JsObject) {
|
||||
let builtin_world = Arc::get_mut(&mut session.builtin_world)
|
||||
.expect("BuiltinWorld must be uniquely owned during init_kernel_builtins");
|
||||
let object_proto_ptr = builtin_world.object_proto.as_ptr() as *mut JsObject;
|
||||
|
||||
for (name, native_fn, arg_count) in STUBS {
|
||||
for (name, native_fn, arg_count, tag) in STUBS {
|
||||
let mut stub = JsObject::new_empty(
|
||||
EMPTY_SHAPE_ID,
|
||||
JsValue::from_js_object(builtin_world.function_proto.as_ptr() as *mut JsObject),
|
||||
@@ -36,6 +72,52 @@ pub fn bind_stubs(core: &Arc<KernelCore>, session: &mut KernelSession, global: &
|
||||
|
||||
let stub = P::new(stub);
|
||||
let stub_ptr = stub.as_ptr() as *mut JsObject;
|
||||
|
||||
// 原型对象:constructor 回指 + @@toStringTag(四弱族);属性装完再登记
|
||||
// 释放登记表,登记点世代即洁净基线。
|
||||
let mut proto = JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(object_proto_ptr));
|
||||
let si_constructor = core.perm_interner().intern("constructor").0;
|
||||
set_data_property(
|
||||
core,
|
||||
&mut proto,
|
||||
si_constructor,
|
||||
JsValue::from_js_object(stub_ptr),
|
||||
PropAttributes::new(true, false, true),
|
||||
);
|
||||
if let Some(tag_name) = tag {
|
||||
let tag_key = oxide_types::private_key::make_well_known_symbol_key(
|
||||
oxide_types::private_key::WELL_KNOWN_SYMBOL_TO_STRING_TAG,
|
||||
);
|
||||
let tag_val =
|
||||
JsValue::perm_string(core.perm_interner().string_ptr(core.perm_interner().intern(tag_name).0));
|
||||
set_data_property(core, &mut proto, tag_key, tag_val, PropAttributes::new(false, false, true));
|
||||
}
|
||||
let proto_ptr = Box::into_raw(Box::new(proto));
|
||||
builtin_world.track_leaked_object(proto_ptr);
|
||||
|
||||
// 构造器面属性:prototype / length / name。
|
||||
// SAFETY: stub 尚未发布到 global,局部 Arc 为唯一持有者,可变访问无读者冲突。
|
||||
let stub_obj = unsafe { &mut *stub_ptr };
|
||||
let si_prototype = core.perm_interner().intern("prototype").0;
|
||||
set_data_property(
|
||||
core,
|
||||
stub_obj,
|
||||
si_prototype,
|
||||
JsValue::from_js_object(proto_ptr),
|
||||
PropAttributes::new(false, false, false),
|
||||
);
|
||||
let si_length = core.perm_interner().intern("length").0;
|
||||
set_data_property(
|
||||
core,
|
||||
stub_obj,
|
||||
si_length,
|
||||
JsValue::int(arg_count as i32),
|
||||
PropAttributes::new(false, false, true),
|
||||
);
|
||||
let si_name = core.perm_interner().intern("name").0;
|
||||
let name_val = JsValue::perm_string(core.perm_interner().string_ptr(core.perm_interner().intern(name).0));
|
||||
set_data_property(core, stub_obj, si_name, name_val, PropAttributes::new(false, false, true));
|
||||
|
||||
bind_global_value(core, global, name, JsValue::from_js_object(stub_ptr));
|
||||
builtin_world.stub_objects.push(stub);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,156 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use oxide_compiler::compiler::Compiler;
|
||||
use oxide_kernel::kernel::{KernelConfig, KernelCore};
|
||||
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(&Arc::new(module))
|
||||
}
|
||||
|
||||
/// 五 stub 构造器原型链钉:prototype 为对象、原型链落 Object.prototype、
|
||||
/// constructor 回指构造器本体。
|
||||
#[test]
|
||||
fn stub_ctor_prototype_chain_all_five() {
|
||||
let mut vm = Vm::new();
|
||||
let result = eval(
|
||||
&mut vm,
|
||||
"(function () { \
|
||||
var names = ['Proxy', 'WeakMap', 'WeakSet', 'WeakRef', 'FinalizationRegistry']; \
|
||||
for (var i = 0; i < names.length; i++) { \
|
||||
var x = globalThis[names[i]]; \
|
||||
if (typeof x.prototype !== 'object') { return false; } \
|
||||
if (Object.getPrototypeOf(x.prototype) !== Object.prototype) { return false; } \
|
||||
if (x.prototype.constructor !== x) { return false; } \
|
||||
} \
|
||||
return true; })()",
|
||||
)
|
||||
.unwrap();
|
||||
assert!(result.as_bool());
|
||||
}
|
||||
|
||||
/// 五 stub 构造器 length/name 值钉 + 非枚举钉:构造器面三属性与原型对象自身
|
||||
/// 属性均不泄漏进 Object.keys。
|
||||
#[test]
|
||||
fn stub_ctor_length_name_identity_non_enumerable() {
|
||||
let mut vm = Vm::new();
|
||||
let result = eval(
|
||||
&mut vm,
|
||||
"(function () { \
|
||||
var names = ['Proxy', 'WeakMap', 'WeakSet', 'WeakRef', 'FinalizationRegistry']; \
|
||||
var expect = { Proxy: 2, WeakMap: 0, WeakSet: 0, WeakRef: 1, FinalizationRegistry: 1 }; \
|
||||
for (var i = 0; i < names.length; i++) { \
|
||||
var name = names[i]; \
|
||||
var x = globalThis[name]; \
|
||||
if (x.length !== expect[name] || x.name !== name) { return false; } \
|
||||
var keys = Object.keys(x); \
|
||||
for (var k = 0; k < keys.length; k++) { \
|
||||
if (keys[k] === 'length' || keys[k] === 'name' || keys[k] === 'prototype') { return false; } \
|
||||
} \
|
||||
if (Object.keys(x.prototype).length !== 0) { return false; } \
|
||||
} \
|
||||
return true; })()",
|
||||
)
|
||||
.unwrap();
|
||||
assert!(result.as_bool());
|
||||
}
|
||||
|
||||
/// 四弱族原型对象 @@toStringTag 钉;Proxy 原型无规范 tag,不钉。
|
||||
#[test]
|
||||
fn weak_family_proto_to_string_tag() {
|
||||
let mut vm = Vm::new();
|
||||
let result = eval(
|
||||
&mut vm,
|
||||
"(function () { \
|
||||
var names = ['WeakMap', 'WeakSet', 'WeakRef', 'FinalizationRegistry']; \
|
||||
for (var i = 0; i < names.length; i++) { \
|
||||
if (globalThis[names[i]].prototype[Symbol.toStringTag] !== names[i]) { return false; } \
|
||||
} \
|
||||
return true; })()",
|
||||
)
|
||||
.unwrap();
|
||||
assert!(result.as_bool());
|
||||
}
|
||||
|
||||
/// 原型成员读不抛钉:缺失方法读返 undefined(非对象 receiver 抛错形态消除)。
|
||||
#[test]
|
||||
fn stub_proto_member_read_returns_undefined() {
|
||||
let mut vm = Vm::new();
|
||||
let result = eval(
|
||||
&mut vm,
|
||||
"(function () { \
|
||||
return typeof WeakMap.prototype.get === 'undefined' \
|
||||
&& typeof WeakSet.prototype.add === 'undefined' \
|
||||
&& typeof WeakRef.prototype.deref === 'undefined' \
|
||||
&& typeof FinalizationRegistry.prototype.register === 'undefined' \
|
||||
&& typeof Proxy.prototype.nonexistent === 'undefined'; })()",
|
||||
)
|
||||
.unwrap();
|
||||
assert!(result.as_bool());
|
||||
}
|
||||
|
||||
/// 构造体不回归钉:五 stub 构造调用仍抛 TypeError(消息含 "… is not implemented")。
|
||||
#[test]
|
||||
fn stub_ctor_call_still_throws_not_implemented() {
|
||||
let mut vm = Vm::new();
|
||||
let result = eval(
|
||||
&mut vm,
|
||||
"(function () { \
|
||||
function probe(ctor, args, name) { \
|
||||
try { Reflect.construct(ctor, args, ctor); } \
|
||||
catch (e) { return e instanceof TypeError && e.message.indexOf(name + ' is not implemented') === 0; } \
|
||||
return false; \
|
||||
} \
|
||||
return probe(WeakMap, [], 'WeakMap') \
|
||||
&& probe(WeakSet, [], 'WeakSet') \
|
||||
&& probe(WeakRef, [{}], 'WeakRef') \
|
||||
&& probe(FinalizationRegistry, [function () {}], 'FinalizationRegistry') \
|
||||
&& probe(Proxy, [ {}, {} ], 'Proxy'); })()",
|
||||
)
|
||||
.unwrap();
|
||||
assert!(result.as_bool());
|
||||
}
|
||||
|
||||
/// isConstructor 探测臂钉:Reflect.construct 以 stub 作 newTarget 经真 prototype
|
||||
/// 走通(构造体抛错由空函数体规避),结果恒 true。
|
||||
#[test]
|
||||
fn stub_ctor_is_constructor_probe_true() {
|
||||
let mut vm = Vm::new();
|
||||
let result = eval(
|
||||
&mut vm,
|
||||
"(function () { \
|
||||
function isCtor(f) { \
|
||||
try { Reflect.construct(function () {}, [], f); } catch (e) { return false; } \
|
||||
return true; \
|
||||
} \
|
||||
return isCtor(Proxy) && isCtor(WeakMap) && isCtor(WeakSet) \
|
||||
&& isCtor(WeakRef) && isCtor(FinalizationRegistry); })()",
|
||||
)
|
||||
.unwrap();
|
||||
assert!(result.as_bool());
|
||||
}
|
||||
|
||||
/// GC 边钉:低阈值 + 长循环触发执行期回收后,stub 原型对象经 stub 属性边存活,
|
||||
/// 原型链与 constructor 回指身份不变。
|
||||
#[test]
|
||||
fn stub_proto_survives_runtime_gc() {
|
||||
let mut config = KernelConfig::minimal();
|
||||
config.set_session_gc_threshold(4096);
|
||||
let mut vm = Vm::with_kernel_core(KernelCore::new(config));
|
||||
let result = eval(
|
||||
&mut vm,
|
||||
"(function () { \
|
||||
var p = WeakMap.prototype; \
|
||||
var c = p.constructor; \
|
||||
var s; \
|
||||
for (var i = 0; i < 500000; i++) { s = 'str' + i; } \
|
||||
return p === WeakMap.prototype && c === WeakMap \
|
||||
&& Object.getPrototypeOf(p) === Object.prototype; })()",
|
||||
)
|
||||
.unwrap();
|
||||
assert!(result.as_bool());
|
||||
}
|
||||
Reference in New Issue
Block a user