in lib/VM/Domain.cpp [109:325]
ExecutionStatus Domain::importCJSModuleTable(
Handle<Domain> self,
Runtime &runtime,
RuntimeModule *runtimeModule) {
if (runtimeModule->getBytecode()->getCJSModuleTable().empty() &&
runtimeModule->getBytecode()->getCJSModuleTableStatic().empty()) {
// Nothing to do, avoid allocating and simply return.
return ExecutionStatus::RETURNED;
}
static_assert(
CJSModuleSize < 10, "CJSModuleSize must be small to avoid overflow");
MutableHandle<ArrayStorage> cjsModules{runtime};
if (!self->cjsModules_) {
// Create the module table on first import.
// If module IDs are contiguous and start from 0, we won't need to resize
// for this RuntimeModule.
const uint64_t firstSegmentModules =
runtimeModule->getBytecode()->getCJSModuleTable().size() +
runtimeModule->getBytecode()->getCJSModuleTableStatic().size();
assert(
firstSegmentModules <= std::numeric_limits<uint32_t>::max() &&
"number of modules is 32 bits due to the bytecode format");
// Use uint64_t to allow us to check for overflow.
const uint64_t requiredSize = firstSegmentModules * CJSModuleSize;
if (requiredSize > std::numeric_limits<uint32_t>::max()) {
return runtime.raiseRangeError("Loaded module count exceeded limit");
}
auto cjsModulesRes = ArrayStorage::create(runtime, requiredSize);
if (LLVM_UNLIKELY(cjsModulesRes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
cjsModules = vmcast<ArrayStorage>(*cjsModulesRes);
self->cjsRuntimeModules_.reserve(firstSegmentModules);
for (size_t i = self->cjsRuntimeModules_.size(); i < firstSegmentModules;
i++) {
self->cjsRuntimeModules_.push_back(nullptr, &runtime.getHeap());
}
auto requireFn = NativeFunction::create(
runtime,
Handle<JSObject>::vmcast(&runtime.functionPrototype),
(void *)TypeErrorKind::InvalidDynamicRequire,
throwTypeError,
Predefined::getSymbolID(Predefined::emptyString),
0,
Runtime::makeNullHandle<JSObject>());
auto context = RequireContext::create(
runtime,
self,
runtime.getPredefinedStringHandle(Predefined::emptyString));
// Set the require.context property.
PropertyFlags pf = PropertyFlags::defaultNewNamedPropertyFlags();
pf.writable = 0;
pf.configurable = 0;
if (LLVM_UNLIKELY(
JSObject::defineNewOwnProperty(
requireFn,
runtime,
Predefined::getSymbolID(Predefined::context),
pf,
context) == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
self->throwingRequire_.set(runtime, *requireFn, &runtime.getHeap());
} else {
cjsModules = self->cjsModules_.get(runtime);
}
assert(cjsModules && "cjsModules not set");
// Find the maximum module ID so we can resize cjsModules at most once per
// RuntimeModule.
uint64_t maxModuleID = cjsModules->size() / CJSModuleSize;
// The non-static module table does not store module IDs. They are assigned
// during registration by counting insertions to cjsModuleTable_. Count the
// insertions up front.
for (const auto &pair : runtimeModule->getBytecode()->getCJSModuleTable()) {
SymbolID symbolId =
runtimeModule->getSymbolIDFromStringIDMayAllocate(pair.first);
if (self->cjsModuleTable_.find(symbolId) == self->cjsModuleTable_.end()) {
++maxModuleID;
}
}
// The static module table stores module IDs in an arbitrary order. Scan for
// the maximum ID.
for (const auto &pair :
runtimeModule->getBytecode()->getCJSModuleTableStatic()) {
const auto &moduleID = pair.first;
if (moduleID > maxModuleID) {
maxModuleID = moduleID;
}
}
assert(
maxModuleID <= std::numeric_limits<uint32_t>::max() &&
"number of modules is 32 bits due to the bytecode format");
// Use uint64_t to allow us to check for overflow.
const uint64_t requiredSize = (maxModuleID + 1) * CJSModuleSize;
if (requiredSize > std::numeric_limits<uint32_t>::max()) {
return runtime.raiseRangeError("Loaded module count exceeded limit");
}
// Resize the array to allow for the new modules, if necessary.
if (requiredSize > cjsModules->size()) {
if (LLVM_UNLIKELY(
ArrayStorage::resize(cjsModules, runtime, requiredSize) ==
ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
self->cjsRuntimeModules_.reserve(maxModuleID + 1);
for (size_t i = self->cjsRuntimeModules_.size(); i <= maxModuleID; i++) {
self->cjsRuntimeModules_.push_back(nullptr, &runtime.getHeap());
}
}
/// \return Whether the module with ID \param moduleID has been registered in
/// cjsModules. \pre Space has been allocated for this module's record in
/// cjsModules.
const auto isModuleRegistered = [&cjsModules,
maxModuleID](uint32_t moduleID) -> bool {
assert(
moduleID <= maxModuleID &&
"CJS module ID exceeds maximum known module ID");
(void)maxModuleID;
uint32_t index = moduleID * CJSModuleSize;
uint32_t requiredSize = index + CJSModuleSize;
assert(
cjsModules->size() >= requiredSize &&
"CJS module ID exceeds allocated storage");
(void)requiredSize;
return !cjsModules->at(index + FunctionIndexOffset).isEmpty();
};
/// Register CJS module \param moduleID in the runtime module table.
/// \return The index into cjsModules where this module's record begins.
/// \pre Space has been allocated for this module's record in cjsModules.
/// \pre Space has been allocated for this module's RuntimeModule* in
/// cjsRuntimeModules_.
/// \pre There is no module already registered under moduleID.
auto &cjsEntryModuleID = self->cjsEntryModuleID_;
auto &cjsRuntimeModules = self->cjsRuntimeModules_;
const auto registerModule =
[&runtime,
&cjsModules,
&cjsRuntimeModules,
runtimeModule,
&isModuleRegistered,
&cjsEntryModuleID](uint32_t moduleID, uint32_t functionID) -> uint32_t {
assert(!isModuleRegistered(moduleID) && "CJS module ID collision occurred");
(void)isModuleRegistered;
if (LLVM_UNLIKELY(!cjsEntryModuleID.hasValue())) {
cjsEntryModuleID = moduleID;
}
uint32_t index = moduleID * CJSModuleSize;
cjsModules->set(
index + CachedExportsOffset,
HermesValue::encodeEmptyValue(),
&runtime.getHeap());
cjsModules->set(
index + ModuleOffset,
HermesValue::encodeNullValue(),
&runtime.getHeap());
cjsModules->set(
index + FunctionIndexOffset,
HermesValue::encodeNativeUInt32(functionID),
&runtime.getHeap());
cjsRuntimeModules[moduleID] = runtimeModule;
assert(isModuleRegistered(moduleID) && "CJS module was not registered");
return index;
};
// Import full table that allows dynamic requires.
for (const auto &pair : runtimeModule->getBytecode()->getCJSModuleTable()) {
SymbolID symbolId =
runtimeModule->getSymbolIDFromStringIDMayAllocate(pair.first);
auto emplaceRes = self->cjsModuleTable_.try_emplace(symbolId, 0xffffffff);
if (emplaceRes.second) {
// This module has not been registered before.
// Assign it an arbitrary unused module ID, because nothing will be
// referencing that ID from outside Domain.
// Counting insertions to cjsModuleTable_ is a valid source of unique IDs
// since a given Domain uses either dynamic requires or statically
// resolved requires.
uint32_t moduleID = self->cjsModuleTable_.size() - 1;
const auto functionID = pair.second;
auto index = registerModule(moduleID, functionID);
// Update the mapping from symbolId to an index into cjsModules.
emplaceRes.first->second = index;
}
}
// Import table to be used for requireFast.
for (const auto &pair :
runtimeModule->getBytecode()->getCJSModuleTableStatic()) {
const auto &moduleID = pair.first;
const auto &functionID = pair.second;
if (!isModuleRegistered(moduleID)) {
registerModule(moduleID, functionID);
}
}
self->cjsModules_.set(runtime, cjsModules.get(), &runtime.getHeap());
return ExecutionStatus::RETURNED;
}