diff options
-rw-r--r-- | src/qmlcompiler/CMakeLists.txt | 1 | ||||
-rw-r--r-- | src/qmlcompiler/qqmljscompilepass_p.h | 307 | ||||
-rw-r--r-- | src/qmlcompiler/qqmljsregistercontent_p.h | 3 | ||||
-rw-r--r-- | src/qmlcompiler/qqmljstypepropagator.cpp | 116 | ||||
-rw-r--r-- | src/qmlcompiler/qqmljstypepropagator_p.h | 68 | ||||
-rw-r--r-- | tools/qmllint/codegen.cpp | 99 | ||||
-rw-r--r-- | tools/qmllint/codegen.h | 24 |
7 files changed, 414 insertions, 204 deletions
diff --git a/src/qmlcompiler/CMakeLists.txt b/src/qmlcompiler/CMakeLists.txt index 6428a3df26..03812d359b 100644 --- a/src/qmlcompiler/CMakeLists.txt +++ b/src/qmlcompiler/CMakeLists.txt @@ -9,6 +9,7 @@ qt_internal_add_module(QmlCompilerPrivate qcoloroutput_p.h qcoloroutput.cpp qdeferredpointer_p.h qqmljsannotation_p.h qqmljsannotation.cpp + qqmljscompilepass_p.h qqmljscompiler.cpp qqmljscompiler_p.h qqmljsimporter.cpp qqmljsimporter_p.h qqmljsimportvisitor.cpp qqmljsimportvisitor_p.h diff --git a/src/qmlcompiler/qqmljscompilepass_p.h b/src/qmlcompiler/qqmljscompilepass_p.h new file mode 100644 index 0000000000..a02d63c0d8 --- /dev/null +++ b/src/qmlcompiler/qqmljscompilepass_p.h @@ -0,0 +1,307 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the tools applications of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** 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 General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQMLJSCOMPILEPASS_P_H +#define QQMLJSCOMPILEPASS_P_H + +// +// 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 <private/qqmljscompiler_p.h> +#include <private/qqmljslogger_p.h> +#include <private/qqmljsregistercontent_p.h> +#include <private/qqmljsscope_p.h> +#include <private/qqmljstyperesolver_p.h> +#include <private/qv4bytecodehandler_p.h> +#include <private/qv4compiler_p.h> + +QT_BEGIN_NAMESPACE + +class QQmlJSCompilePass : public QV4::Moth::ByteCodeHandler +{ + Q_DISABLE_COPY_MOVE(QQmlJSCompilePass) +public: + enum RegisterShortcuts { + Accumulator = QV4::CallData::Accumulator, + FirstArgument = QV4::CallData::OffsetCount + }; + + using SourceLocationTable = QV4::Compiler::Context::SourceLocationTable; + + // map from register index to expected type + using VirtualRegisters = QHash<int, QQmlJSRegisterContent>; + + struct InstructionAnnotation + { + VirtualRegisters registers; + VirtualRegisters expectedTargetTypesBeforeJump; + }; + + using InstructionAnnotations = QHash<int, InstructionAnnotation>; + + struct Function + { + QHash<QString, QQmlJSScope::ConstPtr> addressableScopes; + QList<QQmlJSScope::ConstPtr> argumentTypes; + QQmlJSScope::ConstPtr returnType; + QQmlJSScope::ConstPtr qmlScope; + QByteArray code; + const SourceLocationTable *sourceLocations = nullptr; + bool isSignalHandler = false; + bool isQPropertyBinding = false; + }; + + struct State + { + VirtualRegisters registers; + QQmlJSRegisterContent accumulatorIn; + QQmlJSRegisterContent accumulatorOut; + }; + + QQmlJSCompilePass(const QV4::Compiler::JSUnitGenerator *jsUnitGenerator, + const QQmlJSTypeResolver *typeResolver, QQmlJSLogger *logger) + : m_jsUnitGenerator(jsUnitGenerator) + , m_typeResolver(typeResolver) + , m_logger(logger) + {} + +protected: + const QV4::Compiler::JSUnitGenerator *m_jsUnitGenerator = nullptr; + const QQmlJSTypeResolver *m_typeResolver = nullptr; + QQmlJSLogger *m_logger = nullptr; + + const Function *m_function = nullptr; + QQmlJS::DiagnosticMessage *m_error = nullptr; + + State initialState(const Function *function, const QQmlJSTypeResolver *resolver) + { + State state; + for (int i = 0; i < function->argumentTypes.count(); ++i) { + state.registers[FirstArgument + i] + = resolver->globalType(function->argumentTypes.at(i)); + } + return state; + } + + State nextStateFromAnnotations( + const State &oldState, const InstructionAnnotations &annotations) + { + State newState; + + // Usually the initial accumulator type is the output of the previous instruction, but ... + newState.accumulatorIn = oldState.accumulatorOut; + + const auto instruction = annotations.constFind(currentInstructionOffset()); + if (instruction != annotations.constEnd()) { + const auto target = instruction->expectedTargetTypesBeforeJump.constFind(Accumulator); + if (target != instruction->expectedTargetTypesBeforeJump.constEnd()) { + // ... the initial type of the accumulator is given in expectedTargetTypesBeforeJump + // if the current instruction can be jumped to. + newState.accumulatorIn = *target; + } + + newState.registers = instruction->registers; + newState.accumulatorOut = instruction->registers[Accumulator]; + } else { + newState.registers = VirtualRegisters(); + newState.accumulatorOut = QQmlJSRegisterContent(); + } + + return newState; + } + + QQmlJS::SourceLocation currentSourceLocation() const + { + Q_ASSERT(m_function); + Q_ASSERT(m_function->sourceLocations); + const int offset = currentInstructionOffset(); + const auto &entries = m_function->sourceLocations->entries; + auto item = std::lower_bound(entries.begin(), entries.end(), offset, + [](auto entry, uint offset) { return entry.offset < offset; }); + + Q_ASSERT(item != entries.end()); + return item->location; + } + + void setError(const QString &message) + { + Q_ASSERT(m_error); + if (m_error->isValid()) + return; + m_error->message = message; + m_error->loc = currentSourceLocation(); + } + + // Stub out all the methods so that passes can choose to only implement part of them. + void generate_Add(int) override {} + void generate_As(int) override {} + void generate_BitAnd(int) override {} + void generate_BitAndConst(int) override {} + void generate_BitOr(int) override {} + void generate_BitOrConst(int) override {} + void generate_BitXor(int) override {} + void generate_BitXorConst(int) override {} + void generate_CallElement(int, int, int, int) override {} + void generate_CallGlobalLookup(int, int, int) override {} + void generate_CallName(int, int, int) override {} + void generate_CallPossiblyDirectEval(int, int) override {} + void generate_CallProperty(int, int, int, int) override {} + void generate_CallPropertyLookup(int, int, int, int) override {} + void generate_CallQmlContextPropertyLookup(int, int, int) override {} + void generate_CallValue(int, int, int) override {} + void generate_CallWithReceiver(int, int, int, int) override {} + void generate_CallWithSpread(int, int, int, int) override {} + void generate_CheckException() override {} + void generate_CloneBlockContext() override {} + void generate_CmpEq(int) override {} + void generate_CmpEqInt(int) override {} + void generate_CmpEqNull() override {} + void generate_CmpGe(int) override {} + void generate_CmpGt(int) override {} + void generate_CmpIn(int) override {} + void generate_CmpInstanceOf(int) override {} + void generate_CmpLe(int) override {} + void generate_CmpLt(int) override {} + void generate_CmpNe(int) override {} + void generate_CmpNeInt(int) override {} + void generate_CmpNeNull() override {} + void generate_CmpStrictEqual(int) override {} + void generate_CmpStrictNotEqual(int) override {} + void generate_Construct(int, int, int) override {} + void generate_ConstructWithSpread(int, int, int) override {} + void generate_ConvertThisToObject() override {} + void generate_CreateCallContext() override {} + void generate_CreateClass(int, int, int) override {} + void generate_CreateMappedArgumentsObject() override {} + void generate_CreateRestParameter(int) override {} + void generate_CreateUnmappedArgumentsObject() override {} + void generate_DeadTemporalZoneCheck(int) override {} + void generate_Debug() override {} + void generate_DeclareVar(int, int) override {} + void generate_Decrement() override {} + void generate_DefineArray(int, int) override {} + void generate_DefineObjectLiteral(int, int, int) override {} + void generate_DeleteName(int) override {} + void generate_DeleteProperty(int, int) override {} + void generate_DestructureRestElement() override {} + void generate_Div(int) override {} + void generate_Exp(int) override {} + void generate_GetException() override {} + void generate_GetIterator(int) override {} + void generate_GetLookup(int) override {} + void generate_GetOptionalLookup(int, int) override {} + void generate_GetTemplateObject(int) override {} + void generate_Increment() override {} + void generate_InitializeBlockDeadTemporalZone(int, int) override {} + void generate_IteratorClose(int) override {} + void generate_IteratorNext(int, int) override {} + void generate_IteratorNextForYieldStar(int, int) override {} + void generate_Jump(int) override {} + void generate_JumpFalse(int) override {} + void generate_JumpNoException(int) override {} + void generate_JumpNotUndefined(int) override {} + void generate_JumpTrue(int) override {} + void generate_LoadClosure(int) override {} + void generate_LoadConst(int) override {} + void generate_LoadElement(int) override {} + void generate_LoadFalse() override {} + void generate_LoadGlobalLookup(int) override {} + void generate_LoadImport(int) override {} + void generate_LoadInt(int) override {} + void generate_LoadLocal(int) override {} + void generate_LoadName(int) override {} + void generate_LoadNull() override {} + void generate_LoadOptionalProperty(int, int) override {} + void generate_LoadProperty(int) override {} + void generate_LoadQmlContextPropertyLookup(int) override {} + void generate_LoadReg(int) override {} + void generate_LoadRuntimeString(int) override {} + void generate_LoadScopedLocal(int, int) override {} + void generate_LoadSuperConstructor() override {} + void generate_LoadSuperProperty(int) override {} + void generate_LoadTrue() override {} + void generate_LoadUndefined() override {} + void generate_LoadZero() override {} + void generate_Mod(int) override {} + void generate_MoveConst(int, int) override {} + void generate_MoveReg(int, int) override {} + void generate_MoveRegExp(int, int) override {} + void generate_Mul(int) override {} + void generate_PopContext() override {} + void generate_PopScriptContext() override {} + void generate_PushBlockContext(int) override {} + void generate_PushCatchContext(int, int) override {} + void generate_PushScriptContext(int) override {} + void generate_PushWithContext() override {} + void generate_Resume(int) override {} + void generate_Ret() override {} + void generate_SetException() override {} + void generate_SetLookup(int, int) override {} + void generate_SetUnwindHandler(int) override {} + void generate_Shl(int) override {} + void generate_ShlConst(int) override {} + void generate_Shr(int) override {} + void generate_ShrConst(int) override {} + void generate_StoreElement(int, int) override {} + void generate_StoreLocal(int) override {} + void generate_StoreNameSloppy(int) override {} + void generate_StoreNameStrict(int) override {} + void generate_StoreProperty(int, int) override {} + void generate_StoreReg(int) override {} + void generate_StoreScopedLocal(int, int) override {} + void generate_StoreSuperProperty(int) override {} + void generate_Sub(int) override {} + void generate_TailCall(int, int, int, int) override {} + void generate_ThrowException() override {} + void generate_ThrowOnNullOrUndefined() override {} + void generate_ToObject() override {} + void generate_TypeofName(int) override {} + void generate_TypeofValue() override {} + void generate_UCompl() override {} + void generate_UMinus() override {} + void generate_UNot() override {} + void generate_UPlus() override {} + void generate_UShr(int) override {} + void generate_UShrConst(int) override {} + void generate_UnwindDispatch() override {} + void generate_UnwindToLabel(int, int) override {} + void generate_Yield() override {} + void generate_YieldStar() override {} +}; + +QT_END_NAMESPACE + +#endif // QQMLJSCOMPILEPASS_P_H diff --git a/src/qmlcompiler/qqmljsregistercontent_p.h b/src/qmlcompiler/qqmljsregistercontent_p.h index 2e3f9eb016..dfad7a6cf1 100644 --- a/src/qmlcompiler/qqmljsregistercontent_p.h +++ b/src/qmlcompiler/qqmljsregistercontent_p.h @@ -191,9 +191,6 @@ private: // TODO: Constant string/number/bool/enumval }; -// map from register index to expected type -using QQmlJSVirtualRegisters = QHash<int, QQmlJSRegisterContent>; - QT_END_NAMESPACE #endif // REGISTERCONTENT_H diff --git a/src/qmlcompiler/qqmljstypepropagator.cpp b/src/qmlcompiler/qqmljstypepropagator.cpp index 258e89f26e..198907d3dc 100644 --- a/src/qmlcompiler/qqmljstypepropagator.cpp +++ b/src/qmlcompiler/qqmljstypepropagator.cpp @@ -34,50 +34,28 @@ QT_BEGIN_NAMESPACE QQmlJSTypePropagator::QQmlJSTypePropagator(QV4::Compiler::JSUnitGenerator *unitGenerator, QQmlJSTypeResolver *typeResolver, QQmlJSLogger *logger, QQmlJSTypeInfo *typeInfo) - : m_typeResolver(typeResolver), - m_jsUnitGenerator(unitGenerator), - m_logger(logger), - m_typeInfo(typeInfo) + : QQmlJSCompilePass(unitGenerator, typeResolver, logger), m_typeInfo(typeInfo) { } -QQmlJSTypePropagator::~QQmlJSTypePropagator() { } - -QQmlJSTypePropagator::TypePropagationResult QQmlJSTypePropagator::propagateTypes( - const QV4::Compiler::Context *context, const QQmlJS::AST::BoundNames &arguments, - const QQmlJSScope::ConstPtr &returnType, const QQmlJSScope::ConstPtr &qmlScope, - const QHash<QString, QQmlJSScope::ConstPtr> &addressableScopes, bool isSignalHandler, - Error *error) +QQmlJSCompilePass::InstructionAnnotations QQmlJSTypePropagator::run( + const Function *function, QQmlJS::DiagnosticMessage *error) { + m_function = function; m_error = error; - m_currentScope = qmlScope; - m_currentContext = context; - m_addressableScopes = addressableScopes; - m_arguments = arguments; - m_returnType = m_typeResolver->globalType(returnType); - m_isSignalHandler = isSignalHandler; + m_returnType = m_typeResolver->globalType(m_function->returnType); do { m_state = PassState(); - - for (int i = 0; i < arguments.count(); ++i) { - auto argument = arguments.at(i); - setRegister(FirstArgument + i, - argument.typeAnnotation - ? m_typeResolver->typeFromAST(argument.typeAnnotation->type) - : m_typeResolver->varType()); - } - - const int registerCount = context->registerCountInFunction; - for (int i = FirstArgument + arguments.count(); i < registerCount; ++i) - setRegister(i, m_typeResolver->voidType()); + m_state.State::operator=(initialState(m_function, m_typeResolver)); reset(); - decode(context->code.constData(), static_cast<uint>(context->code.length())); + decode(m_function->code.constData(), static_cast<uint>(m_function->code.length())); - if (m_error->isSet()) - break; - } while (m_state.needsMorePasses); + // If we have found unresolved backwards jumps, we need to start over with a fresh state. + // Mind that m_jumpOriginRegisterStateByTargetInstructionOffset is retained in that case. + // This means that we won't start over for the same reason again. + } while (!m_error->isValid() && m_state.needsMorePasses); return m_state.annotations; } @@ -95,7 +73,7 @@ QQmlJSTypePropagator::TypePropagationResult QQmlJSTypePropagator::propagateTypes void QQmlJSTypePropagator::generate_Ret() { - if (m_isSignalHandler) { + if (m_function->isSignalHandler) { // Signal handlers cannot return anything. } else if (!m_returnType.isValid() && m_state.accumulatorIn.isValid() && m_typeResolver->containedType(m_state.accumulatorIn) @@ -236,7 +214,7 @@ void QQmlJSTypePropagator::generate_LoadClosure(int value) void QQmlJSTypePropagator::generate_LoadName(int nameIndex) { const QString name = m_jsUnitGenerator->stringForIndex(nameIndex); - m_state.accumulatorOut = m_typeResolver->scopedType(m_currentScope, name); + m_state.accumulatorOut = m_typeResolver->scopedType(m_function->qmlScope, name); if (!m_state.accumulatorOut.isValid()) setError(u"Cannot find name "_qs + name); } @@ -248,8 +226,8 @@ void QQmlJSTypePropagator::generate_LoadGlobalLookup(int index) QQmlJS::SourceLocation QQmlJSTypePropagator::getCurrentSourceLocation() const { - Q_ASSERT(m_currentContext->sourceLocationTable); - const auto &entries = m_currentContext->sourceLocationTable->entries; + Q_ASSERT(m_function->sourceLocations); + const auto &entries = m_function->sourceLocations->entries; auto item = std::lower_bound(entries.begin(), entries.end(), currentInstructionOffset(), [](auto entry, uint offset) { return entry.offset < offset; }); @@ -261,8 +239,8 @@ QQmlJS::SourceLocation QQmlJSTypePropagator::getCurrentSourceLocation() const QQmlJS::SourceLocation QQmlJSTypePropagator::getCurrentBindingSourceLocation() const { - Q_ASSERT(m_currentContext->sourceLocationTable); - const auto &entries = m_currentContext->sourceLocationTable->entries; + Q_ASSERT(m_function->sourceLocations); + const auto &entries = m_function->sourceLocations->entries; Q_ASSERT(!entries.isEmpty()); return combine(entries.constFirst().location, entries.constLast().location); @@ -272,17 +250,17 @@ void QQmlJSTypePropagator::handleUnqualifiedAccess(const QString &name) const { auto location = getCurrentSourceLocation(); - if (m_currentScope->isInCustomParserParent()) { - Q_ASSERT(!m_currentScope->baseType().isNull()); + if (m_function->qmlScope->isInCustomParserParent()) { + Q_ASSERT(!m_function->qmlScope->baseType().isNull()); // Only ignore custom parser based elements if it's not Connections. - if (m_currentScope->baseType()->internalName() != u"QQmlConnections"_qs) + if (m_function->qmlScope->baseType()->internalName() != u"QQmlConnections"_qs) return; } m_logger->logWarning(QLatin1String("Unqualified access"), Log_UnqualifiedAccess, location); - auto childScopes = m_currentScope->childScopes(); - for (qsizetype i = 0; i < m_currentScope->childScopes().length(); i++) { + auto childScopes = m_function->qmlScope->childScopes(); + for (qsizetype i = 0; i < m_function->qmlScope->childScopes().length(); i++) { auto &scope = childScopes[i]; if (location.offset > scope->sourceLocation().offset) { if (i + 1 < childScopes.length() @@ -331,10 +309,11 @@ void QQmlJSTypePropagator::handleUnqualifiedAccess(const QString &name) const } } - for (QQmlJSScope::ConstPtr scope = m_currentScope; !scope.isNull(); + for (QQmlJSScope::ConstPtr scope = m_function->qmlScope; !scope.isNull(); scope = scope->parentScope()) { if (scope->hasProperty(name)) { - auto it = std::find_if(m_addressableScopes.constBegin(), m_addressableScopes.constEnd(), + auto it = std::find_if(m_function->addressableScopes.constBegin(), + m_function->addressableScopes.constEnd(), [&](const QQmlJSScope::ConstPtr ptr) { return ptr == scope; }); FixSuggestion suggestion { Log_UnqualifiedAccess, QtInfoMsg, {} }; @@ -342,7 +321,7 @@ void QQmlJSTypePropagator::handleUnqualifiedAccess(const QString &name) const QQmlJS::SourceLocation fixLocation = location; fixLocation.length = 0; - QString id = it == m_addressableScopes.constEnd() ? u"<id>"_qs : it.key(); + QString id = it == m_function->addressableScopes.constEnd() ? u"<id>"_qs : it.key(); suggestion.fixes << FixSuggestion::Fix { name + QLatin1String(" is a member of a parent element\n") @@ -351,7 +330,7 @@ void QQmlJSTypePropagator::handleUnqualifiedAccess(const QString &name) const QtInfoMsg, fixLocation, id + u"."_qs }; - if (it == m_addressableScopes.constEnd()) { + if (it == m_function->addressableScopes.constEnd()) { suggestion.fixes << FixSuggestion::Fix { u"You first have to give the element an id"_qs, QtInfoMsg, @@ -444,7 +423,7 @@ void QQmlJSTypePropagator::generate_LoadQmlContextPropertyLookup(int index) m_state.accumulatorOut = m_typeResolver->scopedType( - m_currentScope, + m_function->qmlScope, m_state.accumulatorIn.isImportNamespace() ? m_jsUnitGenerator->stringForIndex(m_state.accumulatorIn.importNamespace()) + u'.' + name @@ -454,18 +433,18 @@ void QQmlJSTypePropagator::generate_LoadQmlContextPropertyLookup(int index) && m_state.accumulatorOut.variant() == QQmlJSRegisterContent::ScopeAttached) { QQmlJSScope::ConstPtr attachedType = m_state.accumulatorOut.scopeType(); - for (QQmlJSScope::ConstPtr scope = m_currentScope->parentScope(); !scope.isNull(); + for (QQmlJSScope::ConstPtr scope = m_function->qmlScope->parentScope(); !scope.isNull(); scope = scope->parentScope()) { if (m_typeInfo->usedAttachedTypes.values(scope).contains(attachedType)) { - auto it = std::find(m_addressableScopes.constBegin(), - m_addressableScopes.constEnd(), scope); + auto it = std::find(m_function->addressableScopes.constBegin(), + m_function->addressableScopes.constEnd(), scope); FixSuggestion suggestion { Log_AttachedPropertyReuse, QtInfoMsg, {} }; QQmlJS::SourceLocation fixLocation = getCurrentSourceLocation(); fixLocation.length = 0; - QString id = it == m_addressableScopes.constEnd() ? u"<id>"_qs : it.key(); + QString id = it == m_function->addressableScopes.constEnd() ? u"<id>"_qs : it.key(); suggestion.fixes << FixSuggestion::Fix { u"Using attached type %1 already initialized in a parent scope. Reference it by id instead:"_qs @@ -476,7 +455,7 @@ void QQmlJSTypePropagator::generate_LoadQmlContextPropertyLookup(int index) fixLocation = scope->sourceLocation(); fixLocation.length = 0; - if (it == m_addressableScopes.constEnd()) { + if (it == m_function->addressableScopes.constEnd()) { suggestion.fixes << FixSuggestion::Fix { u"You first have to give the element an id"_qs, QtInfoMsg, @@ -488,20 +467,20 @@ void QQmlJSTypePropagator::generate_LoadQmlContextPropertyLookup(int index) } } - m_typeInfo->usedAttachedTypes.insert(m_currentScope, attachedType); + m_typeInfo->usedAttachedTypes.insert(m_function->qmlScope, attachedType); } if (!m_state.accumulatorOut.isValid() && m_typeResolver->isPrefix(name)) { const QQmlJSRegisterContent inType = m_state.accumulatorIn.isValid() ? m_state.accumulatorIn - : m_typeResolver->globalType(m_currentScope); + : m_typeResolver->globalType(m_function->qmlScope); m_state.accumulatorOut = QQmlJSRegisterContent::create( inType.storedType(), nameIndex, QQmlJSRegisterContent::ScopeModulePrefix, m_typeResolver->containedType(inType)); return; } - checkDeprecated(m_currentScope, name, false); + checkDeprecated(m_function->qmlScope, name, false); bool isRestricted = checkRestricted(name); @@ -520,7 +499,7 @@ void QQmlJSTypePropagator::generate_LoadQmlContextPropertyLookup(int index) void QQmlJSTypePropagator::generate_StoreNameSloppy(int nameIndex) { const QString name = m_jsUnitGenerator->stringForIndex(nameIndex); - const QQmlJSRegisterContent type = m_typeResolver->scopedType(m_currentScope, name); + const QQmlJSRegisterContent type = m_typeResolver->scopedType(m_function->qmlScope, name); if (!type.isValid()) { setError(u"Cannot find name "_qs + name); @@ -532,7 +511,7 @@ void QQmlJSTypePropagator::generate_StoreNameSloppy(int nameIndex) return; } - if (!type.isWritable() && !m_currentScope->hasOwnProperty(name)) { + if (!type.isWritable() && !m_function->qmlScope->hasOwnProperty(name)) { setError(u"Can't assign to read-only property %1"_qs.arg(name)); m_logger->logWarning(u"Cannot assign to read-only property %1"_qs.arg(name), Log_Property, @@ -901,7 +880,7 @@ void QQmlJSTypePropagator::generate_CallPossiblyDirectEval(int argc, int argv) void QQmlJSTypePropagator::propagateScopeLookupCall(const QString &functionName, int argc, int argv) { - if (QQmlJSScope::ConstPtr scope = QQmlJSScope::findCurrentQMLScope(m_currentScope)) { + if (QQmlJSScope::ConstPtr scope = QQmlJSScope::findCurrentQMLScope(m_function->qmlScope)) { const auto methods = scope->methods(functionName); if (!methods.isEmpty()) { propagateCall(methods, argc, argv); @@ -921,7 +900,7 @@ void QQmlJSTypePropagator::generate_CallQmlContextPropertyLookup(int index, int { const QString name = m_jsUnitGenerator->lookupName(index); propagateScopeLookupCall(name, argc, argv); - checkDeprecated(m_currentScope, name, true); + checkDeprecated(m_function->qmlScope, name, true); } void QQmlJSTypePropagator::generate_CallWithSpread(int func, int thisObject, int argc, int argv) @@ -1481,7 +1460,7 @@ void QQmlJSTypePropagator::generate_GetTemplateObject(int index) QV4::Moth::ByteCodeHandler::Verdict QQmlJSTypePropagator::startInstruction(QV4::Moth::Instr::Type instr) { - if (m_error->isSet()) + if (m_error->isValid()) return SkipInstruction; if (m_state.jumpTargets.contains(currentInstructionOffset())) @@ -1632,7 +1611,7 @@ void QQmlJSTypePropagator::endInstruction(QV4::Moth::Instr::Type instr) if (m_state.accumulatorOut.isValid()) { setRegister(Accumulator, m_state.accumulatorOut); m_state.accumulatorOut.reset(); - } else if (!m_error->isSet()) { + } else if (!m_error->isValid()) { setError(u"Instruction is expected to populate the accumulator"_qs); return; } @@ -1678,12 +1657,13 @@ QString QQmlJSTypePropagator::registerName(int registerIndex) const { if (registerIndex == Accumulator) return u"accumulator"_qs; - if (registerIndex >= FirstArgument && registerIndex < FirstArgument + m_arguments.count()) { - const int argIdx = registerIndex - FirstArgument; - return u"argument %1 (\"%2\")"_qs.arg(argIdx).arg(m_arguments.at(argIdx).id); + if (registerIndex >= FirstArgument + && registerIndex < FirstArgument + m_function->argumentTypes.count()) { + return u"argument %1"_qs.arg(registerIndex - FirstArgument); } - return u"temporary register %1"_qs.arg(registerIndex - FirstArgument - m_arguments.count()); + return u"temporary register %1"_qs.arg( + registerIndex - FirstArgument - m_function->argumentTypes.count()); } void QQmlJSTypePropagator::setRegister(int index, const QQmlJSRegisterContent &content) @@ -1698,7 +1678,7 @@ void QQmlJSTypePropagator::setRegister(int index, const QQmlJSScope::ConstPtr &c QQmlJSRegisterContent QQmlJSTypePropagator::checkedInputRegister(int reg) { - QQmlJSVirtualRegisters::ConstIterator regIt = m_state.registers.find(reg); + VirtualRegisters::ConstIterator regIt = m_state.registers.find(reg); if (regIt == m_state.registers.constEnd()) { setError(u"Type error: could not infer the type of an expression"_qs); return {}; diff --git a/src/qmlcompiler/qqmljstypepropagator_p.h b/src/qmlcompiler/qqmljstypepropagator_p.h index e40e85054d..718454cfd1 100644 --- a/src/qmlcompiler/qqmljstypepropagator_p.h +++ b/src/qmlcompiler/qqmljstypepropagator_p.h @@ -39,45 +39,20 @@ // // We mean it. -#include <private/qv4bytecodehandler_p.h> -#include <private/qv4compiler_p.h> #include <private/qqmljsast_p.h> #include <private/qqmljsscope_p.h> -#include <private/qqmljslogger_p.h> +#include <private/qqmljscompilepass_p.h> -#include "qqmljstyperesolver_p.h" -#include "qqmljsregistercontent_p.h" QT_BEGIN_NAMESPACE -struct QQmlJSTypePropagator : public QV4::Moth::ByteCodeHandler +struct QQmlJSTypePropagator : public QQmlJSCompilePass { QQmlJSTypePropagator(QV4::Compiler::JSUnitGenerator *unitGenerator, QQmlJSTypeResolver *typeResolver, QQmlJSLogger *logger, QQmlJSTypeInfo *typeInfo = nullptr); - ~QQmlJSTypePropagator(); - auto returnType() const { return m_returnType; } - struct Error - { - QString message; - int instructionOffset; - bool isSet() const { return !message.isEmpty(); } - }; - - struct InstructionAnnotation - { - QQmlJSVirtualRegisters registers; - QQmlJSVirtualRegisters expectedTargetTypesBeforeJump; - }; - - using TypePropagationResult = QHash<int, InstructionAnnotation>; - - TypePropagationResult - propagateTypes(const QV4::Compiler::Context *context, const QQmlJS::AST::BoundNames &arguments, - const QQmlJSScope::ConstPtr &returnType, const QQmlJSScope::ConstPtr &qmlScope, - const QHash<QString, QQmlJSScope::ConstPtr> &addressableScopes, - bool isSignalHandler, Error *error); + InstructionAnnotations run(const Function *m_function, QQmlJS::DiagnosticMessage *error); void generate_Ret() override; void generate_Debug() override; @@ -218,34 +193,18 @@ struct QQmlJSTypePropagator : public QV4::Moth::ByteCodeHandler Verdict startInstruction(QV4::Moth::Instr::Type instr) override; void endInstruction(QV4::Moth::Instr::Type instr) override; - enum RegisterShortcuts { - Accumulator = QV4::CallData::Accumulator, - FirstArgument = QV4::CallData::OffsetCount - }; - private: - void setError(const QString &message) - { - m_error->message = message; - m_error->instructionOffset = currentInstructionOffset(); - } - struct ExpectedRegisterState { int originatingOffset = 0; - QQmlJSVirtualRegisters registers; + VirtualRegisters registers; }; - struct PassState + struct PassState : QQmlJSCompilePass::State { - bool skipInstructionsUntilNextJumpTarget = false; + InstructionAnnotations annotations; QSet<int> jumpTargets; - - QQmlJSVirtualRegisters registers; - QQmlJSRegisterContent accumulatorIn; - QQmlJSRegisterContent accumulatorOut; - - QHash<int, InstructionAnnotation> annotations; + bool skipInstructionsUntilNextJumpTarget = false; bool needsMorePasses = false; }; @@ -273,24 +232,13 @@ private: QQmlJSMetaMethod bestMatchForCall(const QList<QQmlJSMetaMethod> &methods, int argc, int argv, QStringList *errors); - QQmlJS::AST::BoundNames m_arguments; QQmlJSRegisterContent m_returnType; - bool m_isSignalHandler = false; - - QQmlJSTypeResolver *m_typeResolver = nullptr; - QV4::Compiler::JSUnitGenerator *m_jsUnitGenerator = nullptr; - - QQmlJSScope::ConstPtr m_currentScope; - const QV4::Compiler::Context *m_currentContext; - QHash<QString, QQmlJSScope::ConstPtr> m_addressableScopes; - QQmlJSLogger *m_logger; - QQmlJSTypeInfo *m_typeInfo; + QQmlJSTypeInfo *m_typeInfo = nullptr; // Not part of the state, as the back jumps are the reason for running multiple passes QMultiHash<int, ExpectedRegisterState> m_jumpOriginRegisterStateByTargetInstructionOffset; PassState m_state; - Error *m_error = nullptr; }; QT_END_NAMESPACE diff --git a/tools/qmllint/codegen.cpp b/tools/qmllint/codegen.cpp index 4b9137316b..849953f613 100644 --- a/tools/qmllint/codegen.cpp +++ b/tools/qmllint/codegen.cpp @@ -125,7 +125,6 @@ Codegen::compileBinding(const QV4::Compiler::Context *context, const QmlIR::Bind } Function function; - function.contextType = QV4::Compiler::ContextType::Binding; function.qmlScope = m_scopeType; const QString propertyName = m_document->stringAt(irBinding.propertyNameIndex); @@ -183,8 +182,8 @@ Codegen::compileBinding(const QV4::Compiler::Context *context, const QmlIR::Bind auto astNode = m_currentObject->functionsAndExpressions->slowAt(irBinding.value.compiledScriptIndex) ->node; - function.ast = astNode->asFunctionDefinition(); - if (!function.ast) { + auto ast = astNode->asFunctionDefinition(); + if (!ast) { QQmlJS::AST::Statement *stmt = astNode->statementCast(); if (!stmt) { Q_ASSERT(astNode->expressionCast()); @@ -195,20 +194,21 @@ Codegen::compileBinding(const QV4::Compiler::Context *context, const QmlIR::Bind body = body->finish(); QString name = "binding for "; // #### - function.ast = new (m_pool) QQmlJS::AST::FunctionDeclaration(m_pool->newString(name), + ast = new (m_pool) QQmlJS::AST::FunctionDeclaration(m_pool->newString(name), /*formals*/ nullptr, body); - function.ast->lbraceToken = astNode->firstSourceLocation(); - function.ast->functionToken = function.ast->lbraceToken; - function.ast->rbraceToken = astNode->lastSourceLocation(); + ast->lbraceToken = astNode->firstSourceLocation(); + ast->functionToken = ast->lbraceToken; + ast->rbraceToken = astNode->lastSourceLocation(); } - if (!generateFunction(context, &function)) { + QQmlJS::DiagnosticMessage error; + if (!generateFunction(QV4::Compiler::ContextType::Binding, context, ast, &function, &error)) { // If it's a signal and the function just returns a closure, it's harmless. // Otherwise promote the message to warning level. - return diagnose(QStringLiteral("Could not compile binding for %1: %2") - .arg(propertyName, function.error.message), - (isSignal && function.error.type == QtDebugMsg) ? QtDebugMsg : QtWarningMsg, - function.error.loc); + return diagnose( + QStringLiteral("Could not compile binding for %1: %2") + .arg(propertyName, error.message), + (isSignal && error.type == QtDebugMsg) ? QtDebugMsg : QtWarningMsg, error.loc); } return QQmlJSAotFunction {}; @@ -224,17 +224,16 @@ Codegen::compileFunction(const QV4::Compiler::Context *context, const QmlIR::Fun const QString functionName = m_document->stringAt(irFunction.nameIndex); Function function; - function.contextType = QV4::Compiler::ContextType::Function; function.qmlScope = m_scopeType; auto astNode = m_currentObject->functionsAndExpressions->slowAt(irFunction.index)->node; - function.ast = astNode->asFunctionDefinition(); - Q_ASSERT(function.ast); - if (!generateFunction(context, &function)) { + QQmlJS::DiagnosticMessage error; + if (!generateFunction(QV4::Compiler::ContextType::Function, context, + astNode->asFunctionDefinition(), &function, &error)) { return diagnose(QStringLiteral("Could not compile function %1: %2") - .arg(functionName, function.error.message), - QtWarningMsg, function.error.loc); + .arg(functionName, error.message), + QtWarningMsg, error.loc); } return QQmlJSAotFunction {}; @@ -266,31 +265,22 @@ QQmlJS::DiagnosticMessage Codegen::diagnose(const QString &message, QtMsgType ty return QQmlJS::DiagnosticMessage { message, type, location }; } -void Codegen::instructionOffsetToSrcLocation(const QV4::Compiler::Context *context, uint offset, - QQmlJS::SourceLocation *srcLoc) const -{ - Q_ASSERT(context->sourceLocationTable); - const auto &entries = context->sourceLocationTable->entries; - auto item = std::lower_bound(entries.begin(), entries.end(), offset, - [](auto entry, uint offset) { return entry.offset < offset; }); - - Q_ASSERT(item != entries.end()); - *srcLoc = item->location; -} - -bool Codegen::generateFunction(const QV4::Compiler::Context *context, Function *function) const +bool Codegen::generateFunction( + QV4::Compiler::ContextType contextType, + const QV4::Compiler::Context *context, + QQmlJS::AST::FunctionExpression *ast, + Function *function, + QQmlJS::DiagnosticMessage *error) const { - const auto error = [&](const QString &message) { - QQmlJS::DiagnosticMessage msg; - msg.loc = function->ast->firstSourceLocation(); - msg.message = message; - function->error = msg; + const auto fail = [&](const QString &message) { + error->loc = ast->firstSourceLocation(); + error->message = message; return false; }; QQmlJS::AST::BoundNames arguments; - if (function->ast->formals) - arguments = function->ast->formals->formals(); + if (ast->formals) + arguments = ast->formals->formals(); if (function->argumentTypes.isEmpty()) { for (const QQmlJS::AST::BoundName &argument : qAsConst(arguments)) { @@ -301,11 +291,11 @@ bool Codegen::generateFunction(const QV4::Compiler::Context *context, Function * function->argumentTypes.append(rawType); continue; } else { - return error(QStringLiteral("Cannot store the argument type %1.") + return fail(QStringLiteral("Cannot store the argument type %1.") .arg(rawType ? rawType->internalName() : "<unknown>")); } } else { - return error( + return fail( QStringLiteral("Functions without type annotations won't be compiled")); return false; } @@ -315,33 +305,30 @@ bool Codegen::generateFunction(const QV4::Compiler::Context *context, Function * QQmlJSTypePropagator propagator(m_unitGenerator, m_typeResolver.get(), m_logger, m_typeInfo); if (!function->returnType) { - if (function->ast->typeAnnotation) { - function->returnType = m_typeResolver->typeFromAST(function->ast->typeAnnotation->type); + if (ast->typeAnnotation) { + function->returnType = m_typeResolver->typeFromAST(ast->typeAnnotation->type); if (!function->returnType) - return error(QStringLiteral("Cannot resolve return type")); + return fail(QStringLiteral("Cannot resolve return type")); } } if (function->returnType) { if (!m_typeResolver->storedType( function->returnType, QQmlJSTypeResolver::ComponentIsGeneric::Yes)) { - return error(QStringLiteral("Cannot store the return type %1.") + return fail(QStringLiteral("Cannot store the return type %1.") .arg(function->returnType->internalName())); } } - QQmlJSTypePropagator::Error propagationError; - auto typePropagationResult = propagator.propagateTypes( - context, arguments, function->returnType, function->qmlScope, - m_typeResolver->objectsById(), - !function->returnType && function->contextType == QV4::Compiler::ContextType::Binding, - &propagationError); - if (propagationError.isSet()) { - QQmlJS::DiagnosticMessage msg; - msg.type = context->returnsClosure ? QtDebugMsg : QtWarningMsg; - instructionOffsetToSrcLocation(context, propagationError.instructionOffset, &msg.loc); - msg.message = propagationError.message; - function->error = msg; + function->isSignalHandler = !function->returnType + && contextType == QV4::Compiler::ContextType::Binding; + function->addressableScopes = m_typeResolver->objectsById(); + function->code = context->code; + function->sourceLocations = context->sourceLocationTable.get(); + + propagator.run(function, error); + if (error->isValid()) { + error->type = context->returnsClosure ? QtDebugMsg : QtWarningMsg; return false; } diff --git a/tools/qmllint/codegen.h b/tools/qmllint/codegen.h index 9cea0b16d7..1a9dff3ed8 100644 --- a/tools/qmllint/codegen.h +++ b/tools/qmllint/codegen.h @@ -52,6 +52,7 @@ #include <QtQmlCompiler/private/qqmljstyperesolver_p.h> #include <QtQmlCompiler/private/qqmljslogger_p.h> +#include <QtQmlCompiler/private/qqmljscompilepass_p.h> class Codegen : public QQmlJSAotCompiler { @@ -70,20 +71,7 @@ public: QQmlJSAotFunction globalCode() const override; private: - struct Function - { - QV4::Compiler::ContextType contextType = QV4::Compiler::ContextType::Binding; - QQmlJS::AST::FunctionExpression *ast = nullptr; - bool isQPropertyBinding = false; - - QQmlJSScope::ConstPtr returnType; - QList<QQmlJSScope::ConstPtr> argumentTypes; - QQmlJSScope::ConstPtr qmlScope; - - QString generatedCode; - QStringList includes; - QQmlJS::DiagnosticMessage error; - }; + using Function = QQmlJSCompilePass::Function; const QmlIR::Document *m_document = nullptr; const QString m_fileName; @@ -104,9 +92,11 @@ private: QQmlJS::DiagnosticMessage diagnose(const QString &message, QtMsgType type, const QQmlJS::SourceLocation &location); - bool generateFunction(const QV4::Compiler::Context *context, Function *function) const; - void instructionOffsetToSrcLocation(const QV4::Compiler::Context *context, uint offset, - QQmlJS::SourceLocation *srcLoc) const; + bool generateFunction(QV4::Compiler::ContextType contextType, + const QV4::Compiler::Context *context, + QQmlJS::AST::FunctionExpression *ast, + Function *function, + QQmlJS::DiagnosticMessage *error) const; }; #endif |