diff --git a/changelog.d/9899-module-namespace-descriptors.md b/changelog.d/9899-module-namespace-descriptors.md new file mode 100644 index 0000000000..5925f17990 --- /dev/null +++ b/changelog.d/9899-module-namespace-descriptors.md @@ -0,0 +1,5 @@ +### Fixed + +- Dynamic-import namespace exports now report standard data descriptors with + their current live-binding values instead of exposing the runtime's internal + accessor representation. diff --git a/crates/perry-runtime/src/object/descriptors.rs b/crates/perry-runtime/src/object/descriptors.rs index a17edbc176..28f77b966e 100644 --- a/crates/perry-runtime/src/object/descriptors.rs +++ b/crates/perry-runtime/src/object/descriptors.rs @@ -964,6 +964,12 @@ pub extern "C" fn js_object_get_own_property_descriptor(obj_value: f64, key_valu return f64::from_bits(crate::value::TAG_UNDEFINED); } + // #9889: Hide the live binding's internal accessor behind the module + // namespace exotic object's required data-descriptor shape. + if (*obj).class_id == MODULE_NAMESPACE_CLASS_ID { + return module_namespace_own_property_descriptor(obj, key_str); + } + // Look up descriptor flags (default: all true). let attrs = key_rust .as_ref() diff --git a/crates/perry-runtime/src/object/namespace_create.rs b/crates/perry-runtime/src/object/namespace_create.rs index d6fc2d7bf4..bd044366c3 100644 --- a/crates/perry-runtime/src/object/namespace_create.rs +++ b/crates/perry-runtime/src/object/namespace_create.rs @@ -6,6 +6,26 @@ use super::*; pub(crate) const MODULE_NAMESPACE_CLASS_ID: u32 = 0xFFFF_4E53; +/// #9889: Implement the module namespace exotic object's [[GetOwnProperty]] +/// result while retaining accessors as the internal representation of live +/// bindings. The getter can allocate and evacuate both the namespace and key, +/// so keep them rooted until it returns. `build_data_descriptor` roots the +/// resulting value before allocating the descriptor object. +pub(crate) unsafe fn module_namespace_own_property_descriptor( + obj: *mut ObjectHeader, + key: *const crate::StringHeader, +) -> f64 { + let scope = crate::gc::RuntimeHandleScope::new(); + let obj_handle = scope.root_raw_mut_ptr(obj); + let key_handle = scope.root_string_ptr(key); + let value = obj_handle.with_mut_ptr::(|current_obj| { + key_handle.with_const_ptr::(|current_key| { + js_object_get_field_by_name(current_obj, current_key) + }) + }); + super::descriptors::build_data_descriptor(f64::from_bits(value.bits()), true, true, false) +} + /// Apply the host-defined invariants shared by static and dynamic module /// namespace objects. #[no_mangle] diff --git a/test-files/test_gap_dynamic_import_alias_binding.ts b/test-files/test_gap_dynamic_import_alias_binding.ts index ef2d35e430..f8a1de38a7 100644 --- a/test-files/test_gap_dynamic_import_alias_binding.ts +++ b/test-files/test_gap_dynamic_import_alias_binding.ts @@ -3,6 +3,24 @@ async function main(): Promise { const ns = await import("./dynamic_import_alias_binding.ts"); + function logDescriptor( + name: "VAR_SET" | "directConst", + expected: string, + ): void { + const descriptor = Object.getOwnPropertyDescriptor(ns, name); + console.log( + name, + descriptor !== undefined, + descriptor !== undefined && "value" in descriptor, + descriptor?.writable, + descriptor?.enumerable, + descriptor?.configurable, + descriptor?.get === undefined, + descriptor?.set === undefined, + descriptor !== undefined && ns.checkAlias(descriptor.value, expected), + ); + } + console.log( typeof ns.VAR_SET, typeof ns.LET_SET, @@ -16,6 +34,8 @@ async function main(): Promise { ns.checkAlias(ns.CONST_SET, "const"), ns.checkAlias(ns.directConst, "direct"), ); + logDescriptor("VAR_SET", "var-before"); + logDescriptor("directConst", "direct"); ns.reassign(); console.log( @@ -24,6 +44,7 @@ async function main(): Promise { ns.checkAlias(ns.VAR_SET, "var-before"), ns.checkAlias(ns.LET_SET, "let-before"), ); + logDescriptor("VAR_SET", "var-after"); } main();