diff options
Diffstat (limited to 'src/qml/qml')
-rw-r--r-- | src/qml/qml/qml.pri | 14 | ||||
-rw-r--r-- | src/qml/qml/qqmlirloader.cpp | 205 | ||||
-rw-r--r-- | src/qml/qml/qqmlirloader_p.h | 81 | ||||
-rw-r--r-- | src/qml/qml/qqmlpropertycachecreator.cpp | 101 | ||||
-rw-r--r-- | src/qml/qml/qqmlpropertycachecreator_p.h | 843 | ||||
-rw-r--r-- | src/qml/qml/qqmlpropertyresolver.cpp | 92 | ||||
-rw-r--r-- | src/qml/qml/qqmlpropertyresolver_p.h | 87 | ||||
-rw-r--r-- | src/qml/qml/qqmlpropertyvalidator.cpp | 729 | ||||
-rw-r--r-- | src/qml/qml/qqmlpropertyvalidator_p.h | 91 | ||||
-rw-r--r-- | src/qml/qml/qqmltypecompiler.cpp | 1429 | ||||
-rw-r--r-- | src/qml/qml/qqmltypecompiler_p.h | 347 |
11 files changed, 4017 insertions, 2 deletions
diff --git a/src/qml/qml/qml.pri b/src/qml/qml/qml.pri index 9f79bfacdf..c625743f61 100644 --- a/src/qml/qml/qml.pri +++ b/src/qml/qml/qml.pri @@ -52,7 +52,12 @@ SOURCES += \ $$PWD/qqmlfileselector.cpp \ $$PWD/qqmlobjectcreator.cpp \ $$PWD/qqmldelayedcallqueue.cpp \ - $$PWD/qqmlloggingcategory.cpp + $$PWD/qqmlloggingcategory.cpp \ + $$PWD/qqmlirloader.cpp \ + $$PWD/qqmlpropertyresolver.cpp \ + $$PWD/qqmltypecompiler.cpp \ + $$PWD/qqmlpropertycachecreator.cpp \ + $$PWD/qqmlpropertyvalidator.cpp HEADERS += \ $$PWD/qqmlglobal_p.h \ @@ -132,7 +137,12 @@ HEADERS += \ $$PWD/qqmlfileselector.h \ $$PWD/qqmlobjectcreator_p.h \ $$PWD/qqmldelayedcallqueue_p.h \ - $$PWD/qqmlloggingcategory_p.h + $$PWD/qqmlloggingcategory_p.h \ + $$PWD/qqmlirloader_p.h \ + $$PWD/qqmlpropertyresolver_p.h \ + $$PWD/qqmltypecompiler_p.h \ + $$PWD/qqmlpropertycachecreator_p.h \ + $$PWD/qqmlpropertyvalidator_p.h qtConfig(qml-xml-http-request) { HEADERS += \ diff --git a/src/qml/qml/qqmlirloader.cpp b/src/qml/qml/qqmlirloader.cpp new file mode 100644 index 0000000000..c2eb6da2fa --- /dev/null +++ b/src/qml/qml/qqmlirloader.cpp @@ -0,0 +1,205 @@ +/**************************************************************************** +** +** Copyright (C) 2019 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:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qqmlirloader_p.h" +#include <private/qqmlirbuilder_p.h> + +QT_BEGIN_NAMESPACE + +QQmlIRLoader::QQmlIRLoader(const QV4::CompiledData::Unit *qmlData, QmlIR::Document *output) + : unit(qmlData) + , output(output) +{ + pool = output->jsParserEngine.pool(); +} + +void QQmlIRLoader::load() +{ + output->jsGenerator.stringTable.initializeFromBackingUnit(unit); + + const QV4::CompiledData::QmlUnit *qmlUnit = unit->qmlUnit(); + + for (quint32 i = 0; i < qmlUnit->nImports; ++i) + output->imports << qmlUnit->importAt(i); + + if (unit->flags & QV4::CompiledData::Unit::IsSingleton) { + QmlIR::Pragma *p = New<QmlIR::Pragma>(); + p->location = QV4::CompiledData::Location(); + p->type = QmlIR::Pragma::PragmaSingleton; + output->pragmas << p; + } + + for (uint i = 0; i < qmlUnit->nObjects; ++i) { + const QV4::CompiledData::Object *serializedObject = qmlUnit->objectAt(i); + QmlIR::Object *object = loadObject(serializedObject); + output->objects.append(object); + } +} + +struct FakeExpression : public QQmlJS::AST::NullExpression +{ + FakeExpression(int start, int length) + : location(start, length) + {} + + virtual QQmlJS::AST::SourceLocation firstSourceLocation() const + { return location; } + + virtual QQmlJS::AST::SourceLocation lastSourceLocation() const + { return location; } + +private: + QQmlJS::AST::SourceLocation location; +}; + +QmlIR::Object *QQmlIRLoader::loadObject(const QV4::CompiledData::Object *serializedObject) +{ + QmlIR::Object *object = pool->New<QmlIR::Object>(); + object->init(pool, serializedObject->inheritedTypeNameIndex, serializedObject->idNameIndex); + + object->indexOfDefaultPropertyOrAlias = serializedObject->indexOfDefaultPropertyOrAlias; + object->defaultPropertyIsAlias = serializedObject->defaultPropertyIsAlias; + object->flags = serializedObject->flags; + object->id = serializedObject->id; + object->location = serializedObject->location; + object->locationOfIdProperty = serializedObject->locationOfIdProperty; + + QVector<int> functionIndices; + functionIndices.reserve(serializedObject->nFunctions + serializedObject->nBindings / 2); + + for (uint i = 0; i < serializedObject->nBindings; ++i) { + QmlIR::Binding *b = pool->New<QmlIR::Binding>(); + *static_cast<QV4::CompiledData::Binding*>(b) = serializedObject->bindingTable()[i]; + object->bindings->append(b); + if (b->type == QV4::CompiledData::Binding::Type_Script) { + functionIndices.append(b->value.compiledScriptIndex); + b->value.compiledScriptIndex = functionIndices.count() - 1; + + QmlIR::CompiledFunctionOrExpression *foe = pool->New<QmlIR::CompiledFunctionOrExpression>(); + foe->nameIndex = 0; + + QQmlJS::AST::ExpressionNode *expr; + + if (b->stringIndex != quint32(0)) { + const int start = output->code.length(); + const QString script = output->stringAt(b->stringIndex); + const int length = script.length(); + output->code.append(script); + expr = new (pool) FakeExpression(start, length); + } else + expr = new (pool) QQmlJS::AST::NullExpression(); + foe->node = new (pool) QQmlJS::AST::ExpressionStatement(expr); // dummy + object->functionsAndExpressions->append(foe); + } + } + + Q_ASSERT(object->functionsAndExpressions->count == functionIndices.count()); + + for (uint i = 0; i < serializedObject->nSignals; ++i) { + const QV4::CompiledData::Signal *serializedSignal = serializedObject->signalAt(i); + QmlIR::Signal *s = pool->New<QmlIR::Signal>(); + s->nameIndex = serializedSignal->nameIndex; + s->location = serializedSignal->location; + s->parameters = pool->New<QmlIR::PoolList<QmlIR::SignalParameter> >(); + + for (uint i = 0; i < serializedSignal->nParameters; ++i) { + QmlIR::SignalParameter *p = pool->New<QmlIR::SignalParameter>(); + *static_cast<QV4::CompiledData::Parameter*>(p) = *serializedSignal->parameterAt(i); + s->parameters->append(p); + } + + object->qmlSignals->append(s); + } + + for (uint i = 0; i < serializedObject->nEnums; ++i) { + const QV4::CompiledData::Enum *serializedEnum = serializedObject->enumAt(i); + QmlIR::Enum *e = pool->New<QmlIR::Enum>(); + e->nameIndex = serializedEnum->nameIndex; + e->location = serializedEnum->location; + e->enumValues = pool->New<QmlIR::PoolList<QmlIR::EnumValue> >(); + + for (uint i = 0; i < serializedEnum->nEnumValues; ++i) { + QmlIR::EnumValue *v = pool->New<QmlIR::EnumValue>(); + *static_cast<QV4::CompiledData::EnumValue*>(v) = *serializedEnum->enumValueAt(i); + e->enumValues->append(v); + } + + object->qmlEnums->append(e); + } + + const QV4::CompiledData::Property *serializedProperty = serializedObject->propertyTable(); + for (uint i = 0; i < serializedObject->nProperties; ++i, ++serializedProperty) { + QmlIR::Property *p = pool->New<QmlIR::Property>(); + *static_cast<QV4::CompiledData::Property*>(p) = *serializedProperty; + object->properties->append(p); + } + + { + const QV4::CompiledData::Alias *serializedAlias = serializedObject->aliasTable(); + for (uint i = 0; i < serializedObject->nAliases; ++i, ++serializedAlias) { + QmlIR::Alias *a = pool->New<QmlIR::Alias>(); + *static_cast<QV4::CompiledData::Alias*>(a) = *serializedAlias; + object->aliases->append(a); + } + } + + const quint32_le *functionIdx = serializedObject->functionOffsetTable(); + for (uint i = 0; i < serializedObject->nFunctions; ++i, ++functionIdx) { + QmlIR::Function *f = pool->New<QmlIR::Function>(); + const QV4::CompiledData::Function *compiledFunction = unit->functionAt(*functionIdx); + + functionIndices.append(*functionIdx); + f->index = functionIndices.count() - 1; + f->location = compiledFunction->location; + f->nameIndex = compiledFunction->nameIndex; + + f->formals.allocate(pool, int(compiledFunction->nFormals)); + const quint32_le *formalNameIdx = compiledFunction->formalsTable(); + for (uint i = 0; i < compiledFunction->nFormals; ++i, ++formalNameIdx) + f->formals[i] = *formalNameIdx; + + object->functions->append(f); + } + + object->runtimeFunctionIndices.allocate(pool, functionIndices); + + return object; +} + +QT_END_NAMESPACE diff --git a/src/qml/qml/qqmlirloader_p.h b/src/qml/qml/qqmlirloader_p.h new file mode 100644 index 0000000000..aa303c923f --- /dev/null +++ b/src/qml/qml/qqmlirloader_p.h @@ -0,0 +1,81 @@ +/**************************************************************************** +** +** Copyright (C) 2019 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:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQMLIRLOADER_P_H +#define QQMLIRLOADER_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/qv4compileddata_p.h> +#include <private/qqmljsmemorypool_p.h> + +QT_BEGIN_NAMESPACE + +namespace QmlIR { +struct Document; +struct Object; +} + +struct Q_QML_PRIVATE_EXPORT QQmlIRLoader { + QQmlIRLoader(const QV4::CompiledData::Unit *unit, QmlIR::Document *output); + + void load(); + +private: + QmlIR::Object *loadObject(const QV4::CompiledData::Object *serializedObject); + + template <typename _Tp> _Tp *New() { return pool->New<_Tp>(); } + + const QV4::CompiledData::Unit *unit; + QmlIR::Document *output; + QQmlJS::MemoryPool *pool; +}; + +QT_END_NAMESPACE + +#endif // QQMLIRLOADER_P_H diff --git a/src/qml/qml/qqmlpropertycachecreator.cpp b/src/qml/qml/qqmlpropertycachecreator.cpp new file mode 100644 index 0000000000..bd4f2a0612 --- /dev/null +++ b/src/qml/qml/qqmlpropertycachecreator.cpp @@ -0,0 +1,101 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qqmlpropertycachecreator_p.h" + +#include <private/qqmlengine_p.h> + +QT_BEGIN_NAMESPACE + +QAtomicInt QQmlPropertyCacheCreatorBase::classIndexCounter(0); + +QQmlBindingInstantiationContext::QQmlBindingInstantiationContext(int referencingObjectIndex, const QV4::CompiledData::Binding *instantiatingBinding, + const QString &instantiatingPropertyName, QQmlPropertyCache *referencingObjectPropertyCache) + : referencingObjectIndex(referencingObjectIndex) + , instantiatingBinding(instantiatingBinding) + , instantiatingPropertyName(instantiatingPropertyName) + , referencingObjectPropertyCache(referencingObjectPropertyCache) +{ +} + +bool QQmlBindingInstantiationContext::resolveInstantiatingProperty() +{ + if (!instantiatingBinding || instantiatingBinding->type != QV4::CompiledData::Binding::Type_GroupProperty) + return true; + + Q_ASSERT(referencingObjectIndex >= 0); + Q_ASSERT(referencingObjectPropertyCache); + Q_ASSERT(instantiatingBinding->propertyNameIndex != 0); + + bool notInRevision = false; + instantiatingProperty = QQmlPropertyResolver(referencingObjectPropertyCache) + .property(instantiatingPropertyName, ¬InRevision, + QQmlPropertyResolver::IgnoreRevision); + return instantiatingProperty != nullptr; +} + +QQmlRefPointer<QQmlPropertyCache> QQmlBindingInstantiationContext::instantiatingPropertyCache(QQmlEnginePrivate *enginePrivate) const +{ + if (instantiatingProperty) { + if (instantiatingProperty->isQObject()) { + return enginePrivate->rawPropertyCacheForType(instantiatingProperty->propType(), instantiatingProperty->typeMinorVersion()); + } else if (const QMetaObject *vtmo = QQmlValueTypeFactory::metaObjectForMetaType(instantiatingProperty->propType())) { + return enginePrivate->cache(vtmo, instantiatingProperty->typeMinorVersion()); + } + } + return QQmlRefPointer<QQmlPropertyCache>(); +} + +void QQmlPendingGroupPropertyBindings::resolveMissingPropertyCaches(QQmlEnginePrivate *enginePrivate, QQmlPropertyCacheVector *propertyCaches) const +{ + for (QQmlBindingInstantiationContext pendingBinding: *this) { + const int groupPropertyObjectIndex = pendingBinding.instantiatingBinding->value.objectIndex; + + if (propertyCaches->at(groupPropertyObjectIndex)) + continue; + + if (!pendingBinding.resolveInstantiatingProperty()) + continue; + + auto cache = pendingBinding.instantiatingPropertyCache(enginePrivate); + propertyCaches->set(groupPropertyObjectIndex, cache); + } +} + +QT_END_NAMESPACE diff --git a/src/qml/qml/qqmlpropertycachecreator_p.h b/src/qml/qml/qqmlpropertycachecreator_p.h new file mode 100644 index 0000000000..28eea27675 --- /dev/null +++ b/src/qml/qml/qqmlpropertycachecreator_p.h @@ -0,0 +1,843 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#ifndef QQMLPROPERTYCACHECREATOR_P_H +#define QQMLPROPERTYCACHECREATOR_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/qqmlvaluetype_p.h> +#include <private/qqmlengine_p.h> +#include <private/qqmlmetaobject_p.h> +#include <private/qqmlpropertyresolver_p.h> + +QT_BEGIN_NAMESPACE + +struct QQmlBindingInstantiationContext { + QQmlBindingInstantiationContext() {} + QQmlBindingInstantiationContext(int referencingObjectIndex, + const QV4::CompiledData::Binding *instantiatingBinding, + const QString &instantiatingPropertyName, + QQmlPropertyCache *referencingObjectPropertyCache); + + bool resolveInstantiatingProperty(); + QQmlRefPointer<QQmlPropertyCache> instantiatingPropertyCache(QQmlEnginePrivate *enginePrivate) const; + + int referencingObjectIndex = -1; + const QV4::CompiledData::Binding *instantiatingBinding = nullptr; + QString instantiatingPropertyName; + QQmlRefPointer<QQmlPropertyCache> referencingObjectPropertyCache; + QQmlPropertyData *instantiatingProperty = nullptr; +}; + +struct QQmlPendingGroupPropertyBindings : public QVector<QQmlBindingInstantiationContext> +{ + void resolveMissingPropertyCaches(QQmlEnginePrivate *enginePrivate, QQmlPropertyCacheVector *propertyCaches) const; +}; + +struct QQmlPropertyCacheCreatorBase +{ + Q_DECLARE_TR_FUNCTIONS(QQmlPropertyCacheCreatorBase) +public: + static QAtomicInt classIndexCounter; +}; + +template <typename ObjectContainer> +class QQmlPropertyCacheCreator : public QQmlPropertyCacheCreatorBase +{ +public: + typedef typename ObjectContainer::CompiledObject CompiledObject; + + QQmlPropertyCacheCreator(QQmlPropertyCacheVector *propertyCaches, + QQmlPendingGroupPropertyBindings *pendingGroupPropertyBindings, + QQmlEnginePrivate *enginePrivate, + const ObjectContainer *objectContainer, const QQmlImports *imports); + + QQmlCompileError buildMetaObjects(); + +protected: + QQmlCompileError buildMetaObjectRecursively(int objectIndex, const QQmlBindingInstantiationContext &context); + QQmlRefPointer<QQmlPropertyCache> propertyCacheForObject(const CompiledObject *obj, const QQmlBindingInstantiationContext &context, QQmlCompileError *error) const; + QQmlCompileError createMetaObject(int objectIndex, const CompiledObject *obj, const QQmlRefPointer<QQmlPropertyCache> &baseTypeCache); + + QString stringAt(int index) const { return objectContainer->stringAt(index); } + + QQmlEnginePrivate * const enginePrivate; + const ObjectContainer * const objectContainer; + const QQmlImports * const imports; + QQmlPropertyCacheVector *propertyCaches; + QQmlPendingGroupPropertyBindings *pendingGroupPropertyBindings; +}; + +template <typename ObjectContainer> +inline QQmlPropertyCacheCreator<ObjectContainer>::QQmlPropertyCacheCreator(QQmlPropertyCacheVector *propertyCaches, + QQmlPendingGroupPropertyBindings *pendingGroupPropertyBindings, + QQmlEnginePrivate *enginePrivate, + const ObjectContainer *objectContainer, const QQmlImports *imports) + : enginePrivate(enginePrivate) + , objectContainer(objectContainer) + , imports(imports) + , propertyCaches(propertyCaches) + , pendingGroupPropertyBindings(pendingGroupPropertyBindings) +{ + propertyCaches->resize(objectContainer->objectCount()); +} + +template <typename ObjectContainer> +inline QQmlCompileError QQmlPropertyCacheCreator<ObjectContainer>::buildMetaObjects() +{ + QQmlBindingInstantiationContext context; + return buildMetaObjectRecursively(/*root object*/0, context); +} + +template <typename ObjectContainer> +inline QQmlCompileError QQmlPropertyCacheCreator<ObjectContainer>::buildMetaObjectRecursively(int objectIndex, const QQmlBindingInstantiationContext &context) +{ + const CompiledObject *obj = objectContainer->objectAt(objectIndex); + + bool needVMEMetaObject = obj->propertyCount() != 0 || obj->aliasCount() != 0 || obj->signalCount() != 0 || obj->functionCount() != 0 || obj->enumCount() != 0; + if (!needVMEMetaObject) { + auto binding = obj->bindingsBegin(); + auto end = obj->bindingsEnd(); + for ( ; binding != end; ++binding) { + if (binding->type == QV4::CompiledData::Binding::Type_Object && (binding->flags & QV4::CompiledData::Binding::IsOnAssignment)) { + // If the on assignment is inside a group property, we need to distinguish between QObject based + // group properties and value type group properties. For the former the base type is derived from + // the property that references us, for the latter we only need a meta-object on the referencing object + // because interceptors can't go to the shared value type instances. + if (context.instantiatingProperty && QQmlValueTypeFactory::isValueType(context.instantiatingProperty->propType())) { + if (!propertyCaches->needsVMEMetaObject(context.referencingObjectIndex)) { + const CompiledObject *obj = objectContainer->objectAt(context.referencingObjectIndex); + auto *typeRef = objectContainer->resolvedType(obj->inheritedTypeNameIndex); + Q_ASSERT(typeRef); + QQmlRefPointer<QQmlPropertyCache> baseTypeCache = typeRef->createPropertyCache(QQmlEnginePrivate::get(enginePrivate)); + QQmlCompileError error = createMetaObject(context.referencingObjectIndex, obj, baseTypeCache); + if (error.isSet()) + return error; + } + } else { + // On assignments are implemented using value interceptors, which require a VME meta object. + needVMEMetaObject = true; + } + break; + } + } + } + + QQmlRefPointer<QQmlPropertyCache> baseTypeCache; + { + QQmlCompileError error; + baseTypeCache = propertyCacheForObject(obj, context, &error); + if (error.isSet()) + return error; + } + + if (baseTypeCache) { + if (needVMEMetaObject) { + QQmlCompileError error = createMetaObject(objectIndex, obj, baseTypeCache); + if (error.isSet()) + return error; + } else { + propertyCaches->set(objectIndex, baseTypeCache); + } + } + + if (QQmlPropertyCache *thisCache = propertyCaches->at(objectIndex)) { + auto binding = obj->bindingsBegin(); + auto end = obj->bindingsEnd(); + for ( ; binding != end; ++binding) + if (binding->type >= QV4::CompiledData::Binding::Type_Object) { + QQmlBindingInstantiationContext context(objectIndex, &(*binding), stringAt(binding->propertyNameIndex), thisCache); + + // Binding to group property where we failed to look up the type of the + // property? Possibly a group property that is an alias that's not resolved yet. + // Let's attempt to resolve it after we're done with the aliases and fill in the + // propertyCaches entry then. + if (!context.resolveInstantiatingProperty()) + pendingGroupPropertyBindings->append(context); + + QQmlCompileError error = buildMetaObjectRecursively(binding->value.objectIndex, context); + if (error.isSet()) + return error; + } + } + + QQmlCompileError noError; + return noError; +} + +template <typename ObjectContainer> +inline QQmlRefPointer<QQmlPropertyCache> QQmlPropertyCacheCreator<ObjectContainer>::propertyCacheForObject(const CompiledObject *obj, const QQmlBindingInstantiationContext &context, QQmlCompileError *error) const +{ + if (context.instantiatingProperty) { + return context.instantiatingPropertyCache(enginePrivate); + } else if (obj->inheritedTypeNameIndex != 0) { + auto *typeRef = objectContainer->resolvedType(obj->inheritedTypeNameIndex); + Q_ASSERT(typeRef); + + if (typeRef->isFullyDynamicType) { + if (obj->propertyCount() > 0 || obj->aliasCount() > 0) { + *error = QQmlCompileError(obj->location, QQmlPropertyCacheCreatorBase::tr("Fully dynamic types cannot declare new properties.")); + return nullptr; + } + if (obj->signalCount() > 0) { + *error = QQmlCompileError(obj->location, QQmlPropertyCacheCreatorBase::tr("Fully dynamic types cannot declare new signals.")); + return nullptr; + } + if (obj->functionCount() > 0) { + *error = QQmlCompileError(obj->location, QQmlPropertyCacheCreatorBase::tr("Fully Dynamic types cannot declare new functions.")); + return nullptr; + } + } + + return typeRef->createPropertyCache(QQmlEnginePrivate::get(enginePrivate)); + } else if (context.instantiatingBinding && context.instantiatingBinding->isAttachedProperty()) { + auto *typeRef = objectContainer->resolvedType( + context.instantiatingBinding->propertyNameIndex); + Q_ASSERT(typeRef); + QQmlType qmltype = typeRef->type; + if (!qmltype.isValid()) { + QString propertyName = stringAt(context.instantiatingBinding->propertyNameIndex); + if (imports->resolveType(propertyName, &qmltype, nullptr, nullptr, nullptr)) { + if (qmltype.isComposite()) { + QQmlRefPointer<QQmlTypeData> tdata = enginePrivate->typeLoader.getType(qmltype.sourceUrl()); + Q_ASSERT(tdata); + Q_ASSERT(tdata->isComplete()); + + auto compilationUnit = tdata->compilationUnit(); + qmltype = QQmlMetaType::qmlType(compilationUnit->metaTypeId); + } + } + } + + const QMetaObject *attachedMo = qmltype.attachedPropertiesType(enginePrivate); + if (!attachedMo) { + *error = QQmlCompileError(context.instantiatingBinding->location, QQmlPropertyCacheCreatorBase::tr("Non-existent attached object")); + return nullptr; + } + return enginePrivate->cache(attachedMo); + } + return nullptr; +} + +template <typename ObjectContainer> +inline QQmlCompileError QQmlPropertyCacheCreator<ObjectContainer>::createMetaObject(int objectIndex, const CompiledObject *obj, const QQmlRefPointer<QQmlPropertyCache> &baseTypeCache) +{ + QQmlRefPointer<QQmlPropertyCache> cache; + cache.adopt(baseTypeCache->copyAndReserve(obj->propertyCount() + obj->aliasCount(), + obj->functionCount() + obj->propertyCount() + obj->aliasCount() + obj->signalCount(), + obj->signalCount() + obj->propertyCount() + obj->aliasCount(), obj->enumCount())); + + propertyCaches->set(objectIndex, cache); + propertyCaches->setNeedsVMEMetaObject(objectIndex); + + struct TypeData { + QV4::CompiledData::Property::Type dtype; + int metaType; + } builtinTypes[] = { + { QV4::CompiledData::Property::Var, QMetaType::QVariant }, + { QV4::CompiledData::Property::Variant, QMetaType::QVariant }, + { QV4::CompiledData::Property::Int, QMetaType::Int }, + { QV4::CompiledData::Property::Bool, QMetaType::Bool }, + { QV4::CompiledData::Property::Real, QMetaType::Double }, + { QV4::CompiledData::Property::String, QMetaType::QString }, + { QV4::CompiledData::Property::Url, QMetaType::QUrl }, + { QV4::CompiledData::Property::Color, QMetaType::QColor }, + { QV4::CompiledData::Property::Font, QMetaType::QFont }, + { QV4::CompiledData::Property::Time, QMetaType::QTime }, + { QV4::CompiledData::Property::Date, QMetaType::QDate }, + { QV4::CompiledData::Property::DateTime, QMetaType::QDateTime }, + { QV4::CompiledData::Property::Rect, QMetaType::QRectF }, + { QV4::CompiledData::Property::Point, QMetaType::QPointF }, + { QV4::CompiledData::Property::Size, QMetaType::QSizeF }, + { QV4::CompiledData::Property::Vector2D, QMetaType::QVector2D }, + { QV4::CompiledData::Property::Vector3D, QMetaType::QVector3D }, + { QV4::CompiledData::Property::Vector4D, QMetaType::QVector4D }, + { QV4::CompiledData::Property::Matrix4x4, QMetaType::QMatrix4x4 }, + { QV4::CompiledData::Property::Quaternion, QMetaType::QQuaternion } +}; + static const uint builtinTypeCount = sizeof(builtinTypes) / sizeof(TypeData); + + QByteArray newClassName; + + if (objectIndex == /*root object*/0) { + const QString path = objectContainer->url().path(); + int lastSlash = path.lastIndexOf(QLatin1Char('/')); + if (lastSlash > -1) { + const QStringRef nameBase = path.midRef(lastSlash + 1, path.length() - lastSlash - 5); + if (!nameBase.isEmpty() && nameBase.at(0).isUpper()) + newClassName = nameBase.toUtf8() + "_QMLTYPE_" + + QByteArray::number(classIndexCounter.fetchAndAddRelaxed(1)); + } + } + if (newClassName.isEmpty()) { + newClassName = QQmlMetaObject(baseTypeCache.data()).className(); + newClassName.append("_QML_"); + newClassName.append(QByteArray::number(classIndexCounter.fetchAndAddRelaxed(1))); + } + + cache->_dynamicClassName = newClassName; + + int varPropCount = 0; + + QQmlPropertyResolver resolver(baseTypeCache); + + auto p = obj->propertiesBegin(); + auto pend = obj->propertiesEnd(); + for ( ; p != pend; ++p) { + if (p->type == QV4::CompiledData::Property::Var) + varPropCount++; + + bool notInRevision = false; + QQmlPropertyData *d = resolver.property(stringAt(p->nameIndex), ¬InRevision); + if (d && d->isFinal()) + return QQmlCompileError(p->location, QQmlPropertyCacheCreatorBase::tr("Cannot override FINAL property")); + } + + auto a = obj->aliasesBegin(); + auto aend = obj->aliasesEnd(); + for ( ; a != aend; ++a) { + bool notInRevision = false; + QQmlPropertyData *d = resolver.property(stringAt(a->nameIndex), ¬InRevision); + if (d && d->isFinal()) + return QQmlCompileError(a->location, QQmlPropertyCacheCreatorBase::tr("Cannot override FINAL property")); + } + + int effectivePropertyIndex = cache->propertyIndexCacheStart; + int effectiveMethodIndex = cache->methodIndexCacheStart; + + // For property change signal override detection. + // We prepopulate a set of signal names which already exist in the object, + // and throw an error if there is a signal/method defined as an override. + QSet<QString> seenSignals; + seenSignals << QStringLiteral("destroyed") << QStringLiteral("parentChanged") << QStringLiteral("objectNameChanged"); + QQmlPropertyCache *parentCache = cache.data(); + while ((parentCache = parentCache->parent())) { + if (int pSigCount = parentCache->signalCount()) { + int pSigOffset = parentCache->signalOffset(); + for (int i = pSigOffset; i < pSigCount; ++i) { + QQmlPropertyData *currPSig = parentCache->signal(i); + // XXX TODO: find a better way to get signal name from the property data :-/ + for (QQmlPropertyCache::StringCache::ConstIterator iter = parentCache->stringCache.begin(); + iter != parentCache->stringCache.end(); ++iter) { + if (currPSig == (*iter).second) { + seenSignals.insert(iter.key()); + break; + } + } + } + } + } + + // Set up notify signals for properties - first normal, then alias + p = obj->propertiesBegin(); + pend = obj->propertiesEnd(); + for ( ; p != pend; ++p) { + auto flags = QQmlPropertyData::defaultSignalFlags(); + + QString changedSigName = stringAt(p->nameIndex) + QLatin1String("Changed"); + seenSignals.insert(changedSigName); + + cache->appendSignal(changedSigName, flags, effectiveMethodIndex++); + } + + a = obj->aliasesBegin(); + aend = obj->aliasesEnd(); + for ( ; a != aend; ++a) { + auto flags = QQmlPropertyData::defaultSignalFlags(); + + QString changedSigName = stringAt(a->nameIndex) + QLatin1String("Changed"); + seenSignals.insert(changedSigName); + + cache->appendSignal(changedSigName, flags, effectiveMethodIndex++); + } + + auto e = obj->enumsBegin(); + auto eend = obj->enumsEnd(); + for ( ; e != eend; ++e) { + const int enumValueCount = e->enumValueCount(); + QVector<QQmlEnumValue> values; + values.reserve(enumValueCount); + + auto enumValue = e->enumValuesBegin(); + auto end = e->enumValuesEnd(); + for ( ; enumValue != end; ++enumValue) + values.append(QQmlEnumValue(stringAt(enumValue->nameIndex), enumValue->value)); + + cache->appendEnum(stringAt(e->nameIndex), values); + } + + // Dynamic signals + auto s = obj->signalsBegin(); + auto send = obj->signalsEnd(); + for ( ; s != send; ++s) { + const int paramCount = s->parameterCount(); + + QList<QByteArray> names; + names.reserve(paramCount); + QVarLengthArray<int, 10> paramTypes(paramCount?(paramCount + 1):0); + + if (paramCount) { + paramTypes[0] = paramCount; + + int i = 0; + auto param = s->parametersBegin(); + auto end = s->parametersEnd(); + for ( ; param != end; ++param, ++i) { + names.append(stringAt(param->nameIndex).toUtf8()); + if (param->type < builtinTypeCount) { + // built-in type + paramTypes[i + 1] = builtinTypes[param->type].metaType; + } else { + // lazily resolved type + Q_ASSERT(param->type == QV4::CompiledData::Property::Custom); + const QString customTypeName = stringAt(param->customTypeNameIndex); + QQmlType qmltype; + if (!imports->resolveType(customTypeName, &qmltype, nullptr, nullptr, nullptr)) + return QQmlCompileError(s->location, QQmlPropertyCacheCreatorBase::tr("Invalid signal parameter type: %1").arg(customTypeName)); + + if (qmltype.isComposite()) { + QQmlRefPointer<QQmlTypeData> tdata = enginePrivate->typeLoader.getType(qmltype.sourceUrl()); + Q_ASSERT(tdata); + Q_ASSERT(tdata->isComplete()); + + auto compilationUnit = tdata->compilationUnit(); + + paramTypes[i + 1] = compilationUnit->metaTypeId; + } else { + paramTypes[i + 1] = qmltype.typeId(); + } + } + } + } + + auto flags = QQmlPropertyData::defaultSignalFlags(); + if (paramCount) + flags.hasArguments = true; + + QString signalName = stringAt(s->nameIndex); + if (seenSignals.contains(signalName)) + return QQmlCompileError(s->location, QQmlPropertyCacheCreatorBase::tr("Duplicate signal name: invalid override of property change signal or superclass signal")); + seenSignals.insert(signalName); + + cache->appendSignal(signalName, flags, effectiveMethodIndex++, + paramCount?paramTypes.constData():nullptr, names); + } + + + // Dynamic slots + auto function = objectContainer->objectFunctionsBegin(obj); + auto fend = objectContainer->objectFunctionsEnd(obj); + for ( ; function != fend; ++function) { + auto flags = QQmlPropertyData::defaultSlotFlags(); + + const QString slotName = stringAt(function->nameIndex); + if (seenSignals.contains(slotName)) + return QQmlCompileError(function->location, QQmlPropertyCacheCreatorBase::tr("Duplicate method name: invalid override of property change signal or superclass signal")); + // Note: we don't append slotName to the seenSignals list, since we don't + // protect against overriding change signals or methods with properties. + + QList<QByteArray> parameterNames; + auto formal = function->formalsBegin(); + auto end = function->formalsEnd(); + for ( ; formal != end; ++formal) { + flags.hasArguments = true; + parameterNames << stringAt(*formal).toUtf8(); + } + + cache->appendMethod(slotName, flags, effectiveMethodIndex++, parameterNames); + } + + + // Dynamic properties + int effectiveSignalIndex = cache->signalHandlerIndexCacheStart; + int propertyIdx = 0; + p = obj->propertiesBegin(); + pend = obj->propertiesEnd(); + for ( ; p != pend; ++p, ++propertyIdx) { + int propertyType = 0; + int propertTypeMinorVersion = 0; + QQmlPropertyData::Flags propertyFlags; + + if (p->type == QV4::CompiledData::Property::Var) { + propertyType = QMetaType::QVariant; + propertyFlags.type = QQmlPropertyData::Flags::VarPropertyType; + } else if (p->type < builtinTypeCount) { + propertyType = builtinTypes[p->type].metaType; + + if (p->type == QV4::CompiledData::Property::Variant) + propertyFlags.type = QQmlPropertyData::Flags::QVariantType; + } else { + Q_ASSERT(p->type == QV4::CompiledData::Property::CustomList || + p->type == QV4::CompiledData::Property::Custom); + + QQmlType qmltype; + if (!imports->resolveType(stringAt(p->customTypeNameIndex), &qmltype, nullptr, nullptr, nullptr)) { + return QQmlCompileError(p->location, QQmlPropertyCacheCreatorBase::tr("Invalid property type")); + } + + Q_ASSERT(qmltype.isValid()); + if (qmltype.isComposite()) { + QQmlRefPointer<QQmlTypeData> tdata = enginePrivate->typeLoader.getType(qmltype.sourceUrl()); + Q_ASSERT(tdata); + Q_ASSERT(tdata->isComplete()); + + auto compilationUnit = tdata->compilationUnit(); + + if (p->type == QV4::CompiledData::Property::Custom) { + propertyType = compilationUnit->metaTypeId; + } else { + propertyType = compilationUnit->listMetaTypeId; + } + } else { + if (p->type == QV4::CompiledData::Property::Custom) { + propertyType = qmltype.typeId(); + propertTypeMinorVersion = qmltype.minorVersion(); + } else { + propertyType = qmltype.qListTypeId(); + } + } + + if (p->type == QV4::CompiledData::Property::Custom) + propertyFlags.type = QQmlPropertyData::Flags::QObjectDerivedType; + else + propertyFlags.type = QQmlPropertyData::Flags::QListType; + } + + if (!(p->flags & QV4::CompiledData::Property::IsReadOnly) && p->type != QV4::CompiledData::Property::CustomList) + propertyFlags.isWritable = true; + + + QString propertyName = stringAt(p->nameIndex); + if (!obj->defaultPropertyIsAlias && propertyIdx == obj->indexOfDefaultPropertyOrAlias) + cache->_defaultPropertyName = propertyName; + cache->appendProperty(propertyName, propertyFlags, effectivePropertyIndex++, + propertyType, propertTypeMinorVersion, effectiveSignalIndex); + + effectiveSignalIndex++; + } + + QQmlCompileError noError; + return noError; +} + +template <typename ObjectContainer> +class QQmlPropertyCacheAliasCreator +{ +public: + typedef typename ObjectContainer::CompiledObject CompiledObject; + + QQmlPropertyCacheAliasCreator(QQmlPropertyCacheVector *propertyCaches, const ObjectContainer *objectContainer); + + void appendAliasPropertiesToMetaObjects(); + + QQmlCompileError appendAliasesToPropertyCache(const CompiledObject &component, int objectIndex); + +private: + void appendAliasPropertiesInMetaObjectsWithinComponent(const CompiledObject &component, int firstObjectIndex); + QQmlCompileError propertyDataForAlias(const CompiledObject &component, const QV4::CompiledData::Alias &alias, int *type, int *rev, QQmlPropertyData::Flags *propertyFlags); + + void collectObjectsWithAliasesRecursively(int objectIndex, QVector<int> *objectsWithAliases) const; + + int objectForId(const CompiledObject &component, int id) const; + + QQmlPropertyCacheVector *propertyCaches; + const ObjectContainer *objectContainer; +}; + +template <typename ObjectContainer> +inline QQmlPropertyCacheAliasCreator<ObjectContainer>::QQmlPropertyCacheAliasCreator(QQmlPropertyCacheVector *propertyCaches, const ObjectContainer *objectContainer) + : propertyCaches(propertyCaches) + , objectContainer(objectContainer) +{ + +} + +template <typename ObjectContainer> +inline void QQmlPropertyCacheAliasCreator<ObjectContainer>::appendAliasPropertiesToMetaObjects() +{ + // skip the root object (index 0) as that one does not have a first object index originating + // from a binding. + for (int i = 1; i < objectContainer->objectCount(); ++i) { + const CompiledObject &component = *objectContainer->objectAt(i); + if (!(component.flags & QV4::CompiledData::Object::IsComponent)) + continue; + + const auto rootBinding = component.bindingsBegin(); + appendAliasPropertiesInMetaObjectsWithinComponent(component, rootBinding->value.objectIndex); + } + + const int rootObjectIndex = 0; + appendAliasPropertiesInMetaObjectsWithinComponent(*objectContainer->objectAt(rootObjectIndex), rootObjectIndex); +} + +template <typename ObjectContainer> +inline void QQmlPropertyCacheAliasCreator<ObjectContainer>::appendAliasPropertiesInMetaObjectsWithinComponent(const CompiledObject &component, int firstObjectIndex) +{ + QVector<int> objectsWithAliases; + collectObjectsWithAliasesRecursively(firstObjectIndex, &objectsWithAliases); + if (objectsWithAliases.isEmpty()) + return; + + const auto allAliasTargetsExist = [this, &component](const CompiledObject &object) { + auto alias = object.aliasesBegin(); + auto end = object.aliasesEnd(); + for ( ; alias != end; ++alias) { + Q_ASSERT(alias->flags & QV4::CompiledData::Alias::Resolved); + + const int targetObjectIndex = objectForId(component, alias->targetObjectId); + Q_ASSERT(targetObjectIndex >= 0); + + if (alias->aliasToLocalAlias) + continue; + + if (alias->encodedMetaPropertyIndex == -1) + continue; + + const QQmlPropertyCache *targetCache = propertyCaches->at(targetObjectIndex); + Q_ASSERT(targetCache); + + int coreIndex = QQmlPropertyIndex::fromEncoded(alias->encodedMetaPropertyIndex).coreIndex(); + QQmlPropertyData *targetProperty = targetCache->property(coreIndex); + if (!targetProperty) + return false; + } + return true; + }; + + do { + QVector<int> pendingObjects; + + for (int objectIndex: qAsConst(objectsWithAliases)) { + const CompiledObject &object = *objectContainer->objectAt(objectIndex); + + if (allAliasTargetsExist(object)) { + appendAliasesToPropertyCache(component, objectIndex); + } else { + pendingObjects.append(objectIndex); + } + + } + qSwap(objectsWithAliases, pendingObjects); + } while (!objectsWithAliases.isEmpty()); +} + +template <typename ObjectContainer> +inline void QQmlPropertyCacheAliasCreator<ObjectContainer>::collectObjectsWithAliasesRecursively(int objectIndex, QVector<int> *objectsWithAliases) const +{ + const CompiledObject &object = *objectContainer->objectAt(objectIndex); + if (object.aliasCount() > 0) + objectsWithAliases->append(objectIndex); + + // Stop at Component boundary + if (object.flags & QV4::CompiledData::Object::IsComponent && objectIndex != /*root object*/0) + return; + + auto binding = object.bindingsBegin(); + auto end = object.bindingsEnd(); + for (; binding != end; ++binding) { + if (binding->type != QV4::CompiledData::Binding::Type_Object + && binding->type != QV4::CompiledData::Binding::Type_AttachedProperty + && binding->type != QV4::CompiledData::Binding::Type_GroupProperty) + continue; + + collectObjectsWithAliasesRecursively(binding->value.objectIndex, objectsWithAliases); + } +} + +template <typename ObjectContainer> +inline QQmlCompileError QQmlPropertyCacheAliasCreator<ObjectContainer>::propertyDataForAlias( + const CompiledObject &component, const QV4::CompiledData::Alias &alias, int *type, int *minorVersion, + QQmlPropertyData::Flags *propertyFlags) +{ + *type = 0; + bool writable = false; + bool resettable = false; + + propertyFlags->isAlias = true; + + if (alias.aliasToLocalAlias) { + const QV4::CompiledData::Alias *lastAlias = &alias; + QVarLengthArray<const QV4::CompiledData::Alias *, 4> seenAliases({lastAlias}); + + do { + const int targetObjectIndex = objectForId(component, lastAlias->targetObjectId); + Q_ASSERT(targetObjectIndex >= 0); + const CompiledObject *targetObject = objectContainer->objectAt(targetObjectIndex); + Q_ASSERT(targetObject); + + auto nextAlias = targetObject->aliasesBegin(); + for (uint i = 0; i < lastAlias->localAliasIndex; ++i) + ++nextAlias; + + const QV4::CompiledData::Alias *targetAlias = &(*nextAlias); + if (seenAliases.contains(targetAlias)) { + return QQmlCompileError(targetAlias->location, + QQmlPropertyCacheCreatorBase::tr("Cyclic alias")); + } + + seenAliases.append(targetAlias); + lastAlias = targetAlias; + } while (lastAlias->aliasToLocalAlias); + + return propertyDataForAlias(component, *lastAlias, type, minorVersion, propertyFlags); + } + + const int targetObjectIndex = objectForId(component, alias.targetObjectId); + Q_ASSERT(targetObjectIndex >= 0); + const CompiledObject &targetObject = *objectContainer->objectAt(targetObjectIndex); + + if (alias.encodedMetaPropertyIndex == -1) { + Q_ASSERT(alias.flags & QV4::CompiledData::Alias::AliasPointsToPointerObject); + auto *typeRef = objectContainer->resolvedType(targetObject.inheritedTypeNameIndex); + if (!typeRef) { + // Can be caused by the alias target not being a valid id or property. E.g.: + // property alias dataValue: dataVal + // invalidAliasComponent { id: dataVal } + return QQmlCompileError(targetObject.location, QQmlPropertyCacheCreatorBase::tr("Invalid alias target")); + } + + if (typeRef->type.isValid()) + *type = typeRef->type.typeId(); + else + *type = typeRef->compilationUnit->metaTypeId; + + *minorVersion = typeRef->minorVersion; + + propertyFlags->type = QQmlPropertyData::Flags::QObjectDerivedType; + } else { + int coreIndex = QQmlPropertyIndex::fromEncoded(alias.encodedMetaPropertyIndex).coreIndex(); + int valueTypeIndex = QQmlPropertyIndex::fromEncoded(alias.encodedMetaPropertyIndex).valueTypeIndex(); + + QQmlPropertyCache *targetCache = propertyCaches->at(targetObjectIndex); + Q_ASSERT(targetCache); + QQmlPropertyData *targetProperty = targetCache->property(coreIndex); + Q_ASSERT(targetProperty); + + *type = targetProperty->propType(); + + writable = targetProperty->isWritable(); + resettable = targetProperty->isResettable(); + + if (valueTypeIndex != -1) { + const QMetaObject *valueTypeMetaObject = QQmlValueTypeFactory::metaObjectForMetaType(*type); + if (valueTypeMetaObject->property(valueTypeIndex).isEnumType()) + *type = QVariant::Int; + else + *type = valueTypeMetaObject->property(valueTypeIndex).userType(); + } else { + if (targetProperty->isEnum()) { + *type = QVariant::Int; + } else { + // Copy type flags + propertyFlags->copyPropertyTypeFlags(targetProperty->flags()); + + if (targetProperty->isVarProperty()) + propertyFlags->type = QQmlPropertyData::Flags::QVariantType; + } + } + } + + propertyFlags->isWritable = !(alias.flags & QV4::CompiledData::Property::IsReadOnly) && writable; + propertyFlags->isResettable = resettable; + return QQmlCompileError(); +} + +template <typename ObjectContainer> +inline QQmlCompileError QQmlPropertyCacheAliasCreator<ObjectContainer>::appendAliasesToPropertyCache( + const CompiledObject &component, int objectIndex) +{ + const CompiledObject &object = *objectContainer->objectAt(objectIndex); + if (!object.aliasCount()) + return QQmlCompileError(); + + QQmlPropertyCache *propertyCache = propertyCaches->at(objectIndex); + Q_ASSERT(propertyCache); + + int effectiveSignalIndex = propertyCache->signalHandlerIndexCacheStart + propertyCache->propertyIndexCache.count(); + int effectivePropertyIndex = propertyCache->propertyIndexCacheStart + propertyCache->propertyIndexCache.count(); + + int aliasIndex = 0; + auto alias = object.aliasesBegin(); + auto end = object.aliasesEnd(); + for ( ; alias != end; ++alias, ++aliasIndex) { + Q_ASSERT(alias->flags & QV4::CompiledData::Alias::Resolved); + + int type = 0; + int minorVersion = 0; + QQmlPropertyData::Flags propertyFlags; + QQmlCompileError error = propertyDataForAlias(component, *alias, &type, &minorVersion, &propertyFlags); + if (error.isSet()) + return error; + + const QString propertyName = objectContainer->stringAt(alias->nameIndex); + + if (object.defaultPropertyIsAlias && aliasIndex == object.indexOfDefaultPropertyOrAlias) + propertyCache->_defaultPropertyName = propertyName; + + propertyCache->appendProperty(propertyName, propertyFlags, effectivePropertyIndex++, + type, minorVersion, effectiveSignalIndex++); + } + + return QQmlCompileError(); +} + +template <typename ObjectContainer> +inline int QQmlPropertyCacheAliasCreator<ObjectContainer>::objectForId(const CompiledObject &component, int id) const +{ + for (quint32 i = 0, count = component.namedObjectsInComponentCount(); i < count; ++i) { + const int candidateIndex = component.namedObjectsInComponentTable()[i]; + const CompiledObject &candidate = *objectContainer->objectAt(candidateIndex); + if (candidate.id == id) + return candidateIndex; + } + return -1; +} + +QT_END_NAMESPACE + +#endif // QQMLPROPERTYCACHECREATOR_P_H diff --git a/src/qml/qml/qqmlpropertyresolver.cpp b/src/qml/qml/qqmlpropertyresolver.cpp new file mode 100644 index 0000000000..90eaca0b90 --- /dev/null +++ b/src/qml/qml/qqmlpropertyresolver.cpp @@ -0,0 +1,92 @@ +/**************************************************************************** +** +** Copyright (C) 2019 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:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qqmlpropertyresolver_p.h" + +QT_BEGIN_NAMESPACE + +QQmlPropertyData *QQmlPropertyResolver::property(const QString &name, bool *notInRevision, + RevisionCheck check) const +{ + if (notInRevision) *notInRevision = false; + + QQmlPropertyData *d = cache->property(name, nullptr, nullptr); + + // Find the first property + while (d && d->isFunction()) + d = cache->overrideData(d); + + if (check != IgnoreRevision && d && !cache->isAllowedInRevision(d)) { + if (notInRevision) *notInRevision = true; + return nullptr; + } else { + return d; + } +} + + +QQmlPropertyData *QQmlPropertyResolver::signal(const QString &name, bool *notInRevision) const +{ + if (notInRevision) *notInRevision = false; + + QQmlPropertyData *d = cache->property(name, nullptr, nullptr); + if (notInRevision) *notInRevision = false; + + while (d && !(d->isFunction())) + d = cache->overrideData(d); + + if (d && !cache->isAllowedInRevision(d)) { + if (notInRevision) *notInRevision = true; + return nullptr; + } else if (d && d->isSignal()) { + return d; + } + + if (name.endsWith(QLatin1String("Changed"))) { + QString propName = name.mid(0, name.length() - static_cast<int>(strlen("Changed"))); + + d = property(propName, notInRevision); + if (d) + return cache->signal(d->notifyIndex()); + } + + return nullptr; +} + +QT_END_NAMESPACE diff --git a/src/qml/qml/qqmlpropertyresolver_p.h b/src/qml/qml/qqmlpropertyresolver_p.h new file mode 100644 index 0000000000..df857f242e --- /dev/null +++ b/src/qml/qml/qqmlpropertyresolver_p.h @@ -0,0 +1,87 @@ +/**************************************************************************** +** +** Copyright (C) 2019 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:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQMLPROPERTYRESOLVER_P_H +#define QQMLPROPERTYRESOLVER_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/qtqmlglobal_p.h> +#include <private/qqmlpropertycache_p.h> +#include <private/qqmlrefcount_p.h> + +QT_BEGIN_NAMESPACE + +struct Q_QML_EXPORT QQmlPropertyResolver +{ + QQmlPropertyResolver(const QQmlRefPointer<QQmlPropertyCache> &cache) + : cache(cache) + {} + + QQmlPropertyData *property(int index) const + { + return cache->property(index); + } + + enum RevisionCheck { + CheckRevision, + IgnoreRevision + }; + + QQmlPropertyData *property(const QString &name, bool *notInRevision = nullptr, + RevisionCheck check = CheckRevision) const; + + // This code must match the semantics of QQmlPropertyPrivate::findSignalByName + QQmlPropertyData *signal(const QString &name, bool *notInRevision) const; + + QQmlRefPointer<QQmlPropertyCache> cache; +}; + +QT_END_NAMESPACE + +#endif // QQMLPROPERTYRESOLVER_P_H diff --git a/src/qml/qml/qqmlpropertyvalidator.cpp b/src/qml/qml/qqmlpropertyvalidator.cpp new file mode 100644 index 0000000000..71d5318652 --- /dev/null +++ b/src/qml/qml/qqmlpropertyvalidator.cpp @@ -0,0 +1,729 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qqmlpropertyvalidator_p.h" + +#include <private/qqmlcustomparser_p.h> +#include <private/qqmlstringconverters_p.h> +#include <private/qqmlpropertyresolver_p.h> +#include <QtCore/qdatetime.h> + +QT_BEGIN_NAMESPACE + +QQmlPropertyValidator::QQmlPropertyValidator(QQmlEnginePrivate *enginePrivate, const QQmlImports &imports, const QQmlRefPointer<QV4::ExecutableCompilationUnit> &compilationUnit) + : enginePrivate(enginePrivate) + , compilationUnit(compilationUnit) + , imports(imports) + , qmlUnit(compilationUnit->unitData()) + , propertyCaches(compilationUnit->propertyCaches) + , bindingPropertyDataPerObject(&compilationUnit->bindingPropertyDataPerObject) +{ + bindingPropertyDataPerObject->resize(compilationUnit->objectCount()); +} + +QVector<QQmlCompileError> QQmlPropertyValidator::validate() +{ + return validateObject(/*root object*/0, /*instantiatingBinding*/nullptr); +} + +typedef QVarLengthArray<const QV4::CompiledData::Binding *, 8> GroupPropertyVector; + +struct BindingFinder +{ + bool operator()(quint32 name, const QV4::CompiledData::Binding *binding) const + { + return name < binding->propertyNameIndex; + } + bool operator()(const QV4::CompiledData::Binding *binding, quint32 name) const + { + return binding->propertyNameIndex < name; + } + bool operator()(const QV4::CompiledData::Binding *lhs, const QV4::CompiledData::Binding *rhs) const + { + return lhs->propertyNameIndex < rhs->propertyNameIndex; + } +}; + +QVector<QQmlCompileError> QQmlPropertyValidator::validateObject(int objectIndex, const QV4::CompiledData::Binding *instantiatingBinding, bool populatingValueTypeGroupProperty) const +{ + const QV4::CompiledData::Object *obj = compilationUnit->objectAt(objectIndex); + + if (obj->flags & QV4::CompiledData::Object::IsComponent) { + Q_ASSERT(obj->nBindings == 1); + const QV4::CompiledData::Binding *componentBinding = obj->bindingTable(); + Q_ASSERT(componentBinding->type == QV4::CompiledData::Binding::Type_Object); + return validateObject(componentBinding->value.objectIndex, componentBinding); + } + + QQmlPropertyCache *propertyCache = propertyCaches.at(objectIndex); + if (!propertyCache) + return QVector<QQmlCompileError>(); + + QQmlCustomParser *customParser = nullptr; + if (auto typeRef = resolvedType(obj->inheritedTypeNameIndex)) { + if (typeRef->type.isValid()) + customParser = typeRef->type.customParser(); + } + + QList<const QV4::CompiledData::Binding*> customBindings; + + // Collect group properties first for sanity checking + // vector values are sorted by property name string index. + GroupPropertyVector groupProperties; + const QV4::CompiledData::Binding *binding = obj->bindingTable(); + for (quint32 i = 0; i < obj->nBindings; ++i, ++binding) { + if (!binding->isGroupProperty()) + continue; + + if (binding->flags & QV4::CompiledData::Binding::IsOnAssignment) + continue; + + if (populatingValueTypeGroupProperty) { + return recordError(binding->location, tr("Property assignment expected")); + } + + GroupPropertyVector::const_iterator pos = std::lower_bound(groupProperties.constBegin(), groupProperties.constEnd(), binding->propertyNameIndex, BindingFinder()); + groupProperties.insert(pos, binding); + } + + QQmlPropertyResolver propertyResolver(propertyCache); + + QString defaultPropertyName; + QQmlPropertyData *defaultProperty = nullptr; + if (obj->indexOfDefaultPropertyOrAlias != -1) { + QQmlPropertyCache *cache = propertyCache->parent(); + defaultPropertyName = cache->defaultPropertyName(); + defaultProperty = cache->defaultProperty(); + } else { + defaultPropertyName = propertyCache->defaultPropertyName(); + defaultProperty = propertyCache->defaultProperty(); + } + + QV4::CompiledData::BindingPropertyData collectedBindingPropertyData(obj->nBindings); + + binding = obj->bindingTable(); + for (quint32 i = 0; i < obj->nBindings; ++i, ++binding) { + QString name = stringAt(binding->propertyNameIndex); + + if (customParser) { + if (binding->type == QV4::CompiledData::Binding::Type_AttachedProperty) { + if (customParser->flags() & QQmlCustomParser::AcceptsAttachedProperties) { + customBindings << binding; + continue; + } + } else if (QmlIR::IRBuilder::isSignalPropertyName(name) + && !(customParser->flags() & QQmlCustomParser::AcceptsSignalHandlers)) { + customBindings << binding; + continue; + } + } + + bool bindingToDefaultProperty = false; + bool isGroupProperty = instantiatingBinding && instantiatingBinding->type == QV4::CompiledData::Binding::Type_GroupProperty; + + bool notInRevision = false; + QQmlPropertyData *pd = nullptr; + if (!name.isEmpty()) { + if (binding->flags & QV4::CompiledData::Binding::IsSignalHandlerExpression + || binding->flags & QV4::CompiledData::Binding::IsSignalHandlerObject) { + pd = propertyResolver.signal(name, ¬InRevision); + } else { + pd = propertyResolver.property(name, ¬InRevision, + QQmlPropertyResolver::CheckRevision); + } + + if (notInRevision) { + QString typeName = stringAt(obj->inheritedTypeNameIndex); + auto *objectType = resolvedType(obj->inheritedTypeNameIndex); + if (objectType && objectType->type.isValid()) { + return recordError(binding->location, tr("\"%1.%2\" is not available in %3 %4.%5.").arg(typeName).arg(name).arg(objectType->type.module()).arg(objectType->majorVersion).arg(objectType->minorVersion)); + } else { + return recordError(binding->location, tr("\"%1.%2\" is not available due to component versioning.").arg(typeName).arg(name)); + } + } + } else { + if (isGroupProperty) + return recordError(binding->location, tr("Cannot assign a value directly to a grouped property")); + + pd = defaultProperty; + name = defaultPropertyName; + bindingToDefaultProperty = true; + } + + if (pd) + collectedBindingPropertyData[i] = pd; + + if (name.constData()->isUpper() && !binding->isAttachedProperty()) { + QQmlType type; + QQmlImportNamespace *typeNamespace = nullptr; + imports.resolveType(stringAt(binding->propertyNameIndex), &type, nullptr, nullptr, &typeNamespace); + if (typeNamespace) + return recordError(binding->location, tr("Invalid use of namespace")); + return recordError(binding->location, tr("Invalid attached object assignment")); + } + + if (binding->type >= QV4::CompiledData::Binding::Type_Object && (pd || binding->isAttachedProperty())) { + const bool populatingValueTypeGroupProperty + = pd + && QQmlValueTypeFactory::metaObjectForMetaType(pd->propType()) + && !(binding->flags & QV4::CompiledData::Binding::IsOnAssignment); + const QVector<QQmlCompileError> subObjectValidatorErrors + = validateObject(binding->value.objectIndex, binding, + populatingValueTypeGroupProperty); + if (!subObjectValidatorErrors.isEmpty()) + return subObjectValidatorErrors; + } + + // Signal handlers were resolved and checked earlier in the signal handler conversion pass. + if (binding->flags & QV4::CompiledData::Binding::IsSignalHandlerExpression + || binding->flags & QV4::CompiledData::Binding::IsSignalHandlerObject) + continue; + + if (binding->type == QV4::CompiledData::Binding::Type_AttachedProperty) { + if (instantiatingBinding && (instantiatingBinding->isAttachedProperty() || instantiatingBinding->isGroupProperty())) { + return recordError(binding->location, tr("Attached properties cannot be used here")); + } + continue; + } + + if (pd) { + GroupPropertyVector::const_iterator assignedGroupProperty = std::lower_bound(groupProperties.constBegin(), groupProperties.constEnd(), binding->propertyNameIndex, BindingFinder()); + const bool assigningToGroupProperty = assignedGroupProperty != groupProperties.constEnd() && !(binding->propertyNameIndex < (*assignedGroupProperty)->propertyNameIndex); + + if (!pd->isWritable() + && !pd->isQList() + && !binding->isGroupProperty() + && !(binding->flags & QV4::CompiledData::Binding::InitializerForReadOnlyDeclaration) + ) { + + if (assigningToGroupProperty && binding->type < QV4::CompiledData::Binding::Type_Object) + return recordError(binding->valueLocation, tr("Cannot assign a value directly to a grouped property")); + return recordError(binding->valueLocation, tr("Invalid property assignment: \"%1\" is a read-only property").arg(name)); + } + + if (!pd->isQList() && (binding->flags & QV4::CompiledData::Binding::IsListItem)) { + QString error; + if (pd->propType() == qMetaTypeId<QQmlScriptString>()) + error = tr( "Cannot assign multiple values to a script property"); + else + error = tr( "Cannot assign multiple values to a singular property"); + return recordError(binding->valueLocation, error); + } + + if (!bindingToDefaultProperty + && !binding->isGroupProperty() + && !(binding->flags & QV4::CompiledData::Binding::IsOnAssignment) + && assigningToGroupProperty) { + QV4::CompiledData::Location loc = binding->valueLocation; + if (loc < (*assignedGroupProperty)->valueLocation) + loc = (*assignedGroupProperty)->valueLocation; + + if (pd && QQmlValueTypeFactory::isValueType(pd->propType())) + return recordError(loc, tr("Property has already been assigned a value")); + return recordError(loc, tr("Cannot assign a value directly to a grouped property")); + } + + if (binding->type < QV4::CompiledData::Binding::Type_Script) { + QQmlCompileError bindingError = validateLiteralBinding(propertyCache, pd, binding); + if (bindingError.isSet()) + return recordError(bindingError); + } else if (binding->type == QV4::CompiledData::Binding::Type_Object) { + QQmlCompileError bindingError = validateObjectBinding(pd, name, binding); + if (bindingError.isSet()) + return recordError(bindingError); + } else if (binding->isGroupProperty()) { + if (QQmlValueTypeFactory::isValueType(pd->propType())) { + if (QQmlValueTypeFactory::metaObjectForMetaType(pd->propType())) { + if (!pd->isWritable()) { + return recordError(binding->location, tr("Invalid property assignment: \"%1\" is a read-only property").arg(name)); + } + } else { + return recordError(binding->location, tr("Invalid grouped property access")); + } + } else { + if (!enginePrivate->propertyCacheForType(pd->propType())) { + return recordError(binding->location, + tr("Invalid grouped property access: Property \"%1\" with type \"%2\", which is not a value type") + .arg(name) + .arg(QString::fromLatin1(QMetaType::typeName(pd->propType()))) + ); + } + } + } + } else { + if (customParser) { + customBindings << binding; + continue; + } + if (bindingToDefaultProperty) { + return recordError(binding->location, tr("Cannot assign to non-existent default property")); + } else { + return recordError(binding->location, tr("Cannot assign to non-existent property \"%1\"").arg(name)); + } + } + } + + if (obj->idNameIndex) { + if (populatingValueTypeGroupProperty) + return recordError(obj->locationOfIdProperty, tr("Invalid use of id property with a value type")); + + bool notInRevision = false; + collectedBindingPropertyData << propertyResolver.property(QStringLiteral("id"), ¬InRevision); + } + + if (customParser && !customBindings.isEmpty()) { + customParser->clearErrors(); + customParser->validator = this; + customParser->engine = enginePrivate; + customParser->imports = &imports; + customParser->verifyBindings(compilationUnit, customBindings); + customParser->validator = nullptr; + customParser->engine = nullptr; + customParser->imports = (QQmlImports*)nullptr; + QVector<QQmlCompileError> parserErrors = customParser->errors(); + if (!parserErrors.isEmpty()) + return parserErrors; + } + + (*bindingPropertyDataPerObject)[objectIndex] = collectedBindingPropertyData; + + QVector<QQmlCompileError> noError; + return noError; +} + +QQmlCompileError QQmlPropertyValidator::validateLiteralBinding(QQmlPropertyCache *propertyCache, QQmlPropertyData *property, const QV4::CompiledData::Binding *binding) const +{ + if (property->isQList()) { + return QQmlCompileError(binding->valueLocation, tr("Cannot assign primitives to lists")); + } + + QQmlCompileError noError; + + if (property->isEnum()) { + if (binding->flags & QV4::CompiledData::Binding::IsResolvedEnum) + return noError; + + QString value = compilationUnit->bindingValueAsString(binding); + QMetaProperty p = propertyCache->firstCppMetaObject()->property(property->coreIndex()); + bool ok; + if (p.isFlagType()) { + p.enumerator().keysToValue(value.toUtf8().constData(), &ok); + } else + p.enumerator().keyToValue(value.toUtf8().constData(), &ok); + + if (!ok) { + return QQmlCompileError(binding->valueLocation, tr("Invalid property assignment: unknown enumeration")); + } + return noError; + } + + auto warnOrError = [&](const QString &error) { + if (binding->type == QV4::CompiledData::Binding::Type_Null) { + QQmlError warning; + warning.setUrl(compilationUnit->url()); + warning.setLine(binding->valueLocation.line); + warning.setColumn(binding->valueLocation.column); + warning.setDescription(error + tr(" - Assigning null to incompatible properties in QML " + "is deprecated. This will become a compile error in " + "future versions of Qt.")); + enginePrivate->warning(warning); + return noError; + } + return QQmlCompileError(binding->valueLocation, error); + }; + + switch (property->propType()) { + case QMetaType::QVariant: + break; + case QVariant::String: { + if (!binding->evaluatesToString()) { + return warnOrError(tr("Invalid property assignment: string expected")); + } + } + break; + case QVariant::StringList: { + if (!binding->evaluatesToString()) { + return warnOrError(tr("Invalid property assignment: string or string list expected")); + } + } + break; + case QVariant::ByteArray: { + if (binding->type != QV4::CompiledData::Binding::Type_String) { + return warnOrError(tr("Invalid property assignment: byte array expected")); + } + } + break; + case QVariant::Url: { + if (binding->type != QV4::CompiledData::Binding::Type_String) { + return warnOrError(tr("Invalid property assignment: url expected")); + } + } + break; + case QVariant::UInt: { + if (binding->type == QV4::CompiledData::Binding::Type_Number) { + double d = compilationUnit->bindingValueAsNumber(binding); + if (double(uint(d)) == d) + return noError; + } + return warnOrError(tr("Invalid property assignment: unsigned int expected")); + } + break; + case QVariant::Int: { + if (binding->type == QV4::CompiledData::Binding::Type_Number) { + double d = compilationUnit->bindingValueAsNumber(binding); + if (double(int(d)) == d) + return noError; + } + return warnOrError(tr("Invalid property assignment: int expected")); + } + break; + case QMetaType::Float: { + if (binding->type != QV4::CompiledData::Binding::Type_Number) { + return warnOrError(tr("Invalid property assignment: number expected")); + } + } + break; + case QVariant::Double: { + if (binding->type != QV4::CompiledData::Binding::Type_Number) { + return warnOrError(tr("Invalid property assignment: number expected")); + } + } + break; + case QVariant::Color: { + bool ok = false; + QQmlStringConverters::rgbaFromString(compilationUnit->bindingValueAsString(binding), &ok); + if (!ok) { + return warnOrError(tr("Invalid property assignment: color expected")); + } + } + break; +#if QT_CONFIG(datestring) + case QVariant::Date: { + bool ok = false; + QQmlStringConverters::dateFromString(compilationUnit->bindingValueAsString(binding), &ok); + if (!ok) { + return warnOrError(tr("Invalid property assignment: date expected")); + } + } + break; + case QVariant::Time: { + bool ok = false; + QQmlStringConverters::timeFromString(compilationUnit->bindingValueAsString(binding), &ok); + if (!ok) { + return warnOrError(tr("Invalid property assignment: time expected")); + } + } + break; + case QVariant::DateTime: { + bool ok = false; + QQmlStringConverters::dateTimeFromString(compilationUnit->bindingValueAsString(binding), &ok); + if (!ok) { + return warnOrError(tr("Invalid property assignment: datetime expected")); + } + } + break; +#endif // datestring + case QVariant::Point: { + bool ok = false; + QQmlStringConverters::pointFFromString(compilationUnit->bindingValueAsString(binding), &ok); + if (!ok) { + return warnOrError(tr("Invalid property assignment: point expected")); + } + } + break; + case QVariant::PointF: { + bool ok = false; + QQmlStringConverters::pointFFromString(compilationUnit->bindingValueAsString(binding), &ok); + if (!ok) { + return warnOrError(tr("Invalid property assignment: point expected")); + } + } + break; + case QVariant::Size: { + bool ok = false; + QQmlStringConverters::sizeFFromString(compilationUnit->bindingValueAsString(binding), &ok); + if (!ok) { + return warnOrError(tr("Invalid property assignment: size expected")); + } + } + break; + case QVariant::SizeF: { + bool ok = false; + QQmlStringConverters::sizeFFromString(compilationUnit->bindingValueAsString(binding), &ok); + if (!ok) { + return warnOrError(tr("Invalid property assignment: size expected")); + } + } + break; + case QVariant::Rect: { + bool ok = false; + QQmlStringConverters::rectFFromString(compilationUnit->bindingValueAsString(binding), &ok); + if (!ok) { + return warnOrError(tr("Invalid property assignment: rect expected")); + } + } + break; + case QVariant::RectF: { + bool ok = false; + QQmlStringConverters::rectFFromString(compilationUnit->bindingValueAsString(binding), &ok); + if (!ok) { + return warnOrError(tr("Invalid property assignment: point expected")); + } + } + break; + case QVariant::Bool: { + if (binding->type != QV4::CompiledData::Binding::Type_Boolean) { + return warnOrError(tr("Invalid property assignment: boolean expected")); + } + } + break; + case QVariant::Vector2D: { + struct { + float xp; + float yp; + } vec; + if (!QQmlStringConverters::createFromString(QMetaType::QVector2D, compilationUnit->bindingValueAsString(binding), &vec, sizeof(vec))) { + return warnOrError(tr("Invalid property assignment: 2D vector expected")); + } + } + break; + case QVariant::Vector3D: { + struct { + float xp; + float yp; + float zy; + } vec; + if (!QQmlStringConverters::createFromString(QMetaType::QVector3D, compilationUnit->bindingValueAsString(binding), &vec, sizeof(vec))) { + return warnOrError(tr("Invalid property assignment: 3D vector expected")); + } + } + break; + case QVariant::Vector4D: { + struct { + float xp; + float yp; + float zy; + float wp; + } vec; + if (!QQmlStringConverters::createFromString(QMetaType::QVector4D, compilationUnit->bindingValueAsString(binding), &vec, sizeof(vec))) { + return warnOrError(tr("Invalid property assignment: 4D vector expected")); + } + } + break; + case QVariant::Quaternion: { + struct { + float wp; + float xp; + float yp; + float zp; + } vec; + if (!QQmlStringConverters::createFromString(QMetaType::QQuaternion, compilationUnit->bindingValueAsString(binding), &vec, sizeof(vec))) { + return warnOrError(tr("Invalid property assignment: quaternion expected")); + } + } + break; + case QVariant::RegExp: + case QVariant::RegularExpression: + return warnOrError(tr("Invalid property assignment: regular expression expected; use /pattern/ syntax")); + default: { + // generate single literal value assignment to a list property if required + if (property->propType() == qMetaTypeId<QList<qreal> >()) { + if (binding->type != QV4::CompiledData::Binding::Type_Number) { + return warnOrError(tr("Invalid property assignment: number or array of numbers expected")); + } + break; + } else if (property->propType() == qMetaTypeId<QList<int> >()) { + bool ok = (binding->type == QV4::CompiledData::Binding::Type_Number); + if (ok) { + double n = compilationUnit->bindingValueAsNumber(binding); + if (double(int(n)) != n) + ok = false; + } + if (!ok) + return warnOrError(tr("Invalid property assignment: int or array of ints expected")); + break; + } else if (property->propType() == qMetaTypeId<QList<bool> >()) { + if (binding->type != QV4::CompiledData::Binding::Type_Boolean) { + return warnOrError(tr("Invalid property assignment: bool or array of bools expected")); + } + break; + } else if (property->propType() == qMetaTypeId<QList<QUrl> >()) { + if (binding->type != QV4::CompiledData::Binding::Type_String) { + return warnOrError(tr("Invalid property assignment: url or array of urls expected")); + } + break; + } else if (property->propType() == qMetaTypeId<QList<QString> >()) { + if (!binding->evaluatesToString()) { + return warnOrError(tr("Invalid property assignment: string or array of strings expected")); + } + break; + } else if (property->propType() == qMetaTypeId<QJSValue>()) { + break; + } else if (property->propType() == qMetaTypeId<QQmlScriptString>()) { + break; + } else if (property->isQObject() + && binding->type == QV4::CompiledData::Binding::Type_Null) { + break; + } + + // otherwise, try a custom type assignment + QQmlMetaType::StringConverter converter = QQmlMetaType::customStringConverter(property->propType()); + if (!converter) { + return warnOrError(tr("Invalid property assignment: unsupported type \"%1\"").arg(QString::fromLatin1(QMetaType::typeName(property->propType())))); + } + } + break; + } + return noError; +} + +/*! + Returns true if from can be assigned to a (QObject) property of type + to. +*/ +bool QQmlPropertyValidator::canCoerce(int to, QQmlPropertyCache *fromMo) const +{ + QQmlPropertyCache *toMo = enginePrivate->rawPropertyCacheForType(to); + + while (fromMo) { + if (fromMo == toMo) + return true; + fromMo = fromMo->parent(); + } + return false; +} + +QVector<QQmlCompileError> QQmlPropertyValidator::recordError(const QV4::CompiledData::Location &location, const QString &description) const +{ + QVector<QQmlCompileError> errors; + errors.append(QQmlCompileError(location, description)); + return errors; +} + +QVector<QQmlCompileError> QQmlPropertyValidator::recordError(const QQmlCompileError &error) const +{ + QVector<QQmlCompileError> errors; + errors.append(error); + return errors; +} + +QQmlCompileError QQmlPropertyValidator::validateObjectBinding(QQmlPropertyData *property, const QString &propertyName, const QV4::CompiledData::Binding *binding) const +{ + QQmlCompileError noError; + + if (binding->flags & QV4::CompiledData::Binding::IsOnAssignment) { + Q_ASSERT(binding->type == QV4::CompiledData::Binding::Type_Object); + + bool isValueSource = false; + bool isPropertyInterceptor = false; + + const QV4::CompiledData::Object *targetObject = compilationUnit->objectAt(binding->value.objectIndex); + if (auto *typeRef = resolvedType(targetObject->inheritedTypeNameIndex)) { + QQmlRefPointer<QQmlPropertyCache> cache = typeRef->createPropertyCache(QQmlEnginePrivate::get(enginePrivate)); + const QMetaObject *mo = cache->firstCppMetaObject(); + QQmlType qmlType; + while (mo && !qmlType.isValid()) { + qmlType = QQmlMetaType::qmlType(mo); + mo = mo->superClass(); + } + Q_ASSERT(qmlType.isValid()); + + isValueSource = qmlType.propertyValueSourceCast() != -1; + isPropertyInterceptor = qmlType.propertyValueInterceptorCast() != -1; + } + + if (!isValueSource && !isPropertyInterceptor) { + return QQmlCompileError(binding->valueLocation, tr("\"%1\" cannot operate on \"%2\"").arg(stringAt(targetObject->inheritedTypeNameIndex)).arg(propertyName)); + } + + return noError; + } + + if (QQmlMetaType::isInterface(property->propType())) { + // Can only check at instantiation time if the created sub-object successfully casts to the + // target interface. + return noError; + } else if (property->propType() == QMetaType::QVariant || property->propType() == qMetaTypeId<QJSValue>()) { + // We can convert everything to QVariant :) + return noError; + } else if (property->isQList()) { + const int listType = enginePrivate->listType(property->propType()); + if (!QQmlMetaType::isInterface(listType)) { + QQmlPropertyCache *source = propertyCaches.at(binding->value.objectIndex); + if (!canCoerce(listType, source)) { + return QQmlCompileError(binding->valueLocation, tr("Cannot assign object to list property \"%1\"").arg(propertyName)); + } + } + return noError; + } else if (binding->flags & QV4::CompiledData::Binding::IsSignalHandlerObject && property->isFunction()) { + return noError; + } else if (QQmlValueTypeFactory::isValueType(property->propType())) { + auto typeName = QMetaType::typeName(property->propType()); + return QQmlCompileError(binding->location, tr("Can not assign value of type \"%1\" to property \"%2\", expecting an object") + .arg(typeName ? QString::fromLatin1(typeName) : QString::fromLatin1("<unknown type>")) + .arg(propertyName)); + } else if (property->propType() == qMetaTypeId<QQmlScriptString>()) { + return QQmlCompileError(binding->valueLocation, tr("Invalid property assignment: script expected")); + } else { + // We want to use the raw metaObject here as the raw metaobject is the + // actual property type before we applied any extensions that might + // effect the properties on the type, but don't effect assignability + // Using -1 for the minor version ensures that we get the raw metaObject. + QQmlPropertyCache *propertyMetaObject = enginePrivate->rawPropertyCacheForType(property->propType(), -1); + + // Will be true if the assigned type inherits propertyMetaObject + bool isAssignable = false; + // Determine isAssignable value + if (propertyMetaObject) { + QQmlPropertyCache *c = propertyCaches.at(binding->value.objectIndex); + while (c && !isAssignable) { + isAssignable |= c == propertyMetaObject; + c = c->parent(); + } + } + + if (!isAssignable) { + return QQmlCompileError(binding->valueLocation, tr("Cannot assign object of type \"%1\" to property of type \"%2\" as the former is neither the same as the latter nor a sub-class of it.") + .arg(stringAt(compilationUnit->objectAt(binding->value.objectIndex)->inheritedTypeNameIndex)).arg(QLatin1String(QMetaType::typeName(property->propType())))); + } + } + return noError; +} + +QT_END_NAMESPACE diff --git a/src/qml/qml/qqmlpropertyvalidator_p.h b/src/qml/qml/qqmlpropertyvalidator_p.h new file mode 100644 index 0000000000..8244b2df21 --- /dev/null +++ b/src/qml/qml/qqmlpropertyvalidator_p.h @@ -0,0 +1,91 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#ifndef QQMLPROPERTYVALIDATOR_P_H +#define QQMLPROPERTYVALIDATOR_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/qqmltypecompiler_p.h> + +QT_BEGIN_NAMESPACE + +class QQmlPropertyValidator +{ + Q_DECLARE_TR_FUNCTIONS(QQmlPropertyValidator) +public: + QQmlPropertyValidator(QQmlEnginePrivate *enginePrivate, const QQmlImports &imports, const QQmlRefPointer<QV4::ExecutableCompilationUnit> &compilationUnit); + + QVector<QQmlCompileError> validate(); + +private: + QVector<QQmlCompileError> validateObject(int objectIndex, const QV4::CompiledData::Binding *instantiatingBinding, bool populatingValueTypeGroupProperty = false) const; + QQmlCompileError validateLiteralBinding(QQmlPropertyCache *propertyCache, QQmlPropertyData *property, const QV4::CompiledData::Binding *binding) const; + QQmlCompileError validateObjectBinding(QQmlPropertyData *property, const QString &propertyName, const QV4::CompiledData::Binding *binding) const; + + bool canCoerce(int to, QQmlPropertyCache *fromMo) const; + + Q_REQUIRED_RESULT QVector<QQmlCompileError> recordError(const QV4::CompiledData::Location &location, const QString &description) const; + Q_REQUIRED_RESULT QVector<QQmlCompileError> recordError(const QQmlCompileError &error) const; + QString stringAt(int index) const { return compilationUnit->stringAt(index); } + QV4::ResolvedTypeReference *resolvedType(int id) const + { + return compilationUnit->resolvedType(id); + } + + QQmlEnginePrivate *enginePrivate; + QQmlRefPointer<QV4::ExecutableCompilationUnit> compilationUnit; + const QQmlImports &imports; + const QV4::CompiledData::Unit *qmlUnit; + const QQmlPropertyCacheVector &propertyCaches; + + QVector<QV4::CompiledData::BindingPropertyData> * const bindingPropertyDataPerObject; +}; + +QT_END_NAMESPACE + +#endif // QQMLPROPERTYVALIDATOR_P_H diff --git a/src/qml/qml/qqmltypecompiler.cpp b/src/qml/qml/qqmltypecompiler.cpp new file mode 100644 index 0000000000..66320b8db9 --- /dev/null +++ b/src/qml/qml/qqmltypecompiler.cpp @@ -0,0 +1,1429 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qqmltypecompiler_p.h" + +#include <private/qqmlirbuilder_p.h> +#include <private/qqmlobjectcreator_p.h> +#include <private/qqmlcustomparser_p.h> +#include <private/qqmlvmemetaobject_p.h> +#include <private/qqmlcomponent_p.h> +#include <private/qqmlpropertyresolver_p.h> + +#define COMPILE_EXCEPTION(token, desc) \ + { \ + recordError((token)->location, desc); \ + return false; \ + } + +QT_BEGIN_NAMESPACE + +QQmlTypeCompiler::QQmlTypeCompiler(QQmlEnginePrivate *engine, QQmlTypeData *typeData, + QmlIR::Document *parsedQML, const QQmlRefPointer<QQmlTypeNameCache> &typeNameCache, + QV4::ResolvedTypeReferenceMap *resolvedTypeCache, const QV4::CompiledData::DependentTypesHasher &dependencyHasher) + : resolvedTypes(resolvedTypeCache) + , engine(engine) + , typeData(typeData) + , dependencyHasher(dependencyHasher) + , typeNameCache(typeNameCache) + , document(parsedQML) +{ +} + +QQmlRefPointer<QV4::ExecutableCompilationUnit> QQmlTypeCompiler::compile() +{ + // Build property caches and VME meta object data + + for (auto it = resolvedTypes->constBegin(), end = resolvedTypes->constEnd(); + it != end; ++it) { + QQmlCustomParser *customParser = (*it)->type.customParser(); + if (customParser) + customParsers.insert(it.key(), customParser); + } + + QQmlPendingGroupPropertyBindings pendingGroupPropertyBindings; + + + { + QQmlPropertyCacheCreator<QQmlTypeCompiler> propertyCacheBuilder(&m_propertyCaches, &pendingGroupPropertyBindings, + engine, this, imports()); + QQmlCompileError error = propertyCacheBuilder.buildMetaObjects(); + if (error.isSet()) { + recordError(error); + return nullptr; + } + } + + { + QQmlDefaultPropertyMerger merger(this); + merger.mergeDefaultProperties(); + } + + { + SignalHandlerConverter converter(this); + if (!converter.convertSignalHandlerExpressionsToFunctionDeclarations()) + return nullptr; + } + + { + QQmlEnumTypeResolver enumResolver(this); + if (!enumResolver.resolveEnumBindings()) + return nullptr; + } + + { + QQmlCustomParserScriptIndexer cpi(this); + cpi.annotateBindingsWithScriptStrings(); + } + + { + QQmlAliasAnnotator annotator(this); + annotator.annotateBindingsToAliases(); + } + + // Resolve component boundaries and aliases + + { + // Scan for components, determine their scopes and resolve aliases within the scope. + QQmlComponentAndAliasResolver resolver(this); + if (!resolver.resolve()) + return nullptr; + + pendingGroupPropertyBindings.resolveMissingPropertyCaches(engine, &m_propertyCaches); + } + + { + QQmlDeferredAndCustomParserBindingScanner deferredAndCustomParserBindingScanner(this); + if (!deferredAndCustomParserBindingScanner.scanObject()) + return nullptr; + } + + if (!document->javaScriptCompilationUnit.unitData()) { + // Compile JS binding expressions and signal handlers if necessary + { + // We can compile script strings ahead of time, but they must be compiled + // without type optimizations as their scope is always entirely dynamic. + QQmlScriptStringScanner sss(this); + sss.scan(); + } + + document->jsModule.fileName = typeData->urlString(); + document->jsModule.finalUrl = typeData->finalUrlString(); + QmlIR::JSCodeGen v4CodeGenerator(document->code, &document->jsGenerator, &document->jsModule, &document->jsParserEngine, + document->program, &document->jsGenerator.stringTable, engine->v4engine()->illegalNames()); + QQmlJSCodeGenerator jsCodeGen(this, &v4CodeGenerator); + if (!jsCodeGen.generateCodeForComponents()) + return nullptr; + + document->javaScriptCompilationUnit = v4CodeGenerator.generateCompilationUnit(/*generated unit data*/false); + } + + // Generate QML compiled type data structures + + QmlIR::QmlUnitGenerator qmlGenerator; + qmlGenerator.generate(*document, dependencyHasher); + + QQmlRefPointer<QV4::ExecutableCompilationUnit> compilationUnit + = QV4::ExecutableCompilationUnit::create(std::move( + document->javaScriptCompilationUnit)); + compilationUnit->typeNameCache = typeNameCache; + compilationUnit->resolvedTypes = *resolvedTypes; + compilationUnit->propertyCaches = std::move(m_propertyCaches); + Q_ASSERT(compilationUnit->propertyCaches.count() == static_cast<int>(compilationUnit->objectCount())); + + if (errors.isEmpty()) + return compilationUnit; + else + return nullptr; +} + +void QQmlTypeCompiler::recordError(QQmlError error) +{ + error.setUrl(url()); + errors << error; +} + +void QQmlTypeCompiler::recordError(const QV4::CompiledData::Location &location, const QString &description) +{ + QQmlError error; + error.setLine(location.line); + error.setColumn(location.column); + error.setDescription(description); + error.setUrl(url()); + errors << error; +} + +void QQmlTypeCompiler::recordError(const QQmlCompileError &error) +{ + recordError(error.location, error.description); +} + +QString QQmlTypeCompiler::stringAt(int idx) const +{ + return document->stringAt(idx); +} + +int QQmlTypeCompiler::registerString(const QString &str) +{ + return document->jsGenerator.registerString(str); +} + +int QQmlTypeCompiler::registerConstant(QV4::ReturnedValue v) +{ + return document->jsGenerator.registerConstant(v); +} + +const QV4::CompiledData::Unit *QQmlTypeCompiler::qmlUnit() const +{ + return document->javaScriptCompilationUnit.unitData(); +} + +const QQmlImports *QQmlTypeCompiler::imports() const +{ + return &typeData->imports(); +} + +QVector<QmlIR::Object *> *QQmlTypeCompiler::qmlObjects() const +{ + return &document->objects; +} + +void QQmlTypeCompiler::setPropertyCaches(QQmlPropertyCacheVector &&caches) +{ + m_propertyCaches = std::move(caches); + Q_ASSERT(m_propertyCaches.count() > 0); +} + +const QQmlPropertyCacheVector *QQmlTypeCompiler::propertyCaches() const +{ + return &m_propertyCaches; +} + +QQmlPropertyCacheVector &&QQmlTypeCompiler::takePropertyCaches() +{ + return std::move(m_propertyCaches); +} + +QQmlJS::MemoryPool *QQmlTypeCompiler::memoryPool() +{ + return document->jsParserEngine.pool(); +} + +QStringRef QQmlTypeCompiler::newStringRef(const QString &string) +{ + return document->jsParserEngine.newStringRef(string); +} + +const QV4::Compiler::StringTableGenerator *QQmlTypeCompiler::stringPool() const +{ + return &document->jsGenerator.stringTable; +} + +QString QQmlTypeCompiler::bindingAsString(const QmlIR::Object *object, int scriptIndex) const +{ + return object->bindingAsString(document, scriptIndex); +} + +void QQmlTypeCompiler::addImport(const QString &module, const QString &qualifier, int majorVersion, int minorVersion) +{ + const quint32 moduleIdx = registerString(module); + const quint32 qualifierIdx = registerString(qualifier); + + for (int i = 0, count = document->imports.count(); i < count; ++i) { + const QV4::CompiledData::Import *existingImport = document->imports.at(i); + if (existingImport->type == QV4::CompiledData::Import::ImportLibrary + && existingImport->uriIndex == moduleIdx + && existingImport->qualifierIndex == qualifierIdx) + return; + } + auto pool = memoryPool(); + QV4::CompiledData::Import *import = pool->New<QV4::CompiledData::Import>(); + import->type = QV4::CompiledData::Import::ImportLibrary; + import->majorVersion = majorVersion; + import->minorVersion = minorVersion; + import->uriIndex = moduleIdx; + import->qualifierIndex = qualifierIdx; + document->imports.append(import); +} + +QQmlCompilePass::QQmlCompilePass(QQmlTypeCompiler *typeCompiler) + : compiler(typeCompiler) +{ +} + + + +SignalHandlerConverter::SignalHandlerConverter(QQmlTypeCompiler *typeCompiler) + : QQmlCompilePass(typeCompiler) + , enginePrivate(typeCompiler->enginePrivate()) + , qmlObjects(*typeCompiler->qmlObjects()) + , imports(typeCompiler->imports()) + , customParsers(typeCompiler->customParserCache()) + , illegalNames(typeCompiler->enginePrivate()->v4engine()->illegalNames()) + , propertyCaches(typeCompiler->propertyCaches()) +{ +} + +bool SignalHandlerConverter::convertSignalHandlerExpressionsToFunctionDeclarations() +{ + for (int objectIndex = 0; objectIndex < qmlObjects.count(); ++objectIndex) { + const QmlIR::Object * const obj = qmlObjects.at(objectIndex); + QQmlPropertyCache *cache = propertyCaches->at(objectIndex); + if (!cache) + continue; + if (QQmlCustomParser *customParser = customParsers.value(obj->inheritedTypeNameIndex)) { + if (!(customParser->flags() & QQmlCustomParser::AcceptsSignalHandlers)) + continue; + } + const QString elementName = stringAt(obj->inheritedTypeNameIndex); + if (!convertSignalHandlerExpressionsToFunctionDeclarations(obj, elementName, cache)) + return false; + } + return true; +} + +bool SignalHandlerConverter::convertSignalHandlerExpressionsToFunctionDeclarations(const QmlIR::Object *obj, const QString &typeName, QQmlPropertyCache *propertyCache) +{ + // map from signal name defined in qml itself to list of parameters + QHash<QString, QStringList> customSignals; + + for (QmlIR::Binding *binding = obj->firstBinding(); binding; binding = binding->next) { + QString propertyName = stringAt(binding->propertyNameIndex); + // Attached property? + if (binding->type == QV4::CompiledData::Binding::Type_AttachedProperty) { + const QmlIR::Object *attachedObj = qmlObjects.at(binding->value.objectIndex); + auto *typeRef = resolvedType(binding->propertyNameIndex); + QQmlType type = typeRef ? typeRef->type : QQmlType(); + if (!type.isValid()) { + if (imports->resolveType(propertyName, &type, nullptr, nullptr, nullptr)) { + if (type.isComposite()) { + QQmlRefPointer<QQmlTypeData> tdata = enginePrivate->typeLoader.getType(type.sourceUrl()); + Q_ASSERT(tdata); + Q_ASSERT(tdata->isComplete()); + + auto compilationUnit = tdata->compilationUnit(); + type = QQmlMetaType::qmlType(compilationUnit->metaTypeId); + } + } + } + + const QMetaObject *attachedType = type.attachedPropertiesType(enginePrivate); + if (!attachedType) + COMPILE_EXCEPTION(binding, tr("Non-existent attached object")); + QQmlPropertyCache *cache = compiler->enginePrivate()->cache(attachedType); + if (!convertSignalHandlerExpressionsToFunctionDeclarations(attachedObj, propertyName, cache)) + return false; + continue; + } + + if (!QmlIR::IRBuilder::isSignalPropertyName(propertyName)) + continue; + + QQmlPropertyResolver resolver(propertyCache); + + Q_ASSERT(propertyName.startsWith(QLatin1String("on"))); + propertyName.remove(0, 2); + + // Note that the property name could start with any alpha or '_' or '$' character, + // so we need to do the lower-casing of the first alpha character. + for (int firstAlphaIndex = 0; firstAlphaIndex < propertyName.size(); ++firstAlphaIndex) { + if (propertyName.at(firstAlphaIndex).isUpper()) { + propertyName[firstAlphaIndex] = propertyName.at(firstAlphaIndex).toLower(); + break; + } + } + + QList<QString> parameters; + + bool notInRevision = false; + QQmlPropertyData *signal = resolver.signal(propertyName, ¬InRevision); + if (signal) { + int sigIndex = propertyCache->methodIndexToSignalIndex(signal->coreIndex()); + sigIndex = propertyCache->originalClone(sigIndex); + + bool unnamedParameter = false; + + QList<QByteArray> parameterNames = propertyCache->signalParameterNames(sigIndex); + for (int i = 0; i < parameterNames.count(); ++i) { + const QString param = QString::fromUtf8(parameterNames.at(i)); + if (param.isEmpty()) + unnamedParameter = true; + else if (unnamedParameter) { + COMPILE_EXCEPTION(binding, tr("Signal uses unnamed parameter followed by named parameter.")); + } else if (illegalNames.contains(param)) { + COMPILE_EXCEPTION(binding, tr("Signal parameter \"%1\" hides global variable.").arg(param)); + } + parameters += param; + } + } else { + if (notInRevision) { + // Try assinging it as a property later + if (resolver.property(propertyName, /*notInRevision ptr*/nullptr)) + continue; + + const QString &originalPropertyName = stringAt(binding->propertyNameIndex); + + auto *typeRef = resolvedType(obj->inheritedTypeNameIndex); + const QQmlType type = typeRef ? typeRef->type : QQmlType(); + if (type.isValid()) { + COMPILE_EXCEPTION(binding, tr("\"%1.%2\" is not available in %3 %4.%5.").arg(typeName).arg(originalPropertyName).arg(type.module()).arg(type.majorVersion()).arg(type.minorVersion())); + } else { + COMPILE_EXCEPTION(binding, tr("\"%1.%2\" is not available due to component versioning.").arg(typeName).arg(originalPropertyName)); + } + } + + // Try to look up the signal parameter names in the object itself + + // build cache if necessary + if (customSignals.isEmpty()) { + for (const QmlIR::Signal *signal = obj->firstSignal(); signal; signal = signal->next) { + const QString &signalName = stringAt(signal->nameIndex); + customSignals.insert(signalName, signal->parameterStringList(compiler->stringPool())); + } + + for (const QmlIR::Property *property = obj->firstProperty(); property; property = property->next) { + const QString propName = stringAt(property->nameIndex); + customSignals.insert(propName, QStringList()); + } + } + + QHash<QString, QStringList>::ConstIterator entry = customSignals.constFind(propertyName); + if (entry == customSignals.constEnd() && propertyName.endsWith(QLatin1String("Changed"))) { + QString alternateName = propertyName.mid(0, propertyName.length() - static_cast<int>(strlen("Changed"))); + entry = customSignals.constFind(alternateName); + } + + if (entry == customSignals.constEnd()) { + // Can't find even a custom signal, then just don't do anything and try + // keeping the binding as a regular property assignment. + continue; + } + + parameters = entry.value(); + } + + // Binding object to signal means connect the signal to the object's default method. + if (binding->type == QV4::CompiledData::Binding::Type_Object) { + binding->flags |= QV4::CompiledData::Binding::IsSignalHandlerObject; + continue; + } + + if (binding->type != QV4::CompiledData::Binding::Type_Script) { + if (binding->type < QV4::CompiledData::Binding::Type_Script) { + COMPILE_EXCEPTION(binding, tr("Cannot assign a value to a signal (expecting a script to be run)")); + } else { + COMPILE_EXCEPTION(binding, tr("Incorrectly specified signal assignment")); + } + } + + QQmlJS::MemoryPool *pool = compiler->memoryPool(); + + QQmlJS::AST::FormalParameterList *paramList = nullptr; + for (const QString ¶m : qAsConst(parameters)) { + QStringRef paramNameRef = compiler->newStringRef(param); + + QQmlJS::AST::PatternElement *b = new (pool) QQmlJS::AST::PatternElement(paramNameRef, nullptr); + paramList = new (pool) QQmlJS::AST::FormalParameterList(paramList, b); + } + + if (paramList) + paramList = paramList->finish(pool); + + QmlIR::CompiledFunctionOrExpression *foe = obj->functionsAndExpressions->slowAt(binding->value.compiledScriptIndex); + QQmlJS::AST::FunctionDeclaration *functionDeclaration = nullptr; + if (QQmlJS::AST::ExpressionStatement *es = QQmlJS::AST::cast<QQmlJS::AST::ExpressionStatement*>(foe->node)) { + if (QQmlJS::AST::FunctionExpression *fe = QQmlJS::AST::cast<QQmlJS::AST::FunctionExpression*>(es->expression)) { + functionDeclaration = new (pool) QQmlJS::AST::FunctionDeclaration(fe->name, fe->formals, fe->body); + functionDeclaration->functionToken = fe->functionToken; + functionDeclaration->identifierToken = fe->identifierToken; + functionDeclaration->lparenToken = fe->lparenToken; + functionDeclaration->rparenToken = fe->rparenToken; + functionDeclaration->lbraceToken = fe->lbraceToken; + functionDeclaration->rbraceToken = fe->rbraceToken; + } + } + if (!functionDeclaration) { + QQmlJS::AST::Statement *statement = static_cast<QQmlJS::AST::Statement*>(foe->node); + QQmlJS::AST::StatementList *body = new (pool) QQmlJS::AST::StatementList(statement); + body = body->finish(); + + functionDeclaration = new (pool) QQmlJS::AST::FunctionDeclaration(compiler->newStringRef(stringAt(binding->propertyNameIndex)), paramList, body); + functionDeclaration->lbraceToken = functionDeclaration->functionToken + = foe->node->firstSourceLocation(); + functionDeclaration->rbraceToken = foe->node->lastSourceLocation(); + } + foe->node = functionDeclaration; + binding->propertyNameIndex = compiler->registerString(propertyName); + binding->flags |= QV4::CompiledData::Binding::IsSignalHandlerExpression; + } + return true; +} + +QQmlEnumTypeResolver::QQmlEnumTypeResolver(QQmlTypeCompiler *typeCompiler) + : QQmlCompilePass(typeCompiler) + , qmlObjects(*typeCompiler->qmlObjects()) + , propertyCaches(typeCompiler->propertyCaches()) + , imports(typeCompiler->imports()) +{ +} + +bool QQmlEnumTypeResolver::resolveEnumBindings() +{ + for (int i = 0; i < qmlObjects.count(); ++i) { + QQmlPropertyCache *propertyCache = propertyCaches->at(i); + if (!propertyCache) + continue; + const QmlIR::Object *obj = qmlObjects.at(i); + + QQmlPropertyResolver resolver(propertyCache); + + for (QmlIR::Binding *binding = obj->firstBinding(); binding; binding = binding->next) { + if (binding->flags & QV4::CompiledData::Binding::IsSignalHandlerExpression + || binding->flags & QV4::CompiledData::Binding::IsSignalHandlerObject) + continue; + + if (binding->type != QV4::CompiledData::Binding::Type_Script) + continue; + + const QString propertyName = stringAt(binding->propertyNameIndex); + bool notInRevision = false; + QQmlPropertyData *pd = resolver.property(propertyName, ¬InRevision); + if (!pd) + continue; + + if (!pd->isEnum() && pd->propType() != QMetaType::Int) + continue; + + if (!tryQualifiedEnumAssignment(obj, propertyCache, pd, binding)) + return false; + } + } + + return true; +} + +struct StaticQtMetaObject : public QObject +{ + static const QMetaObject *get() + { return &staticQtMetaObject; } +}; + +bool QQmlEnumTypeResolver::assignEnumToBinding(QmlIR::Binding *binding, const QStringRef &enumName, int enumValue, bool isQtObject) +{ + if (enumName.length() > 0 && enumName[0].isLower() && !isQtObject) { + COMPILE_EXCEPTION(binding, tr("Invalid property assignment: Enum value \"%1\" cannot start with a lowercase letter").arg(enumName.toString())); + } + binding->type = QV4::CompiledData::Binding::Type_Number; + binding->value.constantValueIndex = compiler->registerConstant(QV4::Encode((double)enumValue)); +// binding->setNumberValueInternal((double)enumValue); + binding->flags |= QV4::CompiledData::Binding::IsResolvedEnum; + return true; +} + +bool QQmlEnumTypeResolver::tryQualifiedEnumAssignment(const QmlIR::Object *obj, const QQmlPropertyCache *propertyCache, const QQmlPropertyData *prop, QmlIR::Binding *binding) +{ + bool isIntProp = (prop->propType() == QMetaType::Int) && !prop->isEnum(); + if (!prop->isEnum() && !isIntProp) + return true; + + if (!prop->isWritable() && !(binding->flags & QV4::CompiledData::Binding::InitializerForReadOnlyDeclaration)) + COMPILE_EXCEPTION(binding, tr("Invalid property assignment: \"%1\" is a read-only property").arg(stringAt(binding->propertyNameIndex))); + + Q_ASSERT(binding->type == QV4::CompiledData::Binding::Type_Script); + const QString string = compiler->bindingAsString(obj, binding->value.compiledScriptIndex); + if (!string.constData()->isUpper()) + return true; + + // we support one or two '.' in the enum phrase: + // * <TypeName>.<EnumValue> + // * <TypeName>.<ScopedEnumName>.<EnumValue> + + int dot = string.indexOf(QLatin1Char('.')); + if (dot == -1 || dot == string.length()-1) + return true; + + int dot2 = string.indexOf(QLatin1Char('.'), dot+1); + if (dot2 != -1 && dot2 != string.length()-1) { + if (!string.at(dot+1).isUpper()) + return true; + if (string.indexOf(QLatin1Char('.'), dot2+1) != -1) + return true; + } + + QHashedStringRef typeName(string.constData(), dot); + const bool isQtObject = (typeName == QLatin1String("Qt")); + const QStringRef scopedEnumName = (dot2 != -1 ? string.midRef(dot + 1, dot2 - dot - 1) : QStringRef()); + // ### consider supporting scoped enums in Qt namespace + const QStringRef enumValue = string.midRef(!isQtObject && dot2 != -1 ? dot2 + 1 : dot + 1); + + if (isIntProp) { // ### C++11 allows enums to be other integral types. Should we support other integral types here? + // Allow enum assignment to ints. + bool ok; + int enumval = evaluateEnum(typeName.toString(), scopedEnumName, enumValue, &ok); + if (ok) { + if (!assignEnumToBinding(binding, enumValue, enumval, isQtObject)) + return false; + } + return true; + } + QQmlType type; + imports->resolveType(typeName, &type, nullptr, nullptr, nullptr); + + if (!type.isValid() && !isQtObject) + return true; + + int value = 0; + bool ok = false; + + auto *tr = resolvedType(obj->inheritedTypeNameIndex); + if (type.isValid() && tr && tr->type == type) { + // When these two match, we can short cut the search + QMetaProperty mprop = propertyCache->firstCppMetaObject()->property(prop->coreIndex()); + QMetaEnum menum = mprop.enumerator(); + QByteArray enumName = enumValue.toUtf8(); + if (menum.isScoped() && !scopedEnumName.isEmpty() && enumName != scopedEnumName.toUtf8()) + return true; + + if (mprop.isFlagType()) { + value = menum.keysToValue(enumName.constData(), &ok); + } else { + value = menum.keyToValue(enumName.constData(), &ok); + } + } else { + // Otherwise we have to search the whole type + if (type.isValid()) { + if (!scopedEnumName.isEmpty()) + value = type.scopedEnumValue(compiler->enginePrivate(), scopedEnumName, enumValue, &ok); + else + value = type.enumValue(compiler->enginePrivate(), QHashedStringRef(enumValue), &ok); + } else { + QByteArray enumName = enumValue.toUtf8(); + const QMetaObject *metaObject = StaticQtMetaObject::get(); + for (int ii = metaObject->enumeratorCount() - 1; !ok && ii >= 0; --ii) { + QMetaEnum e = metaObject->enumerator(ii); + value = e.keyToValue(enumName.constData(), &ok); + } + } + } + + if (!ok) + return true; + + return assignEnumToBinding(binding, enumValue, value, isQtObject); +} + +int QQmlEnumTypeResolver::evaluateEnum(const QString &scope, const QStringRef &enumName, const QStringRef &enumValue, bool *ok) const +{ + Q_ASSERT_X(ok, "QQmlEnumTypeResolver::evaluateEnum", "ok must not be a null pointer"); + *ok = false; + + if (scope != QLatin1String("Qt")) { + QQmlType type; + imports->resolveType(scope, &type, nullptr, nullptr, nullptr); + if (!type.isValid()) + return -1; + if (!enumName.isEmpty()) + return type.scopedEnumValue(compiler->enginePrivate(), enumName, enumValue, ok); + return type.enumValue(compiler->enginePrivate(), QHashedStringRef(enumValue.constData(), enumValue.length()), ok); + } + + const QMetaObject *mo = StaticQtMetaObject::get(); + int i = mo->enumeratorCount(); + const QByteArray ba = enumValue.toUtf8(); + while (i--) { + int v = mo->enumerator(i).keyToValue(ba.constData(), ok); + if (*ok) + return v; + } + return -1; +} + +QQmlCustomParserScriptIndexer::QQmlCustomParserScriptIndexer(QQmlTypeCompiler *typeCompiler) + : QQmlCompilePass(typeCompiler) + , qmlObjects(*typeCompiler->qmlObjects()) + , customParsers(typeCompiler->customParserCache()) +{ +} + +void QQmlCustomParserScriptIndexer::annotateBindingsWithScriptStrings() +{ + scanObjectRecursively(/*root object*/0); +} + +void QQmlCustomParserScriptIndexer::scanObjectRecursively(int objectIndex, bool annotateScriptBindings) +{ + const QmlIR::Object * const obj = qmlObjects.at(objectIndex); + if (!annotateScriptBindings) + annotateScriptBindings = customParsers.contains(obj->inheritedTypeNameIndex); + for (QmlIR::Binding *binding = obj->firstBinding(); binding; binding = binding->next) { + if (binding->type >= QV4::CompiledData::Binding::Type_Object) { + scanObjectRecursively(binding->value.objectIndex, annotateScriptBindings); + continue; + } else if (binding->type != QV4::CompiledData::Binding::Type_Script) + continue; + if (!annotateScriptBindings) + continue; + const QString script = compiler->bindingAsString(obj, binding->value.compiledScriptIndex); + binding->stringIndex = compiler->registerString(script); + } +} + +QQmlAliasAnnotator::QQmlAliasAnnotator(QQmlTypeCompiler *typeCompiler) + : QQmlCompilePass(typeCompiler) + , qmlObjects(*typeCompiler->qmlObjects()) + , propertyCaches(typeCompiler->propertyCaches()) +{ +} + +void QQmlAliasAnnotator::annotateBindingsToAliases() +{ + for (int i = 0; i < qmlObjects.count(); ++i) { + QQmlPropertyCache *propertyCache = propertyCaches->at(i); + if (!propertyCache) + continue; + + const QmlIR::Object *obj = qmlObjects.at(i); + + QQmlPropertyResolver resolver(propertyCache); + QQmlPropertyData *defaultProperty = obj->indexOfDefaultPropertyOrAlias != -1 ? propertyCache->parent()->defaultProperty() : propertyCache->defaultProperty(); + + for (QmlIR::Binding *binding = obj->firstBinding(); binding; binding = binding->next) { + if (!binding->isValueBinding()) + continue; + bool notInRevision = false; + QQmlPropertyData *pd = binding->propertyNameIndex != quint32(0) ? resolver.property(stringAt(binding->propertyNameIndex), ¬InRevision) : defaultProperty; + if (pd && pd->isAlias()) + binding->flags |= QV4::CompiledData::Binding::IsBindingToAlias; + } + } +} + +QQmlScriptStringScanner::QQmlScriptStringScanner(QQmlTypeCompiler *typeCompiler) + : QQmlCompilePass(typeCompiler) + , qmlObjects(*typeCompiler->qmlObjects()) + , propertyCaches(typeCompiler->propertyCaches()) +{ + +} + +void QQmlScriptStringScanner::scan() +{ + const int scriptStringMetaType = qMetaTypeId<QQmlScriptString>(); + for (int i = 0; i < qmlObjects.count(); ++i) { + QQmlPropertyCache *propertyCache = propertyCaches->at(i); + if (!propertyCache) + continue; + + const QmlIR::Object *obj = qmlObjects.at(i); + + QQmlPropertyResolver resolver(propertyCache); + QQmlPropertyData *defaultProperty = obj->indexOfDefaultPropertyOrAlias != -1 ? propertyCache->parent()->defaultProperty() : propertyCache->defaultProperty(); + + for (QmlIR::Binding *binding = obj->firstBinding(); binding; binding = binding->next) { + if (binding->type != QV4::CompiledData::Binding::Type_Script) + continue; + bool notInRevision = false; + QQmlPropertyData *pd = binding->propertyNameIndex != quint32(0) ? resolver.property(stringAt(binding->propertyNameIndex), ¬InRevision) : defaultProperty; + if (!pd || pd->propType() != scriptStringMetaType) + continue; + + QString script = compiler->bindingAsString(obj, binding->value.compiledScriptIndex); + binding->stringIndex = compiler->registerString(script); + } + } +} + +QQmlComponentAndAliasResolver::QQmlComponentAndAliasResolver(QQmlTypeCompiler *typeCompiler) + : QQmlCompilePass(typeCompiler) + , enginePrivate(typeCompiler->enginePrivate()) + , pool(typeCompiler->memoryPool()) + , qmlObjects(typeCompiler->qmlObjects()) + , propertyCaches(std::move(typeCompiler->takePropertyCaches())) +{ +} + +static bool isUsableComponent(const QMetaObject *metaObject) +{ + // The metaObject is a component we're interested in if it either is a QQmlComponent itself + // or if any of its parents is a QQmlAbstractDelegateComponent. We don't want to include + // qqmldelegatecomponent_p.h because it belongs to QtQmlModels. + + if (metaObject == &QQmlComponent::staticMetaObject) + return true; + + for (; metaObject; metaObject = metaObject->superClass()) { + if (qstrcmp(metaObject->className(), "QQmlAbstractDelegateComponent") == 0) + return true; + } + + return false; +} + +void QQmlComponentAndAliasResolver::findAndRegisterImplicitComponents(const QmlIR::Object *obj, QQmlPropertyCache *propertyCache) +{ + QQmlPropertyResolver propertyResolver(propertyCache); + + QQmlPropertyData *defaultProperty = obj->indexOfDefaultPropertyOrAlias != -1 ? propertyCache->parent()->defaultProperty() : propertyCache->defaultProperty(); + + for (QmlIR::Binding *binding = obj->firstBinding(); binding; binding = binding->next) { + if (binding->type != QV4::CompiledData::Binding::Type_Object) + continue; + if (binding->flags & QV4::CompiledData::Binding::IsSignalHandlerObject) + continue; + + const QmlIR::Object *targetObject = qmlObjects->at(binding->value.objectIndex); + auto *tr = resolvedType(targetObject->inheritedTypeNameIndex); + Q_ASSERT(tr); + + const QMetaObject *firstMetaObject = nullptr; + if (tr->type.isValid()) + firstMetaObject = tr->type.metaObject(); + else if (tr->compilationUnit) + firstMetaObject = tr->compilationUnit->rootPropertyCache()->firstCppMetaObject(); + if (isUsableComponent(firstMetaObject)) + continue; + // if here, not a QQmlComponent, so needs wrapping + + QQmlPropertyData *pd = nullptr; + if (binding->propertyNameIndex != quint32(0)) { + bool notInRevision = false; + pd = propertyResolver.property(stringAt(binding->propertyNameIndex), ¬InRevision); + } else { + pd = defaultProperty; + } + if (!pd || !pd->isQObject()) + continue; + + QQmlPropertyCache *pc = enginePrivate->rawPropertyCacheForType(pd->propType(), pd->typeMinorVersion()); + const QMetaObject *mo = pc ? pc->firstCppMetaObject() : nullptr; + while (mo) { + if (mo == &QQmlComponent::staticMetaObject) + break; + mo = mo->superClass(); + } + + if (!mo) + continue; + + // emulate "import Qml 2.0 as QmlInternals" and then wrap the component in "QmlInternals.Component {}" + QQmlType componentType = QQmlMetaType::qmlType(&QQmlComponent::staticMetaObject); + Q_ASSERT(componentType.isValid()); + const QString qualifier = QStringLiteral("QmlInternals"); + + compiler->addImport(componentType.module(), qualifier, componentType.majorVersion(), componentType.minorVersion()); + + QmlIR::Object *syntheticComponent = pool->New<QmlIR::Object>(); + syntheticComponent->init(pool, compiler->registerString(qualifier + QLatin1Char('.') + componentType.elementName()), compiler->registerString(QString())); + syntheticComponent->location = binding->valueLocation; + syntheticComponent->flags |= QV4::CompiledData::Object::IsComponent; + + if (!containsResolvedType(syntheticComponent->inheritedTypeNameIndex)) { + auto typeRef = new QV4::ResolvedTypeReference; + typeRef->type = componentType; + typeRef->majorVersion = componentType.majorVersion(); + typeRef->minorVersion = componentType.minorVersion(); + insertResolvedType(syntheticComponent->inheritedTypeNameIndex, typeRef); + } + + qmlObjects->append(syntheticComponent); + const int componentIndex = qmlObjects->count() - 1; + // Keep property caches symmetric + QQmlPropertyCache *componentCache = enginePrivate->cache(&QQmlComponent::staticMetaObject); + propertyCaches.append(componentCache); + + QmlIR::Binding *syntheticBinding = pool->New<QmlIR::Binding>(); + *syntheticBinding = *binding; + syntheticBinding->type = QV4::CompiledData::Binding::Type_Object; + QString error = syntheticComponent->appendBinding(syntheticBinding, /*isListBinding*/false); + Q_ASSERT(error.isEmpty()); + Q_UNUSED(error); + + binding->value.objectIndex = componentIndex; + + componentRoots.append(componentIndex); + } +} + +bool QQmlComponentAndAliasResolver::resolve() +{ + // Detect real Component {} objects as well as implicitly defined components, such as + // someItemDelegate: Item {} + // In the implicit case Item is surrounded by a synthetic Component {} because the property + // on the left hand side is of QQmlComponent type. + const int objCountWithoutSynthesizedComponents = qmlObjects->count(); + for (int i = 0; i < objCountWithoutSynthesizedComponents; ++i) { + QmlIR::Object *obj = qmlObjects->at(i); + QQmlPropertyCache *cache = propertyCaches.at(i); + if (obj->inheritedTypeNameIndex == 0 && !cache) + continue; + + bool isExplicitComponent = false; + + if (obj->inheritedTypeNameIndex) { + auto *tref = resolvedType(obj->inheritedTypeNameIndex); + Q_ASSERT(tref); + if (tref->type.metaObject() == &QQmlComponent::staticMetaObject) + isExplicitComponent = true; + } + if (!isExplicitComponent) { + if (cache) + findAndRegisterImplicitComponents(obj, cache); + continue; + } + + obj->flags |= QV4::CompiledData::Object::IsComponent; + + if (obj->functionCount() > 0) + COMPILE_EXCEPTION(obj, tr("Component objects cannot declare new functions.")); + if (obj->propertyCount() > 0 || obj->aliasCount() > 0) + COMPILE_EXCEPTION(obj, tr("Component objects cannot declare new properties.")); + if (obj->signalCount() > 0) + COMPILE_EXCEPTION(obj, tr("Component objects cannot declare new signals.")); + + if (obj->bindingCount() == 0) + COMPILE_EXCEPTION(obj, tr("Cannot create empty component specification")); + + const QmlIR::Binding *rootBinding = obj->firstBinding(); + + for (const QmlIR::Binding *b = rootBinding; b; b = b->next) { + if (b->propertyNameIndex != 0) + COMPILE_EXCEPTION(rootBinding, tr("Component elements may not contain properties other than id")); + } + + if (rootBinding->next || rootBinding->type != QV4::CompiledData::Binding::Type_Object) + COMPILE_EXCEPTION(obj, tr("Invalid component body specification")); + + // For the root object, we are going to collect ids/aliases and resolve them for as a separate + // last pass. + if (i != 0) + componentRoots.append(i); + + } + + for (int i = 0; i < componentRoots.count(); ++i) { + QmlIR::Object *component = qmlObjects->at(componentRoots.at(i)); + const QmlIR::Binding *rootBinding = component->firstBinding(); + + _idToObjectIndex.clear(); + + _objectsWithAliases.clear(); + + if (!collectIdsAndAliases(rootBinding->value.objectIndex)) + return false; + + component->namedObjectsInComponent.allocate(pool, _idToObjectIndex); + + if (!resolveAliases(componentRoots.at(i))) + return false; + } + + // Collect ids and aliases for root + _idToObjectIndex.clear(); + _objectsWithAliases.clear(); + + collectIdsAndAliases(/*root object*/0); + + QmlIR::Object *rootComponent = qmlObjects->at(/*root object*/0); + rootComponent->namedObjectsInComponent.allocate(pool, _idToObjectIndex); + + if (!resolveAliases(/*root object*/0)) + return false; + + // Implicit component insertion may have added objects and thus we also need + // to extend the symmetric propertyCaches. + compiler->setPropertyCaches(std::move(propertyCaches)); + compiler->setComponentRoots(componentRoots); + + return true; +} + +bool QQmlComponentAndAliasResolver::collectIdsAndAliases(int objectIndex) +{ + QmlIR::Object *obj = qmlObjects->at(objectIndex); + + if (obj->idNameIndex != 0) { + if (_idToObjectIndex.contains(obj->idNameIndex)) { + recordError(obj->locationOfIdProperty, tr("id is not unique")); + return false; + } + obj->id = _idToObjectIndex.count(); + _idToObjectIndex.insert(obj->idNameIndex, objectIndex); + } + + if (obj->aliasCount() > 0) + _objectsWithAliases.append(objectIndex); + + // Stop at Component boundary + if (obj->flags & QV4::CompiledData::Object::IsComponent && objectIndex != /*root object*/0) + return true; + + for (const QmlIR::Binding *binding = obj->firstBinding(); binding; binding = binding->next) { + if (binding->type != QV4::CompiledData::Binding::Type_Object + && binding->type != QV4::CompiledData::Binding::Type_AttachedProperty + && binding->type != QV4::CompiledData::Binding::Type_GroupProperty) + continue; + + if (!collectIdsAndAliases(binding->value.objectIndex)) + return false; + } + + return true; +} + +bool QQmlComponentAndAliasResolver::resolveAliases(int componentIndex) +{ + if (_objectsWithAliases.isEmpty()) + return true; + + QQmlPropertyCacheAliasCreator<QQmlTypeCompiler> aliasCacheCreator(&propertyCaches, compiler); + + bool atLeastOneAliasResolved; + do { + atLeastOneAliasResolved = false; + QVector<int> pendingObjects; + + for (int objectIndex: qAsConst(_objectsWithAliases)) { + + QQmlCompileError error; + const auto result = resolveAliasesInObject(objectIndex, &error); + + if (error.isSet()) { + recordError(error); + return false; + } + + if (result == AllAliasesResolved) { + QQmlCompileError error = aliasCacheCreator.appendAliasesToPropertyCache(*qmlObjects->at(componentIndex), objectIndex); + if (error.isSet()) { + recordError(error); + return false; + } + atLeastOneAliasResolved = true; + } else if (result == SomeAliasesResolved) { + atLeastOneAliasResolved = true; + pendingObjects.append(objectIndex); + } else { + pendingObjects.append(objectIndex); + } + } + qSwap(_objectsWithAliases, pendingObjects); + } while (!_objectsWithAliases.isEmpty() && atLeastOneAliasResolved); + + if (!atLeastOneAliasResolved && !_objectsWithAliases.isEmpty()) { + const QmlIR::Object *obj = qmlObjects->at(_objectsWithAliases.first()); + for (auto alias = obj->aliasesBegin(), end = obj->aliasesEnd(); alias != end; ++alias) { + if (!(alias->flags & QV4::CompiledData::Alias::Resolved)) { + recordError(alias->location, tr("Circular alias reference detected")); + return false; + } + } + } + + return true; +} + +QQmlComponentAndAliasResolver::AliasResolutionResult QQmlComponentAndAliasResolver::resolveAliasesInObject(int objectIndex, QQmlCompileError *error) +{ + const QmlIR::Object * const obj = qmlObjects->at(objectIndex); + if (!obj->aliasCount()) + return AllAliasesResolved; + + int numResolvedAliases = 0; + bool seenUnresolvedAlias = false; + + for (QmlIR::Alias *alias = obj->firstAlias(); alias; alias = alias->next) { + if (alias->flags & QV4::CompiledData::Alias::Resolved) + continue; + + seenUnresolvedAlias = true; + + const int idIndex = alias->idIndex; + const int targetObjectIndex = _idToObjectIndex.value(idIndex, -1); + if (targetObjectIndex == -1) { + *error = QQmlCompileError(alias->referenceLocation, tr("Invalid alias reference. Unable to find id \"%1\"").arg(stringAt(idIndex))); + break; + } + + const QmlIR::Object *targetObject = qmlObjects->at(targetObjectIndex); + Q_ASSERT(targetObject->id >= 0); + alias->targetObjectId = targetObject->id; + alias->aliasToLocalAlias = false; + + const QString aliasPropertyValue = stringAt(alias->propertyNameIndex); + + QStringRef property; + QStringRef subProperty; + + const int propertySeparator = aliasPropertyValue.indexOf(QLatin1Char('.')); + if (propertySeparator != -1) { + property = aliasPropertyValue.leftRef(propertySeparator); + subProperty = aliasPropertyValue.midRef(propertySeparator + 1); + } else + property = QStringRef(&aliasPropertyValue, 0, aliasPropertyValue.length()); + + QQmlPropertyIndex propIdx; + + if (property.isEmpty()) { + alias->flags |= QV4::CompiledData::Alias::AliasPointsToPointerObject; + } else { + QQmlPropertyCache *targetCache = propertyCaches.at(targetObjectIndex); + if (!targetCache) { + *error = QQmlCompileError(alias->referenceLocation, tr("Invalid alias target location: %1").arg(property.toString())); + break; + } + + QQmlPropertyResolver resolver(targetCache); + + QQmlPropertyData *targetProperty = resolver.property(property.toString()); + + // If it's an alias that we haven't resolved yet, try again later. + if (!targetProperty) { + bool aliasPointsToOtherAlias = false; + int localAliasIndex = 0; + for (auto targetAlias = targetObject->aliasesBegin(), end = targetObject->aliasesEnd(); targetAlias != end; ++targetAlias, ++localAliasIndex) { + if (stringAt(targetAlias->nameIndex) == property) { + aliasPointsToOtherAlias = true; + break; + } + } + if (aliasPointsToOtherAlias) { + if (targetObjectIndex == objectIndex) { + alias->localAliasIndex = localAliasIndex; + alias->aliasToLocalAlias = true; + alias->flags |= QV4::CompiledData::Alias::Resolved; + ++numResolvedAliases; + continue; + } + + // restore + alias->idIndex = idIndex; + // Try again later and resolve the target alias first. + break; + } + } + + if (!targetProperty || targetProperty->coreIndex() > 0x0000FFFF) { + *error = QQmlCompileError(alias->referenceLocation, tr("Invalid alias target location: %1").arg(property.toString())); + break; + } + + propIdx = QQmlPropertyIndex(targetProperty->coreIndex()); + + if (!subProperty.isEmpty()) { + const QMetaObject *valueTypeMetaObject = QQmlValueTypeFactory::metaObjectForMetaType(targetProperty->propType()); + if (!valueTypeMetaObject) { + *error = QQmlCompileError(alias->referenceLocation, tr("Invalid alias target location: %1").arg(subProperty.toString())); + break; + } + + int valueTypeIndex = + valueTypeMetaObject->indexOfProperty(subProperty.toString().toUtf8().constData()); + if (valueTypeIndex == -1) { + *error = QQmlCompileError(alias->referenceLocation, tr("Invalid alias target location: %1").arg(subProperty.toString())); + break; + } + Q_ASSERT(valueTypeIndex <= 0x0000FFFF); + + propIdx = QQmlPropertyIndex(propIdx.coreIndex(), valueTypeIndex); + } else { + if (targetProperty->isQObject()) + alias->flags |= QV4::CompiledData::Alias::AliasPointsToPointerObject; + } + } + + alias->encodedMetaPropertyIndex = propIdx.toEncoded(); + alias->flags |= QV4::CompiledData::Alias::Resolved; + numResolvedAliases++; + } + + if (numResolvedAliases == 0) + return seenUnresolvedAlias ? NoAliasResolved : AllAliasesResolved; + + return SomeAliasesResolved; +} + +QQmlDeferredAndCustomParserBindingScanner::QQmlDeferredAndCustomParserBindingScanner(QQmlTypeCompiler *typeCompiler) + : QQmlCompilePass(typeCompiler) + , qmlObjects(typeCompiler->qmlObjects()) + , propertyCaches(typeCompiler->propertyCaches()) + , customParsers(typeCompiler->customParserCache()) + , _seenObjectWithId(false) +{ +} + +bool QQmlDeferredAndCustomParserBindingScanner::scanObject() +{ + return scanObject(/*root object*/0); +} + +bool QQmlDeferredAndCustomParserBindingScanner::scanObject(int objectIndex) +{ + QmlIR::Object *obj = qmlObjects->at(objectIndex); + if (obj->idNameIndex != 0) + _seenObjectWithId = true; + + if (obj->flags & QV4::CompiledData::Object::IsComponent) { + Q_ASSERT(obj->bindingCount() == 1); + const QV4::CompiledData::Binding *componentBinding = obj->firstBinding(); + Q_ASSERT(componentBinding->type == QV4::CompiledData::Binding::Type_Object); + return scanObject(componentBinding->value.objectIndex); + } + + QQmlPropertyCache *propertyCache = propertyCaches->at(objectIndex); + if (!propertyCache) + return true; + + QString defaultPropertyName; + QQmlPropertyData *defaultProperty = nullptr; + if (obj->indexOfDefaultPropertyOrAlias != -1) { + QQmlPropertyCache *cache = propertyCache->parent(); + defaultPropertyName = cache->defaultPropertyName(); + defaultProperty = cache->defaultProperty(); + } else { + defaultPropertyName = propertyCache->defaultPropertyName(); + defaultProperty = propertyCache->defaultProperty(); + } + + QQmlCustomParser *customParser = customParsers.value(obj->inheritedTypeNameIndex); + + QQmlPropertyResolver propertyResolver(propertyCache); + + QStringList deferredPropertyNames; + { + const QMetaObject *mo = propertyCache->firstCppMetaObject(); + const int namesIndex = mo->indexOfClassInfo("DeferredPropertyNames"); + if (namesIndex != -1) { + QMetaClassInfo classInfo = mo->classInfo(namesIndex); + deferredPropertyNames = QString::fromUtf8(classInfo.value()).split(QLatin1Char(',')); + } + } + + for (QmlIR::Binding *binding = obj->firstBinding(); binding; binding = binding->next) { + QQmlPropertyData *pd = nullptr; + QString name = stringAt(binding->propertyNameIndex); + + if (customParser) { + if (binding->type == QV4::CompiledData::Binding::Type_AttachedProperty) { + if (customParser->flags() & QQmlCustomParser::AcceptsAttachedProperties) { + binding->flags |= QV4::CompiledData::Binding::IsCustomParserBinding; + obj->flags |= QV4::CompiledData::Object::HasCustomParserBindings; + continue; + } + } else if (QmlIR::IRBuilder::isSignalPropertyName(name) + && !(customParser->flags() & QQmlCustomParser::AcceptsSignalHandlers)) { + obj->flags |= QV4::CompiledData::Object::HasCustomParserBindings; + binding->flags |= QV4::CompiledData::Binding::IsCustomParserBinding; + continue; + } + } + + if (name.isEmpty()) { + pd = defaultProperty; + name = defaultPropertyName; + } else { + if (name.constData()->isUpper()) + continue; + + bool notInRevision = false; + pd = propertyResolver.property(name, ¬InRevision, + QQmlPropertyResolver::CheckRevision); + } + + bool seenSubObjectWithId = false; + + if (binding->type >= QV4::CompiledData::Binding::Type_Object && (pd || binding->isAttachedProperty())) { + qSwap(_seenObjectWithId, seenSubObjectWithId); + const bool subObjectValid = scanObject(binding->value.objectIndex); + qSwap(_seenObjectWithId, seenSubObjectWithId); + if (!subObjectValid) + return false; + _seenObjectWithId |= seenSubObjectWithId; + } + + if (!seenSubObjectWithId && binding->type != QV4::CompiledData::Binding::Type_GroupProperty + && !deferredPropertyNames.isEmpty() && deferredPropertyNames.contains(name)) { + + binding->flags |= QV4::CompiledData::Binding::IsDeferredBinding; + obj->flags |= QV4::CompiledData::Object::HasDeferredBindings; + } + + if (binding->flags & QV4::CompiledData::Binding::IsSignalHandlerExpression + || binding->flags & QV4::CompiledData::Binding::IsSignalHandlerObject) + continue; + + if (!pd) { + if (customParser) { + obj->flags |= QV4::CompiledData::Object::HasCustomParserBindings; + binding->flags |= QV4::CompiledData::Binding::IsCustomParserBinding; + } + } + } + + return true; +} + +QQmlJSCodeGenerator::QQmlJSCodeGenerator(QQmlTypeCompiler *typeCompiler, QmlIR::JSCodeGen *v4CodeGen) + : QQmlCompilePass(typeCompiler) + , customParsers(typeCompiler->customParserCache()) + , qmlObjects(*typeCompiler->qmlObjects()) + , propertyCaches(typeCompiler->propertyCaches()) + , v4CodeGen(v4CodeGen) +{ +} + +bool QQmlJSCodeGenerator::generateCodeForComponents() +{ + const QVector<quint32> &componentRoots = compiler->componentRoots(); + for (int i = 0; i < componentRoots.count(); ++i) { + if (!compileComponent(componentRoots.at(i))) + return false; + } + + return compileComponent(/*root object*/0); +} + +bool QQmlJSCodeGenerator::compileComponent(int contextObject) +{ + const QmlIR::Object *obj = qmlObjects.at(contextObject); + if (obj->flags & QV4::CompiledData::Object::IsComponent) { + Q_ASSERT(obj->bindingCount() == 1); + const QV4::CompiledData::Binding *componentBinding = obj->firstBinding(); + Q_ASSERT(componentBinding->type == QV4::CompiledData::Binding::Type_Object); + contextObject = componentBinding->value.objectIndex; + } + + if (!compileJavaScriptCodeInObjectsRecursively(contextObject, contextObject)) + return false; + + return true; +} + +bool QQmlJSCodeGenerator::compileJavaScriptCodeInObjectsRecursively(int objectIndex, int scopeObjectIndex) +{ + QmlIR::Object *object = qmlObjects.at(objectIndex); + if (object->flags & QV4::CompiledData::Object::IsComponent) + return true; + + if (object->functionsAndExpressions->count > 0) { + QList<QmlIR::CompiledFunctionOrExpression> functionsToCompile; + for (QmlIR::CompiledFunctionOrExpression *foe = object->functionsAndExpressions->first; foe; foe = foe->next) + functionsToCompile << *foe; + const QVector<int> runtimeFunctionIndices = v4CodeGen->generateJSCodeForFunctionsAndBindings(functionsToCompile); + const QList<QQmlError> jsErrors = v4CodeGen->qmlErrors(); + if (!jsErrors.isEmpty()) { + for (const QQmlError &e : jsErrors) + compiler->recordError(e); + return false; + } + + QQmlJS::MemoryPool *pool = compiler->memoryPool(); + object->runtimeFunctionIndices.allocate(pool, runtimeFunctionIndices); + } + + for (const QmlIR::Binding *binding = object->firstBinding(); binding; binding = binding->next) { + if (binding->type < QV4::CompiledData::Binding::Type_Object) + continue; + + int target = binding->value.objectIndex; + int scope = binding->type == QV4::CompiledData::Binding::Type_Object ? target : scopeObjectIndex; + + if (!compileJavaScriptCodeInObjectsRecursively(binding->value.objectIndex, scope)) + return false; + } + + return true; +} + +QQmlDefaultPropertyMerger::QQmlDefaultPropertyMerger(QQmlTypeCompiler *typeCompiler) + : QQmlCompilePass(typeCompiler) + , qmlObjects(*typeCompiler->qmlObjects()) + , propertyCaches(typeCompiler->propertyCaches()) +{ + +} + +void QQmlDefaultPropertyMerger::mergeDefaultProperties() +{ + for (int i = 0; i < qmlObjects.count(); ++i) + mergeDefaultProperties(i); +} + +void QQmlDefaultPropertyMerger::mergeDefaultProperties(int objectIndex) +{ + QQmlPropertyCache *propertyCache = propertyCaches->at(objectIndex); + if (!propertyCache) + return; + + QmlIR::Object *object = qmlObjects.at(objectIndex); + + QString defaultProperty = object->indexOfDefaultPropertyOrAlias != -1 ? propertyCache->parent()->defaultPropertyName() : propertyCache->defaultPropertyName(); + QmlIR::Binding *bindingsToReinsert = nullptr; + QmlIR::Binding *tail = nullptr; + + QmlIR::Binding *previousBinding = nullptr; + QmlIR::Binding *binding = object->firstBinding(); + while (binding) { + if (binding->propertyNameIndex == quint32(0) || stringAt(binding->propertyNameIndex) != defaultProperty) { + previousBinding = binding; + binding = binding->next; + continue; + } + + QmlIR::Binding *toReinsert = binding; + binding = object->unlinkBinding(previousBinding, binding); + + if (!tail) { + bindingsToReinsert = toReinsert; + tail = toReinsert; + } else { + tail->next = toReinsert; + tail = tail->next; + } + tail->next = nullptr; + } + + binding = bindingsToReinsert; + while (binding) { + QmlIR::Binding *toReinsert = binding; + binding = binding->next; + object->insertSorted(toReinsert); + } +} + +QT_END_NAMESPACE diff --git a/src/qml/qml/qqmltypecompiler_p.h b/src/qml/qml/qqmltypecompiler_p.h new file mode 100644 index 0000000000..f588909c42 --- /dev/null +++ b/src/qml/qml/qqmltypecompiler_p.h @@ -0,0 +1,347 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#ifndef QQMLTYPECOMPILER_P_H +#define QQMLTYPECOMPILER_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 <qglobal.h> +#include <qqmlerror.h> +#include <qhash.h> +#include <private/qqmltypeloader_p.h> +#include <private/qqmlirbuilder_p.h> +#include <private/qqmlpropertycachecreator_p.h> + +QT_BEGIN_NAMESPACE + +class QQmlEnginePrivate; +class QQmlError; +class QQmlTypeData; +class QQmlImports; + +namespace QmlIR { +struct Document; +} + +namespace QV4 { +namespace CompiledData { +struct QmlUnit; +struct Location; +} +} + +struct QQmlTypeCompiler +{ + Q_DECLARE_TR_FUNCTIONS(QQmlTypeCompiler) +public: + QQmlTypeCompiler(QQmlEnginePrivate *engine, QQmlTypeData *typeData, QmlIR::Document *document, + const QQmlRefPointer<QQmlTypeNameCache> &typeNameCache, + QV4::ResolvedTypeReferenceMap *resolvedTypeCache, + const QV4::CompiledData::DependentTypesHasher &dependencyHasher); + + // --- interface used by QQmlPropertyCacheCreator + typedef QmlIR::Object CompiledObject; + const QmlIR::Object *objectAt(int index) const { return document->objects.at(index); } + int objectCount() const { return document->objects.count(); } + QString stringAt(int idx) const; + QmlIR::PoolList<QmlIR::Function>::Iterator objectFunctionsBegin(const QmlIR::Object *object) const { return object->functionsBegin(); } + QmlIR::PoolList<QmlIR::Function>::Iterator objectFunctionsEnd(const QmlIR::Object *object) const { return object->functionsEnd(); } + QV4::ResolvedTypeReferenceMap *resolvedTypes = nullptr; + // --- + + QQmlRefPointer<QV4::ExecutableCompilationUnit> compile(); + + QList<QQmlError> compilationErrors() const { return errors; } + void recordError(QQmlError error); + void recordError(const QV4::CompiledData::Location &location, const QString &description); + void recordError(const QQmlCompileError &error); + + int registerString(const QString &str); + int registerConstant(QV4::ReturnedValue v); + + const QV4::CompiledData::Unit *qmlUnit() const; + + QUrl url() const { return typeData->finalUrl(); } + QQmlEnginePrivate *enginePrivate() const { return engine; } + const QQmlImports *imports() const; + QVector<QmlIR::Object *> *qmlObjects() const; + void setPropertyCaches(QQmlPropertyCacheVector &&caches); + const QQmlPropertyCacheVector *propertyCaches() const; + QQmlPropertyCacheVector &&takePropertyCaches(); + void setComponentRoots(const QVector<quint32> &roots) { m_componentRoots = roots; } + const QVector<quint32> &componentRoots() const { return m_componentRoots; } + QQmlJS::MemoryPool *memoryPool(); + QStringRef newStringRef(const QString &string); + const QV4::Compiler::StringTableGenerator *stringPool() const; + + const QHash<int, QQmlCustomParser*> &customParserCache() const { return customParsers; } + + QString bindingAsString(const QmlIR::Object *object, int scriptIndex) const; + + void addImport(const QString &module, const QString &qualifier, int majorVersion, int minorVersion); + + QV4::ResolvedTypeReference *resolvedType(int id) const + { + return resolvedTypes->value(id); + } + +private: + QList<QQmlError> errors; + QQmlEnginePrivate *engine; + QQmlTypeData *typeData; + const QV4::CompiledData::DependentTypesHasher &dependencyHasher; + QQmlRefPointer<QQmlTypeNameCache> typeNameCache; + QmlIR::Document *document; + // index is string index of type name (use obj->inheritedTypeNameIndex) + QHash<int, QQmlCustomParser*> customParsers; + + // index in first hash is component index, vector inside contains object indices of objects with id property + QVector<quint32> m_componentRoots; + QQmlPropertyCacheVector m_propertyCaches; +}; + +struct QQmlCompilePass +{ + QQmlCompilePass(QQmlTypeCompiler *typeCompiler); + + QString stringAt(int idx) const { return compiler->stringAt(idx); } +protected: + void recordError(const QV4::CompiledData::Location &location, const QString &description) const + { compiler->recordError(location, description); } + void recordError(const QQmlCompileError &error) + { compiler->recordError(error); } + + QV4::ResolvedTypeReference *resolvedType(int id) const + { return compiler->resolvedType(id); } + bool containsResolvedType(int id) const + { return compiler->resolvedTypes->contains(id); } + QV4::ResolvedTypeReferenceMap::iterator insertResolvedType( + int id, QV4::ResolvedTypeReference *value) + { return compiler->resolvedTypes->insert(id, value); } + + QQmlTypeCompiler *compiler; +}; + +// "Converts" signal expressions to full-fleged function declarations with +// parameters taken from the signal declarations +// It also updates the QV4::CompiledData::Binding objects to set the property name +// to the final signal name (onTextChanged -> textChanged) and sets the IsSignalExpression flag. +struct SignalHandlerConverter : public QQmlCompilePass +{ + Q_DECLARE_TR_FUNCTIONS(SignalHandlerConverter) +public: + SignalHandlerConverter(QQmlTypeCompiler *typeCompiler); + + bool convertSignalHandlerExpressionsToFunctionDeclarations(); + +private: + bool convertSignalHandlerExpressionsToFunctionDeclarations(const QmlIR::Object *obj, const QString &typeName, QQmlPropertyCache *propertyCache); + + QQmlEnginePrivate *enginePrivate; + const QVector<QmlIR::Object*> &qmlObjects; + const QQmlImports *imports; + const QHash<int, QQmlCustomParser*> &customParsers; + const QSet<QString> &illegalNames; + const QQmlPropertyCacheVector * const propertyCaches; +}; + +// ### This will go away when the codegen resolves all enums to constant expressions +// and we replace the constant expression with a literal binding instead of using +// a script. +class QQmlEnumTypeResolver : public QQmlCompilePass +{ + Q_DECLARE_TR_FUNCTIONS(QQmlEnumTypeResolver) +public: + QQmlEnumTypeResolver(QQmlTypeCompiler *typeCompiler); + + bool resolveEnumBindings(); + +private: + bool assignEnumToBinding(QmlIR::Binding *binding, const QStringRef &enumName, int enumValue, bool isQtObject); + bool assignEnumToBinding(QmlIR::Binding *binding, const QString &enumName, int enumValue, bool isQtObject) + { + return assignEnumToBinding(binding, QStringRef(&enumName), enumValue, isQtObject); + } + bool tryQualifiedEnumAssignment(const QmlIR::Object *obj, const QQmlPropertyCache *propertyCache, + const QQmlPropertyData *prop, + QmlIR::Binding *binding); + int evaluateEnum(const QString &scope, const QStringRef &enumName, const QStringRef &enumValue, bool *ok) const; + + + const QVector<QmlIR::Object*> &qmlObjects; + const QQmlPropertyCacheVector * const propertyCaches; + const QQmlImports *imports; +}; + +class QQmlCustomParserScriptIndexer: public QQmlCompilePass +{ +public: + QQmlCustomParserScriptIndexer(QQmlTypeCompiler *typeCompiler); + + void annotateBindingsWithScriptStrings(); + +private: + void scanObjectRecursively(int objectIndex, bool annotateScriptBindings = false); + + const QVector<QmlIR::Object*> &qmlObjects; + const QHash<int, QQmlCustomParser*> &customParsers; +}; + +// Annotate properties bound to aliases with a flag +class QQmlAliasAnnotator : public QQmlCompilePass +{ +public: + QQmlAliasAnnotator(QQmlTypeCompiler *typeCompiler); + + void annotateBindingsToAliases(); +private: + const QVector<QmlIR::Object*> &qmlObjects; + const QQmlPropertyCacheVector * const propertyCaches; +}; + +class QQmlScriptStringScanner : public QQmlCompilePass +{ +public: + QQmlScriptStringScanner(QQmlTypeCompiler *typeCompiler); + + void scan(); + +private: + const QVector<QmlIR::Object*> &qmlObjects; + const QQmlPropertyCacheVector * const propertyCaches; +}; + +class QQmlComponentAndAliasResolver : public QQmlCompilePass +{ + Q_DECLARE_TR_FUNCTIONS(QQmlAnonymousComponentResolver) +public: + QQmlComponentAndAliasResolver(QQmlTypeCompiler *typeCompiler); + + bool resolve(); + +protected: + void findAndRegisterImplicitComponents(const QmlIR::Object *obj, QQmlPropertyCache *propertyCache); + bool collectIdsAndAliases(int objectIndex); + bool resolveAliases(int componentIndex); + void propertyDataForAlias(QmlIR::Alias *alias, int *type, quint32 *propertyFlags); + + enum AliasResolutionResult { + NoAliasResolved, + SomeAliasesResolved, + AllAliasesResolved + }; + + AliasResolutionResult resolveAliasesInObject(int objectIndex, QQmlCompileError *error); + + QQmlEnginePrivate *enginePrivate; + QQmlJS::MemoryPool *pool; + + QVector<QmlIR::Object*> *qmlObjects; + + // indices of the objects that are actually Component {} + QVector<quint32> componentRoots; + + // Deliberate choice of map over hash here to ensure stable generated output. + QMap<int, int> _idToObjectIndex; + QVector<int> _objectsWithAliases; + + QQmlPropertyCacheVector propertyCaches; +}; + +class QQmlDeferredAndCustomParserBindingScanner : public QQmlCompilePass +{ +public: + QQmlDeferredAndCustomParserBindingScanner(QQmlTypeCompiler *typeCompiler); + + bool scanObject(); + +private: + bool scanObject(int objectIndex); + + QVector<QmlIR::Object*> *qmlObjects; + const QQmlPropertyCacheVector * const propertyCaches; + const QHash<int, QQmlCustomParser*> &customParsers; + + bool _seenObjectWithId; +}; + +// ### merge with QtQml::JSCodeGen and operate directly on object->functionsAndExpressions once old compiler is gone. +class QQmlJSCodeGenerator : public QQmlCompilePass +{ +public: + QQmlJSCodeGenerator(QQmlTypeCompiler *typeCompiler, QmlIR::JSCodeGen *v4CodeGen); + + bool generateCodeForComponents(); + +private: + bool compileComponent(int componentRoot); + bool compileJavaScriptCodeInObjectsRecursively(int objectIndex, int scopeObjectIndex); + + const QHash<int, QQmlCustomParser*> &customParsers; + const QVector<QmlIR::Object*> &qmlObjects; + const QQmlPropertyCacheVector * const propertyCaches; + QmlIR::JSCodeGen * const v4CodeGen; +}; + +class QQmlDefaultPropertyMerger : public QQmlCompilePass +{ +public: + QQmlDefaultPropertyMerger(QQmlTypeCompiler *typeCompiler); + + void mergeDefaultProperties(); + +private: + void mergeDefaultProperties(int objectIndex); + + const QVector<QmlIR::Object*> &qmlObjects; + const QQmlPropertyCacheVector * const propertyCaches; +}; + +QT_END_NAMESPACE + +#endif // QQMLTYPECOMPILER_P_H |