diff options
30 files changed, 905 insertions, 73 deletions
diff --git a/src/qml/compiler/qv4bytecodegenerator_p.h b/src/qml/compiler/qv4bytecodegenerator_p.h index dca5771356..4f3dc27acc 100644 --- a/src/qml/compiler/qv4bytecodegenerator_p.h +++ b/src/qml/compiler/qv4bytecodegenerator_p.h @@ -163,8 +163,11 @@ public: Q_REQUIRED_RESULT Jump jump() { +QT_WARNING_PUSH +QT_WARNING_DISABLE_GCC("-Wmaybe-uninitialized") // broken gcc warns about Instruction::Debug() Instruction::Jump data; return addJumpInstruction(data); +QT_WARNING_POP } Q_REQUIRED_RESULT Jump jumpTrue() diff --git a/src/qml/compiler/qv4bytecodehandler.cpp b/src/qml/compiler/qv4bytecodehandler.cpp index 23f7051718..d3c5582b28 100644 --- a/src/qml/compiler/qv4bytecodehandler.cpp +++ b/src/qml/compiler/qv4bytecodehandler.cpp @@ -161,6 +161,9 @@ std::vector<int> ByteCodeHandler::collectLabelsInBytecode(const char *code, uint COLLECTOR_BEGIN_INSTR(MoveConst) COLLECTOR_END_INSTR(MoveConst) + COLLECTOR_BEGIN_INSTR(LoadImport) + COLLECTOR_END_INSTR(LoadImport) + COLLECTOR_BEGIN_INSTR(LoadLocal) COLLECTOR_END_INSTR(LoadLocal) diff --git a/src/qml/compiler/qv4codegen.cpp b/src/qml/compiler/qv4codegen.cpp index 30a73d31bd..7b059b3fb4 100644 --- a/src/qml/compiler/qv4codegen.cpp +++ b/src/qml/compiler/qv4codegen.cpp @@ -126,6 +126,49 @@ void Codegen::generateFromProgram(const QString &fileName, defineFunction(QStringLiteral("%entry"), node, nullptr, node->statements); } +void Codegen::generateFromModule(const QString &fileName, + const QString &finalUrl, + const QString &sourceCode, + ESModule *node, + Module *module) +{ + Q_ASSERT(node); + + _module = module; + _context = nullptr; + + // ### should be set on the module outside of this method + _module->fileName = fileName; + _module->finalUrl = finalUrl; + + ScanFunctions scan(this, sourceCode, ContextType::ESModule); + scan(node); + + if (hasError) + return; + + { + Compiler::Context *moduleContext = _module->contextMap.value(node); + for (const auto &entry: moduleContext->exportEntries) { + if (entry.moduleRequest.isEmpty()) { + // ### check against imported bound names + _module->localExportEntries << entry; + } else if (entry.importName == QLatin1Char('*')) { + _module->starExportEntries << entry; + } else { + _module->indirectExportEntries << entry; + } + } + _module->importEntries = moduleContext->importEntries; + } + + std::sort(_module->localExportEntries.begin(), _module->localExportEntries.end(), ExportEntry::lessThan); + std::sort(_module->starExportEntries.begin(), _module->starExportEntries.end(), ExportEntry::lessThan); + std::sort(_module->indirectExportEntries.begin(), _module->indirectExportEntries.end(), ExportEntry::lessThan); + + defineFunction(QStringLiteral("%entry"), node, nullptr, node->statements); +} + void Codegen::enterContext(Node *node) { _context = _module->contextMap.value(node); @@ -2163,13 +2206,21 @@ Codegen::Reference Codegen::referenceForName(const QString &name, bool isLhs) { Context::ResolvedName resolved = _context->resolveName(name); - if (resolved.type == Context::ResolvedName::Local || resolved.type == Context::ResolvedName::Stack) { + if (resolved.type == Context::ResolvedName::Local || resolved.type == Context::ResolvedName::Stack + || resolved.type == Context::ResolvedName::Import) { if (resolved.isArgOrEval && isLhs) // ### add correct source location throwSyntaxError(SourceLocation(), QStringLiteral("Variable name may not be eval or arguments in strict mode")); - Reference r = (resolved.type == Context::ResolvedName::Local) ? - Reference::fromScopedLocal(this, resolved.index, resolved.scope) : - Reference::fromStackSlot(this, resolved.index, true /*isLocal*/); + Reference r; + switch (resolved.type) { + case Context::ResolvedName::Local: + r = Reference::fromScopedLocal(this, resolved.index, resolved.scope); break; + case Context::ResolvedName::Stack: + r = Reference::fromStackSlot(this, resolved.index, true /*isLocal*/); break; + case Context::ResolvedName::Import: + r = Reference::fromImport(this, resolved.index); break; + default: Q_UNREACHABLE(); + } if (r.isStackSlot() && _volatileMemoryLocations.isVolatile(name)) r.isVolatile = true; r.isArgOrEval = resolved.isArgOrEval; @@ -3744,6 +3795,9 @@ Codegen::Reference &Codegen::Reference::operator =(const Reference &other) elementBase = other.elementBase; elementSubscript = other.elementSubscript; break; + case Import: + index = other.index; + break; case Const: constant = other.constant; break; @@ -3789,6 +3843,8 @@ bool Codegen::Reference::operator==(const Codegen::Reference &other) const return propertyBase == other.propertyBase && propertyNameIndex == other.propertyNameIndex; case Subscript: return elementBase == other.elementBase && elementSubscript == other.elementSubscript; + case Import: + return index == other.index; case Const: return constant == other.constant; case QmlScopeObject: @@ -4031,6 +4087,7 @@ void Codegen::Reference::storeAccumulator() const case Invalid: case Accumulator: case Const: + case Import: break; } @@ -4145,6 +4202,11 @@ QT_WARNING_POP codegen->bytecodeGenerator->addInstruction(load); } return; + case Import: { + Instruction::LoadImport load; + load.index = index; + codegen->bytecodeGenerator->addInstruction(load); + } return; case Subscript: { elementSubscript.loadInAccumulator(); Instruction::LoadElement load; diff --git a/src/qml/compiler/qv4codegen_p.h b/src/qml/compiler/qv4codegen_p.h index 92f8b0b13d..e3617254a7 100644 --- a/src/qml/compiler/qv4codegen_p.h +++ b/src/qml/compiler/qv4codegen_p.h @@ -104,6 +104,12 @@ public: Module *module, ContextType contextType = ContextType::Global); + void generateFromModule(const QString &fileName, + const QString &finalUrl, + const QString &sourceCode, + AST::ESModule *ast, + Module *module); + public: class VolatileMemoryLocationScanner; class VolatileMemoryLocations { @@ -186,6 +192,7 @@ public: Name, Member, Subscript, + Import, QmlScopeObject, QmlContextObject, LastLValue = QmlContextObject, @@ -273,6 +280,11 @@ public: r.scope = scope; return r; } + static Reference fromImport(Codegen *cg, int index) { + Reference r(cg, Import); + r.index = index; + return r; + } static Reference fromName(Codegen *cg, const QString &name) { Reference r(cg, Name); r.name = name; diff --git a/src/qml/compiler/qv4compileddata.cpp b/src/qml/compiler/qv4compileddata.cpp index 00a36cbb46..8e46ebf230 100644 --- a/src/qml/compiler/qv4compileddata.cpp +++ b/src/qml/compiler/qv4compileddata.cpp @@ -50,6 +50,7 @@ #include <private/qqmltypeloader_p.h> #include <private/qqmlengine_p.h> #include <private/qv4vme_moth_p.h> +#include <private/qv4module_p.h> #include "qv4compilationunitmapper_p.h" #include <QQmlPropertyMap> #include <QDateTime> @@ -113,6 +114,9 @@ CompilationUnit::~CompilationUnit() delete [] constants; constants = nullptr; #endif + + delete [] imports; + imports = nullptr; } QString CompilationUnit::localCacheFilePath(const QUrl &url) @@ -291,6 +295,9 @@ void CompilationUnit::markObjects(QV4::MarkStack *markStack) for (uint i = 0; i < data->lookupTableSize; ++i) runtimeLookups[i].markObjects(markStack); } + + if (m_module) + m_module->mark(markStack); } IdentifierHash CompilationUnit::createNamedObjectsPerComponent(int componentObjectIndex) @@ -370,6 +377,116 @@ bool CompilationUnit::verifyChecksum(const DependentTypesHasher &dependencyHashe sizeof(data->dependencyMD5Checksum)) == 0; } +QStringList CompilationUnit::moduleRequests() const +{ + QStringList requests; + + for (uint i = 0; i < data->importEntryTableSize; ++i) { + const ImportEntry &entry = data->importEntryTable()[i]; + requests << stringAt(entry.moduleRequest); + } + + return requests; +} + +Heap::Module *CompilationUnit::instantiate(ExecutionEngine *engine) +{ + if (m_module) + return m_module; + + if (!this->engine) + linkToEngine(engine); + + m_module = engine->memoryManager->allocate<Module>(engine, this); + + for (const QString &request: moduleRequests()) { + auto dependentModuleUnit = engine->loadModule(QUrl(request), this); + if (engine->hasException) + return nullptr; + dependentModuleUnit->instantiate(engine); + } + + Scope scope(engine); + ScopedString importName(scope); + + const uint importCount = data->importEntryTableSize; + imports = new const Value *[importCount]; + memset(imports, 0, importCount * sizeof(Value *)); + for (uint i = 0; i < importCount; ++i) { + const CompiledData::ImportEntry &entry = data->importEntryTable()[i]; + auto dependentModuleUnit = engine->loadModule(QUrl(stringAt(entry.moduleRequest)), this); + importName = runtimeStrings[entry.importName]; + const Value *valuePtr = dependentModuleUnit->resolveExport(importName); + if (!valuePtr) { + QString referenceErrorMessage = QStringLiteral("Unable to resolve import reference "); + referenceErrorMessage += importName->toQString(); + engine->throwReferenceError(referenceErrorMessage, fileName(), /*### line*/1, /*### column*/1); + return nullptr; + } + imports[i] = valuePtr; + } + + return m_module; +} + +const Value *CompilationUnit::resolveExport(QV4::String *exportName) +{ + if (!m_module) + return nullptr; + + Scope scope(engine); + ScopedString localName(scope, localNameForExportName(exportName)); + if (!localName) + return nullptr; + + uint index = m_module->scope->internalClass->find(localName->toPropertyKey()); + if (index < UINT_MAX) + return &m_module->scope->locals[index]; + + return nullptr; +} + +Heap::String *CompilationUnit::localNameForExportName(QV4::String *exportName) const +{ + const CompiledData::ExportEntry *firstExport = data->localExportEntryTable(); + const CompiledData::ExportEntry *lastExport = data->localExportEntryTable() + data->localExportEntryTableSize; + auto matchingExport = std::lower_bound(firstExport, lastExport, exportName, [this](const CompiledData::ExportEntry &lhs, QV4::String *name) { + return stringAt(lhs.exportName) < name->toQString(); + }); + if (matchingExport == lastExport || stringAt(matchingExport->exportName) != exportName->toQString()) + return nullptr; + return runtimeStrings[matchingExport->localName]; +} + +void CompilationUnit::evaluate() +{ + if (m_moduleEvaluated) + return; + m_moduleEvaluated = true; + + for (const QString &request: moduleRequests()) { + auto dependentModuleUnit = engine->loadModule(QUrl(request), this); + if (engine->hasException) + return; + dependentModuleUnit->evaluate(); + if (engine->hasException) + return; + } + + QV4::Function *moduleFunction = runtimeFunctions[data->indexOfRootFunction]; + CppStackFrame frame; + frame.init(engine, moduleFunction, nullptr, 0); + frame.setupJSFrame(engine->jsStackTop, Primitive::undefinedValue(), m_module->scope, + Primitive::undefinedValue(), Primitive::undefinedValue()); + + frame.push(); + engine->jsStackTop += frame.requiredJSStackFrameSize(); + auto frameCleanup = qScopeGuard([&frame]() { + frame.pop(); + }); + Moth::VME::exec(&frame, engine); +} + bool CompilationUnit::loadFromDisk(const QUrl &url, const QDateTime &sourceTimeStamp, QString *errorString) { if (!QQmlFile::isLocalFile(url)) { diff --git a/src/qml/compiler/qv4compileddata_p.h b/src/qml/compiler/qv4compileddata_p.h index e7d152c7f8..e65c04ad69 100644 --- a/src/qml/compiler/qv4compileddata_p.h +++ b/src/qml/compiler/qv4compileddata_p.h @@ -88,6 +88,10 @@ struct Document; namespace QV4 { +namespace Heap { +struct Module; +}; + struct Function; class EvalISelFactory; class CompilationUnitMapper; @@ -358,6 +362,23 @@ struct Class }; static_assert(sizeof(Class) == 24, "Class structure needs to have the expected size to be binary compatible on disk when generated by host compiler and loaded by target"); +struct ExportEntry +{ + quint32_le exportName; + quint32_le moduleRequest; + quint32_le importName; + quint32_le localName; +}; +static_assert(sizeof(ExportEntry) == 16, "ExportEntry structure needs to have the expected size to be binary compatible on disk when generated by host compiler and loaded by target"); + +struct ImportEntry +{ + quint32_le moduleRequest; + quint32_le importName; + quint32_le localName; + quint32_le padding; +}; +static_assert(sizeof(ImportEntry) == 16, "ImportEntry structure needs to have the expected size to be binary compatible on disk when generated by host compiler and loaded by target"); // Qml data structures @@ -819,6 +840,14 @@ struct Unit quint32_le offsetToJSClassTable; quint32_le translationTableSize; quint32_le offsetToTranslationTable; + quint32_le localExportEntryTableSize; + quint32_le offsetToLocalExportEntryTable; + quint32_le indirectExportEntryTableSize; + quint32_le offsetToIndirectExportEntryTable; + quint32_le starExportEntryTableSize; + quint32_le offsetToStarExportEntryTable; + quint32_le importEntryTableSize; + quint32_le offsetToImportEntryTable; qint32_le indexOfRootFunction; quint32_le sourceFileIndex; quint32_le finalUrlIndex; @@ -904,9 +933,14 @@ struct Unit const TranslationData *translations() const { return reinterpret_cast<const TranslationData *>(reinterpret_cast<const char *>(this) + offsetToTranslationTable); } + + const ImportEntry *importEntryTable() const { return reinterpret_cast<const ImportEntry *>(reinterpret_cast<const char *>(this) + offsetToImportEntryTable); } + const ExportEntry *localExportEntryTable() const { return reinterpret_cast<const ExportEntry *>(reinterpret_cast<const char *>(this) + offsetToLocalExportEntryTable); } + const ExportEntry *indirectExportEntryTable() const { return reinterpret_cast<const ExportEntry *>(reinterpret_cast<const char *>(this) + offsetToIndirectExportEntryTable); } + const ExportEntry *starExportEntryTable() const { return reinterpret_cast<const ExportEntry *>(reinterpret_cast<const char *>(this) + offsetToStarExportEntryTable); } }; -static_assert(sizeof(Unit) == 200, "Unit structure needs to have the expected size to be binary compatible on disk when generated by host compiler and loaded by target"); +static_assert(sizeof(Unit) == 232, "Unit structure needs to have the expected size to be binary compatible on disk when generated by host compiler and loaded by target"); struct TypeReference { @@ -991,12 +1025,15 @@ struct Q_QML_PRIVATE_EXPORT CompilationUnitBase const Value* constants = nullptr; QV4::Value *runtimeRegularExpressions = nullptr; QV4::Heap::InternalClass **runtimeClasses = nullptr; + const Value** imports = nullptr; }; Q_STATIC_ASSERT(std::is_standard_layout<CompilationUnitBase>::value); Q_STATIC_ASSERT(offsetof(CompilationUnitBase, runtimeStrings) == 0); Q_STATIC_ASSERT(offsetof(CompilationUnitBase, constants) == sizeof(QV4::Heap::String **)); Q_STATIC_ASSERT(offsetof(CompilationUnitBase, runtimeRegularExpressions) == offsetof(CompilationUnitBase, constants) + sizeof(const Value *)); +Q_STATIC_ASSERT(offsetof(CompilationUnitBase, runtimeClasses) == offsetof(CompilationUnitBase, runtimeRegularExpressions) + sizeof(const Value *)); +Q_STATIC_ASSERT(offsetof(CompilationUnitBase, imports) == offsetof(CompilationUnitBase, runtimeClasses) + sizeof(const Value *)); struct Q_QML_PRIVATE_EXPORT CompilationUnit final : public CompilationUnitBase { @@ -1122,6 +1159,12 @@ public: FunctionIterator objectFunctionsEnd(const Object *object) const { return FunctionIterator(data, object, object->nFunctions); } // --- + QStringList moduleRequests() const; + Heap::Module *instantiate(ExecutionEngine *engine); + const Value *resolveExport(QV4::String *exportName); + Heap::String *localNameForExportName(QV4::String *exportName) const; + void evaluate(); + QV4::Function *linkToEngine(QV4::ExecutionEngine *engine); void unlink(); @@ -1149,6 +1192,9 @@ private: Q_NEVER_INLINE IdentifierHash createNamedObjectsPerComponent(int componentObjectIndex); + Heap::Module *m_module = nullptr; + bool m_moduleEvaluated = false; + public: #if defined(V4_BOOTSTRAP) bool saveToDisk(const QString &outputFileName, QString *errorString); diff --git a/src/qml/compiler/qv4compiler.cpp b/src/qml/compiler/qv4compiler.cpp index f301f867c1..4e902eca65 100644 --- a/src/qml/compiler/qv4compiler.cpp +++ b/src/qml/compiler/qv4compiler.cpp @@ -241,6 +241,24 @@ QV4::CompiledData::Unit *QV4::Compiler::JSUnitGenerator::generateUnit(GeneratorO for (int i = 0; i < c->locals.size(); ++i) registerString(c->locals.at(i)); } + { + const auto registerExportEntry = [this](const Compiler::ExportEntry &entry) { + registerString(entry.exportName); + registerString(entry.moduleRequest); + registerString(entry.importName); + registerString(entry.localName); + }; + std::for_each(module->localExportEntries.constBegin(), module->localExportEntries.constEnd(), registerExportEntry); + std::for_each(module->indirectExportEntries.constBegin(), module->indirectExportEntries.constEnd(), registerExportEntry); + std::for_each(module->starExportEntries.constBegin(), module->starExportEntries.constEnd(), registerExportEntry); + } + { + for (const auto &entry: module->importEntries) { + registerString(entry.moduleRequest); + registerString(entry.importName); + registerString(entry.localName); + } + } Q_ALLOCA_VAR(quint32_le, blockClassAndFunctionOffsets, (module->functions.size() + module->classes.size() + module->blocks.size()) * sizeof(quint32_le)); uint jsClassDataOffset = 0; @@ -304,8 +322,35 @@ QV4::CompiledData::Unit *QV4::Compiler::JSUnitGenerator::generateUnit(GeneratorO jsClassOffsetTable[i] = jsClassDataOffset + jsClassOffsets.at(i); } + memcpy(dataPtr + unit->offsetToTranslationTable, translations.constData(), translations.count() * sizeof(CompiledData::TranslationData)); + { + const auto populateExportEntryTable = [this, dataPtr](const QVector<Compiler::ExportEntry> &table, quint32_le offset) { + CompiledData::ExportEntry *entryToWrite = reinterpret_cast<CompiledData::ExportEntry *>(dataPtr + offset); + for (const Compiler::ExportEntry &entry: table) { + entryToWrite->exportName = getStringId(entry.exportName); + entryToWrite->moduleRequest = getStringId(entry.moduleRequest); + entryToWrite->importName = getStringId(entry.importName); + entryToWrite->localName = getStringId(entry.localName); + entryToWrite++; + } + }; + populateExportEntryTable(module->localExportEntries, unit->offsetToLocalExportEntryTable); + populateExportEntryTable(module->indirectExportEntries, unit->offsetToIndirectExportEntryTable); + populateExportEntryTable(module->starExportEntries, unit->offsetToStarExportEntryTable); + } + + { + CompiledData::ImportEntry *entryToWrite = reinterpret_cast<CompiledData::ImportEntry *>(dataPtr + unit->offsetToImportEntryTable); + for (const Compiler::ImportEntry &entry: module->importEntries) { + entryToWrite->moduleRequest = getStringId(entry.moduleRequest); + entryToWrite->importName = getStringId(entry.importName); + entryToWrite->localName = getStringId(entry.localName); + entryToWrite++; + } + } + // write strings and string table if (option == GenerateWithStringTable) stringTable.serialize(unit); @@ -547,8 +592,23 @@ QV4::CompiledData::Unit QV4::Compiler::JSUnitGenerator::generateHeader(QV4::Comp nextOffset = (nextOffset + 7) & ~quint32(0x7); - quint32 functionSize = 0; + const auto reserveExportTable = [&nextOffset](int count, quint32_le *tableSizePtr, quint32_le *offsetPtr) { + *tableSizePtr = count; + *offsetPtr = nextOffset; + nextOffset += count * sizeof(CompiledData::ExportEntry); + nextOffset = (nextOffset + 7) & ~quint32(0x7); + }; + reserveExportTable(module->localExportEntries.count(), &unit.localExportEntryTableSize, &unit.offsetToLocalExportEntryTable); + reserveExportTable(module->indirectExportEntries.count(), &unit.indirectExportEntryTableSize, &unit.offsetToIndirectExportEntryTable); + reserveExportTable(module->starExportEntries.count(), &unit.starExportEntryTableSize, &unit.offsetToStarExportEntryTable); + + unit.importEntryTableSize = module->importEntries.count(); + unit.offsetToImportEntryTable = nextOffset; + nextOffset += unit.importEntryTableSize * sizeof(CompiledData::ImportEntry); + nextOffset = (nextOffset + 7) & ~quint32(0x7); + + quint32 functionSize = 0; for (int i = 0; i < module->functions.size(); ++i) { Context *f = module->functions.at(i); blockAndFunctionOffsets[i] = nextOffset; diff --git a/src/qml/compiler/qv4compilercontext.cpp b/src/qml/compiler/qv4compilercontext.cpp index 9dfe3be7e0..77ac703ee3 100644 --- a/src/qml/compiler/qv4compilercontext.cpp +++ b/src/qml/compiler/qv4compilercontext.cpp @@ -156,6 +156,17 @@ Context::ResolvedName Context::resolveName(const QString &name) c = c->parent; } + if (c && c->contextType == ContextType::ESModule) { + for (int i = 0; i < c->importEntries.count(); ++i) { + if (c->importEntries.at(i).localName == name) { + result.index = i; + result.type = ResolvedName::Import; + result.isConst = true; + return result; + } + } + } + // ### can we relax the restrictions here? if (contextType == ContextType::Eval || c->contextType == ContextType::Binding) return result; @@ -219,7 +230,7 @@ void Context::emitBlockHeader(Codegen *codegen) } } - if (contextType == ContextType::Function || contextType == ContextType::Binding) { + if (contextType == ContextType::Function || contextType == ContextType::Binding || contextType == ContextType::ESModule) { for (Context::MemberMap::iterator it = members.begin(), end = members.end(); it != end; ++it) { if (it->canEscape && it->type == Context::ThisFunctionName) { // move the function from the stack to the call context @@ -285,6 +296,7 @@ void Context::setupFunctionIndices(Moth::BytecodeGenerator *bytecodeGenerator) registerOffset = bytecodeGenerator->currentRegister(); switch (contextType) { + case ContextType::ESModule: case ContextType::Block: case ContextType::Function: case ContextType::Binding: { diff --git a/src/qml/compiler/qv4compilercontext_p.h b/src/qml/compiler/qv4compilercontext_p.h index 52c3fc5b05..6a54be2aca 100644 --- a/src/qml/compiler/qv4compilercontext_p.h +++ b/src/qml/compiler/qv4compilercontext_p.h @@ -74,7 +74,8 @@ enum class ContextType { // * function declarations are moved to the return address when encountered // * return statements are allowed everywhere (like in FunctionCode) // * variable declarations are treated as true locals (like in FunctionCode) - Block + Block, + ESModule }; struct Context; @@ -97,6 +98,24 @@ struct Class { QVector<Method> methods; }; +struct ExportEntry +{ + QString exportName; + QString moduleRequest; + QString importName; + QString localName; + + static bool lessThan(const ExportEntry &lhs, const ExportEntry &rhs) + { return lhs.exportName < rhs.exportName; } +}; + +struct ImportEntry +{ + QString moduleRequest; + QString importName; + QString localName; +}; + struct Module { Module(bool debugMode) : debugMode(debugMode) @@ -117,6 +136,10 @@ struct Module { QDateTime sourceTimeStamp; uint unitFlags = 0; // flags merged into CompiledData::Unit::flags bool debugMode = false; + QVector<ExportEntry> localExportEntries; + QVector<ExportEntry> indirectExportEntries; + QVector<ExportEntry> starExportEntries; + QVector<ImportEntry> importEntries; }; @@ -153,6 +176,8 @@ struct Context { QQmlJS::AST::FormalParameterList *formals = nullptr; QStringList arguments; QStringList locals; + QVector<ImportEntry> importEntries; + QVector<ExportEntry> exportEntries; QVector<Context *> nestedContexts; ControlFlow *controlFlow = nullptr; @@ -289,7 +314,8 @@ struct Context { Unresolved, Global, Local, - Stack + Stack, + Import }; Type type = Unresolved; bool isArgOrEval = false; diff --git a/src/qml/compiler/qv4compilerscanfunctions.cpp b/src/qml/compiler/qv4compilerscanfunctions.cpp index 9be55c6ad0..63ed748048 100644 --- a/src/qml/compiler/qv4compilerscanfunctions.cpp +++ b/src/qml/compiler/qv4compilerscanfunctions.cpp @@ -149,6 +149,120 @@ void ScanFunctions::endVisit(Program *) leaveEnvironment(); } +bool ScanFunctions::visit(ESModule *ast) +{ + enterEnvironment(ast, defaultProgramType, QStringLiteral("%ModuleCode")); + _context->isStrict = true; + return true; +} + +void ScanFunctions::endVisit(ESModule *) +{ + leaveEnvironment(); +} + +bool ScanFunctions::visit(ExportDeclaration *declaration) +{ + QString module; + if (declaration->fromClause) + module = declaration->fromClause->moduleSpecifier.toString(); + + if (declaration->exportAll) { + Compiler::ExportEntry entry; + entry.moduleRequest = declaration->fromClause->moduleSpecifier.toString(); + entry.importName = QStringLiteral("*"); + _context->exportEntries << entry; + } else if (declaration->exportClause) { + for (ExportsList *it = declaration->exportClause->exportsList; it; it = it->next) { + ExportSpecifier *spec = it->exportSpecifier; + Compiler::ExportEntry entry; + if (module.isEmpty()) + entry.localName = spec->identifier.toString(); + else + entry.importName = spec->identifier.toString(); + + entry.moduleRequest = module; + entry.exportName = spec->exportedIdentifier.toString(); + + _context->exportEntries << entry; + } + } else if (auto *vstmt = AST::cast<AST::VariableStatement*>(declaration->variableStatementOrDeclaration)) { + QStringList boundNames; + for (VariableDeclarationList *it = vstmt->declarations; it; it = it->next) { + if (!it->declaration) + continue; + it->declaration->boundNames(&boundNames); + } + for (const QString &name: boundNames) { + Compiler::ExportEntry entry; + entry.localName = name; + entry.exportName = name; + _context->exportEntries << entry; + } + } else if (auto *classDecl = AST::cast<AST::ClassDeclaration*>(declaration->variableStatementOrDeclaration)) { + QString name = classDecl->name.toString(); + Compiler::ExportEntry entry; + entry.localName = name; + entry.exportName = name; + _context->exportEntries << entry; + } else if (auto *fdef = declaration->variableStatementOrDeclaration->asFunctionDefinition()) { + QString name = fdef->name.toString(); + Compiler::ExportEntry entry; + entry.localName = name; + entry.exportName = name; + _context->exportEntries << entry; + } else if (declaration->exportDefault) { + Compiler::ExportEntry entry; + entry.localName = QStringLiteral("*default*"); + entry.exportName = QStringLiteral("default"); + _context->exportEntries << entry; + } + + return true; // scan through potential assignment expression code, etc. +} + +bool ScanFunctions::visit(ImportDeclaration *declaration) +{ + QString module; + if (declaration->fromClause) + module = declaration->fromClause->moduleSpecifier.toString(); + + if (ImportClause *import = declaration->importClause) { + if (!import->importedDefaultBinding.isEmpty()) { + Compiler::ImportEntry entry; + entry.moduleRequest = module; + entry.importName = QStringLiteral("default"); + entry.localName = import->importedDefaultBinding.toString(); + _context->importEntries << entry; + } + + if (import->nameSpaceImport) { + Compiler::ImportEntry entry; + entry.moduleRequest = module; + entry.importName = QStringLiteral("*"); + entry.localName = import->nameSpaceImport->importedBinding.toString(); + _context->importEntries << entry; + + _cg->throwSyntaxError(import->nameSpaceImport->importedBindingToken, QStringLiteral("* imports are currently not supported.")); + return false; + } + + if (import->namedImports) { + for (ImportsList *it = import->namedImports->importsList; it; it = it->next) { + Compiler::ImportEntry entry; + entry.moduleRequest = module; + entry.localName = it->importSpecifier->importedBinding.toString(); + if (!it->importSpecifier->identifier.isEmpty()) + entry.importName = it->importSpecifier->identifier.toString(); + else + entry.importName = entry.localName; + _context->importEntries << entry; + } + } + } + return false; +} + bool ScanFunctions::visit(CallExpression *ast) { if (!_context->hasDirectEval) { @@ -529,6 +643,17 @@ void ScanFunctions::calcEscapingVariables() } } + for (Context *c : qAsConst(m->contextMap)) { + if (c->contextType != ContextType::ESModule) + continue; + for (const auto &entry: c->exportEntries) { + auto m = c->members.find(entry.localName); + if (m != c->members.end()) + m->canEscape = true; + } + break; + } + for (Context *inner : qAsConst(m->contextMap)) { for (const QString &var : qAsConst(inner->usedVariables)) { Context *c = inner; @@ -545,6 +670,10 @@ void ScanFunctions::calcEscapingVariables() if (c->parent || it->isLexicallyScoped()) { it->canEscape = true; c->requiresExecutionContext = true; + } else if (c->contextType == ContextType::ESModule) { + // Module instantiation provides a context, but vars used from inner + // scopes need to be stored in its locals[]. + it->canEscape = true; } break; } diff --git a/src/qml/compiler/qv4compilerscanfunctions_p.h b/src/qml/compiler/qv4compilerscanfunctions_p.h index 4c273600b3..53b2336cb1 100644 --- a/src/qml/compiler/qv4compilerscanfunctions_p.h +++ b/src/qml/compiler/qv4compilerscanfunctions_p.h @@ -103,6 +103,12 @@ protected: bool visit(AST::Program *ast) override; void endVisit(AST::Program *) override; + bool visit(AST::ESModule *ast) override; + void endVisit(AST::ESModule *) override; + + bool visit(AST::ExportDeclaration *declaration) override; + bool visit(AST::ImportDeclaration *declaration) override; + bool visit(AST::CallExpression *ast) override; bool visit(AST::PatternElement *ast) override; bool visit(AST::IdentifierExpression *ast) override; diff --git a/src/qml/compiler/qv4instr_moth.cpp b/src/qml/compiler/qv4instr_moth.cpp index 8e474b3783..d186c4e7e6 100644 --- a/src/qml/compiler/qv4instr_moth.cpp +++ b/src/qml/compiler/qv4instr_moth.cpp @@ -207,6 +207,10 @@ void dumpBytecode(const char *code, int len, int nLocals, int nFormals, int /*st d << dumpRegister(destReg, nFormals) << ", " << dumpRegister(srcReg, nFormals); MOTH_END_INSTR(MoveReg) + MOTH_BEGIN_INSTR(LoadImport) + d << "i" << index; + MOTH_END_INSTR(LoadImport) + MOTH_BEGIN_INSTR(LoadConst) d << "C" << index; MOTH_END_INSTR(LoadConst) diff --git a/src/qml/compiler/qv4instr_moth_p.h b/src/qml/compiler/qv4instr_moth_p.h index ce92a31590..25a07208c2 100644 --- a/src/qml/compiler/qv4instr_moth_p.h +++ b/src/qml/compiler/qv4instr_moth_p.h @@ -76,6 +76,7 @@ QT_BEGIN_NAMESPACE #define INSTR_LoadReg(op) INSTRUCTION(op, LoadReg, 1, reg) #define INSTR_StoreReg(op) INSTRUCTION(op, StoreReg, 1, reg) #define INSTR_MoveReg(op) INSTRUCTION(op, MoveReg, 2, srcReg, destReg) +#define INSTR_LoadImport(op) INSTRUCTION(op, LoadImport, 1, index) #define INSTR_LoadLocal(op) INSTRUCTION(op, LoadLocal, 1, index) #define INSTR_StoreLocal(op) INSTRUCTION(op, StoreLocal, 1, index) #define INSTR_LoadScopedLocal(op) INSTRUCTION(op, LoadScopedLocal, 2, scope, index) @@ -210,6 +211,7 @@ QT_BEGIN_NAMESPACE F(LoadReg) \ F(StoreReg) \ F(MoveReg) \ + F(LoadImport) \ F(LoadLocal) \ F(StoreLocal) \ F(LoadScopedLocal) \ diff --git a/src/qml/jit/qv4assembler.cpp b/src/qml/jit/qv4assembler.cpp index efd226539e..3379d9a0e4 100644 --- a/src/qml/jit/qv4assembler.cpp +++ b/src/qml/jit/qv4assembler.cpp @@ -1509,6 +1509,16 @@ void JIT::Assembler::storeHeapObject(int reg) pasm()->storeHeapObject(PlatformAssembler::ReturnValueRegisterValue, regAddr(reg)); } +void JIT::Assembler::loadImport(int index) +{ + Address addr = pasm()->loadCompilationUnitPtr(PlatformAssembler::ScratchRegister); + addr.offset = offsetof(QV4::CompiledData::CompilationUnitBase, imports); + pasm()->loadPtr(addr, PlatformAssembler::ScratchRegister); + addr.offset = index * int(sizeof(QV4::Value*)); + pasm()->loadPtr(addr, PlatformAssembler::ScratchRegister); + pasm()->loadAccumulator(Address(PlatformAssembler::ScratchRegister)); +} + void Assembler::toNumber() { pasm()->toNumber(); diff --git a/src/qml/jit/qv4assembler_p.h b/src/qml/jit/qv4assembler_p.h index 2cf59f53ee..1379c72f9a 100644 --- a/src/qml/jit/qv4assembler_p.h +++ b/src/qml/jit/qv4assembler_p.h @@ -97,6 +97,7 @@ public: void loadString(int stringId); void loadValue(ReturnedValue value); void storeHeapObject(int reg); + void loadImport(int index); // numeric ops void unot(); diff --git a/src/qml/jit/qv4baselinejit.cpp b/src/qml/jit/qv4baselinejit.cpp index 7200e44f0c..bf442de741 100644 --- a/src/qml/jit/qv4baselinejit.cpp +++ b/src/qml/jit/qv4baselinejit.cpp @@ -140,6 +140,11 @@ void BaselineJIT::generate_MoveReg(int srcReg, int destReg) as->moveReg(srcReg, destReg); } +void BaselineJIT::generate_LoadImport(int index) +{ + as->loadImport(index); +} + void BaselineJIT::generate_LoadLocal(int index) { as->loadLocal(index); diff --git a/src/qml/jit/qv4baselinejit_p.h b/src/qml/jit/qv4baselinejit_p.h index d96fd6ea6a..71b9dda9b9 100644 --- a/src/qml/jit/qv4baselinejit_p.h +++ b/src/qml/jit/qv4baselinejit_p.h @@ -87,6 +87,7 @@ public: void generate_LoadReg(int reg) override; void generate_StoreReg(int reg) override; void generate_MoveReg(int srcReg, int destReg) override; + void generate_LoadImport(int index) override; void generate_LoadLocal(int index) override; void generate_StoreLocal(int index) override; void generate_LoadScopedLocal(int scope, int index) override; diff --git a/src/qml/jsruntime/jsruntime.pri b/src/qml/jsruntime/jsruntime.pri index ec5803b2df..c42c2d48c8 100644 --- a/src/qml/jsruntime/jsruntime.pri +++ b/src/qml/jsruntime/jsruntime.pri @@ -55,7 +55,8 @@ SOURCES += \ $$PWD/qv4vme_moth.cpp \ $$PWD/qv4mapobject.cpp \ $$PWD/qv4mapiterator.cpp \ - $$PWD/qv4estable.cpp + $$PWD/qv4estable.cpp \ + $$PWD/qv4module.cpp qtConfig(qml-debug): SOURCES += $$PWD/qv4profiling.cpp @@ -123,7 +124,8 @@ HEADERS += \ $$PWD/qv4mapobject_p.h \ $$PWD/qv4mapiterator_p.h \ $$PWD/qv4estable_p.h \ - $$PWD/qv4vtable_p.h + $$PWD/qv4vtable_p.h \ + $$PWD/qv4module_p.h qtConfig(qml-sequence-object) { HEADERS += \ diff --git a/src/qml/jsruntime/qv4engine.cpp b/src/qml/jsruntime/qv4engine.cpp index fcc2feced4..69b23484a8 100644 --- a/src/qml/jsruntime/qv4engine.cpp +++ b/src/qml/jsruntime/qv4engine.cpp @@ -75,6 +75,10 @@ #include "qv4reflect_p.h" #include "qv4proxy_p.h" #include "qv4stackframe_p.h" +#include <private/qqmljsengine_p.h> +#include <private/qqmljslexer_p.h> +#include <private/qqmljsparser_p.h> +#include <private/qqmljsast_p.h> #if QT_CONFIG(qml_sequence_object) #include "qv4sequenceobject_p.h" @@ -92,12 +96,16 @@ #include <private/qqmlvaluetype_p.h> #include <private/qqmllistwrapper_p.h> #include <private/qqmllist_p.h> +#include <private/qqmltypeloader_p.h> #if QT_CONFIG(qml_locale) #include <private/qqmllocale_p.h> #endif +#include <qqmlfile.h> #include <QtCore/QTextStream> #include <QDateTime> +#include <QDir> +#include <QFileInfo> #if USE(PTHREADS) # include <pthread.h> @@ -593,6 +601,7 @@ ExecutionEngine::ExecutionEngine(QJSEngine *jsEngine) ExecutionEngine::~ExecutionEngine() { + modules.clear(); delete m_multiplyWrappedQObjects; m_multiplyWrappedQObjects = nullptr; delete identifierTable; @@ -1621,6 +1630,96 @@ ReturnedValue ExecutionEngine::global() return globalObject->asReturnedValue(); } +QQmlRefPointer<CompiledData::CompilationUnit> ExecutionEngine::compileModule(const QUrl &url) +{ + QFile f(QQmlFile::urlToLocalFileOrQrc(url)); + if (!f.open(QIODevice::ReadOnly)) + return nullptr; + + const QString sourceCode = QString::fromUtf8(f.readAll()); + f.close(); + + return compileModule(url, sourceCode); +} + + +QQmlRefPointer<CompiledData::CompilationUnit> ExecutionEngine::compileModule(const QUrl &url, const QString &sourceCode) +{ + QList<QQmlJS::DiagnosticMessage> diagnostics; + auto unit = compileModule(/*debugMode*/debugger() != nullptr, url, sourceCode, &diagnostics); + for (const QQmlJS::DiagnosticMessage &m : diagnostics) { + if (m.isError()) { + throwSyntaxError(m.message, url.toString(), m.loc.startLine, m.loc.startColumn); + return nullptr; + } else { + qWarning() << url << ':' << m.loc.startLine << ':' << m.loc.startColumn + << ": warning: " << m.message; + } + } + return unit; +} + +QQmlRefPointer<CompiledData::CompilationUnit> ExecutionEngine::compileModule(bool debugMode, const QUrl &url, const QString &sourceCode, QList<QQmlJS::DiagnosticMessage> *diagnostics) +{ + QQmlJS::Engine ee; + QQmlJS::Lexer lexer(&ee); + lexer.setCode(sourceCode, /*line*/1, /*qml mode*/false); + QQmlJS::Parser parser(&ee); + + const bool parsed = parser.parseModule(); + + if (diagnostics) + *diagnostics = parser.diagnosticMessages(); + + if (!parsed) + return nullptr; + + QQmlJS::AST::ESModule *moduleNode = QQmlJS::AST::cast<QQmlJS::AST::ESModule*>(parser.rootNode()); + if (!moduleNode) { + // if parsing was successful, and we have no module, then + // the file was empty. + if (diagnostics) + diagnostics->clear(); + return nullptr; + } + + using namespace QV4::Compiler; + Compiler::Module compilerModule(debugMode); + JSUnitGenerator jsGenerator(&compilerModule); + Codegen cg(&jsGenerator, /*strictMode*/true); + cg.generateFromModule(url.fileName(), url.toString(), sourceCode, moduleNode, &compilerModule); + auto errors = cg.errors(); + if (diagnostics) + *diagnostics << errors; + + if (!errors.isEmpty()) + return nullptr; + + return cg.generateCompilationUnit(); +} + +void ExecutionEngine::injectModule(const QQmlRefPointer<CompiledData::CompilationUnit> &moduleUnit) +{ + modules.insert(moduleUnit->finalUrl(), moduleUnit); +} + +QQmlRefPointer<CompiledData::CompilationUnit> ExecutionEngine::loadModule(const QUrl &_url, CompiledData::CompilationUnit *referrer) +{ + QUrl url = QQmlTypeLoader::normalize(_url); + if (referrer) + url = referrer->finalUrl().resolved(url); + + auto existingModule = modules.find(url); + if (existingModule != modules.end()) + return *existingModule; + + auto newModule = compileModule(url); + if (newModule) + modules.insert(url, newModule); + + return newModule; +} + // Converts a JS value to a meta-type. // data must point to a place that can store a value of the given type. // Returns true if conversion succeeded, false otherwise. diff --git a/src/qml/jsruntime/qv4engine_p.h b/src/qml/jsruntime/qv4engine_p.h index e1ea89c699..e605be9901 100644 --- a/src/qml/jsruntime/qv4engine_p.h +++ b/src/qml/jsruntime/qv4engine_p.h @@ -86,6 +86,10 @@ namespace CompiledData { struct CompilationUnit; } +namespace Heap { +struct Module; +}; + struct Function; @@ -556,6 +560,17 @@ public: QV4::ReturnedValue global(); double localTZA = 0.0; // local timezone, initialized at startup + +#ifndef V4_BOOTSTRAP + QQmlRefPointer<CompiledData::CompilationUnit> compileModule(const QUrl &url); + QQmlRefPointer<CompiledData::CompilationUnit> compileModule(const QUrl &url, const QString &sourceCode); + static QQmlRefPointer<CompiledData::CompilationUnit> compileModule(bool debugMode, const QUrl &url, const QString &sourceCode, QList<QQmlJS::DiagnosticMessage> *diagnostics); + + QHash<QUrl, QQmlRefPointer<CompiledData::CompilationUnit>> modules; + void injectModule(const QQmlRefPointer<CompiledData::CompilationUnit> &moduleUnit); + QQmlRefPointer<CompiledData::CompilationUnit> loadModule(const QUrl &_url, CompiledData::CompilationUnit *referrer = nullptr); +#endif + private: #if QT_CONFIG(qml_debug) QScopedPointer<QV4::Debugging::Debugger> m_debugger; diff --git a/src/qml/jsruntime/qv4module.cpp b/src/qml/jsruntime/qv4module.cpp new file mode 100644 index 0000000000..ed7df459b6 --- /dev/null +++ b/src/qml/jsruntime/qv4module.cpp @@ -0,0 +1,69 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + + +#include "qv4module_p.h" + +#include <private/qv4mm_p.h> +#include <private/qv4vme_moth_p.h> +#include <private/qv4context_p.h> + +using namespace QV4; + +DEFINE_OBJECT_VTABLE(Module); + +void Heap::Module::init(ExecutionEngine *engine, CompiledData::CompilationUnit *moduleUnit) +{ + Object::init(); + + // This is a back pointer and there is no need to call addref() on the unit, because the unit + // owns this object instead. + unit = moduleUnit; + + Function *moduleFunction = unit->runtimeFunctions[unit->unitData()->indexOfRootFunction]; + + const uint locals = moduleFunction->compiledFunction->nLocals; + const size_t requiredMemory = sizeof(QV4::CallContext::Data) - sizeof(Value) + sizeof(Value) * locals; + scope.set(engine, engine->memoryManager->allocManaged<QV4::CallContext>(requiredMemory, moduleFunction->internalClass)); + scope->init(); + scope->outer.set(engine, engine->rootContext()->d()); + scope->locals.size = locals; + scope->locals.alloc = locals; + scope->nArgs = 0; +} diff --git a/src/qml/jsruntime/qv4module_p.h b/src/qml/jsruntime/qv4module_p.h new file mode 100644 index 0000000000..1958258ef0 --- /dev/null +++ b/src/qml/jsruntime/qv4module_p.h @@ -0,0 +1,82 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#ifndef QV4MODULE +#define QV4MODULE + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qv4object_p.h" +#include "qv4context_p.h" + +QT_BEGIN_NAMESPACE + +namespace QV4 { + +namespace Heap { + +#define ModuleMembers(class, Member) \ + Member(class, NoMark, CompiledData::CompilationUnit *, unit) \ + Member(class, Pointer, CallContext *, scope) + +DECLARE_EXPORTED_HEAP_OBJECT(Module, Object) { + DECLARE_MARKOBJECTS(Module) + + void init(ExecutionEngine *engine, CompiledData::CompilationUnit *moduleUnit); +}; + +} + +struct Q_QML_EXPORT Module : public Object { + V4_OBJECT2(Module, Object) +}; + +} + +QT_END_NAMESPACE + +#endif // QV4MODULE diff --git a/src/qml/jsruntime/qv4vme_moth.cpp b/src/qml/jsruntime/qv4vme_moth.cpp index 53e5632eff..575cad70e4 100644 --- a/src/qml/jsruntime/qv4vme_moth.cpp +++ b/src/qml/jsruntime/qv4vme_moth.cpp @@ -503,6 +503,10 @@ QV4::ReturnedValue VME::interpret(CppStackFrame *frame, ExecutionEngine *engine, STACK_VALUE(destReg) = STACK_VALUE(srcReg); MOTH_END_INSTR(MoveReg) + MOTH_BEGIN_INSTR(LoadImport) + acc = function->compilationUnit->imports[index]->asReturnedValue(); + MOTH_END_INSTR(LoadImport) + MOTH_BEGIN_INSTR(LoadLocal) auto cc = static_cast<Heap::CallContext *>(stack[CallData::Context].m()); Q_ASSERT(cc->type != QV4::Heap::CallContext::Type_GlobalContext); diff --git a/src/qml/parser/qqmljsast.cpp b/src/qml/parser/qqmljsast.cpp index f43956814a..6b315c9140 100644 --- a/src/qml/parser/qqmljsast.cpp +++ b/src/qml/parser/qqmljsast.cpp @@ -1166,6 +1166,10 @@ void ModuleItemList::accept0(Visitor *visitor) { if (visitor->visit(this)) { for (ModuleItemList *it = this; it; it = it->next) { + // The statement list is concatenated together between module list + // items and stored in the ESModule, thus not visited from there. + if (it->item && it->item->kind == Kind_StatementList) + continue; accept(it->item, visitor); } } @@ -1173,10 +1177,29 @@ void ModuleItemList::accept0(Visitor *visitor) visitor->endVisit(this); } +StatementList *ModuleItemList::buildStatementList() const +{ + StatementList *statements = nullptr; + for (const ModuleItemList *item = this; item; item = item->next) { + AST::StatementList *listItem = AST::cast<AST::StatementList*>(item->item); + if (!listItem) + continue; + if (statements) + statements = statements->append(listItem); + else + statements = listItem; + } + if (statements) + statements = statements->finish(); + return statements; +} + + void ESModule::accept0(Visitor *visitor) { if (visitor->visit(this)) { accept(body, visitor); + accept(statements, visitor); } visitor->endVisit(this); diff --git a/src/qml/parser/qqmljsast_p.h b/src/qml/parser/qqmljsast_p.h index 4d161e9e51..0729c99931 100644 --- a/src/qml/parser/qqmljsast_p.h +++ b/src/qml/parser/qqmljsast_p.h @@ -2753,6 +2753,8 @@ public: return head; } + StatementList *buildStatementList() const; + void accept0(Visitor *visitor) override; SourceLocation firstSourceLocation() const override @@ -2773,7 +2775,11 @@ public: ESModule(ModuleItemList *body) : body(body) - { kind = K; } + { + kind = K; + if (body) + statements = body->buildStatementList(); + } void accept0(Visitor *visitor) override; @@ -2785,6 +2791,7 @@ public: // attributes ModuleItemList *body; + StatementList *statements = nullptr; }; class QML_PARSER_EXPORT DebuggerStatement: public Statement diff --git a/tests/auto/qml/ecmascripttests/TestExpectations b/tests/auto/qml/ecmascripttests/TestExpectations index 038aa8a09c..f7b9065c15 100644 --- a/tests/auto/qml/ecmascripttests/TestExpectations +++ b/tests/auto/qml/ecmascripttests/TestExpectations @@ -2171,11 +2171,8 @@ language/literals/regexp/u-surrogate-pairs-atom-escape-decimal.js fails language/literals/regexp/u-surrogate-pairs.js fails language/literals/regexp/u-unicode-esc.js fails language/literals/regexp/y-assertion-start.js fails -language/module-code/eval-export-cls-semi.js strictFails -language/module-code/eval-export-dflt-cls-anon-semi.js strictFails language/module-code/eval-export-dflt-cls-anon.js strictFails language/module-code/eval-export-dflt-cls-name-meth.js strictFails -language/module-code/eval-export-dflt-cls-named-semi.js strictFails language/module-code/eval-export-dflt-cls-named.js strictFails language/module-code/eval-export-dflt-expr-cls-anon.js strictFails language/module-code/eval-export-dflt-expr-cls-name-meth.js strictFails @@ -2185,42 +2182,36 @@ language/module-code/eval-export-dflt-expr-fn-named.js strictFails language/module-code/eval-export-dflt-expr-gen-anon.js strictFails language/module-code/eval-export-dflt-expr-gen-named.js strictFails language/module-code/eval-export-dflt-expr-in.js strictFails -language/module-code/eval-export-dflt-fun-anon-semi.js strictFails -language/module-code/eval-export-dflt-fun-named-semi.js strictFails -language/module-code/eval-export-dflt-gen-anon-semi.js strictFails -language/module-code/eval-export-dflt-gen-named-semi.js strictFails -language/module-code/eval-export-fun-semi.js strictFails -language/module-code/eval-export-gen-semi.js strictFails +language/module-code/eval-export-dflt-expr-err-eval.js strictFails +language/module-code/eval-export-dflt-expr-err-get-value.js strictFails language/module-code/eval-gtbndng-indirect-trlng-comma.js strictFails language/module-code/eval-gtbndng-indirect-update-as.js strictFails language/module-code/eval-gtbndng-indirect-update-dflt.js strictFails -language/module-code/eval-gtbndng-indirect-update.js strictFails language/module-code/eval-rqstd-once.js strictFails language/module-code/eval-rqstd-order.js strictFails language/module-code/eval-self-once.js strictFails -language/module-code/eval-this.js strictFails language/module-code/instn-iee-bndng-cls.js strictFails language/module-code/instn-iee-bndng-const.js strictFails language/module-code/instn-iee-bndng-fun.js strictFails language/module-code/instn-iee-bndng-gen.js strictFails language/module-code/instn-iee-bndng-let.js strictFails language/module-code/instn-iee-bndng-var.js strictFails -language/module-code/instn-iee-iee-cycle.js strictFails language/module-code/instn-iee-star-cycle.js strictFails language/module-code/instn-iee-trlng-comma.js strictFails +language/module-code/instn-iee-err-ambiguous-as.js strictFails +language/module-code/instn-iee-err-ambiguous.js strictFails +language/module-code/instn-iee-err-circular-as.js strictFails +language/module-code/instn-iee-err-circular.js strictFails +language/module-code/instn-iee-err-dflt-thru-star-as.js strictFails +language/module-code/instn-iee-err-dflt-thru-star.js strictFails +language/module-code/instn-iee-err-not-found-as.js strictFails +language/module-code/instn-iee-err-not-found.js strictFails language/module-code/instn-local-bndng-cls.js strictFails language/module-code/instn-local-bndng-const.js strictFails language/module-code/instn-local-bndng-export-cls.js strictFails language/module-code/instn-local-bndng-export-const.js strictFails -language/module-code/instn-local-bndng-export-fun.js strictFails -language/module-code/instn-local-bndng-export-gen.js strictFails language/module-code/instn-local-bndng-export-let.js strictFails -language/module-code/instn-local-bndng-export-var.js strictFails -language/module-code/instn-local-bndng-for.js strictFails -language/module-code/instn-local-bndng-fun.js strictFails -language/module-code/instn-local-bndng-gen.js strictFails language/module-code/instn-local-bndng-let.js strictFails -language/module-code/instn-local-bndng-var.js strictFails language/module-code/instn-named-bndng-cls.js strictFails language/module-code/instn-named-bndng-const.js strictFails language/module-code/instn-named-bndng-dflt-cls.js strictFails @@ -2231,11 +2222,7 @@ language/module-code/instn-named-bndng-dflt-gen-anon.js strictFails language/module-code/instn-named-bndng-dflt-gen-named.js strictFails language/module-code/instn-named-bndng-dflt-named.js strictFails language/module-code/instn-named-bndng-dflt-star.js strictFails -language/module-code/instn-named-bndng-fun.js strictFails -language/module-code/instn-named-bndng-gen.js strictFails language/module-code/instn-named-bndng-let.js strictFails -language/module-code/instn-named-bndng-trlng-comma.js strictFails -language/module-code/instn-named-bndng-var.js strictFails language/module-code/instn-named-id-name.js strictFails language/module-code/instn-named-iee-cycle.js strictFails language/module-code/instn-named-star-cycle.js strictFails @@ -2252,7 +2239,6 @@ language/module-code/instn-star-props-dflt-keep-local.js strictFails language/module-code/instn-star-props-dflt-skip.js strictFails language/module-code/instn-star-props-nrml.js strictFails language/module-code/instn-star-star-cycle.js strictFails -language/module-code/instn-uniq-env-rec.js strictFails language/module-code/namespace/Symbol.iterator.js strictFails language/module-code/namespace/Symbol.toStringTag.js strictFails language/module-code/namespace/internals/define-own-property.js strictFails @@ -2287,7 +2273,6 @@ language/module-code/namespace/internals/prevent-extensions.js strictFails language/module-code/namespace/internals/set-prototype-of-null.js strictFails language/module-code/namespace/internals/set-prototype-of.js strictFails language/module-code/namespace/internals/set.js strictFails -language/module-code/parse-export-empty.js strictFails language/statements/async-function/cptn-decl.js fails language/statements/async-function/declaration-returns-promise.js fails language/statements/async-function/evaluation-body.js fails diff --git a/tests/auto/qml/ecmascripttests/qjstest/test262runner.cpp b/tests/auto/qml/ecmascripttests/qjstest/test262runner.cpp index 4fa7137820..a48b70668d 100644 --- a/tests/auto/qml/ecmascripttests/qjstest/test262runner.cpp +++ b/tests/auto/qml/ecmascripttests/qjstest/test262runner.cpp @@ -441,27 +441,59 @@ void Test262Runner::writeTestExpectations() } -static bool executeTest(const QByteArray &data) +static bool executeTest(const QByteArray &data, bool runAsModule = false, const QString &testCasePath = QString(), const QByteArray &harnessForModules = QByteArray()) { QString testData = QString::fromUtf8(data); QV4::ExecutionEngine vm; QV4::Scope scope(&vm); - QV4::ScopedContext ctx(scope, vm.rootContext()); QV4::GlobalExtensions::init(vm.globalObject, QJSEngine::ConsoleExtension | QJSEngine::GarbageCollectionExtension); - QV4::Script script(ctx, QV4::Compiler::ContextType::Global, testData, QString()); - script.parse(); + if (runAsModule) { + const QUrl rootModuleUrl = QUrl::fromLocalFile(testCasePath); + // inject all modules with the harness + QVector<QUrl> modulesToLoad = { rootModuleUrl }; + while (!modulesToLoad.isEmpty()) { + QUrl url = modulesToLoad.takeFirst(); + QQmlRefPointer<QV4::CompiledData::CompilationUnit> module; + + QFile f(url.toLocalFile()); + if (f.open(QIODevice::ReadOnly)) { + QByteArray content = harnessForModules + f.readAll(); + module = vm.compileModule(url, QString::fromUtf8(content)); + if (vm.hasException) + break; + vm.injectModule(module); + } else { + vm.throwError(QStringLiteral("Could not load module")); + break; + } + + for (const QString &request: module->moduleRequests()) { + const QUrl absoluteRequest = module->finalUrl().resolved(QUrl(request)); + if (!vm.modules.contains(absoluteRequest)) + modulesToLoad << absoluteRequest; + } + } - QV4::ScopedValue result(scope); - if (!scope.engine->hasException) - result = script.run(); + if (!vm.hasException) { + if (auto rootModuleUnit = vm.loadModule(rootModuleUrl)) { + if (rootModuleUnit->instantiate(&vm)) + rootModuleUnit->evaluate(); + } + } + } else { + QV4::ScopedContext ctx(scope, vm.rootContext()); - if (scope.engine->hasException) - return false; - return true; + QV4::Script script(ctx, QV4::Compiler::ContextType::Global, testData); + script.parse(); + + if (!vm.hasException) + script.run(); + } + return !vm.hasException; } class SingleTest : public QRunnable @@ -498,11 +530,9 @@ void SingleTest::run() data.sloppyResult = TestCase::Skipped; } if (data.runInStrictMode) { - QByteArray c = data.content; - // modules are strict by default. - if (!data.runAsModuleCode) - c.prepend("'use strict';\n"); - bool ok = ::executeTest(c); + const QString testCasePath = QFileInfo(runner->testDir + "/test/" + data.test).absoluteFilePath(); + QByteArray c = "'use strict';\n" + data.content; + bool ok = ::executeTest(c, data.runAsModuleCode, testCasePath, data.harness); if (data.negative) ok = !ok; @@ -623,18 +653,18 @@ TestData Test262Runner::getTestData(const TestCase &testCase) TestData data(testCase); parseYaml(content, &data); - data.content += harness("assert.js"); - data.content += harness("sta.js"); + data.harness += harness("assert.js"); + data.harness += harness("sta.js"); for (QByteArray inc : qAsConst(data.includes)) { inc = inc.trimmed(); - data.content += harness(inc); + data.harness += harness(inc); } if (data.async) - data.content += harness("doneprintHandle.js"); + data.harness += harness("doneprintHandle.js"); - data.content += content; + data.content = data.harness + content; return data; } diff --git a/tests/auto/qml/ecmascripttests/qjstest/test262runner.h b/tests/auto/qml/ecmascripttests/qjstest/test262runner.h index 6b64298f42..e20d4a7bdd 100644 --- a/tests/auto/qml/ecmascripttests/qjstest/test262runner.h +++ b/tests/auto/qml/ecmascripttests/qjstest/test262runner.h @@ -67,6 +67,7 @@ struct TestData : TestCase { QList<QByteArray> includes; + QByteArray harness; QByteArray content; }; diff --git a/tests/auto/toolsupport/tst_toolsupport.cpp b/tests/auto/toolsupport/tst_toolsupport.cpp index cf90ab0531..eec96f9174 100644 --- a/tests/auto/toolsupport/tst_toolsupport.cpp +++ b/tests/auto/toolsupport/tst_toolsupport.cpp @@ -101,7 +101,7 @@ void tst_toolsupport::offsets_data() = QTest::newRow("CompiledData::CompilationUnit::data") << pmm_to_offsetof(&QV4::CompiledData::CompilationUnit::data); - data << 16 << 32; + data << 20 << 40; } { diff --git a/tools/qmljs/qmljs.cpp b/tools/qmljs/qmljs.cpp index 51670fd44c..56d4a7d383 100644 --- a/tools/qmljs/qmljs.cpp +++ b/tools/qmljs/qmljs.cpp @@ -37,6 +37,7 @@ #include "private/qv4context_p.h" #include "private/qv4script_p.h" #include "private/qv4string_p.h" +#include "private/qv4module_p.h" #include "private/qqmlbuiltinfunctions_p.h" #include <QtCore/QCoreApplication> @@ -77,6 +78,7 @@ int main(int argc, char *argv[]) args.removeFirst(); bool runAsQml = false; + bool runAsModule = false; bool cache = false; if (!args.isEmpty()) { @@ -94,6 +96,11 @@ int main(int argc, char *argv[]) args.removeFirst(); } + if (args.constFirst() == QLatin1String("--module")) { + runAsModule = true; + args.removeFirst(); + } + if (args.constFirst() == QLatin1String("--cache")) { cache = true; args.removeFirst(); @@ -113,8 +120,21 @@ int main(int argc, char *argv[]) QV4::GlobalExtensions::init(vm.globalObject, QJSEngine::ConsoleExtension | QJSEngine::GarbageCollectionExtension); for (const QString &fn : qAsConst(args)) { - QFile file(fn); - if (file.open(QFile::ReadOnly)) { + QV4::ScopedValue result(scope); + if (runAsModule) { + auto moduleUnit = vm.loadModule(QUrl::fromLocalFile(QFileInfo(fn).absoluteFilePath())); + if (moduleUnit) { + if (moduleUnit->instantiate(&vm)) + moduleUnit->evaluate(); + } else { + vm.throwError(QStringLiteral("Could not load module file")); + } + } else { + QFile file(fn); + if (!file.open(QFile::ReadOnly)) { + std::cerr << "Error: cannot open file " << fn.toUtf8().constData() << std::endl; + return EXIT_FAILURE; + } QScopedPointer<QV4::Script> script; if (cache && QFile::exists(fn + QLatin1Char('c'))) { QQmlRefPointer<QV4::CompiledData::CompilationUnit> unit = QV4::Compiler::Codegen::createUnitForLoading(); @@ -133,7 +153,6 @@ int main(int argc, char *argv[]) script->parseAsBinding = runAsQml; script->parse(); } - QV4::ScopedValue result(scope); if (!scope.engine->hasException) { const auto unit = script->compilationUnit; if (cache && unit && !(unit->unitData()->flags & QV4::CompiledData::Unit::StaticData)) { @@ -149,20 +168,17 @@ int main(int argc, char *argv[]) result = script->run(); // std::cout << t.elapsed() << " ms. elapsed" << std::endl; } - if (scope.engine->hasException) { - QV4::StackTrace trace; - QV4::ScopedValue ex(scope, scope.engine->catchException(&trace)); - showException(ctx, ex, trace); - return EXIT_FAILURE; - } - if (!result->isUndefined()) { - if (! qgetenv("SHOW_EXIT_VALUE").isEmpty()) - std::cout << "exit value: " << qPrintable(result->toQString()) << std::endl; - } - } else { - std::cerr << "Error: cannot open file " << fn.toUtf8().constData() << std::endl; + } + if (scope.engine->hasException) { + QV4::StackTrace trace; + QV4::ScopedValue ex(scope, scope.engine->catchException(&trace)); + showException(ctx, ex, trace); return EXIT_FAILURE; } + if (!result->isUndefined()) { + if (! qgetenv("SHOW_EXIT_VALUE").isEmpty()) + std::cout << "exit value: " << qPrintable(result->toQString()) << std::endl; + } } return EXIT_SUCCESS; |