JSModuleRecord.cpp [plain text]
#include "config.h"
#include "JSModuleRecord.h"
#include "Error.h"
#include "Executable.h"
#include "IdentifierInlines.h"
#include "JSCJSValueInlines.h"
#include "JSCellInlines.h"
#include "JSMap.h"
#include "JSModuleEnvironment.h"
#include "JSModuleNamespaceObject.h"
#include "SlotVisitorInlines.h"
#include "StructureInlines.h"
namespace JSC {
const ClassInfo JSModuleRecord::s_info = { "ModuleRecord", &Base::s_info, 0, CREATE_METHOD_TABLE(JSModuleRecord) };
void JSModuleRecord::destroy(JSCell* cell)
{
JSModuleRecord* thisObject = jsCast<JSModuleRecord*>(cell);
thisObject->JSModuleRecord::~JSModuleRecord();
}
void JSModuleRecord::finishCreation(VM& vm)
{
Base::finishCreation(vm);
ASSERT(inherits(info()));
putDirect(vm, Identifier::fromString(&vm, ASCIILiteral("registryEntry")), jsUndefined());
putDirect(vm, Identifier::fromString(&vm, ASCIILiteral("evaluated")), jsBoolean(false));
m_dependenciesMap.set(vm, this, JSMap::create(vm, globalObject()->mapStructure()));
putDirect(vm, Identifier::fromString(&vm, ASCIILiteral("dependenciesMap")), m_dependenciesMap.get());
}
void JSModuleRecord::visitChildren(JSCell* cell, SlotVisitor& visitor)
{
JSModuleRecord* thisObject = jsCast<JSModuleRecord*>(cell);
Base::visitChildren(thisObject, visitor);
visitor.append(&thisObject->m_moduleEnvironment);
visitor.append(&thisObject->m_moduleNamespaceObject);
visitor.append(&thisObject->m_moduleProgramExecutable);
visitor.append(&thisObject->m_dependenciesMap);
}
void JSModuleRecord::appendRequestedModule(const Identifier& moduleName)
{
m_requestedModules.add(moduleName.impl());
}
void JSModuleRecord::addStarExportEntry(const Identifier& moduleName)
{
m_starExportEntries.add(moduleName.impl());
}
void JSModuleRecord::addImportEntry(const ImportEntry& entry)
{
bool isNewEntry = m_importEntries.add(entry.localName.impl(), entry).isNewEntry;
ASSERT_UNUSED(isNewEntry, isNewEntry); }
void JSModuleRecord::addExportEntry(const ExportEntry& entry)
{
bool isNewEntry = m_exportEntries.add(entry.exportName.impl(), entry).isNewEntry;
ASSERT_UNUSED(isNewEntry, isNewEntry); }
auto JSModuleRecord::tryGetImportEntry(UniquedStringImpl* localName) -> Optional<ImportEntry>
{
const auto iterator = m_importEntries.find(localName);
if (iterator == m_importEntries.end())
return Nullopt;
return Optional<ImportEntry>(iterator->value);
}
auto JSModuleRecord::tryGetExportEntry(UniquedStringImpl* exportName) -> Optional<ExportEntry>
{
const auto iterator = m_exportEntries.find(exportName);
if (iterator == m_exportEntries.end())
return Nullopt;
return Optional<ExportEntry>(iterator->value);
}
auto JSModuleRecord::ExportEntry::createLocal(const Identifier& exportName, const Identifier& localName) -> ExportEntry
{
return ExportEntry { Type::Local, exportName, Identifier(), Identifier(), localName };
}
auto JSModuleRecord::ExportEntry::createIndirect(const Identifier& exportName, const Identifier& importName, const Identifier& moduleName) -> ExportEntry
{
return ExportEntry { Type::Indirect, exportName, moduleName, importName, Identifier() };
}
auto JSModuleRecord::Resolution::notFound() -> Resolution
{
return Resolution { Type::NotFound, nullptr, Identifier() };
}
auto JSModuleRecord::Resolution::error() -> Resolution
{
return Resolution { Type::Error, nullptr, Identifier() };
}
auto JSModuleRecord::Resolution::ambiguous() -> Resolution
{
return Resolution { Type::Ambiguous, nullptr, Identifier() };
}
static JSValue identifierToJSValue(ExecState* exec, const Identifier& identifier)
{
if (identifier.isSymbol())
return Symbol::create(exec->vm(), static_cast<SymbolImpl&>(*identifier.impl()));
return jsString(&exec->vm(), identifier.impl());
}
JSModuleRecord* JSModuleRecord::hostResolveImportedModule(ExecState* exec, const Identifier& moduleName)
{
JSValue moduleNameValue = identifierToJSValue(exec, moduleName);
JSValue pair = m_dependenciesMap->JSMap::get(exec, moduleNameValue);
return jsCast<JSModuleRecord*>(pair.get(exec, Identifier::fromString(exec, "value")));
}
auto JSModuleRecord::resolveImport(ExecState* exec, const Identifier& localName) -> Resolution
{
Optional<ImportEntry> optionalImportEntry = tryGetImportEntry(localName.impl());
if (!optionalImportEntry)
return Resolution::notFound();
const ImportEntry& importEntry = *optionalImportEntry;
if (importEntry.isNamespace(exec->vm()))
return Resolution::notFound();
JSModuleRecord* importedModule = hostResolveImportedModule(exec, importEntry.moduleRequest);
return importedModule->resolveExport(exec, importEntry.importName);
}
struct JSModuleRecord::ResolveQuery {
struct Hash {
static unsigned hash(const ResolveQuery&);
static bool equal(const ResolveQuery&, const ResolveQuery&);
static const bool safeToCompareToEmptyOrDeleted = true;
};
ResolveQuery(JSModuleRecord* moduleRecord, UniquedStringImpl* exportName)
: moduleRecord(moduleRecord)
, exportName(exportName)
{
}
ResolveQuery(JSModuleRecord* moduleRecord, const Identifier& exportName)
: ResolveQuery(moduleRecord, exportName.impl())
{
}
enum EmptyValueTag { EmptyValue };
ResolveQuery(EmptyValueTag)
{
}
enum DeletedValueTag { DeletedValue };
ResolveQuery(DeletedValueTag)
: moduleRecord(nullptr)
, exportName(WTF::HashTableDeletedValue)
{
}
bool isEmptyValue() const
{
return !exportName;
}
bool isDeletedValue() const
{
return exportName.isHashTableDeletedValue();
}
JSModuleRecord* moduleRecord;
RefPtr<UniquedStringImpl> exportName;
};
inline unsigned JSModuleRecord::ResolveQuery::Hash::hash(const ResolveQuery& query)
{
return WTF::PtrHash<JSModuleRecord*>::hash(query.moduleRecord) + IdentifierRepHash::hash(query.exportName);
}
inline bool JSModuleRecord::ResolveQuery::Hash::equal(const ResolveQuery& lhs, const ResolveQuery& rhs)
{
return lhs.moduleRecord == rhs.moduleRecord && lhs.exportName == rhs.exportName;
}
auto JSModuleRecord::tryGetCachedResolution(UniquedStringImpl* exportName) -> Optional<Resolution>
{
const auto iterator = m_resolutionCache.find(exportName);
if (iterator == m_resolutionCache.end())
return Nullopt;
return Optional<Resolution>(iterator->value);
}
void JSModuleRecord::cacheResolution(UniquedStringImpl* exportName, const Resolution& resolution)
{
m_resolutionCache.add(exportName, resolution);
}
auto JSModuleRecord::resolveExportImpl(ExecState* exec, const ResolveQuery& root) -> Resolution
{
typedef WTF::HashSet<ResolveQuery, ResolveQuery::Hash, WTF::CustomHashTraits<ResolveQuery>> ResolveSet;
enum class Type { Query, IndirectFallback, GatherStars };
struct Task {
ResolveQuery query;
Type type;
};
Vector<Task, 8> pendingTasks;
ResolveSet resolveSet;
HashSet<JSModuleRecord*> starSet;
Vector<Resolution, 8> frames;
bool foundStarLinks = false;
frames.append(Resolution::notFound());
auto resolveNonLocal = [&](const ResolveQuery& query) -> bool {
if (query.exportName == exec->propertyNames().defaultKeyword.impl())
return false;
if (!starSet.add(query.moduleRecord).isNewEntry)
return true;
pendingTasks.append(Task { query, Type::GatherStars });
foundStarLinks = true;
frames.append(Resolution::notFound());
for (auto iterator = query.moduleRecord->starExportEntries().rbegin(), end = query.moduleRecord->starExportEntries().rend(); iterator != end; ++iterator) {
const RefPtr<UniquedStringImpl>& starModuleName = *iterator;
JSModuleRecord* importedModuleRecord = query.moduleRecord->hostResolveImportedModule(exec, Identifier::fromUid(exec, starModuleName.get()));
pendingTasks.append(Task { ResolveQuery(importedModuleRecord, query.exportName.get()), Type::Query });
}
return true;
};
auto currentTop = [&] () -> Resolution& {
ASSERT(!frames.isEmpty());
return frames.last();
};
auto mergeToCurrentTop = [&] (const Resolution& resolution) -> bool {
if (resolution.type == Resolution::Type::NotFound)
return true;
if (currentTop().type == Resolution::Type::NotFound) {
currentTop() = resolution;
return true;
}
if (currentTop().moduleRecord != resolution.moduleRecord || currentTop().localName != resolution.localName)
return false;
return true;
};
auto cacheResolutionForQuery = [] (const ResolveQuery& query, const Resolution& resolution) {
ASSERT(resolution.type == Resolution::Type::Resolved);
query.moduleRecord->cacheResolution(query.exportName.get(), resolution);
};
pendingTasks.append(Task { root, Type::Query });
while (!pendingTasks.isEmpty()) {
const Task task = pendingTasks.takeLast();
const ResolveQuery& query = task.query;
switch (task.type) {
case Type::Query: {
JSModuleRecord* moduleRecord = query.moduleRecord;
if (!resolveSet.add(task.query).isNewEntry)
continue;
if (!moduleRecord->starExportEntries().isEmpty())
foundStarLinks = true;
if (!foundStarLinks) {
if (Optional<Resolution> cachedResolution = moduleRecord->tryGetCachedResolution(query.exportName.get())) {
if (!mergeToCurrentTop(*cachedResolution))
return Resolution::ambiguous();
continue;
}
}
const Optional<ExportEntry> optionalExportEntry = moduleRecord->tryGetExportEntry(query.exportName.get());
if (!optionalExportEntry) {
if (!resolveNonLocal(task.query))
return Resolution::error();
continue;
}
const ExportEntry& exportEntry = *optionalExportEntry;
switch (exportEntry.type) {
case ExportEntry::Type::Local: {
ASSERT(!exportEntry.localName.isNull());
Resolution resolution { Resolution::Type::Resolved, moduleRecord, exportEntry.localName };
cacheResolutionForQuery(query, resolution);
if (!mergeToCurrentTop(resolution))
return Resolution::ambiguous();
continue;
}
case ExportEntry::Type::Indirect: {
JSModuleRecord* importedModuleRecord = moduleRecord->hostResolveImportedModule(exec, exportEntry.moduleName);
pendingTasks.append(Task { query, Type::IndirectFallback });
frames.append(Resolution::notFound());
pendingTasks.append(Task { ResolveQuery(importedModuleRecord, exportEntry.importName), Type::Query });
continue;
}
}
break;
}
case Type::IndirectFallback: {
Resolution resolution = frames.takeLast();
if (resolution.type == Resolution::Type::NotFound) {
if (!resolveNonLocal(task.query))
return Resolution::error();
continue;
}
ASSERT_WITH_MESSAGE(resolution.type == Resolution::Type::Resolved, "When we see Error and Ambiguous, we immediately return from this loop. So here, only Resolved comes.");
if (!foundStarLinks)
cacheResolutionForQuery(query, resolution);
if (!mergeToCurrentTop(resolution))
return Resolution::ambiguous();
break;
}
case Type::GatherStars: {
Resolution resolution = frames.takeLast();
ASSERT_WITH_MESSAGE(resolution.type == Resolution::Type::Resolved || resolution.type == Resolution::Type::NotFound, "When we see Error and Ambiguous, we immediately return from this loop. So here, only Resolved and NotFound comes.");
if (!mergeToCurrentTop(resolution))
return Resolution::ambiguous();
break;
}
}
}
ASSERT(frames.size() == 1);
if (frames[0].type == Resolution::Type::Resolved)
cacheResolutionForQuery(root, frames[0]);
return frames[0];
}
auto JSModuleRecord::resolveExport(ExecState* exec, const Identifier& exportName) -> Resolution
{
if (Optional<Resolution> cachedResolution = tryGetCachedResolution(exportName.impl()))
return *cachedResolution;
return resolveExportImpl(exec, ResolveQuery(this, exportName.impl()));
}
static void getExportedNames(ExecState* exec, JSModuleRecord* root, IdentifierSet& exportedNames)
{
HashSet<JSModuleRecord*> exportStarSet;
Vector<JSModuleRecord*, 8> pendingModules;
pendingModules.append(root);
while (!pendingModules.isEmpty()) {
JSModuleRecord* moduleRecord = pendingModules.takeLast();
if (exportStarSet.contains(moduleRecord))
continue;
exportStarSet.add(moduleRecord);
for (const auto& pair : moduleRecord->exportEntries()) {
const JSModuleRecord::ExportEntry& exportEntry = pair.value;
if (moduleRecord == root || exec->propertyNames().defaultKeyword != exportEntry.exportName)
exportedNames.add(exportEntry.exportName.impl());
}
for (const auto& starModuleName : moduleRecord->starExportEntries()) {
JSModuleRecord* requestedModuleRecord = moduleRecord->hostResolveImportedModule(exec, Identifier::fromUid(exec, starModuleName.get()));
pendingModules.append(requestedModuleRecord);
}
}
}
JSModuleNamespaceObject* JSModuleRecord::getModuleNamespace(ExecState* exec)
{
if (m_moduleNamespaceObject)
return m_moduleNamespaceObject.get();
JSGlobalObject* globalObject = exec->lexicalGlobalObject();
IdentifierSet exportedNames;
getExportedNames(exec, this, exportedNames);
IdentifierSet unambiguousNames;
for (auto& name : exportedNames) {
const JSModuleRecord::Resolution resolution = resolveExport(exec, Identifier::fromUid(exec, name.get()));
switch (resolution.type) {
case Resolution::Type::NotFound:
throwSyntaxError(exec, makeString("Exported binding name '", String(name.get()), "' is not found."));
return nullptr;
case Resolution::Type::Error:
throwSyntaxError(exec, makeString("Exported binding name 'default' cannot be resolved by star export entries."));
return nullptr;
case Resolution::Type::Ambiguous:
break;
case Resolution::Type::Resolved:
unambiguousNames.add(name);
break;
}
}
m_moduleNamespaceObject.set(exec->vm(), this, JSModuleNamespaceObject::create(exec, globalObject, globalObject->moduleNamespaceObjectStructure(), this, unambiguousNames));
return m_moduleNamespaceObject.get();
}
void JSModuleRecord::link(ExecState* exec)
{
ModuleProgramExecutable* executable = ModuleProgramExecutable::create(exec, sourceCode());
if (!executable) {
throwSyntaxError(exec);
return;
}
m_moduleProgramExecutable.set(exec->vm(), this, executable);
instantiateDeclarations(exec, executable);
}
void JSModuleRecord::instantiateDeclarations(ExecState* exec, ModuleProgramExecutable* moduleProgramExecutable)
{
SymbolTable* symbolTable = moduleProgramExecutable->moduleEnvironmentSymbolTable();
JSModuleEnvironment* moduleEnvironment = JSModuleEnvironment::create(exec->vm(), exec->lexicalGlobalObject(), exec->lexicalGlobalObject(), symbolTable, jsTDZValue(), this);
VM& vm = exec->vm();
for (const auto& pair : m_exportEntries) {
const ExportEntry& exportEntry = pair.value;
if (exportEntry.type == JSModuleRecord::ExportEntry::Type::Indirect) {
Resolution resolution = resolveExport(exec, exportEntry.exportName);
switch (resolution.type) {
case Resolution::Type::NotFound:
throwSyntaxError(exec, makeString("Indirectly exported binding name '", String(exportEntry.exportName.impl()), "' is not found."));
return;
case Resolution::Type::Ambiguous:
throwSyntaxError(exec, makeString("Indirectly exported binding name '", String(exportEntry.exportName.impl()), "' cannot be resolved due to ambiguous multiple bindings."));
return;
case Resolution::Type::Error:
throwSyntaxError(exec, makeString("Indirectly exported binding name 'default' cannot be resolved by star export entries."));
return;
case Resolution::Type::Resolved:
break;
}
}
}
for (const auto& pair : m_importEntries) {
const ImportEntry& importEntry = pair.value;
JSModuleRecord* importedModule = hostResolveImportedModule(exec, importEntry.moduleRequest);
if (importEntry.isNamespace(vm)) {
JSModuleNamespaceObject* namespaceObject = importedModule->getModuleNamespace(exec);
if (exec->hadException())
return;
bool putResult = false;
symbolTablePutTouchWatchpointSet(moduleEnvironment, exec, importEntry.localName, namespaceObject, false, true, putResult);
} else {
Resolution resolution = importedModule->resolveExport(exec, importEntry.importName);
switch (resolution.type) {
case Resolution::Type::NotFound:
throwSyntaxError(exec, makeString("Importing binding name '", String(importEntry.importName.impl()), "' is not found."));
return;
case Resolution::Type::Ambiguous:
throwSyntaxError(exec, makeString("Importing binding name '", String(importEntry.importName.impl()), "' cannot be resolved due to ambiguous multiple bindings."));
return;
case Resolution::Type::Error:
throwSyntaxError(exec, makeString("Importing binding name 'default' cannot be resolved by star export entries."));
return;
case Resolution::Type::Resolved:
break;
}
}
}
for (const auto& variable : m_declaredVariables) {
SymbolTableEntry entry = symbolTable->get(variable.key.get());
VarOffset offset = entry.varOffset();
if (!offset.isStack()) {
bool putResult = false;
symbolTablePutTouchWatchpointSet(moduleEnvironment, exec, Identifier::fromUid(exec, variable.key.get()), jsUndefined(), false, true, putResult);
}
}
UnlinkedModuleProgramCodeBlock* unlinkedCodeBlock = moduleProgramExecutable->unlinkedModuleProgramCodeBlock();
for (size_t i = 0, numberOfFunctions = unlinkedCodeBlock->numberOfFunctionDecls(); i < numberOfFunctions; ++i) {
UnlinkedFunctionExecutable* unlinkedFunctionExecutable = unlinkedCodeBlock->functionDecl(i);
SymbolTableEntry entry = symbolTable->get(unlinkedFunctionExecutable->name().impl());
VarOffset offset = entry.varOffset();
if (!offset.isStack()) {
ASSERT(!unlinkedFunctionExecutable->name().isEmpty());
if (vm.typeProfiler() || vm.controlFlowProfiler()) {
vm.functionHasExecutedCache()->insertUnexecutedRange(moduleProgramExecutable->sourceID(),
unlinkedFunctionExecutable->typeProfilingStartOffset(),
unlinkedFunctionExecutable->typeProfilingEndOffset());
}
JSFunction* function = JSFunction::create(vm, unlinkedFunctionExecutable->link(vm, moduleProgramExecutable->source()), moduleEnvironment);
bool putResult = false;
symbolTablePutTouchWatchpointSet(moduleEnvironment, exec, unlinkedFunctionExecutable->name(), function, false, true, putResult);
}
}
m_moduleEnvironment.set(vm, this, moduleEnvironment);
}
JSValue JSModuleRecord::evaluate(ExecState* exec)
{
if (!m_moduleProgramExecutable)
return jsUndefined();
JSValue result = exec->interpreter()->execute(m_moduleProgramExecutable.get(), exec, m_moduleEnvironment.get());
m_moduleProgramExecutable.clear();
return result;
}
static String printableName(const RefPtr<UniquedStringImpl>& uid)
{
if (uid->isSymbol())
return uid.get();
return WTF::makeString("'", String(uid.get()), "'");
}
static String printableName(const Identifier& ident)
{
return printableName(ident.impl());
}
void JSModuleRecord::dump()
{
dataLog("\nAnalyzing ModuleRecord key(", printableName(m_moduleKey), ")\n");
dataLog(" Dependencies: ", m_requestedModules.size(), " modules\n");
for (const auto& moduleName : m_requestedModules)
dataLog(" module(", printableName(moduleName), ")\n");
dataLog(" Import: ", m_importEntries.size(), " entries\n");
for (const auto& pair : m_importEntries) {
const ImportEntry& importEntry = pair.value;
dataLog(" import(", printableName(importEntry.importName), "), local(", printableName(importEntry.localName), "), module(", printableName(importEntry.moduleRequest), ")\n");
}
dataLog(" Export: ", m_exportEntries.size(), " entries\n");
for (const auto& pair : m_exportEntries) {
const ExportEntry& exportEntry = pair.value;
switch (exportEntry.type) {
case ExportEntry::Type::Local:
dataLog(" [Local] ", "export(", printableName(exportEntry.exportName), "), local(", printableName(exportEntry.localName), ")\n");
break;
case ExportEntry::Type::Indirect:
dataLog(" [Indirect] ", "export(", printableName(exportEntry.exportName), "), import(", printableName(exportEntry.importName), "), module(", printableName(exportEntry.moduleName), ")\n");
break;
}
}
for (const auto& moduleName : m_starExportEntries)
dataLog(" [Star] module(", printableName(moduleName.get()), ")\n");
}
}