diff options
Diffstat (limited to 'src/qml/qml/qqmltypecompiler.cpp')
-rw-r--r-- | src/qml/qml/qqmltypecompiler.cpp | 1212 |
1 files changed, 1212 insertions, 0 deletions
diff --git a/src/qml/qml/qqmltypecompiler.cpp b/src/qml/qml/qqmltypecompiler.cpp new file mode 100644 index 0000000000..94e6e8f351 --- /dev/null +++ b/src/qml/qml/qqmltypecompiler.cpp @@ -0,0 +1,1212 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "qqmltypecompiler_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> +#include <private/qqmlcomponentandaliasresolver_p.h> +#include <private/qqmlsignalnames_p.h> + +#define COMPILE_EXCEPTION(token, desc) \ + { \ + recordError((token)->location, desc); \ + return false; \ + } + +QT_BEGIN_NAMESPACE + +DEFINE_BOOL_CONFIG_OPTION( + disableInternalDeferredProperties, QML_DISABLE_INTERNAL_DEFERRED_PROPERTIES); + +Q_LOGGING_CATEGORY(lcQmlTypeCompiler, "qt.qml.typecompiler"); + +QQmlTypeCompiler::QQmlTypeCompiler( + QQmlEnginePrivate *engine, QQmlTypeData *typeData, QmlIR::Document *parsedQML, + QV4::CompiledData::ResolvedTypeReferenceMap *resolvedTypeCache, + const QV4::CompiledData::DependentTypesHasher &dependencyHasher) + : resolvedTypes(resolvedTypeCache) + , engine(engine) + , dependencyHasher(dependencyHasher) + , document(parsedQML) + , typeData(typeData) +{ +} + +QQmlRefPointer<QV4::CompiledData::CompilationUnit> 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(), typeData->typeClassName()); + QQmlError cycleError = propertyCacheBuilder.verifyNoICCycle(); + if (cycleError.isValid()) { + recordError(cycleError); + return nullptr; + } + QQmlPropertyCacheCreatorBase::IncrementalResult result; + do { + result = propertyCacheBuilder.buildMetaObjectsIncrementally(); + const QQmlError &error = result.error; + if (error.isValid()) { + recordError(error); + return nullptr; + } else { + // Resolve component boundaries and aliases + + QQmlComponentAndAliasResolver resolver(this, enginePrivate(), &m_propertyCaches); + if (QQmlError error = resolver.resolve(result.processedRoot); error.isValid()) { + recordError(error); + return nullptr; + } + pendingGroupPropertyBindings.resolveMissingPropertyCaches(&m_propertyCaches); + pendingGroupPropertyBindings.clear(); // anything that can be processed is now processed + } + } while (result.canResume); + } + + { + QQmlDefaultPropertyMerger merger(this); + merger.mergeDefaultProperties(); + } + + { + SignalHandlerResolver converter(this); + if (!converter.resolveSignalHandlerExpressions()) + return nullptr; + } + + { + QQmlEnumTypeResolver enumResolver(this); + if (!enumResolver.resolveEnumBindings()) + return nullptr; + } + + { + QQmlCustomParserScriptIndexer cpi(this); + cpi.annotateBindingsWithScriptStrings(); + } + + { + QQmlAliasAnnotator annotator(this); + annotator.annotateBindingsToAliases(); + } + + { + QQmlDeferredAndCustomParserBindingScanner deferredAndCustomParserBindingScanner(this); + if (!deferredAndCustomParserBindingScanner.scanObject()) + return nullptr; + } + + if (!document->javaScriptCompilationUnit || !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, engine->v4engine()->illegalNames()); + for (QmlIR::Object *object : std::as_const(document->objects)) { + if (!v4CodeGenerator.generateRuntimeFunctions(object)) { + Q_ASSERT(v4CodeGenerator.hasError()); + recordError(v4CodeGenerator.error()); + return nullptr; + } + } + document->javaScriptCompilationUnit = v4CodeGenerator.generateCompilationUnit(/*generated unit data*/false); + } + + // Generate QML compiled type data structures + + QmlIR::QmlUnitGenerator qmlGenerator; + qmlGenerator.generate(*document, dependencyHasher); + + if (!errors.isEmpty()) + return nullptr; + + return std::move(document->javaScriptCompilationUnit); +} + +void QQmlTypeCompiler::recordError(const QV4::CompiledData::Location &location, const QString &description) +{ + QQmlError error; + error.setLine(qmlConvertSourceCoordinate<quint32, int>(location.line())); + error.setColumn(qmlConvertSourceCoordinate<quint32, int>(location.column())); + error.setDescription(description); + error.setUrl(url()); + errors << error; +} + +void QQmlTypeCompiler::recordError(const QQmlJS::DiagnosticMessage &message) +{ + QQmlError error; + error.setDescription(message.message); + error.setLine(qmlConvertSourceCoordinate<quint32, int>(message.loc.startLine)); + error.setColumn(qmlConvertSourceCoordinate<quint32, int>(message.loc.startColumn)); + error.setUrl(url()); + errors << error; +} + +void QQmlTypeCompiler::recordError(const QQmlError &e) +{ + QQmlError error = e; + error.setUrl(url()); + errors << error; +} + +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; +} + +QQmlPropertyCacheVector *QQmlTypeCompiler::propertyCaches() +{ + return &m_propertyCaches; +} + +const QQmlPropertyCacheVector *QQmlTypeCompiler::propertyCaches() const +{ + return &m_propertyCaches; +} + +QQmlJS::MemoryPool *QQmlTypeCompiler::memoryPool() +{ + return document->jsParserEngine.pool(); +} + +QStringView 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, QTypeRevision version) +{ + const quint32 moduleIdx = registerString(module); + const quint32 qualifierIdx = registerString(qualifier); + + for (int i = 0, count = document->imports.size(); 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->version = version; + import->uriIndex = moduleIdx; + import->qualifierIndex = qualifierIdx; + document->imports.append(import); +} + +QQmlType QQmlTypeCompiler::qmlTypeForComponent(const QString &inlineComponentName) const +{ + return typeData->qmlType(inlineComponentName); +} + +QQmlCompilePass::QQmlCompilePass(QQmlTypeCompiler *typeCompiler) + : compiler(typeCompiler) +{ +} + +SignalHandlerResolver::SignalHandlerResolver(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 SignalHandlerResolver::resolveSignalHandlerExpressions() +{ + for (int objectIndex = 0; objectIndex < qmlObjects.size(); ++objectIndex) { + const QmlIR::Object * const obj = qmlObjects.at(objectIndex); + QQmlPropertyCache::ConstPtr 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 (!resolveSignalHandlerExpressions(obj, elementName, cache)) + return false; + } + return true; +} + +bool SignalHandlerResolver::resolveSignalHandlerExpressions( + const QmlIR::Object *obj, const QString &typeName, + const QQmlPropertyCache::ConstPtr &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) { + const QString bindingPropertyName = stringAt(binding->propertyNameIndex); + // Attached property? + const QV4::CompiledData::Binding::Type bindingType = binding->type(); + if (bindingType == 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()) { + imports->resolveType( + QQmlTypeLoader::get(enginePrivate), bindingPropertyName, &type, nullptr, + nullptr); + } + + const QMetaObject *attachedType = type.attachedPropertiesType(enginePrivate); + if (!attachedType) + COMPILE_EXCEPTION(binding, tr("Non-existent attached object")); + QQmlPropertyCache::ConstPtr cache = QQmlMetaType::propertyCache(attachedType); + if (!resolveSignalHandlerExpressions(attachedObj, bindingPropertyName, cache)) + return false; + continue; + } + + QString qPropertyName; + QString signalName; + if (auto propertyName = + QQmlSignalNames::changedHandlerNameToPropertyName(bindingPropertyName)) { + qPropertyName = *propertyName; + signalName = *QQmlSignalNames::changedHandlerNameToSignalName(bindingPropertyName); + } else { + signalName = QQmlSignalNames::handlerNameToSignalName(bindingPropertyName) + .value_or(QString()); + } + if (signalName.isEmpty()) + continue; + + QQmlPropertyResolver resolver(propertyCache); + + bool notInRevision = false; + const QQmlPropertyData * const signal = resolver.signal(signalName, ¬InRevision); + const QQmlPropertyData * const signalPropertyData = resolver.property(signalName, /*notInRevision ptr*/nullptr); + const QQmlPropertyData * const qPropertyData = !qPropertyName.isEmpty() ? resolver.property(qPropertyName) : nullptr; + QString finalSignalHandlerPropertyName = signalName; + QV4::CompiledData::Binding::Flag flag + = QV4::CompiledData::Binding::IsSignalHandlerExpression; + + const bool isPropertyObserver = !signalPropertyData && qPropertyData && qPropertyData->isBindable(); + if (signal && !(qPropertyData && qPropertyData->isAlias() && isPropertyObserver)) { + 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.size(); ++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)); + } + } + } else if (isPropertyObserver) { + finalSignalHandlerPropertyName = qPropertyName; + flag = QV4::CompiledData::Binding::IsPropertyObserver; + } else { + if (notInRevision) { + // Try assinging it as a property later + if (signalPropertyData) + 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.version().majorVersion()) + .arg(type.version().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(signalName); + if (entry == customSignals.constEnd() && !qPropertyName.isEmpty()) + entry = customSignals.constFind(qPropertyName); + + 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; + } + } + + // Binding object to signal means connect the signal to the object's default method. + if (bindingType == QV4::CompiledData::Binding::Type_Object) { + binding->setFlag(QV4::CompiledData::Binding::IsSignalHandlerObject); + continue; + } + + if (bindingType != QV4::CompiledData::Binding::Type_Script) { + if (bindingType < 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")); + } + } + + binding->propertyNameIndex = compiler->registerString(finalSignalHandlerPropertyName); + binding->setFlag(flag); + } + 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.size(); ++i) { + QQmlPropertyCache::ConstPtr 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) { + const QV4::CompiledData::Binding::Flags bindingFlags = binding->flags(); + if (bindingFlags & QV4::CompiledData::Binding::IsSignalHandlerExpression + || bindingFlags & QV4::CompiledData::Binding::IsSignalHandlerObject + || bindingFlags & QV4::CompiledData::Binding::IsPropertyObserver) + continue; + + if (binding->type() != QV4::CompiledData::Binding::Type_Script) + continue; + + const QString propertyName = stringAt(binding->propertyNameIndex); + bool notInRevision = false; + const QQmlPropertyData *pd = resolver.property(propertyName, ¬InRevision); + if (!pd || pd->isQList()) + continue; + + if (!pd->isEnum() && pd->propType().id() != QMetaType::Int) + continue; + + if (!tryQualifiedEnumAssignment(obj, propertyCache, pd, binding)) + return false; + } + } + + return true; +} + +bool QQmlEnumTypeResolver::assignEnumToBinding(QmlIR::Binding *binding, QStringView, int enumValue, bool) +{ + binding->setType(QV4::CompiledData::Binding::Type_Number); + binding->value.constantValueIndex = compiler->registerConstant(QV4::Encode((double)enumValue)); +// binding->setNumberValueInternal((double)enumValue); + binding->setFlag(QV4::CompiledData::Binding::IsResolvedEnum); + return true; +} + +bool QQmlEnumTypeResolver::tryQualifiedEnumAssignment( + const QmlIR::Object *obj, const QQmlPropertyCache::ConstPtr &propertyCache, + const QQmlPropertyData *prop, QmlIR::Binding *binding) +{ + bool isIntProp = (prop->propType().id() == QMetaType::Int) && !prop->isEnum(); + if (!prop->isEnum() && !isIntProp) + return true; + + if (!prop->isWritable() + && !(binding->hasFlag(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; + + // reject any "complex" expression (even simple arithmetic) + // we do this by excluding everything that is not part of a + // valid identifier or a dot + for (const QChar &c : string) + if (!(c.isLetterOrNumber() || c == u'.' || c == u'_' || c.isSpace())) + 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.size()-1) + return true; + + int dot2 = string.indexOf(QLatin1Char('.'), dot+1); + if (dot2 != -1 && dot2 != string.size()-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 QStringView scopedEnumName = (dot2 != -1 ? QStringView{string}.mid(dot + 1, dot2 - dot - 1) : QStringView()); + // ### consider supporting scoped enums in Qt namespace + const QStringView enumValue = QStringView{string}.mid(!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( + QQmlTypeLoader::get(compiler->enginePrivate()), typeName, &type, nullptr, nullptr); + + if (!type.isValid() && !isQtObject) + return true; + + int value = 0; + bool ok = false; + + auto *tr = resolvedType(obj->inheritedTypeNameIndex); + + // When these two match, we can short cut the search, unless... + bool useFastPath = type.isValid() && tr && tr->type() == type; + QMetaProperty mprop; + QMetaEnum menum; + if (useFastPath) { + mprop = propertyCache->firstCppMetaObject()->property(prop->coreIndex()); + menum = mprop.enumerator(); + // ...the enumerator merely comes from a related metaobject, but the enum scope does not match + // the typename we resolved + if (!menum.isScoped() && scopedEnumName.isEmpty() && typeName != QString::fromUtf8(menum.scope())) + useFastPath = false;; + } + if (useFastPath) { + 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 = &Qt::staticMetaObject; + 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, QStringView enumName, QStringView 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( + QQmlTypeLoader::get(compiler->enginePrivate()), scope, &type, 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.size()), ok); + } + + const QMetaObject *mo = &Qt::staticMetaObject; + 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); + for (int i = 0; i < qmlObjects.size(); ++i) + if (qmlObjects.at(i)->flags & QV4::CompiledData::Object::IsInlineComponentRoot) + scanObjectRecursively(i); +} + +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) { + switch (binding->type()) { + case QV4::CompiledData::Binding::Type_Script: + if (annotateScriptBindings) { + binding->stringIndex = compiler->registerString( + compiler->bindingAsString(obj, binding->value.compiledScriptIndex)); + } + break; + case QV4::CompiledData::Binding::Type_Object: + case QV4::CompiledData::Binding::Type_AttachedProperty: + case QV4::CompiledData::Binding::Type_GroupProperty: + scanObjectRecursively(binding->value.objectIndex, annotateScriptBindings); + break; + default: + break; + } + } +} + +QQmlAliasAnnotator::QQmlAliasAnnotator(QQmlTypeCompiler *typeCompiler) + : QQmlCompilePass(typeCompiler) + , qmlObjects(*typeCompiler->qmlObjects()) + , propertyCaches(typeCompiler->propertyCaches()) +{ +} + +void QQmlAliasAnnotator::annotateBindingsToAliases() +{ + for (int i = 0; i < qmlObjects.size(); ++i) { + QQmlPropertyCache::ConstPtr propertyCache = propertyCaches->at(i); + if (!propertyCache) + continue; + + const QmlIR::Object *obj = qmlObjects.at(i); + + QQmlPropertyResolver resolver(propertyCache); + const 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; + const QQmlPropertyData *pd = binding->propertyNameIndex != quint32(0) ? resolver.property(stringAt(binding->propertyNameIndex), ¬InRevision) : defaultProperty; + if (pd && pd->isAlias()) + binding->setFlag(QV4::CompiledData::Binding::IsBindingToAlias); + } + } +} + +QQmlScriptStringScanner::QQmlScriptStringScanner(QQmlTypeCompiler *typeCompiler) + : QQmlCompilePass(typeCompiler) + , qmlObjects(*typeCompiler->qmlObjects()) + , propertyCaches(typeCompiler->propertyCaches()) +{ + +} + +void QQmlScriptStringScanner::scan() +{ + const QMetaType scriptStringMetaType = QMetaType::fromType<QQmlScriptString>(); + for (int i = 0; i < qmlObjects.size(); ++i) { + QQmlPropertyCache::ConstPtr propertyCache = propertyCaches->at(i); + if (!propertyCache) + continue; + + const QmlIR::Object *obj = qmlObjects.at(i); + + QQmlPropertyResolver resolver(propertyCache); + const 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; + const 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); + } + } +} + +template<> +void QQmlComponentAndAliasResolver<QQmlTypeCompiler>::allocateNamedObjects( + QmlIR::Object *object) const +{ + object->namedObjectsInComponent.allocate(m_compiler->memoryPool(), m_idToObjectIndex); +} + +template<> +bool QQmlComponentAndAliasResolver<QQmlTypeCompiler>::markAsComponent(int index) const +{ + m_compiler->qmlObjects()->at(index)->flags |= QV4::CompiledData::Object::IsComponent; + return true; +} + +template<> +void QQmlComponentAndAliasResolver<QQmlTypeCompiler>::setObjectId(int index) const +{ + m_compiler->qmlObjects()->at(index)->id = m_idToObjectIndex.size(); +} + +template<> +bool QQmlComponentAndAliasResolver<QQmlTypeCompiler>::wrapImplicitComponent(QmlIR::Binding *binding) +{ + QQmlJS::MemoryPool *pool = m_compiler->memoryPool(); + QVector<QmlIR::Object *> *qmlObjects = m_compiler->qmlObjects(); + + // emulate "import QML 1.0" and then wrap the component in "QML.Component {}" + QQmlType componentType = QQmlMetaType::qmlType( + &QQmlComponent::staticMetaObject, QStringLiteral("QML"), + QTypeRevision::fromVersion(1, 0)); + Q_ASSERT(componentType.isValid()); + const QString qualifier = QStringLiteral("QML"); + + m_compiler->addImport(componentType.module(), qualifier, componentType.version()); + + QmlIR::Object *syntheticComponent = pool->New<QmlIR::Object>(); + syntheticComponent->init( + pool, + m_compiler->registerString( + qualifier + QLatin1Char('.') + componentType.elementName()), + m_compiler->registerString(QString()), binding->valueLocation); + syntheticComponent->flags |= QV4::CompiledData::Object::IsComponent; + + if (!m_compiler->resolvedTypes->contains(syntheticComponent->inheritedTypeNameIndex)) { + auto typeRef = new QV4::ResolvedTypeReference; + typeRef->setType(componentType); + typeRef->setVersion(componentType.version()); + m_compiler->resolvedTypes->insert(syntheticComponent->inheritedTypeNameIndex, typeRef); + } + + qmlObjects->append(syntheticComponent); + const int componentIndex = qmlObjects->size() - 1; + // Keep property caches symmetric + QQmlPropertyCache::ConstPtr componentCache + = QQmlMetaType::propertyCache(&QQmlComponent::staticMetaObject); + m_propertyCaches->append(componentCache); + + QmlIR::Binding *syntheticBinding = pool->New<QmlIR::Binding>(); + *syntheticBinding = *binding; + + // The synthetic binding inside Component has no name. It's just "Component { Foo {} }". + syntheticBinding->propertyNameIndex = 0; + + syntheticBinding->setType(QV4::CompiledData::Binding::Type_Object); + QString error = syntheticComponent->appendBinding(syntheticBinding, /*isListBinding*/false); + Q_ASSERT(error.isEmpty()); + Q_UNUSED(error); + + binding->value.objectIndex = componentIndex; + + m_componentRoots.append(componentIndex); + return true; +} + +template<> +void QQmlComponentAndAliasResolver<QQmlTypeCompiler>::resolveGeneralizedGroupProperty( + const CompiledObject &component, CompiledBinding *binding) +{ + Q_UNUSED(component); + // We cannot make it fail here. It might be a custom-parsed property + const int targetObjectIndex = m_idToObjectIndex.value(binding->propertyNameIndex, -1); + if (targetObjectIndex != -1) + m_propertyCaches->set(binding->value.objectIndex, m_propertyCaches->at(targetObjectIndex)); +} + +template<> +typename QQmlComponentAndAliasResolver<QQmlTypeCompiler>::AliasResolutionResult +QQmlComponentAndAliasResolver<QQmlTypeCompiler>::resolveAliasesInObject( + const CompiledObject &component, int objectIndex, QQmlError *error) +{ + Q_UNUSED(component); + + const QmlIR::Object * const obj = m_compiler->objectAt(objectIndex); + if (!obj->aliasCount()) + return AllAliasesResolved; + + int numResolvedAliases = 0; + bool seenUnresolvedAlias = false; + + for (QmlIR::Alias *alias = obj->firstAlias(); alias; alias = alias->next) { + if (alias->hasFlag(QV4::CompiledData::Alias::Resolved)) + continue; + + seenUnresolvedAlias = true; + + const int idIndex = alias->idIndex(); + const int targetObjectIndex = m_idToObjectIndex.value(idIndex, -1); + if (targetObjectIndex == -1) { + *error = qQmlCompileError( + alias->referenceLocation, + QQmlComponentAndAliasResolverBase::tr("Invalid alias reference. Unable to find id \"%1\"").arg(stringAt(idIndex))); + break; + } + + const QmlIR::Object *targetObject = m_compiler->objectAt(targetObjectIndex); + Q_ASSERT(targetObject->id >= 0); + alias->setTargetObjectId(targetObject->id); + alias->setIsAliasToLocalAlias(false); + + const QString aliasPropertyValue = stringAt(alias->propertyNameIndex); + + QStringView property; + QStringView subProperty; + + const int propertySeparator = aliasPropertyValue.indexOf(QLatin1Char('.')); + if (propertySeparator != -1) { + property = QStringView{aliasPropertyValue}.left(propertySeparator); + subProperty = QStringView{aliasPropertyValue}.mid(propertySeparator + 1); + } else + property = QStringView(aliasPropertyValue); + + QQmlPropertyIndex propIdx; + + if (property.isEmpty()) { + alias->setFlag(QV4::CompiledData::Alias::AliasPointsToPointerObject); + } else { + QQmlPropertyCache::ConstPtr targetCache = m_propertyCaches->at(targetObjectIndex); + if (!targetCache) { + *error = qQmlCompileError( + alias->referenceLocation, + QQmlComponentAndAliasResolverBase::tr("Invalid alias target location: %1").arg(property.toString())); + break; + } + + QQmlPropertyResolver resolver(targetCache); + + const 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->setIsAliasToLocalAlias(true); + alias->setFlag(QV4::CompiledData::Alias::Resolved); + ++numResolvedAliases; + continue; + } + + // restore + alias->setIdIndex(idIndex); + // Try again later and resolve the target alias first. + break; + } + } + + if (!targetProperty || targetProperty->coreIndex() > 0x0000FFFF) { + *error = qQmlCompileError( + alias->referenceLocation, + QQmlComponentAndAliasResolverBase::tr("Invalid alias target location: %1").arg(property.toString())); + break; + } + + propIdx = QQmlPropertyIndex(targetProperty->coreIndex()); + + if (!subProperty.isEmpty()) { + const QMetaObject *valueTypeMetaObject = QQmlMetaType::metaObjectForValueType(targetProperty->propType()); + if (!valueTypeMetaObject) { + // could be a deep alias + bool isDeepAlias = subProperty.at(0).isLower(); + if (isDeepAlias) { + isDeepAlias = false; + for (auto it = targetObject->bindingsBegin(); it != targetObject->bindingsEnd(); ++it) { + auto binding = *it; + if (m_compiler->stringAt(binding.propertyNameIndex) == property) { + resolver = QQmlPropertyResolver(m_propertyCaches->at(binding.value.objectIndex)); + const QQmlPropertyData *actualProperty = resolver.property(subProperty.toString()); + if (actualProperty) { + propIdx = QQmlPropertyIndex(propIdx.coreIndex(), actualProperty->coreIndex()); + isDeepAlias = true; + } + } + } + } + if (!isDeepAlias) { + *error = qQmlCompileError( + alias->referenceLocation, + QQmlComponentAndAliasResolverBase::tr("Invalid alias target location: %1").arg(subProperty.toString())); + break; + } + } else { + + int valueTypeIndex = + valueTypeMetaObject->indexOfProperty(subProperty.toString().toUtf8().constData()); + if (valueTypeIndex == -1) { + *error = qQmlCompileError( + alias->referenceLocation, + QQmlComponentAndAliasResolverBase::tr("Invalid alias target location: %1").arg(subProperty.toString())); + break; + } + Q_ASSERT(valueTypeIndex <= 0x0000FFFF); + + propIdx = QQmlPropertyIndex(propIdx.coreIndex(), valueTypeIndex); + } + } else { + if (targetProperty->isQObject()) + alias->setFlag(QV4::CompiledData::Alias::AliasPointsToPointerObject); + } + } + + alias->encodedMetaPropertyIndex = propIdx.toEncoded(); + alias->setFlag(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() +{ + for (int i = 0; i < qmlObjects->size(); ++i) { + if ((qmlObjects->at(i)->flags & QV4::CompiledData::Object::IsInlineComponentRoot) + && !scanObject(i, ScopeDeferred::False)) { + return false; + } + } + return scanObject(/*root object*/0, ScopeDeferred::False); +} + +bool QQmlDeferredAndCustomParserBindingScanner::scanObject( + int objectIndex, ScopeDeferred scopeDeferred) +{ + using namespace QV4::CompiledData; + + QmlIR::Object *obj = qmlObjects->at(objectIndex); + if (obj->idNameIndex != 0) + _seenObjectWithId = true; + + if (obj->flags & Object::IsComponent) { + Q_ASSERT(obj->bindingCount() == 1); + const Binding *componentBinding = obj->firstBinding(); + Q_ASSERT(componentBinding->type() == Binding::Type_Object); + // Components are separate from their surrounding scope. They cannot be deferred. + return scanObject(componentBinding->value.objectIndex, ScopeDeferred::False); + } + + QQmlPropertyCache::ConstPtr propertyCache = propertyCaches->at(objectIndex); + if (!propertyCache) + return true; + + QString defaultPropertyName; + const QQmlPropertyData *defaultProperty = nullptr; + if (obj->indexOfDefaultPropertyOrAlias != -1) { + const QQmlPropertyCache *cache = propertyCache->parent().data(); + defaultPropertyName = cache->defaultPropertyName(); + defaultProperty = cache->defaultProperty(); + } else { + defaultPropertyName = propertyCache->defaultPropertyName(); + defaultProperty = propertyCache->defaultProperty(); + } + + QQmlCustomParser *customParser = customParsers.value(obj->inheritedTypeNameIndex); + + QQmlPropertyResolver propertyResolver(propertyCache); + + QStringList deferredPropertyNames; + QStringList immediatePropertyNames; + { + const QMetaObject *mo = propertyCache->firstCppMetaObject(); + const int deferredNamesIndex = mo->indexOfClassInfo("DeferredPropertyNames"); + const int immediateNamesIndex = mo->indexOfClassInfo("ImmediatePropertyNames"); + if (deferredNamesIndex != -1) { + if (immediateNamesIndex != -1) { + COMPILE_EXCEPTION(obj, tr("You cannot define both DeferredPropertyNames and " + "ImmediatePropertyNames on the same type.")); + } + const QMetaClassInfo classInfo = mo->classInfo(deferredNamesIndex); + deferredPropertyNames = QString::fromUtf8(classInfo.value()).split(u','); + } else if (immediateNamesIndex != -1) { + const QMetaClassInfo classInfo = mo->classInfo(immediateNamesIndex); + immediatePropertyNames = QString::fromUtf8(classInfo.value()).split(u','); + + // If the property contains an empty string, all properties shall be deferred. + if (immediatePropertyNames.isEmpty()) + immediatePropertyNames.append(QString()); + } + } + + for (QmlIR::Binding *binding = obj->firstBinding(); binding; binding = binding->next) { + QString name = stringAt(binding->propertyNameIndex); + + if (customParser) { + if (binding->type() == Binding::Type_AttachedProperty) { + if (customParser->flags() & QQmlCustomParser::AcceptsAttachedProperties) { + binding->setFlag(Binding::IsCustomParserBinding); + obj->flags |= Object::HasCustomParserBindings; + continue; + } + } else if (QQmlSignalNames::isHandlerName(name) + && !(customParser->flags() & QQmlCustomParser::AcceptsSignalHandlers)) { + obj->flags |= Object::HasCustomParserBindings; + binding->setFlag(Binding::IsCustomParserBinding); + continue; + } + } + + const bool hasPropertyData = [&]() { + if (name.isEmpty()) { + name = defaultPropertyName; + if (defaultProperty) + return true; + } else if (name.constData()->isUpper()) { + // Upper case names cannot be custom-parsed unless they are attached properties + // and the custom parser explicitly accepts them. See above for that case. + return false; + } else { + bool notInRevision = false; + if (propertyResolver.property( + name, ¬InRevision, QQmlPropertyResolver::CheckRevision)) { + return true; + } + } + + if (!customParser) + return false; + + const Binding::Flags bindingFlags = binding->flags(); + if (bindingFlags & Binding::IsSignalHandlerExpression + || bindingFlags & Binding::IsSignalHandlerObject + || bindingFlags & Binding::IsPropertyObserver) { + // These signal handlers cannot be custom-parsed. We have already established + // that the signal exists. + return false; + } + + // If the property isn't found, we may want to custom-parse the binding. + obj->flags |= Object::HasCustomParserBindings; + binding->setFlag(Binding::IsCustomParserBinding); + return false; + }(); + + bool seenSubObjectWithId = false; + bool isExternal = false; + if (binding->type() >= Binding::Type_Object) { + const bool isOwnProperty = hasPropertyData || binding->isAttachedProperty(); + isExternal = !isOwnProperty && binding->isGroupProperty(); + if (isOwnProperty || isExternal) { + qSwap(_seenObjectWithId, seenSubObjectWithId); + const bool subObjectValid = scanObject( + binding->value.objectIndex, + (isExternal || scopeDeferred == ScopeDeferred::True) + ? ScopeDeferred::True + : ScopeDeferred::False); + qSwap(_seenObjectWithId, seenSubObjectWithId); + if (!subObjectValid) + return false; + _seenObjectWithId |= seenSubObjectWithId; + } + } + + bool isDeferred = false; + if (!immediatePropertyNames.isEmpty() && !immediatePropertyNames.contains(name)) { + if (seenSubObjectWithId) { + COMPILE_EXCEPTION(binding, tr("You cannot assign an id to an object assigned " + "to a deferred property.")); + } + if (isExternal || !disableInternalDeferredProperties()) + isDeferred = true; + } else if (!deferredPropertyNames.isEmpty() && deferredPropertyNames.contains(name)) { + if (!seenSubObjectWithId && binding->type() != Binding::Type_GroupProperty) { + if (isExternal || !disableInternalDeferredProperties()) + isDeferred = true; + } + } + + if (binding->type() >= Binding::Type_Object) { + if (isExternal && !isDeferred && !customParser) { + COMPILE_EXCEPTION( + binding, tr("Cannot assign to non-existent property \"%1\"").arg(name)); + } + } + + if (isDeferred) { + binding->setFlag(Binding::IsDeferredBinding); + obj->flags |= Object::HasDeferredBindings; + } + } + + return true; +} + +QQmlDefaultPropertyMerger::QQmlDefaultPropertyMerger(QQmlTypeCompiler *typeCompiler) + : QQmlCompilePass(typeCompiler) + , qmlObjects(*typeCompiler->qmlObjects()) + , propertyCaches(typeCompiler->propertyCaches()) +{ + +} + +void QQmlDefaultPropertyMerger::mergeDefaultProperties() +{ + for (int i = 0; i < qmlObjects.size(); ++i) + mergeDefaultProperties(i); +} + +void QQmlDefaultPropertyMerger::mergeDefaultProperties(int objectIndex) +{ + QQmlPropertyCache::ConstPtr 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 |