Skip to content

Commit f7185b1

Browse files
committed
module: link module with a module request record
When a module is being statically linked with module requests, if two module requests with a same specifier but different attributes are resolved to two modules, the module requests should be linked to these two modules.
1 parent 4d5ee24 commit f7185b1

File tree

10 files changed

+211
-72
lines changed

10 files changed

+211
-72
lines changed

lib/internal/modules/esm/module_job.js

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -190,8 +190,7 @@ class ModuleJob extends ModuleJobBase {
190190
const evaluationDepJobs = Array(moduleRequests.length);
191191
ObjectSetPrototypeOf(evaluationDepJobs, null);
192192

193-
// Specifiers should be aligned with the moduleRequests array in order.
194-
const specifiers = Array(moduleRequests.length);
193+
// Modules should be aligned with the moduleRequests array in order.
195194
const modulePromises = Array(moduleRequests.length);
196195
// Track each loop for whether it is an evaluation phase or source phase request.
197196
let isEvaluation;
@@ -217,11 +216,10 @@ class ModuleJob extends ModuleJobBase {
217216
return job.modulePromise;
218217
});
219218
modulePromises[idx] = modulePromise;
220-
specifiers[idx] = specifier;
221219
}
222220

223221
const modules = await SafePromiseAllReturnArrayLike(modulePromises);
224-
this.module.link(specifiers, modules);
222+
this.module.link(modules);
225223

226224
return evaluationDepJobs;
227225
}
@@ -433,22 +431,20 @@ class ModuleJobSync extends ModuleJobBase {
433431
this.#loader.loadCache.set(this.url, this.type, this);
434432
try {
435433
const moduleRequests = this.module.getModuleRequests();
436-
// Specifiers should be aligned with the moduleRequests array in order.
437-
const specifiers = Array(moduleRequests.length);
434+
// Modules should be aligned with the moduleRequests array in order.
438435
const modules = Array(moduleRequests.length);
439436
const evaluationDepJobs = Array(moduleRequests.length);
440437
let j = 0;
441438
for (let i = 0; i < moduleRequests.length; ++i) {
442439
const { specifier, attributes, phase } = moduleRequests[i];
443440
const job = this.#loader.getModuleJobForRequire(specifier, this.url, attributes, phase);
444-
specifiers[i] = specifier;
445441
modules[i] = job.module;
446442
if (phase === kEvaluationPhase) {
447443
evaluationDepJobs[j++] = job;
448444
}
449445
}
450446
evaluationDepJobs.length = j;
451-
this.module.link(specifiers, modules);
447+
this.module.link(modules);
452448
this.linked = evaluationDepJobs;
453449
} finally {
454450
// Restore it - if it succeeds, we'll reset in the caller; Otherwise it's

lib/internal/vm/module.js

Lines changed: 15 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -148,23 +148,10 @@ class Module {
148148
});
149149
}
150150

