aboutsummaryrefslogtreecommitdiffstats
path: root/src/qmlcompiler
diff options
context:
space:
mode:
authorMaximilian Goldstein <max.goldstein@qt.io>2022-05-02 16:51:01 +0200
committerMaximilian Goldstein <max.goldstein@qt.io>2022-05-16 18:22:45 +0200
commit2ebee301fd6629f2d5c604fd021c61c15692775f (patch)
tree19a0835b68243cdcb845062840229fff2fe89872 /src/qmlcompiler
parent8c3b77da082b11d4b5839276d655e7f2540f1c32 (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.h1
-rw-r--r--src/qmlcompiler/qqmljsfunctioninitializer.cpp7
-rw-r--r--src/qmlcompiler/qqmljslinter.cpp11
-rw-r--r--src/qmlcompiler/qqmljslintercodegen.cpp3
-rw-r--r--src/qmlcompiler/qqmljslintercodegen_p.h9
-rw-r--r--src/qmlcompiler/qqmljstypepropagator.cpp42
-rw-r--r--src/qmlcompiler/qqmljstypepropagator_p.h9
-rw-r--r--src/qmlcompiler/qqmlsa.cpp232
-rw-r--r--src/qmlcompiler/qqmlsa_p.h83
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 &currentScope = 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;
};
}