From 3df08dddff275f506adf1b6972482396f71a725d Mon Sep 17 00:00:00 2001 From: pjh456 <147148383@qq.com> Date: Sun, 16 Aug 2026 00:25:30 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20object=20=E5=AE=B6=E6=97=8F=20dirty=20?= =?UTF-8?q?=E6=97=B6=E9=87=8D=E5=BB=BA=E8=BF=AD=E4=BB=A3=E5=99=A8=E7=BB=91?= =?UTF-8?q?=E5=AE=9A=E5=B9=B6=E5=AF=B9=E9=BD=90=20Iterator.prototype?= =?UTF-8?q?=EF=BC=8C=E7=BB=91=E5=AE=9A=E5=B9=82=E7=AD=89=E5=8C=96=E9=81=BF?= =?UTF-8?q?=E5=85=8D=E9=87=8D=E5=A4=8D=E6=A7=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/oxide_vm/src/bindings/bind_iterator.rs | 51 +------ crates/oxide_vm/src/bindings/mod.rs | 131 ++++++++++++------ crates/oxide_vm/src/vm_support.rs | 77 ++++++++++ 3 files changed, 173 insertions(+), 86 deletions(-) diff --git a/crates/oxide_vm/src/bindings/bind_iterator.rs b/crates/oxide_vm/src/bindings/bind_iterator.rs index bcf65b8f..133dcba8 100644 --- a/crates/oxide_vm/src/bindings/bind_iterator.rs +++ b/crates/oxide_vm/src/bindings/bind_iterator.rs @@ -6,11 +6,14 @@ use oxide_types::object::JsObject; use oxide_types::value::JsValue; use crate::bindings::{ - apply_binding_table, bind_global_value, bind_iterator_function_prototype, bind_iterator_proto_next, - configure_native_constructor, + apply_binding_table, bind_global_value, bind_iterator_function_prototype, configure_native_constructor, }; -/// 绑定迭代器基础设施:`%IteratorPrototype%` 与 `%ArrayIteratorPrototype%` 等。 +/// 绑定 `Iterator` 全局构造器(`prototype` = %IteratorPrototype%、`from` 方法)。 +/// +/// 迭代器原型方法(%IteratorPrototype% 的 `@@iterator` 与各集合原型 `next`)由 +/// `rebind_dirty_builtins` 的 object 分支统一安装,本入口不重复绑定,避免 +/// 保留原型经 dirty reset 时属性槽膨胀。 pub fn bind_iterator(core: &Arc, session: &KernelSession, global: &mut JsObject) { let function_proto = session.builtin_world().function_proto.as_ptr() as *mut JsObject; let mut iterator = Box::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(function_proto))); @@ -30,46 +33,4 @@ pub fn bind_iterator(core: &Arc, session: &KernelSession, global: &m ); bind_global_value(core, global, "Iterator", JsValue::from_js_object(Box::into_raw(iterator))); - - // %IteratorPrototype% 自身可迭代:@@iterator 返回 this,经原型链被所有 - // 集合迭代器继承(it[Symbol.iterator]() === it 恒等)。 - let iter_proto_ptr = session.builtin_world().iterator_proto.as_ptr() as *mut JsObject; - let iter_proto = unsafe { &mut *iter_proto_ptr }; - crate::bindings::bind_well_known_method( - session.builtin_world(), - core, - iter_proto, - 0, - "iterator", - oxide_builtins::iterator::iterator_symbol_iterator:: as *const (), - 0, - ); - - // 集合迭代器原型各自安装 next(%ArrayIteratorPrototype% 服务 Array/TA 两族; - // Map/Set 共用按 `__mode__` 分发的实现;%RegExpStringIteratorPrototype% 供 matchAll)。 - let world = session.builtin_world(); - bind_iterator_proto_next( - core, - session, - world.array_iterator_proto.as_ptr() as *mut JsObject, - oxide_builtins::array::array_iterator_next:: as *const (), - ); - bind_iterator_proto_next( - core, - session, - world.map_iterator_proto.as_ptr() as *mut JsObject, - oxide_builtins::iterator::map_set_iterator_next:: as *const (), - ); - bind_iterator_proto_next( - core, - session, - world.set_iterator_proto.as_ptr() as *mut JsObject, - oxide_builtins::iterator::map_set_iterator_next:: as *const (), - ); - bind_iterator_proto_next( - core, - session, - world.regexp_string_iterator_proto.as_ptr() as *mut JsObject, - oxide_builtins::string::string_match_all_next:: as *const (), - ); } diff --git a/crates/oxide_vm/src/bindings/mod.rs b/crates/oxide_vm/src/bindings/mod.rs index 6efbdefa..3b3b025c 100644 --- a/crates/oxide_vm/src/bindings/mod.rs +++ b/crates/oxide_vm/src/bindings/mod.rs @@ -251,13 +251,96 @@ pub(crate) fn bind_iterator_function_prototype( } /// 在集合迭代器原型上绑定 `next` 方法(规范形状:next 挂原型,wrapper 不设 own)。 +/// +/// `next` 键已存在时跳过:保留原型在 dirty reset 重复经过时不再追加重复属性槽。 pub(crate) fn bind_iterator_proto_next( core: &Arc, session: &KernelSession, proto: *mut JsObject, func: *const (), ) { let proto = unsafe { &mut *proto }; + let si_next = core.perm_interner().intern("next").0; + if core.shape_forge().lookup_position(proto.shape_id(), si_next).is_some() { + return; + } apply_binding_table(session.builtin_world(), proto, core, &[("next", func, 0)]); } +/// 把迭代器原型方法安装到当前 builtin world 的迭代器原型上: +/// `%IteratorPrototype%` 的 `@@iterator`(返回 this)与 Array/Map/Set/ +/// RegExpString 四个集合原型的 `next`。 +/// +/// 在迭代器原型全新重建后调用(object 家族 dirty 或全量初始化);各绑定点 +/// 自带幂等检查,保留原型重复经过时安全跳过。 +fn bind_iterator_protos(core: &Arc, session: &KernelSession) { + let world = session.builtin_world(); + + // %IteratorPrototype% 自身可迭代:@@iterator 返回 this,经原型链被全部 + // 集合迭代器继承(it[Symbol.iterator]() === it 恒等)。 + let iter_proto_ptr = world.iterator_proto.as_ptr() as *mut JsObject; + let iter_proto = unsafe { &mut *iter_proto_ptr }; + let sym_iter = oxide_types::private_key::make_well_known_symbol_key(0); + if core.shape_forge().lookup_position(iter_proto.shape_id(), sym_iter).is_none() { + bind_well_known_method( + world, + core, + iter_proto, + 0, + "iterator", + oxide_builtins::iterator::iterator_symbol_iterator:: as *const (), + 0, + ); + } + + // %ArrayIteratorPrototype% 服务 Array/TA 两族;Map/Set 共用按 `__mode__` + // 分发的实现;%RegExpStringIteratorPrototype% 供 matchAll。 + bind_iterator_proto_next( + core, + session, + world.array_iterator_proto.as_ptr() as *mut JsObject, + oxide_builtins::array::array_iterator_next:: as *const (), + ); + bind_iterator_proto_next( + core, + session, + world.map_iterator_proto.as_ptr() as *mut JsObject, + oxide_builtins::iterator::map_set_iterator_next:: as *const (), + ); + bind_iterator_proto_next( + core, + session, + world.set_iterator_proto.as_ptr() as *mut JsObject, + oxide_builtins::iterator::map_set_iterator_next:: as *const (), + ); + bind_iterator_proto_next( + core, + session, + world.regexp_string_iterator_proto.as_ptr() as *mut JsObject, + oxide_builtins::string::string_match_all_next:: as *const (), + ); +} + +/// 对齐保留 global 上 `Iterator` 函数对象的 `prototype` 属性:object 家族重建会 +/// 产生新的 `%IteratorPrototype%`,保留的 Iterator 函数对象须指向新原型。 +/// +/// global 同时重建(global dirty)或初始化时无 `Iterator` 属性,直接跳过—— +/// 彼时由 `bind_iterator_global` 以新原型创建函数对象。 +fn sync_iterator_function_prototype(core: &Arc, session: &KernelSession, global: &mut JsObject) { + let si_iterator = core.perm_interner().intern("Iterator").0; + let Some(pos) = core.shape_forge().lookup_position(global.shape_id(), si_iterator) else { + return; + }; + let iterator_val = global.get_prop_at(pos); + if !iterator_val.is_object() { + return; + } + let iterator = unsafe { &mut *iterator_val.as_js_object_ptr() }; + let si_prototype = core.perm_interner().intern("prototype").0; + let Some(proto_pos) = core.shape_forge().lookup_position(iterator.shape_id(), si_prototype) else { + return; + }; + let new_proto = JsValue::from_js_object(session.builtin_world().iterator_proto.as_ptr() as *mut JsObject); + iterator.set_prop_at(proto_pos, new_proto); +} + pub(crate) fn bind_global_value(core: &Arc, global: &mut JsObject, name: &str, value: JsValue) { let si = core.perm_interner().intern(name).0; let shape = core.shape_forge().make_shape(global.shape_id(), si); @@ -405,47 +488,9 @@ fn bind_iterator_global(core: &Arc, session: &KernelSession, global: &[("from", oxide_builtins::iterator::iterator_from:: as *const (), 1)], ); bind_existing_global(core, global, "Iterator", JsValue::from_js_object(Box::into_raw(iterator))); - - // %IteratorPrototype% 自身可迭代(@@iterator 返回 this,经原型链被全部集合迭代器继承)。 - let iter_proto_ptr = session.builtin_world().iterator_proto.as_ptr() as *mut JsObject; - let iter_proto = unsafe { &mut *iter_proto_ptr }; - bind_well_known_method( - session.builtin_world(), - core, - iter_proto, - 0, - "iterator", - oxide_builtins::iterator::iterator_symbol_iterator:: as *const (), - 0, - ); - - // 集合迭代器原型各自安装 next(%ArrayIteratorPrototype% 服务 Array/TA 两族; - // Map/Set 共用按 `__mode__` 分发的实现;%RegExpStringIteratorPrototype% 供 matchAll)。 - let world = session.builtin_world(); - bind_iterator_proto_next( - core, - session, - world.array_iterator_proto.as_ptr() as *mut JsObject, - oxide_builtins::array::array_iterator_next:: as *const (), - ); - bind_iterator_proto_next( - core, - session, - world.map_iterator_proto.as_ptr() as *mut JsObject, - oxide_builtins::iterator::map_set_iterator_next:: as *const (), - ); - bind_iterator_proto_next( - core, - session, - world.set_iterator_proto.as_ptr() as *mut JsObject, - oxide_builtins::iterator::map_set_iterator_next:: as *const (), - ); - bind_iterator_proto_next( - core, - session, - world.regexp_string_iterator_proto.as_ptr() as *mut JsObject, - oxide_builtins::string::string_match_all_next:: as *const (), - ); + // 迭代器原型方法(%IteratorPrototype% 的 @@iterator 与各集合原型 next)由 + // `bind_iterator_protos` 在 object 家族重建时统一安装,这里不重复绑定, + // 避免保留原型经 dirty reset 时属性槽膨胀。 } fn bind_stub_globals(core: &Arc, session: &KernelSession, global: &mut JsObject) { @@ -699,6 +744,10 @@ pub fn rebind_dirty_builtins(core: &Arc, session: &mut KernelSession if dirty.map_or(true, |d| d.object) { bind_object::bind_object(core, session, global); + // object 家族重建连带重建 6 个迭代器原型(其链到新 Object.prototype), + // 须同步安装原型方法并让保留 global 上的 Iterator 指向新 %IteratorPrototype%。 + bind_iterator_protos(core, session); + sync_iterator_function_prototype(core, session, global); } if dirty.map_or(true, |d| d.array) { bind_array::bind_array(core, session, global); diff --git a/crates/oxide_vm/src/vm_support.rs b/crates/oxide_vm/src/vm_support.rs index 133d2ffd..d7656abe 100644 --- a/crates/oxide_vm/src/vm_support.rs +++ b/crates/oxide_vm/src/vm_support.rs @@ -1004,6 +1004,83 @@ mod tests { assert!(!vm.session.is_dirty_since_snapshot()); } + #[test] + fn full_reset_object_dirty_rebinds_iterator_family() { + let mut vm = Vm::new(); + let old_object_proto = vm.session.builtin_world().object_proto.as_ptr(); + let old_iterator_proto = vm.session.builtin_world().iterator_proto.as_ptr(); + // 用户修改 Object.prototype:object 家族世代递增,global 未动。 + unsafe { &mut *(old_object_proto as *mut JsObject) }.bump_generation(); + + vm.full_reset(); + + // object 家族与迭代器原型全部重建(新原型链到新 Object.prototype)。 + assert!(!std::ptr::eq(old_object_proto, vm.session.builtin_world().object_proto.as_ptr())); + assert!(!std::ptr::eq(old_iterator_proto, vm.session.builtin_world().iterator_proto.as_ptr())); + // global 保留(dirty.global=false):其 Iterator 函数对象的 prototype + // 属性须对齐到重建后的 %IteratorPrototype%。 + let iter_val = global_prop(&vm, "Iterator"); + let si_prototype = vm.kernel_core.perm_interner().intern("prototype").0; + let iter_obj = unsafe { &*iter_val.as_js_object_ptr() }; + let proto_pos = vm + .kernel_core + .shape_forge() + .lookup_position(iter_obj.shape_id(), si_prototype) + .expect("Iterator should have prototype slot"); + assert!(std::ptr::eq( + iter_obj.get_prop_at(proto_pos).as_js_object_ptr(), + vm.session.builtin_world().iterator_proto.as_ptr() as *mut JsObject + )); + // full_reset 后 session 干净;此后 run_source 执行才重新累积世代变化。 + assert!(!vm.session.is_dirty_since_snapshot()); + // 迭代器家族功能完整:原型 next 就位,for-of/spread/Array.from/Iterator.from/ + // Map/Set/String/yield* 全部可用,原型链与 Iterator.prototype 一致。 + let r = run_source(&mut vm, "[...[1,2,3]].join(',')"); + assert_eq!(vm.lookup_str(r).as_deref(), Some("1,2,3")); + let r = run_source(&mut vm, "Array.from([1,2]).join(',')"); + assert_eq!(vm.lookup_str(r).as_deref(), Some("1,2")); + let r = run_source(&mut vm, "Iterator.from({next:function(){return {value:42,done:false}}}).next().value"); + assert_eq!(r, JsValue::int(42)); + let r = run_source(&mut vm, "[...new Map([[1,2],[3,4]])].map(function(x){return x.join(':')}).join(';')"); + assert_eq!(vm.lookup_str(r).as_deref(), Some("1:2;3:4")); + let r = run_source(&mut vm, "[...new Set([1,2,3])].join(',')"); + assert_eq!(vm.lookup_str(r).as_deref(), Some("1,2,3")); + let r = run_source(&mut vm, "[...'ab'].join(',')"); + assert_eq!(vm.lookup_str(r).as_deref(), Some("a,b")); + let r = run_source(&mut vm, "function* g(){yield* [1,2]} [...g()].join(',')"); + assert_eq!(vm.lookup_str(r).as_deref(), Some("1,2")); + let r = run_source( + &mut vm, + "Object.getPrototypeOf(Object.getPrototypeOf([].values())) === Iterator.prototype", + ); + assert_eq!(r, JsValue::bool(true)); + let r = run_source(&mut vm, "var it=[].values(); it[Symbol.iterator]()===it"); + assert_eq!(r, JsValue::bool(true)); + } + + #[test] + fn full_reset_global_dirty_keeps_iterator_proto_slots_stable() { + let mut vm = Vm::new(); + let arr_iter_proto = vm.session.builtin_world().array_iterator_proto.as_ptr() as *mut JsObject; + let slots_before = unsafe { &*arr_iter_proto }.hash_props_vec().map_or(0, |v| v.len()); + // global 世代递增(用户写 global),builtin 家族未动。 + unsafe { &mut *(vm.session.global_object.as_ptr() as *mut JsObject) }.bump_generation(); + + vm.full_reset(); + + // builtin 未脏 → 迭代器原型保留原对象且属性槽不膨胀(重复 full_reset 不再追加)。 + let arr_iter_proto_after = vm.session.builtin_world().array_iterator_proto.as_ptr() as *mut JsObject; + assert!(std::ptr::eq(arr_iter_proto, arr_iter_proto_after)); + let slots_after = unsafe { &*arr_iter_proto_after }.hash_props_vec().map_or(0, |v| v.len()); + assert_eq!(slots_before, slots_after); + assert!(!vm.session.is_dirty_since_snapshot()); + // 迭代器功能经保留原型仍完整(run_source 起再次累积世代变化)。 + let r = run_source(&mut vm, "[...[1,2,3]].join(',')"); + assert_eq!(vm.lookup_str(r).as_deref(), Some("1,2,3")); + let r = run_source(&mut vm, "var it=new Set([1]).values(); it[Symbol.iterator]()===it"); + assert_eq!(r, JsValue::bool(true)); + } + #[test] fn bigint_literal_arithmetic_and_comparison() { let mut vm = Vm::new();