151-
let registry = { __proto__: null };
152151
if (sourceText !== undefined) {
153152
this[kWrap] = new ModuleWrap(identifier, context, sourceText,
154153
options.lineOffset, options.columnOffset,
155154
options.cachedData);
156-
registry = {
157-
__proto__: null,
158-
initializeImportMeta: options.initializeImportMeta,
159-
importModuleDynamically: options.importModuleDynamically ?
160-
importModuleDynamicallyWrap(options.importModuleDynamically) :
161-
undefined,
162-
};
163-
// This will take precedence over the referrer as the object being
164-
// passed into the callbacks.
165-
registry.callbackReferrer = this;
166-
const { registerModule } = require('internal/modules/esm/utils');
167-
registerModule(this[kWrap], registry);
168155
} else {
169156
assert(syntheticEvaluationSteps);
170157
this[kWrap] = new ModuleWrap(identifier, context,
@@ -315,6 +302,19 @@ class SourceTextModule extends Module {
315302
importModuleDynamically,
316303
});
317304

305+
const registry = {
306+
__proto__: null,
307+
initializeImportMeta: options.initializeImportMeta,
308+
importModuleDynamically: options.importModuleDynamically ?
309+
importModuleDynamicallyWrap(options.importModuleDynamically) :
310+
undefined,
311+
};
312+
// This will take precedence over the referrer as the object being
313+
// passed into the callbacks.
314+
registry.callbackReferrer = this;
315+
const { registerModule } = require('internal/modules/esm/utils');
316+
registerModule(this[kWrap], registry);
317+
318318
this.#moduleRequests = ObjectFreeze(ArrayPrototypeMap(this[kWrap].getModuleRequests(), (request) => {
319319
return ObjectFreeze({
320320
__proto__: null,
@@ -329,8 +329,7 @@ class SourceTextModule extends Module {
329329
this.#statusOverride = 'linking';
330330

331331
// Iterates the module requests and links with the linker.
332-
// Specifiers should be aligned with the moduleRequests array in order.
333-
const specifiers = Array(this.#moduleRequests.length);
332+
// Modules should be aligned with the moduleRequests array in order.
334333
const modulePromises = Array(this.#moduleRequests.length);
335334
// Iterates with index to avoid calling into userspace with `Symbol.iterator`.
336335
for (let idx = 0; idx < this.#moduleRequests.length; idx++) {
@@ -357,12 +356,11 @@ class SourceTextModule extends Module {
357356
return module[kWrap];
358357
});
359358
modulePromises[idx] = modulePromise;
360-
specifiers[idx] = specifier;
361359
}
362360

363361
try {
364362
const modules = await SafePromiseAllReturnArrayLike(modulePromises);
365-
this[kWrap].link(specifiers, modules);
363+
this[kWrap].link(modules);
366364
} catch (e) {
367365
this.#error = e;
368366
throw e;

src/module_wrap.cc

Lines changed: 80 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,6 @@ using v8::Message;
4646
using v8::MicrotaskQueue;
4747
using v8::Module;
4848
using v8::ModuleImportPhase;
49-
using v8::ModuleRequest;
5049
using v8::Name;
5150
using v8::Nothing;
5251
using v8::Null;
@@ -63,6 +62,56 @@ using v8::UnboundModuleScript;
6362
using v8::Undefined;
6463
using v8::Value;
6564

65+
ModulePhase to_phase_constant(ModuleImportPhase phase) {
66+
switch (phase) {
67+
case ModuleImportPhase::kEvaluation:
68+
return kEvaluationPhase;
69+
case ModuleImportPhase::kSource:
70+
return kSourcePhase;
71+
}
72+
UNREACHABLE();
73+
}
74+
75+
void ModuleRequest::MemoryInfo(MemoryTracker* tracker) const {
76+
tracker->TrackField("specifier", specifier);
77+
tracker->TrackField("import_attributes", import_attributes);
78+
tracker->TrackField("phase", static_cast<int>(phase));
79+
}
80+
81+
template <int elements_per_attribute>
82+
ModuleRequest ModuleRequest::From(Local<Context> context,
83+
Local<String> specifier,
84+
Local<FixedArray> import_attributes,
85+
ModulePhase phase) {
86+
CHECK_EQ(import_attributes->Length() % elements_per_attribute, 0);
87+
Isolate* isolate = context->GetIsolate();
88+
size_t num_attributes = import_attributes->Length() / elements_per_attribute;
89+
ImportAttributeVector attributes;
90+
attributes.reserve(num_attributes);
91+
92+
for (int i = 0; i < import_attributes->Length();
93+
i += elements_per_attribute) {
94+
Local<String> v8_key = import_attributes->Get(context, i).As<String>();
95+
Local<String> v8_value =
96+
import_attributes->Get(context, i + 1).As<String>();
97+
Utf8Value key_utf8(isolate, v8_key);
98+
Utf8Value value_utf8(isolate, v8_value);
99+
100+
attributes.emplace_back(key_utf8.ToString(), value_utf8.ToString());
101+
}
102+
103+
Utf8Value utf8_specifier(isolate, specifier);
104+
return ModuleRequest{utf8_specifier.ToString(), attributes, phase};
105+
}
106+
107+
ModuleRequest ModuleRequest::From(Local<Context> context,
108+
Local<v8::ModuleRequest> v8_request) {
109+
return From(context,
110+
v8_request->GetSpecifier(),
111+
v8_request->GetImportAttributes(),
112+
to_phase_constant(v8_request->GetPhase()));
113+
}
114+
66115
ModuleWrap::ModuleWrap(Realm* realm,
67116
Local<Object> object,
68117
Local<Module> module,
@@ -422,16 +471,6 @@ MaybeLocal<Module> ModuleWrap::CompileSourceTextModule(
422471
return scope.Escape(module);
423472
}
424473

425-
ModulePhase to_phase_constant(ModuleImportPhase phase) {
426-
switch (phase) {
427-
case ModuleImportPhase::kEvaluation:
428-
return kEvaluationPhase;
429-
case ModuleImportPhase::kSource:
430-
return kSourcePhase;
431-
}
432-
UNREACHABLE();
433-
}
434-
435474
static Local<Object> createImportAttributesContainer(
436475
Realm* realm,
437476
Isolate* isolate,
@@ -462,8 +501,8 @@ static Local<Array> createModuleRequestsContainer(
462501
LocalVector<Value> requests(isolate, raw_requests->Length());
463502

464503
for (int i = 0; i < raw_requests->Length(); i++) {
465-
Local<ModuleRequest> module_request =
466-
raw_requests->Get(realm->context(), i).As<ModuleRequest>();
504+
Local<v8::ModuleRequest> module_request =
505+
raw_requests->Get(realm->context(), i).As<v8::ModuleRequest>();
467506

468507
Local<String> specifier = module_request->GetSpecifier();
469508

@@ -509,7 +548,7 @@ void ModuleWrap::GetModuleRequests(const FunctionCallbackInfo<Value>& args) {
509548
realm, isolate, module->GetModuleRequests()));
510549
}
511550

512-
// moduleWrap.link(specifiers, moduleWraps)
551+
// moduleWrap.link(moduleWraps)
513552
void ModuleWrap::Link(const FunctionCallbackInfo<Value>& args) {
514553
Realm* realm = Realm::GetCurrent(args);
515554
Isolate* isolate = args.GetIsolate();
@@ -518,33 +557,28 @@ void ModuleWrap::Link(const FunctionCallbackInfo<Value>& args) {
518557
ModuleWrap* dependent;
519558
ASSIGN_OR_RETURN_UNWRAP(&dependent, args.This());
520559

521-
CHECK_EQ(args.Length(), 2);
560+
CHECK_EQ(args.Length(), 1);
522561

523-
Local<Array> specifiers = args[0].As<Array>();
524-
Local<Array> modules = args[1].As<Array>();
525-
CHECK_EQ(specifiers->Length(), modules->Length());
562+
Local<FixedArray> requests =
563+
dependent->module_.Get(isolate)->GetModuleRequests();
564+
Local<Array> modules = args[0].As<Array>();
565+
CHECK_EQ(modules->Length(), static_cast<uint32_t>(requests->Length()));
526566

527-
std::vector<Global<Value>> specifiers_buffer;
528-
if (FromV8Array(context, specifiers, &specifiers_buffer).IsNothing()) {
529-
return;
530-
}
531567
std::vector<Global<Value>> modules_buffer;
532568
if (FromV8Array(context, modules, &modules_buffer).IsNothing()) {
533569
return;
534570
}
535571

536-
for (uint32_t i = 0; i < specifiers->Length(); i++) {
537-
Local<String> specifier_str =
538-
specifiers_buffer[i].Get(isolate).As<String>();
572+
for (uint32_t i = 0; i < modules_buffer.size(); i++) {
539573
Local<Object> module_object = modules_buffer[i].Get(isolate).As<Object>();
540574

541575
CHECK(
542576
realm->isolate_data()->module_wrap_constructor_template()->HasInstance(
543577
module_object));
544578

545-
Utf8Value specifier(isolate, specifier_str);
546-
dependent->resolve_cache_[specifier.ToString()].Reset(isolate,
547-
module_object);
579+
ModuleRequest module_request = ModuleRequest::From(
580+
context, requests->Get(context, i).As<v8::ModuleRequest>());
581+
dependent->resolve_cache_[module_request].Reset(isolate, module_object);
548582
}
549583
}
550584

@@ -924,27 +958,28 @@ MaybeLocal<Module> ModuleWrap::ResolveModuleCallback(
924958
return MaybeLocal<Module>();
925959
}
926960

927-
Utf8Value specifier_utf8(isolate, specifier);
928-
std::string specifier_std(*specifier_utf8, specifier_utf8.length());
961+
// ResolveModuleCallback for kEvaluationPhase only.
962+
const ModulePhase phase = ModulePhase::kEvaluationPhase;
963+
ModuleRequest request =
964+
ModuleRequest::From(context, specifier, import_attributes, phase);
929965

930966
ModuleWrap* dependent = GetFromModule(env, referrer);
931967
if (dependent == nullptr) {
932968
THROW_ERR_VM_MODULE_LINK_FAILURE(
933-
env, "request for '%s' is from invalid module", specifier_std);
969+
env, "request for '%s' is from invalid module", request.specifier);
934970
return MaybeLocal<Module>();
935971
}
936972

937-
if (dependent->resolve_cache_.count(specifier_std) != 1) {
973+
if (dependent->resolve_cache_.count(request) != 1) {
938974
THROW_ERR_VM_MODULE_LINK_FAILURE(
939-
env, "request for '%s' is not in cache", specifier_std);
975+
env, "request for '%s' is not in cache", request.specifier);
940976
return MaybeLocal<Module>();
941977
}
942978

943-
Local<Object> module_object =
944-
dependent->resolve_cache_[specifier_std].Get(isolate);
979+
Local<Object> module_object = dependent->resolve_cache_[request].Get(isolate);
945980
if (module_object.IsEmpty() || !module_object->IsObject()) {
946981
THROW_ERR_VM_MODULE_LINK_FAILURE(
947-
env, "request for '%s' did not return an object", specifier_std);
982+
env, "request for '%s' did not return an object", request.specifier);
948983
return MaybeLocal<Module>();
949984
}
950985

@@ -965,27 +1000,28 @@ MaybeLocal<Object> ModuleWrap::ResolveSourceCallback(
9651000
return MaybeLocal<Object>();
9661001
}
9671002

968-
Utf8Value specifier_utf8(isolate, specifier);
969-
std::string specifier_std(*specifier_utf8, specifier_utf8.length());
1003+
// ResolveSourceCallback for kSourcePhase only.
1004+
const ModulePhase phase = ModulePhase::kSourcePhase;
1005+
ModuleRequest request =
1006+
ModuleRequest::From(context, specifier, import_attributes, phase);
9701007

9711008
ModuleWrap* dependent = GetFromModule(env, referrer);
9721009
if (dependent == nullptr) {
9731010
THROW_ERR_VM_MODULE_LINK_FAILURE(
974-
env, "request for '%s' is from invalid module", specifier_std);
1011+
env, "request for '%s' is from invalid module", request.specifier);
9751012
return MaybeLocal<Object>();
9761013
}
9771014

978-
if (dependent->resolve_cache_.count(specifier_std) != 1) {
1015+
if (dependent->resolve_cache_.count(request) != 1) {
9791016
THROW_ERR_VM_MODULE_LINK_FAILURE(
980-
env, "request for '%s' is not in cache", specifier_std);
1017+
env, "request for '%s' is not in cache", request.specifier);
9811018
return MaybeLocal<Object>();
9821019
}
9831020

984-
Local<Object> module_object =
985-
dependent->resolve_cache_[specifier_std].Get(isolate);
1021+
Local<Object> module_object = dependent->resolve_cache_[request].Get(isolate);
9861022
if (module_object.IsEmpty() || !module_object->IsObject()) {
9871023
THROW_ERR_VM_MODULE_LINK_FAILURE(
988-
env, "request for '%s' did not return an object", specifier_std);
1024+
env, "request for '%s' did not return an object", request.specifier);
9891025
return MaybeLocal<Object>();
9901026
}
9911027

src/module_wrap.h

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@
33

44
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
55

6+
#include <map>
67
#include <optional>
78
#include <string>
8-
#include <unordered_map>
99
#include <vector>
1010
#include "base_object.h"
1111
#include "v8-script.h"
@@ -38,6 +38,43 @@ enum ModulePhase : int {
3838
kEvaluationPhase = 2,
3939
};
4040

41+
struct ModuleRequest : public MemoryRetainer {
42+
using ImportAttributeVector =
43+
std::vector<std::pair<std::string, std::string>>;
44+
45+
std::string specifier;
46+
ImportAttributeVector import_attributes;
47+
ModulePhase phase;
48+
49+
ModuleRequest(std::string specifier,
50+
ImportAttributeVector import_attributes,
51+
ModulePhase phase)
52+
: specifier(specifier),
53+
import_attributes(import_attributes),
54+
phase(phase) {}
55+
56+
SET_MEMORY_INFO_NAME(ModuleRequest)
57+
SET_SELF_SIZE(ModuleRequest)
58+
void MemoryInfo(MemoryTracker* tracker) const override;
59+
60+
template <int elements_per_attribute = 3>
61+
static ModuleRequest From(v8::Local<v8::Context> context,
62+
v8::Local<v8::String> specifier,
63+
v8::Local<v8::FixedArray> import_attributes,
64+
ModulePhase phase);
65+
static ModuleRequest From(v8::Local<v8::Context> context,
66+
v8::Local<v8::ModuleRequest> v8_request);
67+
68+
bool operator<(const ModuleRequest& other) const {
69+
if (specifier != other.specifier) {
70+
return specifier < other.specifier;
71+
} else if (import_attributes != other.import_attributes) {
72+
return import_attributes < other.import_attributes;
73+
}
74+
return phase < other.phase;
75+
}
76+
};
77+
4178
class ModuleWrap : public BaseObject {
4279
public:
4380
enum InternalFields {
@@ -149,7 +186,7 @@ class ModuleWrap : public BaseObject {
149186
static ModuleWrap* GetFromModule(node::Environment*, v8::Local<v8::Module>);
150187

151188
v8::Global<v8::Module> module_;
152-
std::unordered_map<std::string, v8::Global<v8::Object>> resolve_cache_;
189+
std::map<ModuleRequest, v8::Global<v8::Object>> resolve_cache_;
153190
contextify::ContextifyContext* contextify_context_ = nullptr;
154191
bool synthetic_ = false;
155192
int module_hash_;

0 commit comments

Comments
 (0)