Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions changelog.d/9899-module-namespace-descriptors.md
Original file line number Diff line number Diff line change
@@ -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.
6 changes: 6 additions & 0 deletions crates/perry-runtime/src/object/descriptors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
20 changes: 20 additions & 0 deletions crates/perry-runtime/src/object/namespace_create.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::<ObjectHeader, _>(|current_obj| {
key_handle.with_const_ptr::<crate::StringHeader, _>(|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]
Expand Down
21 changes: 21 additions & 0 deletions test-files/test_gap_dynamic_import_alias_binding.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,24 @@
async function main(): Promise<void> {
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,
Expand All @@ -16,6 +34,8 @@ async function main(): Promise<void> {
ns.checkAlias(ns.CONST_SET, "const"),
ns.checkAlias(ns.directConst, "direct"),
);
logDescriptor("VAR_SET", "var-before");
logDescriptor("directConst", "direct");

ns.reassign();
console.log(
Expand All @@ -24,6 +44,7 @@ async function main(): Promise<void> {
ns.checkAlias(ns.VAR_SET, "var-before"),
ns.checkAlias(ns.LET_SET, "let-before"),
);
logDescriptor("VAR_SET", "var-after");
}

main();
Loading