diff options
| author | Maximilian Goldstein <max.goldstein@qt.io> | 2022-05-02 16:51:01 +0200 |
|---|---|---|
| committer | Maximilian Goldstein <max.goldstein@qt.io> | 2022-05-16 18:22:45 +0200 |
| commit | 2ebee301fd6629f2d5c604fd021c61c15692775f (patch) | |
| tree | 19a0835b68243cdcb845062840229fff2fe89872 /src/qmlcompiler | |
| parent | 8c3b77da082b11d4b5839276d655e7f2540f1c32 (diff) | |
Reimplement PropertyPass to evaluate bindings
This change reimplements the PropertyPass to be based on bindings
rather than on just iterating over elements. This is a lot less
cost intensive than iterating over all properties regardless of
whether they are actually used. You may still achieve the same
thing with the more flexible element pass, just with the benefit
that you can choose what properties you want to iterate over
instead of iterating over all of them.
To demonstrate the passes usefulness the existing attached property
warnings are ported to use the binding pass and can now also warn
when an attached property is read or written in a context where
it's not supposed to be used.
Fixes: QTBUG-102860
Fixes: QTBUG-102418
Task-number: QTBUG-102859
Change-Id: Iea87a1b05b954429b8bf00fd27b60487940af679
Reviewed-by: Ulf Hermann <ulf.hermann@qt.io>
Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org>
Diffstat (limited to 'src/qmlcompiler')
| -rw-r--r-- | src/qmlcompiler/qqmljscompilepass_p.h | 1 | ||||
| -rw-r--r-- | src/qmlcompiler/qqmljsfunctioninitializer.cpp | 7 | ||||
| -rw-r--r-- | src/qmlcompiler/qqmljslinter.cpp | 11 | ||||
| -rw-r--r-- | src/qmlcompiler/qqmljslintercodegen.cpp | 3 | ||||
| -rw-r--r-- | src/qmlcompiler/qqmljslintercodegen_p.h | 9 | ||||
| -rw-r--r-- | src/qmlcompiler/qqmljstypepropagator.cpp | 42 | ||||
| -rw-r--r-- | src/qmlcompiler/qqmljstypepropagator_p.h | 9 | ||||
| -rw-r--r-- | src/qmlcompiler/qqmlsa.cpp | 232 | ||||
| -rw-r--r-- | src/qmlcompiler/qqmlsa_p.h | 83 |
9 files changed, 322 insertions, 75 deletions
diff --git a/src/qmlcompiler/qqmljscompilepass_p.h b/src/qmlcompiler/qqmljscompilepass_p.h index c16527e286..d395059c1b 100644 --- a/src/qmlcompiler/qqmljscompilepass_p.h +++ b/src/qmlcompiler/qqmljscompilepass_p.h @@ -91,6 +91,7 @@ public: const SourceLocationTable *sourceLocations = nullptr; bool isSignalHandler = false; bool isQPropertyBinding = false; + bool isProperty = false; }; struct State diff --git a/src/qmlcompiler/qqmljsfunctioninitializer.cpp b/src/qmlcompiler/qqmljsfunctioninitializer.cpp index 91a6de790d..2fd9ff6f57 100644 --- a/src/qmlcompiler/qqmljsfunctioninitializer.cpp +++ b/src/qmlcompiler/qqmljsfunctioninitializer.cpp @@ -156,8 +156,8 @@ QQmlJSCompilePass::Function QQmlJSFunctionInitializer::run( QtDebugMsg, bindingLocation, error); } - const bool isProperty = m_objectType->hasProperty(propertyName); - if (!isProperty && QmlIR::IRBuilder::isSignalPropertyName(propertyName)) { + function.isProperty = m_objectType->hasProperty(propertyName); + if (!function.isProperty && QmlIR::IRBuilder::isSignalPropertyName(propertyName)) { const QString signalName = QmlIR::IRBuilder::signalNameFromSignalPropertyName(propertyName); if (signalName.endsWith(u"Changed"_s) @@ -180,9 +180,8 @@ QQmlJSCompilePass::Function QQmlJSFunctionInitializer::run( } } - if (!function.isSignalHandler) { - if (!isProperty) { + if (!function.isProperty) { diagnose(u"Could not compile binding for %1: The property does not exist"_s.arg( propertyName), QtWarningMsg, bindingLocation, error); diff --git a/src/qmlcompiler/qqmljslinter.cpp b/src/qmlcompiler/qqmljslinter.cpp index ab78b28f66..8f475790e1 100644 --- a/src/qmlcompiler/qqmljslinter.cpp +++ b/src/qmlcompiler/qqmljslinter.cpp @@ -41,6 +41,7 @@ #include <QtCore/qlibraryinfo.h> #include <QtCore/qdir.h> #include <QtCore/private/qduplicatetracker_p.h> +#include <QtCore/qscopedpointer.h> #include <QtQmlCompiler/private/qqmlsa_p.h> @@ -504,8 +505,10 @@ QQmlJSLinter::LintResult QQmlJSLinter::lintFile(const QString &filename, QQmlJSLiteralBindingCheck literalCheck; literalCheck.run(&v, &typeResolver); + QScopedPointer<QQmlSA::PassManager> passMan; + if (m_enablePlugins) { - QQmlSA::PassManager passMan(&v, &typeResolver); + passMan.reset(new QQmlSA::PassManager(&v, &typeResolver)); for (const Plugin &plugin : m_plugins) { if (!plugin.isValid() || !plugin.isEnabled()) @@ -513,10 +516,10 @@ QQmlJSLinter::LintResult QQmlJSLinter::lintFile(const QString &filename, QQmlSA::LintPlugin *instance = plugin.m_instance; Q_ASSERT(instance); - instance->registerPasses(&passMan, v.result()); + instance->registerPasses(passMan.get(), v.result()); } - passMan.analyze(v.result()); + passMan->analyze(v.result()); } success = !m_logger->hasWarnings() && !m_logger->hasErrors(); @@ -537,6 +540,8 @@ QQmlJSLinter::LintResult QQmlJSLinter::lintFile(const QString &filename, QQmlJSLinterCodegen codegen { &m_importer, resolvedPath, qmldirFiles, m_logger.get(), &typeInfo }; codegen.setTypeResolver(std::move(typeResolver)); + if (passMan) + codegen.setPassManager(passMan.get()); QQmlJSSaveFunction saveFunction = [](const QV4::CompiledData::SaveableUnitPointer &, const QQmlJSAotFunctionMap &, QString *) { return true; }; diff --git a/src/qmlcompiler/qqmljslintercodegen.cpp b/src/qmlcompiler/qqmljslintercodegen.cpp index fd22e95ef9..6e8472d385 100644 --- a/src/qmlcompiler/qqmljslintercodegen.cpp +++ b/src/qmlcompiler/qqmljslintercodegen.cpp @@ -108,7 +108,8 @@ bool QQmlJSLinterCodegen::analyzeFunction(const QV4::Compiler::Context *context, QQmlJSCompilePass::Function *function, QQmlJS::DiagnosticMessage *error) { - QQmlJSTypePropagator propagator(m_unitGenerator, &m_typeResolver, m_logger, m_typeInfo); + QQmlJSTypePropagator propagator(m_unitGenerator, &m_typeResolver, m_logger, m_typeInfo, + m_passManager); QQmlJSCompilePass::InstructionAnnotations annotations = propagator.run(function, error); if (!error->isValid()) { QQmlJSShadowCheck shadowCheck(m_unitGenerator, &m_typeResolver, m_logger); diff --git a/src/qmlcompiler/qqmljslintercodegen_p.h b/src/qmlcompiler/qqmljslintercodegen_p.h index 53a7c04c89..c23a6fb34d 100644 --- a/src/qmlcompiler/qqmljslintercodegen_p.h +++ b/src/qmlcompiler/qqmljslintercodegen_p.h @@ -56,6 +56,10 @@ QT_BEGIN_NAMESPACE +namespace QQmlSA { +class PassManager; +}; + class QQmlJSLinterCodegen : public QQmlJSAotCompiler { public: @@ -77,8 +81,13 @@ public: QQmlJSTypeResolver *typeResolver() { return &m_typeResolver; } + void setPassManager(QQmlSA::PassManager *passManager) { m_passManager = passManager; } + + QQmlSA::PassManager *passManager() { return m_passManager; } + private: QQmlJSTypeInfo *m_typeInfo; + QQmlSA::PassManager *m_passManager = nullptr; bool analyzeFunction(const QV4::Compiler::Context *context, QQmlJSCompilePass::Function *function, QQmlJS::DiagnosticMessage *error); diff --git a/src/qmlcompiler/qqmljstypepropagator.cpp b/src/qmlcompiler/qqmljstypepropagator.cpp index e5e0bb367c..cc733ec283 100644 --- a/src/qmlcompiler/qqmljstypepropagator.cpp +++ b/src/qmlcompiler/qqmljstypepropagator.cpp @@ -29,6 +29,7 @@ #include "qqmljstypepropagator_p.h" #include "qqmljsutils_p.h" +#include "qqmlsa_p.h" #include <private/qv4compilerscanfunctions_p.h> @@ -48,9 +49,12 @@ using namespace Qt::StringLiterals; */ QQmlJSTypePropagator::QQmlJSTypePropagator(const QV4::Compiler::JSUnitGenerator *unitGenerator, - const QQmlJSTypeResolver *typeResolver, QQmlJSLogger *logger, - QQmlJSTypeInfo *typeInfo) - : QQmlJSCompilePass(unitGenerator, typeResolver, logger), m_typeInfo(typeInfo) + const QQmlJSTypeResolver *typeResolver, + QQmlJSLogger *logger, QQmlJSTypeInfo *typeInfo, + QQmlSA::PassManager *passManager) + : QQmlJSCompilePass(unitGenerator, typeResolver, logger), + m_typeInfo(typeInfo), + m_passManager(passManager) { } @@ -121,6 +125,12 @@ void QQmlJSTypePropagator::generate_Ret() addReadAccumulator(m_returnType); } + if (m_passManager != nullptr && m_function->isProperty) { + m_passManager->analyzeBinding(m_function->qmlScope, + m_typeResolver->containedType(m_state.accumulatorIn()), + getCurrentBindingSourceLocation()); + } + m_state.setHasSideEffects(true); m_state.skipInstructionsUntilNextJumpTarget = true; } @@ -557,6 +567,9 @@ void QQmlJSTypePropagator::generate_LoadQmlContextPropertyLookup(int index) // It should really be valid. // We get the generic type from aotContext->loadQmlContextPropertyIdLookup(). setError(u"Cannot determine generic type for "_s + name); + } else if (m_passManager != nullptr) { + m_passManager->analyzeRead(m_function->qmlScope, name, m_function->qmlScope, + getCurrentSourceLocation()); } } @@ -589,6 +602,12 @@ void QQmlJSTypePropagator::generate_StoreNameSloppy(int nameIndex) .arg(m_state.accumulatorIn().descriptiveName(), type.descriptiveName())); } + if (m_passManager != nullptr) { + m_passManager->analyzeWrite(m_function->qmlScope, name, + m_typeResolver->containedType(m_state.accumulatorIn()), + m_function->qmlScope, getCurrentSourceLocation()); + } + m_state.setHasSideEffects(true); addReadAccumulator(type); } @@ -797,6 +816,11 @@ void QQmlJSTypePropagator::propagatePropertyLookup(const QString &propertyName) } } + if (m_passManager != nullptr) { + m_passManager->analyzeRead(m_typeResolver->containedType(m_state.accumulatorIn()), + propertyName, m_function->qmlScope, getCurrentSourceLocation()); + } + switch (m_state.accumulatorOut().variant()) { case QQmlJSRegisterContent::ObjectEnum: case QQmlJSRegisterContent::ExtensionObjectEnum: @@ -863,6 +887,12 @@ void QQmlJSTypePropagator::generate_StoreProperty(int nameIndex, int base) return; } + if (m_passManager != nullptr) { + m_passManager->analyzeWrite(m_typeResolver->containedType(callBase), propertyName, + m_typeResolver->containedType(m_state.accumulatorIn()), + m_function->qmlScope, getCurrentSourceLocation()); + } + m_state.setHasSideEffects(true); addReadAccumulator(property); addReadRegister(base, callBase); @@ -982,6 +1012,12 @@ void QQmlJSTypePropagator::generate_CallProperty(int nameIndex, int base, int ar checkDeprecated(m_typeResolver->containedType(callBase), propertyName, true); + if (m_passManager != nullptr) { + // TODO: Should there be an analyzeCall() in the future? (w. corresponding onCall in Pass) + m_passManager->analyzeRead(m_typeResolver->containedType(m_state.accumulatorIn()), + propertyName, m_function->qmlScope, getCurrentSourceLocation()); + } + addReadRegister(base, callBase); propagateCall(member.method(), argc, argv); } diff --git a/src/qmlcompiler/qqmljstypepropagator_p.h b/src/qmlcompiler/qqmljstypepropagator_p.h index 323d07a595..ac6937da67 100644 --- a/src/qmlcompiler/qqmljstypepropagator_p.h +++ b/src/qmlcompiler/qqmljstypepropagator_p.h @@ -43,14 +43,18 @@ #include <private/qqmljsscope_p.h> #include <private/qqmljscompilepass_p.h> - QT_BEGIN_NAMESPACE +namespace QQmlSA { +class PassManager; +}; + struct Q_QMLCOMPILER_PRIVATE_EXPORT QQmlJSTypePropagator : public QQmlJSCompilePass { QQmlJSTypePropagator(const QV4::Compiler::JSUnitGenerator *unitGenerator, const QQmlJSTypeResolver *typeResolver, QQmlJSLogger *logger, - QQmlJSTypeInfo *typeInfo = nullptr); + QQmlJSTypeInfo *typeInfo = nullptr, + QQmlSA::PassManager *passManager = nullptr); InstructionAnnotations run(const Function *m_function, QQmlJS::DiagnosticMessage *error); @@ -246,6 +250,7 @@ private: QQmlJSRegisterContent m_returnType; QQmlJSTypeInfo *m_typeInfo = nullptr; + QQmlSA::PassManager *m_passManager = nullptr; // Not part of the state, as the back jumps are the reason for running multiple passes QMultiHash<int, ExpectedRegisterState> m_jumpOriginRegisterStateByTargetInstructionOffset; diff --git a/src/qmlcompiler/qqmlsa.cpp b/src/qmlcompiler/qqmlsa.cpp index f86b3f0704..184ac79ee0 100644 --- a/src/qmlcompiler/qqmlsa.cpp +++ b/src/qmlcompiler/qqmlsa.cpp @@ -67,24 +67,6 @@ Element GenericPass::resolveType(QAnyStringView moduleName, QAnyStringView typeN return module[typeName.toString()].scope; } -void SimplePropertyPass::run(const QQmlJSMetaProperty &property, - const QList<QQmlJSMetaPropertyBinding> &bindings) -{ - if (!bindings.isEmpty()) - run(property, bindings.constFirst()); - else - run(property, {}); -} - -bool SimplePropertyPass::shouldRun(const Element &element, const QQmlJSMetaProperty &property, - const QList<QQmlJSMetaPropertyBinding> &bindings) -{ - if (!bindings.isEmpty()) - return shouldRun(element, property, bindings.constFirst()); - else - return shouldRun(element, property, {}); -} - /*! * \brief PassManager::registerElementPass registers ElementPass with the pass manager. @@ -95,9 +77,72 @@ void PassManager::registerElementPass(std::unique_ptr<ElementPass> pass) m_elementPasses.push_back(std::move(pass)); } -void PassManager::registerPropertyPass(std::unique_ptr<PropertyPass> pass) +enum LookupMode { Register, Lookup }; +static QString lookupName(const QQmlSA::Element &element, LookupMode mode = Lookup) +{ + QString name; + if (element.isNull() || element->internalName().isEmpty()) { + // Bail out with an invalid name, this type is so screwed up we can't do anything reasonable + // with it We should have warned about it in another plac + if (element.isNull() || element->baseType().isNull()) + return u"$INVALID$"_s; + name = element->baseType()->internalName(); + } else { + name = element->internalName(); + } + + const QString filePath = + (mode == Register || !element->baseType() ? element : element->baseType())->filePath(); + + if (element->isComposite() && !filePath.endsWith(u".h")) + name += u'@' + filePath; + return name; +} + +bool PassManager::registerPropertyPass(std::shared_ptr<PropertyPass> pass, + QAnyStringView moduleName, QAnyStringView typeName, + QAnyStringView propertyName) +{ + QString name; + if (!moduleName.isEmpty() && !typeName.isEmpty()) { + auto typeImporter = m_visitor->importer(); + auto module = typeImporter->importModule(moduleName.toString()); + auto element = module[typeName.toString()].scope; + + if (element.isNull()) + return false; + + name = lookupName(element, Register); + } + m_propertyPasses.insert({ std::make_pair<>(name, propertyName.toString()), std::move(pass) }); + + return true; +} + +void PassManager::addBindingSourceLocations(const Element &element, const Element &scope, + const QString prefix, bool isAttached) { - m_propertyPasses.push_back(std::move(pass)); + const Element ¤tScope = scope.isNull() ? element : scope; + const auto ownBindings = currentScope->ownPropertyBindings(); + for (const auto &binding : ownBindings.values()) { + switch (binding.bindingType()) { + case QQmlJSMetaPropertyBinding::GroupProperty: + addBindingSourceLocations(element, binding.groupType(), + prefix + binding.propertyName() + u'.'); + break; + case QQmlJSMetaPropertyBinding::AttachedProperty: + addBindingSourceLocations(element, binding.attachingType(), + prefix + binding.propertyName() + u'.', true); + break; + default: + m_bindingsByLocation.insert({ binding.sourceLocation().offset, + BindingInfo { prefix + binding.propertyName(), binding, + currentScope, isAttached } }); + + if (binding.bindingType() != QQmlJSMetaPropertyBinding::Script) + analyzeBinding(element, QQmlSA::Element(), binding.sourceLocation()); + } + } } void PassManager::analyze(const Element &root) @@ -106,16 +151,11 @@ void PassManager::analyze(const Element &root) runStack.push_back(root); while (!runStack.isEmpty()) { auto element = runStack.takeLast(); + addBindingSourceLocations(element); for (auto &elementPass : m_elementPasses) if (elementPass->shouldRun(element)) elementPass->run(element); const auto ownPropertyBindings = element->ownPropertyBindings(); - for (auto it = ownPropertyBindings.keyBegin(); it != ownPropertyBindings.keyEnd(); ++it) { - const auto bindings = element->propertyBindings(*it); - for (auto &propertyPass : m_propertyPasses) - if (propertyPass->shouldRun(element, element->property(*it), bindings)) - propertyPass->run(element->property(*it), bindings); - } for (auto it = element->childScopesBegin(); it != element->childScopesEnd(); ++it) { if ((*it)->scopeType() == QQmlJSScope::QMLScope) @@ -124,11 +164,67 @@ void PassManager::analyze(const Element &root) } } +void PassManager::analyzeWrite(const Element &element, QString propertyName, const Element &value, + const Element &writeScope, QQmlJS::SourceLocation location) +{ + for (PropertyPass *pass : findPropertyUsePasses(element, propertyName)) + pass->onWrite(element, propertyName, value, writeScope, location); +} + +void PassManager::analyzeRead(const Element &element, QString propertyName, + const Element &readScope, QQmlJS::SourceLocation location) +{ + for (PropertyPass *pass : findPropertyUsePasses(element, propertyName)) + pass->onRead(element, propertyName, readScope, location); +} + +void PassManager::analyzeBinding(const Element &element, const QQmlSA::Element &value, + QQmlJS::SourceLocation location) +{ + const auto info = m_bindingsByLocation.find(location.offset); + + // If there's no matching binding that means we're in a nested Ret somewhere inside an + // expression + if (info == m_bindingsByLocation.end()) + return; + + const QQmlSA::Element &bindingScope = info->second.bindingScope; + const QQmlJSMetaPropertyBinding &binding = info->second.binding; + const QString &propertyName = info->second.fullPropertyName; + + for (PropertyPass *pass : findPropertyUsePasses(element, propertyName)) + pass->onBinding(element, propertyName, binding, bindingScope, value); + + if (!info->second.isAttached) + return; + + for (PropertyPass *pass : findPropertyUsePasses(bindingScope->baseType(), propertyName)) + pass->onBinding(element, propertyName, binding, bindingScope, value); +} + bool PassManager::hasImportedModule(QAnyStringView module) const { return m_visitor->imports().contains(u"$module$." + module.toString()); } +std::vector<PropertyPass *> PassManager::findPropertyUsePasses(const QQmlSA::Element &element, + const QString &propertyName) +{ + const QString typeName = lookupName(element); + std::vector<PropertyPass *> passes; + for (const auto &key : + { std::make_pair<>(typeName, propertyName), std::make_pair<>(QString(), propertyName), + std::make_pair<>(typeName, QString()) }) { + auto pass = m_propertyPasses.equal_range(key); + if (pass.first == pass.second) + continue; + + for (auto it = pass.first; it != pass.second; it++) + passes.push_back(it->second.get()); + } + return passes; +} + void DebugElementPass::run(const Element &element) { emitWarning(u"Type: " + element->baseTypeName()); if (auto bindings = element->propertyBindings(u"objectName"_s); !bindings.isEmpty()) { @@ -140,23 +236,91 @@ void DebugElementPass::run(const Element &element) { } } -void DebugPropertyPass::run(const QQmlJSMetaProperty &property, - const QList<QQmlJSMetaPropertyBinding> &bindings) +bool ElementPass::shouldRun(const Element &) { - emitWarning(u">> Property name: " + property.propertyName() + u"(binding count: " - + QString::number(bindings.count()) + u')'); + return true; } -bool PropertyPass::shouldRun(const Element &, const QQmlJSMetaProperty &, const QList<QQmlJSMetaPropertyBinding> &) +PropertyPass::PropertyPass(PassManager *manager) : GenericPass(manager) { } + +void PropertyPass::onBinding(const Element &element, const QString &propertyName, + const QQmlJSMetaPropertyBinding &binding, const Element &bindingScope, + const Element &value) { - return true; + Q_UNUSED(element); + Q_UNUSED(propertyName); + Q_UNUSED(binding); + Q_UNUSED(bindingScope); + Q_UNUSED(value); } -bool ElementPass::shouldRun(const Element &) +void PropertyPass::onRead(const Element &element, const QString &propertyName, + const Element &readScope, QQmlJS::SourceLocation location) { - return true; + Q_UNUSED(element); + Q_UNUSED(propertyName); + Q_UNUSED(readScope); + Q_UNUSED(location); +} + +void PropertyPass::onWrite(const Element &element, const QString &propertyName, + const Element &value, const Element &writeScope, + QQmlJS::SourceLocation location) +{ + Q_UNUSED(element); + Q_UNUSED(propertyName); + Q_UNUSED(writeScope); + Q_UNUSED(value); + Q_UNUSED(location); } +DebugPropertyPass::DebugPropertyPass(QQmlSA::PassManager *manager) : QQmlSA::PropertyPass(manager) +{ +} + +void DebugPropertyPass::onRead(const QQmlSA::Element &element, const QString &propertyName, + const QQmlSA::Element &readScope, QQmlJS::SourceLocation location) +{ + emitWarning(u"onRead "_s + + (element->internalName().isEmpty() ? element->baseTypeName() + : element->internalName()) + + u' ' + propertyName + u' ' + readScope->internalName() + u' ' + + QString::number(location.startLine) + u':' + + QString::number(location.startColumn), + location); +} + +void DebugPropertyPass::onBinding(const QQmlSA::Element &element, const QString &propertyName, + const QQmlJSMetaPropertyBinding &binding, + const QQmlSA::Element &bindingScope, const QQmlSA::Element &value) +{ + const auto location = binding.sourceLocation(); + emitWarning(u"onBinding element: '"_s + + (element->internalName().isEmpty() ? element->baseTypeName() + : element->internalName()) + + u"' property: '"_s + propertyName + u"' value: '"_s + + (value.isNull() + ? u"NULL"_s + : (value->internalName().isNull() ? value->baseTypeName() + : value->internalName())) + + u"' binding_scope: '"_s + + (bindingScope->internalName().isEmpty() ? bindingScope->baseTypeName() + : bindingScope->internalName()) + + u"' "_s + QString::number(location.startLine) + u':' + + QString::number(location.startColumn), + location); +} + +void DebugPropertyPass::onWrite(const QQmlSA::Element &element, const QString &propertyName, + const QQmlSA::Element &value, const QQmlSA::Element &writeScope, + QQmlJS::SourceLocation location) +{ + emitWarning(u"onWrite "_s + element->baseTypeName() + u' ' + propertyName + u' ' + + value->internalName() + u' ' + writeScope->internalName() + u' ' + + QString::number(location.startLine) + u':' + + QString::number(location.startColumn), + location); +} } QT_END_NAMESPACE diff --git a/src/qmlcompiler/qqmlsa_p.h b/src/qmlcompiler/qqmlsa_p.h index 9faa1fbcb9..5f2ebcd91a 100644 --- a/src/qmlcompiler/qqmlsa_p.h +++ b/src/qmlcompiler/qqmlsa_p.h @@ -43,12 +43,15 @@ #include <private/qqmljsscope_p.h> +#include <map> +#include <unordered_map> #include <vector> #include <memory> QT_BEGIN_NAMESPACE class QQmlJSTypeResolver; +struct QQmlJSTypePropagator; class QQmlJSImportVisitor; namespace QQmlSA { @@ -85,29 +88,16 @@ public: class Q_QMLCOMPILER_EXPORT PropertyPass : public GenericPass { public: - PropertyPass(PassManager *manager) : GenericPass(manager) { } - - virtual bool shouldRun(const Element &element, const QQmlJSMetaProperty &property, - const QList<QQmlJSMetaPropertyBinding> &bindings); - virtual void run(const QQmlJSMetaProperty &property, - const QList<QQmlJSMetaPropertyBinding> &bindings) = 0; -}; - -class SimplePropertyPass : public PropertyPass -{ -protected: - SimplePropertyPass(PassManager *manager) : PropertyPass(manager) { } - - virtual void run(const QQmlJSMetaProperty &property, - const QQmlJSMetaPropertyBinding &binding) = 0; - virtual bool shouldRun(const Element &element, const QQmlJSMetaProperty &property, - const QQmlJSMetaPropertyBinding &binding) = 0; - -private: - void run(const QQmlJSMetaProperty &property, - const QList<QQmlJSMetaPropertyBinding> &bindings) override; - bool shouldRun(const Element &element, const QQmlJSMetaProperty &property, - const QList<QQmlJSMetaPropertyBinding> &bindings) override; + PropertyPass(PassManager *manager); + + virtual void onBinding(const QQmlSA::Element &element, const QString &propertyName, + const QQmlJSMetaPropertyBinding &binding, + const QQmlSA::Element &bindingScope, const QQmlSA::Element &value); + virtual void onRead(const QQmlSA::Element &element, const QString &propertyName, + const QQmlSA::Element &readScope, QQmlJS::SourceLocation location); + virtual void onWrite(const QQmlSA::Element &element, const QString &propertyName, + const QQmlSA::Element &value, const QQmlSA::Element &writeScope, + QQmlJS::SourceLocation location); }; class Q_QMLCOMPILER_EXPORT LintPlugin @@ -134,14 +124,42 @@ public: Q_UNUSED(m_typeResolver); } void registerElementPass(std::unique_ptr<ElementPass> pass); - void registerPropertyPass(std::unique_ptr<PropertyPass> pass); + bool registerPropertyPass(std::shared_ptr<PropertyPass> pass, QAnyStringView moduleName, + QAnyStringView typeName, + QAnyStringView propertyName = QAnyStringView()); void analyze(const Element &root); bool hasImportedModule(QAnyStringView name) const; private: + friend struct ::QQmlJSTypePropagator; + + std::vector<PropertyPass *> findPropertyUsePasses(const QQmlSA::Element &element, + const QString &propertyName); + + void analyzeWrite(const QQmlSA::Element &element, QString propertyName, + const QQmlSA::Element &value, const QQmlSA::Element &writeScope, + QQmlJS::SourceLocation location); + void analyzeRead(const QQmlSA::Element &element, QString propertyName, + const QQmlSA::Element &readScope, QQmlJS::SourceLocation location); + void analyzeBinding(const QQmlSA::Element &element, const QQmlSA::Element &value, + QQmlJS::SourceLocation location); + + struct BindingInfo + { + QString fullPropertyName; + QQmlJSMetaPropertyBinding binding; + QQmlSA::Element bindingScope; + bool isAttached; + }; + + void addBindingSourceLocations(const QQmlSA::Element &element, + const QQmlSA::Element &scope = QQmlSA::Element(), + const QString prefix = QString(), bool isAttached = false); + std::vector<std::unique_ptr<ElementPass>> m_elementPasses; - std::vector<std::unique_ptr<PropertyPass>> m_propertyPasses; + std::multimap<std::pair<QString, QString>, std::shared_ptr<PropertyPass>> m_propertyPasses; + std::unordered_map<quint32, BindingInfo> m_bindingsByLocation; QQmlJSImportVisitor *m_visitor; QQmlJSTypeResolver *m_typeResolver; }; @@ -151,10 +169,19 @@ class Q_QMLCOMPILER_EXPORT DebugElementPass : public ElementPass void run(const Element &element) override; }; -class Q_QMLCOMPILER_EXPORT DebugPropertyPass : public PropertyPass +class Q_QMLCOMPILER_EXPORT DebugPropertyPass : public QQmlSA::PropertyPass { - virtual void run(const QQmlJSMetaProperty &property, - const QList<QQmlJSMetaPropertyBinding> &bindings) override; +public: + DebugPropertyPass(QQmlSA::PassManager *manager); + + void onRead(const QQmlSA::Element &element, const QString &propertyName, + const QQmlSA::Element &readScope, QQmlJS::SourceLocation location) override; + void onBinding(const QQmlSA::Element &element, const QString &propertyName, + const QQmlJSMetaPropertyBinding &binding, const QQmlSA::Element &bindingScope, + const QQmlSA::Element &value) override; + void onWrite(const QQmlSA::Element &element, const QString &propertyName, + const QQmlSA::Element &value, const QQmlSA::Element &writeScope, + QQmlJS::SourceLocation location) override; }; } |
