From 627226520a2bbb977ce32a21bdffd2004cb28796 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BCri=20Valdmann?= Date: Fri, 12 Oct 2018 16:56:14 +0200 Subject: Expose let/const variables from imported JS scripts This patch allows QML to access let/const variables defined in JS files. Detailed changes: - The recently added ContextType::ScriptImportedByQML is changed to avoid creating Push/PopScriptContext instructions, similar to ContextType::ESModule. - QV4::Module is changed to also work with CompilationUnits which are not ESModules. In this case QV4::Module will behave as if all lexically scoped variables were exported. - CompilationUnit is changed to support instantiating and evaluating QV4::Modules for non-ESModules as well. - QQmlTypeLoader is changed to always create QV4::Modules for evaluating scripts. For the non-ESModule case, the QV4::Module is evaluated inside a QV4::QmlContext, as before. - A pointer to the QV4::Module is added to QV4::QQmlContextWrapper, and used in virtualGet to access the let/const variables in the CallContext. Access is read-only. Fixes: QTBUG-69408 Change-Id: I6f299363fdf5e1c5a4a0f1d9e655b4dc5112dd00 Reviewed-by: Simon Hausmann --- src/qml/compiler/qv4compileddata.cpp | 36 ++++++-------- src/qml/compiler/qv4compileddata_p.h | 2 +- src/qml/compiler/qv4compilercontext.cpp | 8 +-- src/qml/jsruntime/qv4module.cpp | 77 +++++++++++++++++++++++------ src/qml/jsruntime/qv4module_p.h | 6 ++- src/qml/jsruntime/qv4qmlcontext.cpp | 15 +++++- src/qml/jsruntime/qv4qmlcontext_p.h | 7 ++- src/qml/qml/qqmltypeloader.cpp | 86 ++++++++++++--------------------- src/qml/qml/qqmltypeloader_p.h | 3 -- 9 files changed, 138 insertions(+), 102 deletions(-) (limited to 'src') diff --git a/src/qml/compiler/qv4compileddata.cpp b/src/qml/compiler/qv4compileddata.cpp index 244e762faf..39f48f67f8 100644 --- a/src/qml/compiler/qv4compileddata.cpp +++ b/src/qml/compiler/qv4compileddata.cpp @@ -381,13 +381,20 @@ QStringList CompilationUnit::moduleRequests() const Heap::Module *CompilationUnit::instantiate(ExecutionEngine *engine) { - if (m_module) + if (isESModule() && m_module) return m_module; + if (data->indexOfRootFunction < 0) + return nullptr; + if (!this->engine) linkToEngine(engine); - m_module = engine->memoryManager->allocate(engine, this); + Scope scope(engine); + Scoped module(scope, engine->memoryManager->allocate(engine, this)); + + if (isESModule()) + m_module = module->d(); for (const QString &request: moduleRequests()) { auto dependentModuleUnit = engine->loadModule(QUrl(request), this); @@ -396,7 +403,6 @@ Heap::Module *CompilationUnit::instantiate(ExecutionEngine *engine) dependentModuleUnit->instantiate(engine); } - Scope scope(engine); ScopedString importName(scope); const uint importCount = data->importEntryTableSize; @@ -431,7 +437,7 @@ Heap::Module *CompilationUnit::instantiate(ExecutionEngine *engine) } } - return m_module; + return module->d(); } const Value *CompilationUnit::resolveExport(QV4::String *exportName) @@ -556,10 +562,13 @@ void CompilationUnit::getExportedNamesRecursively(QStringList *names, QVector module(scope, m_module); + module->evaluate(); +} +void CompilationUnit::evaluateModuleRequests() +{ for (const QString &request: moduleRequests()) { auto dependentModuleUnit = engine->loadModule(QUrl(request), this); if (engine->hasException) @@ -568,19 +577,6 @@ void CompilationUnit::evaluate() if (engine->hasException) return; } - - QV4::Function *moduleFunction = runtimeFunctions[data->indexOfRootFunction]; - CppStackFrame frame; - frame.init(engine, moduleFunction, nullptr, 0); - frame.setupJSFrame(engine->jsStackTop, Value::undefinedValue(), m_module->scope, - Value::undefinedValue(), Value::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) diff --git a/src/qml/compiler/qv4compileddata_p.h b/src/qml/compiler/qv4compileddata_p.h index b36b1a91ea..5c03303029 100644 --- a/src/qml/compiler/qv4compileddata_p.h +++ b/src/qml/compiler/qv4compileddata_p.h @@ -1183,6 +1183,7 @@ public: const Value *resolveExport(QV4::String *exportName); QStringList exportedNames() const; void evaluate(); + void evaluateModuleRequests(); QV4::Function *linkToEngine(QV4::ExecutionEngine *engine); void unlink(); @@ -1225,7 +1226,6 @@ private: Q_NEVER_INLINE IdentifierHash createNamedObjectsPerComponent(int componentObjectIndex); Heap::Module *m_module = nullptr; - bool m_moduleEvaluated = false; public: #if defined(V4_BOOTSTRAP) diff --git a/src/qml/compiler/qv4compilercontext.cpp b/src/qml/compiler/qv4compilercontext.cpp index 572f24f148..ca4cbfc4fc 100644 --- a/src/qml/compiler/qv4compilercontext.cpp +++ b/src/qml/compiler/qv4compilercontext.cpp @@ -207,7 +207,7 @@ void Context::emitBlockHeader(Codegen *codegen) blockIndex = codegen->module()->blocks.count() - 1; } - if (contextType == ContextType::Global || contextType == ContextType::ScriptImportedByQML) { + if (contextType == ContextType::Global) { Instruction::PushScriptContext scriptContext; scriptContext.index = blockIndex; bytecodeGenerator->addInstruction(scriptContext); @@ -222,7 +222,7 @@ void Context::emitBlockHeader(Codegen *codegen) blockContext.index = blockIndex; bytecodeGenerator->addInstruction(blockContext); } - } else if (contextType != ContextType::ESModule) { + } else if (contextType != ContextType::ESModule && contextType != ContextType::ScriptImportedByQML) { Instruction::CreateCallContext createContext; bytecodeGenerator->addInstruction(createContext); } @@ -316,9 +316,9 @@ void Context::emitBlockFooter(Codegen *codegen) QT_WARNING_PUSH QT_WARNING_DISABLE_GCC("-Wmaybe-uninitialized") // the loads below are empty structs. - if (contextType == ContextType::Global || contextType == ContextType::ScriptImportedByQML) + if (contextType == ContextType::Global) bytecodeGenerator->addInstruction(Instruction::PopScriptContext()); - else if (contextType != ContextType::ESModule) + else if (contextType != ContextType::ESModule && contextType != ContextType::ScriptImportedByQML) bytecodeGenerator->addInstruction(Instruction::PopContext()); QT_WARNING_POP } diff --git a/src/qml/jsruntime/qv4module.cpp b/src/qml/jsruntime/qv4module.cpp index 19a036374f..237ada8321 100644 --- a/src/qml/jsruntime/qv4module.cpp +++ b/src/qml/jsruntime/qv4module.cpp @@ -46,6 +46,8 @@ #include #include +#include + using namespace QV4; DEFINE_OBJECT_VTABLE(Module); @@ -98,20 +100,60 @@ void Heap::Module::init(ExecutionEngine *engine, CompiledData::CompilationUnit * This->setPrototypeUnchecked(nullptr); } +void Module::evaluate() +{ + if (d()->evaluated) + return; + d()->evaluated = true; + + CompiledData::CompilationUnit *unit = d()->unit; + + unit->evaluateModuleRequests(); + + ExecutionEngine *v4 = engine(); + Function *moduleFunction = unit->runtimeFunctions[unit->data->indexOfRootFunction]; + CppStackFrame frame; + frame.init(v4, moduleFunction, nullptr, 0); + frame.setupJSFrame(v4->jsStackTop, Value::undefinedValue(), d()->scope, + Value::undefinedValue(), Value::undefinedValue()); + + frame.push(); + v4->jsStackTop += frame.requiredJSStackFrameSize(); + auto frameCleanup = qScopeGuard([&frame]() { + frame.pop(); + }); + Moth::VME::exec(&frame, v4); +} + +const Value *Module::resolveExport(PropertyKey id) const +{ + if (d()->unit->isESModule()) { + if (!id.isString()) + return nullptr; + Scope scope(engine()); + ScopedString name(scope, id.asStringOrSymbol()); + return d()->unit->resolveExport(name); + } else { + InternalClassEntry entry = d()->scope->internalClass->find(id); + if (entry.isValid()) + return &d()->scope->locals[entry.index]; + return nullptr; + } +} + ReturnedValue Module::virtualGet(const Managed *m, PropertyKey id, const Value *receiver, bool *hasProperty) { if (id.isSymbol()) return Object::virtualGet(m, id, receiver, hasProperty); const Module *module = static_cast(m); - Scope scope(m->engine()); - ScopedString expectedName(scope, id.toStringOrSymbol(scope.engine)); - const Value *v = module->d()->unit->resolveExport(expectedName); + const Value *v = module->resolveExport(id); if (hasProperty) *hasProperty = v != nullptr; if (!v) return Encode::undefined(); if (v->isEmpty()) { + Scope scope(m->engine()); ScopedValue propName(scope, id.toStringOrSymbol(scope.engine)); return scope.engine->throwReferenceError(propName); } @@ -124,9 +166,7 @@ PropertyAttributes Module::virtualGetOwnProperty(const Managed *m, PropertyKey i return Object::virtualGetOwnProperty(m, id, p); const Module *module = static_cast(m); - Scope scope(m->engine()); - ScopedString expectedName(scope, id.toStringOrSymbol(scope.engine)); - const Value *v = module->d()->unit->resolveExport(expectedName); + const Value *v = module->resolveExport(id); if (!v) { if (p) p->value = Encode::undefined(); @@ -135,6 +175,7 @@ PropertyAttributes Module::virtualGetOwnProperty(const Managed *m, PropertyKey i if (p) p->value = v->isEmpty() ? Encode::undefined() : v->asReturnedValue(); if (v->isEmpty()) { + Scope scope(m->engine()); ScopedValue propName(scope, id.toStringOrSymbol(scope.engine)); scope.engine->throwReferenceError(propName); } @@ -147,9 +188,7 @@ bool Module::virtualHasProperty(const Managed *m, PropertyKey id) return Object::virtualHasProperty(m, id); const Module *module = static_cast(m); - Scope scope(m->engine()); - ScopedString expectedName(scope, id.toStringOrSymbol(scope.engine)); - const Value *v = module->d()->unit->resolveExport(expectedName); + const Value *v = module->resolveExport(id); return v != nullptr; } @@ -173,11 +212,7 @@ bool Module::virtualDeleteProperty(Managed *m, PropertyKey id) if (id.isSymbol()) return Object::virtualDeleteProperty(m, id); const Module *module = static_cast(m); - Scope scope(m->engine()); - ScopedString expectedName(scope, id.toStringOrSymbol(scope.engine)); - if (!expectedName) - return true; - const Value *v = module->d()->unit->resolveExport(expectedName); + const Value *v = module->resolveExport(id); if (v) return false; return true; @@ -202,7 +237,7 @@ PropertyKey ModuleNamespaceIterator::next(const Object *o, Property *pd, Propert Scope scope(module->engine()); ScopedString exportName(scope, scope.engine->newString(exportedNames.at(exportIndex))); exportIndex++; - const Value *v = module->d()->unit->resolveExport(exportName); + const Value *v = module->resolveExport(exportName->toPropertyKey()); if (pd) { if (v->isEmpty()) scope.engine->throwReferenceError(exportName); @@ -218,7 +253,17 @@ OwnPropertyKeyIterator *Module::virtualOwnPropertyKeys(const Object *o, Value *t { const Module *module = static_cast(o); *target = *o; - return new ModuleNamespaceIterator(module->d()->unit->exportedNames()); + + QStringList names; + if (module->d()->unit->isESModule()) { + names = module->d()->unit->exportedNames(); + } else { + Heap::InternalClass *scopeClass = module->d()->scope->internalClass; + for (uint i = 0; i < scopeClass->size; ++i) + names << scopeClass->keyAt(i); + } + + return new ModuleNamespaceIterator(names); } Heap::Object *Module::virtualGetPrototypeOf(const Managed *) diff --git a/src/qml/jsruntime/qv4module_p.h b/src/qml/jsruntime/qv4module_p.h index 0cab161b82..dca0678fe9 100644 --- a/src/qml/jsruntime/qv4module_p.h +++ b/src/qml/jsruntime/qv4module_p.h @@ -62,7 +62,8 @@ namespace Heap { #define ModuleMembers(class, Member) \ Member(class, NoMark, CompiledData::CompilationUnit *, unit) \ Member(class, Pointer, CallContext *, scope) \ - Member(class, HeapValue, HeapValue, self) + Member(class, HeapValue, HeapValue, self) \ + Member(class, NoMark, bool, evaluated) DECLARE_EXPORTED_HEAP_OBJECT(Module, Object) { DECLARE_MARKOBJECTS(Module) @@ -75,6 +76,9 @@ DECLARE_EXPORTED_HEAP_OBJECT(Module, Object) { struct Q_QML_EXPORT Module : public Object { V4_OBJECT2(Module, Object) + void evaluate(); + const Value *resolveExport(PropertyKey key) const; + static ReturnedValue virtualGet(const Managed *m, PropertyKey id, const Value *receiver, bool *hasProperty); static PropertyAttributes virtualGetOwnProperty(const Managed *m, PropertyKey id, Property *p); static bool virtualHasProperty(const Managed *m, PropertyKey id); diff --git a/src/qml/jsruntime/qv4qmlcontext.cpp b/src/qml/jsruntime/qv4qmlcontext.cpp index cc0b0feeee..88b0822f42 100644 --- a/src/qml/jsruntime/qv4qmlcontext.cpp +++ b/src/qml/jsruntime/qv4qmlcontext.cpp @@ -54,6 +54,7 @@ #include #include #include +#include QT_BEGIN_NAMESPACE @@ -87,8 +88,20 @@ ReturnedValue QQmlContextWrapper::virtualGet(const Managed *m, PropertyKey id, c QV4::ExecutionEngine *v4 = resource->engine(); QV4::Scope scope(v4); - if (v4->callingQmlContext() != *resource->d()->context) + if (v4->callingQmlContext() != *resource->d()->context) { + if (resource->d()->module) { + Scoped module(scope, resource->d()->module); + bool hasProp = false; + ScopedValue value(scope, module->get(id, receiver, &hasProp)); + if (hasProp) { + if (hasProperty) + *hasProperty = hasProp; + return value->asReturnedValue(); + } + } + return Object::virtualGet(m, id, receiver, hasProperty); + } bool hasProp = false; ScopedValue result(scope, Object::virtualGet(m, id, receiver, &hasProp)); diff --git a/src/qml/jsruntime/qv4qmlcontext_p.h b/src/qml/jsruntime/qv4qmlcontext_p.h index 4fe34a0a06..dd6de3323d 100644 --- a/src/qml/jsruntime/qv4qmlcontext_p.h +++ b/src/qml/jsruntime/qv4qmlcontext_p.h @@ -66,7 +66,12 @@ struct QQmlContextWrapper; namespace Heap { -struct QQmlContextWrapper : Object { +#define QQmlContextWrapperMembers(class, Member) \ + Member(class, Pointer, Module *, module) + +DECLARE_HEAP_OBJECT(QQmlContextWrapper, Object) { + DECLARE_MARKOBJECTS(QQmlContextWrapper); + void init(QQmlContextData *context, QObject *scopeObject); void destroy(); diff --git a/src/qml/qml/qqmltypeloader.cpp b/src/qml/qml/qqmltypeloader.cpp index 89b023c164..7480475ca7 100644 --- a/src/qml/qml/qqmltypeloader.cpp +++ b/src/qml/qml/qqmltypeloader.cpp @@ -2881,30 +2881,9 @@ void QQmlTypeData::scriptImported(const QQmlRefPointer &blob, co QQmlScriptData::QQmlScriptData() : typeNameCache(nullptr) , m_loaded(false) - , m_program(nullptr) { } -QQmlScriptData::~QQmlScriptData() -{ - delete m_program; -} - -void QQmlScriptData::initialize(QQmlEngine *engine) -{ - Q_ASSERT(!m_program); - Q_ASSERT(engine); - Q_ASSERT(!hasEngine()); - - QV4::ExecutionEngine *v4 = engine->handle(); - - m_program = new QV4::Script(v4, nullptr, m_precompiledScript); - - addToEngine(engine); - - addref(); -} - QQmlContextData *QQmlScriptData::qmlContextDataForContext(QQmlContextData *parentQmlContextData) { Q_ASSERT(parentQmlContextData && parentQmlContextData->engine); @@ -2954,57 +2933,54 @@ QQmlContextData *QQmlScriptData::qmlContextDataForContext(QQmlContextData *paren return qmlContextData; } -QV4::ReturnedValue QQmlScriptData::scriptValueForContext(QQmlContextData *parentCtxt) +QV4::ReturnedValue QQmlScriptData::scriptValueForContext(QQmlContextData *parentQmlContextData) { if (m_loaded) return m_value.value(); - Q_ASSERT(parentCtxt && parentCtxt->engine); - QV4::ExecutionEngine *v4 = parentCtxt->engine->handle(); - - if (m_precompiledScript->isESModule()) { - m_loaded = true; - - m_value.set(v4, m_precompiledScript->instantiate(v4)); - if (!m_value.isNullOrUndefined()) - m_precompiledScript->evaluate(); + Q_ASSERT(parentQmlContextData && parentQmlContextData->engine); + QV4::ExecutionEngine *v4 = parentQmlContextData->engine->handle(); + QV4::Scope scope(v4); - return m_value.value(); + if (!hasEngine()) { + addToEngine(parentQmlContextData->engine); + addref(); } - QQmlEnginePrivate *ep = QQmlEnginePrivate::get(parentCtxt->engine); - QV4::Scope scope(v4); - - // Create the script context if required - QQmlContextDataRef ctxt(qmlContextDataForContext(parentCtxt)); + QQmlContextDataRef qmlContextData = qmlContextDataForContext(parentQmlContextData); + QV4::Scoped qmlExecutionContext(scope); + if (qmlContextData) + qmlExecutionContext = + QV4::QmlContext::create(v4->rootContext(), qmlContextData, /* scopeObject: */ nullptr); - if (!hasEngine()) - initialize(parentCtxt->engine); + QV4::Scoped module(scope, m_precompiledScript->instantiate(v4)); + if (module) { + if (qmlContextData) { + module->d()->scope->outer.set(v4, qmlExecutionContext->d()); + qmlExecutionContext->d()->qml()->module.set(v4, module->d()); + } - if (!m_program) { - if (m_precompiledScript->isSharedLibrary()) - m_loaded = true; - return QV4::Encode::undefined(); + module->evaluate(); } - QV4::Scoped qmlContext(scope, QV4::QmlContext::create(v4->rootContext(), ctxt, nullptr)); - - m_program->qmlContext.set(scope.engine, qmlContext); - m_program->run(); - m_program->qmlContext.clear(); - if (scope.engine->hasException) { - QQmlError error = scope.engine->catchExceptionAsQmlError(); + if (v4->hasException) { + QQmlError error = v4->catchExceptionAsQmlError(); if (error.isValid()) - ep->warning(error); + QQmlEnginePrivate::get(v4)->warning(error); } - QV4::ScopedValue retval(scope, qmlContext->d()->qml()); - if (m_precompiledScript->isSharedLibrary()) { - m_value.set(scope.engine, retval); + QV4::ScopedValue value(scope); + if (qmlContextData) + value = qmlExecutionContext->d()->qml(); + else if (module) + value = module->d(); + + if (m_precompiledScript->isSharedLibrary() || m_precompiledScript->isESModule()) { m_loaded = true; + m_value.set(v4, value); } - return retval->asReturnedValue(); + return value->asReturnedValue(); } void QQmlScriptData::clear() diff --git a/src/qml/qml/qqmltypeloader_p.h b/src/qml/qml/qqmltypeloader_p.h index 29c91346de..5bb4e9d490 100644 --- a/src/qml/qml/qqmltypeloader_p.h +++ b/src/qml/qml/qqmltypeloader_p.h @@ -534,8 +534,6 @@ private: QQmlScriptData(); public: - ~QQmlScriptData() override; - QUrl url; QString urlString; QQmlTypeNameCache *typeNameCache; @@ -556,7 +554,6 @@ private: bool m_loaded; QQmlRefPointer m_precompiledScript; - QV4::Script *m_program; QV4::PersistentValue m_value; }; -- cgit v1.2.3