module: use symbol in WeakMap to manage host defined options · nodejs/node@a86a2e1
@@ -7,6 +7,11 @@ const {
77 ObjectFreeze,
88} = primordials;
9910+const {
11+privateSymbols: {
12+ host_defined_option_symbol,
13+},
14+} = internalBinding('util');
1015const {
1116ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING,
1217ERR_INVALID_ARG_VALUE,
@@ -21,16 +26,8 @@ const {
2126 setImportModuleDynamicallyCallback,
2227 setInitializeImportMetaObjectCallback,
2328} = internalBinding('module_wrap');
24-const {
25- getModuleFromWrap,
26-} = require('internal/vm/module');
2729const assert = require('internal/assert');
283029-const callbackMap = new SafeWeakMap();
30-function setCallbackForWrap(wrap, data) {
31-callbackMap.set(wrap, data);
32-}
33-3431let defaultConditions;
3532/**
3633 * Returns the default conditions for ES module loading.
@@ -83,34 +80,86 @@ function getConditionsSet(conditions) {
8380return getDefaultConditionsSet();
8481}
858283+/**
84+ * @callback ImportModuleDynamicallyCallback
85+ * @param {string} specifier
86+ * @param {ModuleWrap|ContextifyScript|Function|vm.Module} callbackReferrer
87+ * @param {object} attributes
88+ * @returns { Promise<void> }
89+ */
90+91+/**
92+ * @callback InitializeImportMetaCallback
93+ * @param {object} meta
94+ * @param {ModuleWrap|ContextifyScript|Function|vm.Module} callbackReferrer
95+ */
96+97+/**
98+ * @typedef {{
99+ * callbackReferrer: ModuleWrap|ContextifyScript|Function|vm.Module
100+ * initializeImportMeta? : InitializeImportMetaCallback,
101+ * importModuleDynamically? : ImportModuleDynamicallyCallback
102+ * }} ModuleRegistry
103+ */
104+105+/**
106+ * @type {WeakMap<symbol, ModuleRegistry>}
107+ */
108+const moduleRegistries = new SafeWeakMap();
109+110+/**
111+ * V8 would make sure that as long as import() can still be initiated from
112+ * the referrer, the symbol referenced by |host_defined_option_symbol| should
113+ * be alive, which in term would keep the settings object alive through the
114+ * WeakMap, and in turn that keeps the referrer object alive, which would be
115+ * passed into the callbacks.
116+ * The reference goes like this:
117+ * [v8::internal::Script] (via host defined options) ----1--> [idSymbol]
118+ * [callbackReferrer] (via host_defined_option_symbol) ------2------^ |
119+ * ^----------3---- (via WeakMap)------
120+ * 1+3 makes sure that as long as import() can still be initiated, the
121+ * referrer wrap is still around and can be passed into the callbacks.
122+ * 2 is only there so that we can get the id symbol to configure the
123+ * weak map.
124+ * @param {ModuleWrap|ContextifyScript|Function} referrer The referrer to
125+ * get the id symbol from. This is different from callbackReferrer which
126+ * could be set by the caller.
127+ * @param {ModuleRegistry} registry
128+ */
129+function registerModule(referrer, registry) {
130+const idSymbol = referrer[host_defined_option_symbol];
131+// To prevent it from being GC'ed.
132+registry.callbackReferrer ??= referrer;
133+moduleRegistries.set(idSymbol, registry);
134+}
135+86136/**
87137 * Defines the `import.meta` object for a given module.
88- * @param {object} wrap - Reference to the module.
138+ * @param {symbol} symbol - Reference to the module.
89139 * @param {Record<string, string | Function>} meta - The import.meta object to initialize.
90140 */
91-function initializeImportMetaObject(wrap, meta) {
92-if (callbackMap.has(wrap)) {
93-const { initializeImportMeta } = callbackMap.get(wrap);
141+function initializeImportMetaObject(symbol, meta) {
142+if (moduleRegistries.has(symbol)) {
143+const { initializeImportMeta, callbackReferrer } = moduleRegistries.get(symbol);
94144if (initializeImportMeta !== undefined) {
95-meta = initializeImportMeta(meta, getModuleFromWrap(wrap) || wrap);
145+meta = initializeImportMeta(meta, callbackReferrer);
96146}
97147}
98148}
99149100150/**
101151 * Asynchronously imports a module dynamically using a callback function. The native callback.
102- * @param {object} wrap - Reference to the module.
152+ * @param {symbol} symbol - Reference to the module.
103153 * @param {string} specifier - The module specifier string.
104154 * @param {Record<string, string>} attributes - The import attributes object.
105155 * @returns {Promise<import('internal/modules/esm/loader.js').ModuleExports>} - The imported module object.
106156 * @throws {ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING} - If the callback function is missing.
107157 */
108-async function importModuleDynamicallyCallback(wrap, specifier, attributes) {
109-if (callbackMap.has(wrap)) {
110-const { importModuleDynamically } = callbackMap.get(wrap);
158+async function importModuleDynamicallyCallback(symbol, specifier, attributes) {
159+if (moduleRegistries.has(symbol)) {
160+const { importModuleDynamically, callbackReferrer } = moduleRegistries.get(symbol);
111161if (importModuleDynamically !== undefined) {
112-return importModuleDynamically(
113-specifier, getModuleFromWrap(wrap) || wrap, attributes);
162+return importModuleDynamically(specifier, callbackReferrer, attributes);
114163}
115164}
116165throw new ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING();
@@ -176,7 +225,7 @@ async function initializeHooks() {
176225}
177226178227module.exports = {
179-setCallbackForWrap,
228+registerModule,
180229 initializeESM,
181230 initializeHooks,
182231 getDefaultConditions,