From 834ef9e2d80be9af2c4c5026f1fd5d68c6a53323 Mon Sep 17 00:00:00 2001 From: pjh456 <147148383@qq.com> Date: Thu, 24 Sep 2026 03:59:07 +0000 Subject: [PATCH] =?UTF-8?q?feat:=20Weak=20=E6=97=8F=20stub=20=E6=9E=84?= =?UTF-8?q?=E9=80=A0=E5=99=A8=E9=9D=A2=E8=A1=A5=20prototype=20=E4=B8=8E=20?= =?UTF-8?q?name/length=20=E5=B1=9E=E6=80=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/oxide_vm/src/bindings/bind_stubs.rs | 100 ++++++++++- crates/oxide_vm/tests/builtins_stubs_tests.rs | 156 ++++++++++++++++++ 2 files changed, 247 insertions(+), 9 deletions(-) create mode 100644 crates/oxide_vm/tests/builtins_stubs_tests.rs diff --git a/crates/oxide_vm/src/bindings/bind_stubs.rs b/crates/oxide_vm/src/bindings/bind_stubs.rs index cd6bfaa1..724d1a62 100644 --- a/crates/oxide_vm/src/bindings/bind_stubs.rs +++ b/crates/oxide_vm/src/bindings/bind_stubs.rs @@ -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:: as *const (), 2), - ("WeakMap", oxide_builtins::stubs::weakmap_stub:: as *const (), 0), - ("WeakSet", oxide_builtins::stubs::weakset_stub:: as *const (), 0), - ("WeakRef", oxide_builtins::stubs::weakref_stub:: as *const (), 1), +/// 未实现内置的 stub 表:(全局名、native 函数项指针、形参个数、原型 @@toStringTag)。 +/// +/// `tag` 为 `None` 的条目(Proxy)原型对象不设 @@toStringTag;四弱族各取族名。 +const STUBS: [(&str, *const (), u8, Option<&str>); 5] = [ + ("Proxy", oxide_builtins::stubs::proxy_stub:: as *const (), 2, None), + ( + "WeakMap", + oxide_builtins::stubs::weakmap_stub:: as *const (), + 0, + Some("WeakMap"), + ), + ( + "WeakSet", + oxide_builtins::stubs::weakset_stub:: as *const (), + 0, + Some("WeakSet"), + ), + ( + "WeakRef", + oxide_builtins::stubs::weakref_stub:: as *const (), + 1, + Some("WeakRef"), + ), ( "FinalizationRegistry", oxide_builtins::stubs::finalization_registry_stub:: as *const (), 1, + Some("FinalizationRegistry"), ), ]; -/// 把未实现内置(Proxy/WeakMap/WeakSet/WeakRef/FinalizationRegistry) -/// 的 stub 构造器绑定到 global,并登记到 `stub_objects` 供快照跟踪。 +/// 在 `obj` 上按键 `si` 安装数据属性,描述符为 `attributes`: +/// 开 shape 槽,值写入命名属性区并置描述符元数据。 +fn set_data_property(core: &Arc, 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, 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, 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); } diff --git a/crates/oxide_vm/tests/builtins_stubs_tests.rs b/crates/oxide_vm/tests/builtins_stubs_tests.rs new file mode 100644 index 00000000..1c3b7e60 --- /dev/null +++ b/crates/oxide_vm/tests/builtins_stubs_tests.rs @@ -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 { + 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()); +}