Revision control

++ // Copyright (C) 2026 The Qt Company Ltd.
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
++ // Qt-Security score:significant
++
++ #include "qqmljslookupsignatures_p.h"
++
++ #include <private/qqmljstyperesolver_p.h>
++
++ using namespace Qt::StringLiterals;
++
++ QT_BEGIN_NAMESPACE
++
++ QQmlJSLookupSignaturesRecorder::QQmlJSLookupSignaturesRecorder(
++ const QString &currentFilePath, const QQmlJSTypeResolver *typeResolver)
++ : m_currentFilePath(currentFilePath), m_typeResolver(typeResolver)
++ {
++ Q_ASSERT(!m_currentFilePath.isEmpty());
++ Q_ASSERT(m_typeResolver);
++ }
++
++ #define CHECKED_OPT_ASSIGN(var, expr) \
++ { \
++ const auto opt = expr; \
++ if (!opt.has_value()) \
++ return false; \
++ var = opt.value(); \
++ }
++
++ std::optional<QQmlPrivate::AOTLookupValidation::Type>
++ QQmlJSLookupSignaturesRecorder::type(const QQmlJSScope::ConstPtr &type)
++ {
++ using namespace QQmlPrivate::AOTLookupValidation;
++
++ QQmlPrivate::AOTLookupValidation::Type res;
++ if (!type->isComposite()) {
++ if (type->accessSemantics() == QQmlSA::AccessSemantics::Value && type->isSelfExtension()) {
++ res.name = type->internalName();
++ res.icNameOrExtensionTypeName = type->extensionTypeName();
++ } else {
++ res.name = type->internalName();
++ }
++
++ return res;
++ }
++
++ res.isComposite = IsComposite::Yes;
++ if (isUnnamedCompositeType(type)) {
++ // An unnamed type can only be used as the base of a lookup, not the type in a property or
++ // method declaration. Assume the target property/method is declared on a base type, and
++ // record the base type of \a type as the base of the lookup. Otherwise, we would have
++ // discarded the lookup earlier. See recordPropertyLookup.
++ if (!type->baseType()) {
++ const QString msg = "unknown base type for composite object at %1:%2"_L1;
++ m_rejectMessage = msg.arg(type->filePath(), QString::number(type->lineNumber()));
++ return {};
++ }
++ return QQmlJSLookupSignaturesRecorder::type(type->baseType());
++ }
++
++ QQmlJSScope::ConstPtr root = type;
++ while (!root->isFileRootComponent())
++ root = root->parentScope();
++
++ bool isFileBeingCompiled = root->filePath() == m_typeResolver->logger()->filePath();
++
++ Q_ASSERT(!root->internalName().isEmpty());
++ if (isFileBeingCompiled) {
++ res.module = s_thisCuModule;
++ res.name = s_thisCuType;
++ } else {
++ // At run time, the type is looked up by name through an import of its module. Record an
++ // import a document has. Prefer the imports of the document being compiled, since they
++ // are registered first.
++ const auto imports = m_typeResolver->importer()->versionedImports(root->filePath());
++ const auto import = std::find_if(imports.cbegin(), imports.cend(),
++ [&](const QQmlJSImporter::VersionedImport &import) {
++ return import.module == root->moduleName();
++ });
++ if (import == imports.cend()) {
++ m_rejectMessage = "%1 is not imported through its module %2"_L1.arg(root->filePath(),
++ root->moduleName());
++ return {};
++ }
++
++ res.module = import->module;
++ res.name = import->name;
++ res.version = import->version;
++
++ // A C++ type of the same name takes precedence there. Then we cannot find this type that
++ // way.
++ const QQmlJSScope::ConstPtr found =
++ m_typeResolver->importer()
++ ->importModule(res.module, 0, QString(), res.version)
++ .type(res.name)
++ .scope;
++ if (found && !found->isComposite()) {
++ m_rejectMessage = "%1 is shadowed by C++ type %2 in module %3"_L1.arg(
++ root->filePath(), res.name, res.module);
++ return {};
++ }
++ }
++
++ if (type->isInlineComponent()) {
++ res.isInlineComponent = IsIC::Yes;
++ res.icNameOrExtensionTypeName = *type->inlineComponentName();
++ return res;
++ }
++
++ return res;
++ }
++
++ bool QQmlJSLookupSignaturesRecorder::cantDesync(const QQmlJSScope::ConstPtr &type) const
++ {
++ Q_ASSERT(!m_typeResolver->logger()->filePath().isEmpty());
++ // Same file -> If one changes the other also gets recompiled and adapts to the change
++ return type->filePath() == m_typeResolver->logger()->filePath();
++ }
++
++ bool QQmlJSLookupSignaturesRecorder::safeBase(const QQmlJSScope::ConstPtr &base) const
++ {
++ return base == m_typeResolver->varType() || base == m_typeResolver->jsValueType()
++ || base->inherits(m_typeResolver->qmlPropertyMapType());
++ }
++
++ bool QQmlJSLookupSignaturesRecorder::isUnnamedCompositeType(const QQmlJSScope::ConstPtr &type) const
++ {
++ return type->isComposite() && !type->isFileRootComponent() && !type->isInlineComponent();
++ }
++
++ // On the MetaObject, signals come before regular functions
++ static int ownRegularMethodCountBeforeIndex(const QQmlJSScope::ConstPtr &type, int index)
++ {
++ const auto &ms = type->ownMethods();
++ return std::count_if(ms.cbegin(), ms.cend(), [&](const QQmlJSMetaMethod &m) {
++ Q_ASSERT(m.isConstructor() || m.methodIndex() != QQmlJSMetaMethod::RelativeFunctionIndex::Invalid);
++ return m.methodType() != QQmlSA::MethodType::Signal && !m.isConstructor()
++ && int(m.methodIndex()) < index;
++ });
++ }
++
++ // On the MetaObject, signals come before regular functions
++ static int ownSignalCountAfterIndex(const QQmlJSScope::ConstPtr &type, int index)
++ {
++ const auto &ms = type->ownMethods();
++ return std::count_if(ms.cbegin(), ms.cend(), [&](const QQmlJSMetaMethod &m) {
++ Q_ASSERT(m.isConstructor() || m.methodIndex() != QQmlJSMetaMethod::RelativeFunctionIndex::Invalid);
++ return m.methodType() == QQmlSA::MethodType::Signal && int(m.methodIndex()) > index;
++ });
++ }
++
++ // On the MetaObject, regular properties come before aliases
++ static int ownAliasCountBeforeIndex(const QQmlJSScope::ConstPtr &type, int index)
++ {
++ const auto &ps = type->ownProperties();
++ return std::count_if(ps.cbegin(), ps.cend(), [&](const auto &p) {
++ return p.isAlias() && p.index() < index;
++ });
++ }
++
++ // On the MetaObject, regular properties come before aliases
++ static int ownRegularPropertyCountAfterIndex(const QQmlJSScope::ConstPtr &type, int index)
++ {
++ const auto &ps = type->ownProperties();
++ return std::count_if(ps.cbegin(), ps.cend(), [&](const auto &p) {
++ return !p.isAlias() && p.index() > index;
++ });
++ }
++
++ bool QQmlJSLookupSignaturesRecorder::recordPropertyLookup(const QQmlJSScope::ConstPtr &base,
++ const QQmlJSMetaProperty &property)
++ {
++ using namespace QQmlPrivate::AOTLookupValidation;
++
++ const QString &name = property.propertyName();
++ const auto [owner, extensionSpecifier] = QQmlJSScope::ownerOfProperty(base, name);
++ if (base->isScript() || safeBase(base) || cantDesync(owner))
++ return true;
++
++ // When performing a lookup on an inner object of unnamed type, one of two things is true:
++ // 1) The target property (or method) is declared within the object. Then, because the object
++ // is unnamed, it must be defined in the same file as the lookup and they cannot desync and
++ // we don't have to record it.
++ // 2) Or the target property is declared on a base type of the inner object. Then we can
++ // simply record the lookup as a being performed on the base type instead. The addition of
++ // a property on the inner object that would shadow the base type's property can only be
++ // introduced by recompiling the lookup as well.
++ if (isUnnamedCompositeType(base) && owner == base)
++ return true;
++
++ PropertySignature propertySignature;
++ CHECKED_OPT_ASSIGN(propertySignature.type, type(property.type()))
++
++ // Compiler indexes follow document order. On the MO, aliases come after regular properties.
++ propertySignature.relativeIndex = property.isAlias()
++ ? property.index() + ownRegularPropertyCountAfterIndex(owner, property.index())
++ : property.index() - ownAliasCountBeforeIndex(owner, property.index());
++
++ Lookup lookup;
++ CHECKED_OPT_ASSIGN(lookup.base, type(base))
++ lookup.member = name;
++
++ m_signatures.insert(lookup, propertySignature);
++ return true;
++ }
++
++ bool QQmlJSLookupSignaturesRecorder::recordMethodLookup(const QQmlJSScope::ConstPtr &base,
++ const QQmlJSMetaMethod &method)
++ {
++ using namespace QQmlPrivate::AOTLookupValidation;
++
++ const QString &name = method.methodName();
++ const auto [owner, extensionSpecifier] = QQmlJSScope::ownerOfMethod(base, name);
++ if (base->isScript() || safeBase(base) || cantDesync(owner))
++ return true;
++
++ // See recordPropertyLookup
++ if (isUnnamedCompositeType(base) && owner == base)
++ return true;
++
++ // destroy and toString are special?
++ if (name == QStringLiteral("destroy") || name == QStringLiteral("toString"))
++ return true;
++
++ MethodSignature methodSignature;
++
++ if (method.methodType() == QQmlSA::MethodType::Signal) {
++ methodSignature.isSignal = IsSignal::Yes;
++ int index = int(method.methodIndex());
++ methodSignature.relativeIndex = index - ownRegularMethodCountBeforeIndex(owner, index);
++ } else {
++ methodSignature.isSignal = IsSignal::No;
++ int index = int(method.methodIndex());
++ methodSignature.relativeIndex = index + ownSignalCountAfterIndex(owner, index);
++ }
++
++ methodSignature.types.push_back({});
++ CHECKED_OPT_ASSIGN(methodSignature.types.back(), type(method.returnType()))
++ for (const auto &param : method.parameters()) {
++ methodSignature.paramNames.push_back(param.name());
++ methodSignature.types.push_back({});
++ CHECKED_OPT_ASSIGN(methodSignature.types.back(), type(param.type()))
++ }
++
++ Lookup lookup;
++ CHECKED_OPT_ASSIGN(lookup.base, type(base))
++ lookup.member = name;
++
++ m_signatures.insert(lookup, methodSignature);
++ return true;
++ }
++
++ bool QQmlJSLookupSignaturesRecorder::recordEnumKeyLookup(const QQmlJSScope::ConstPtr &base,
++ const QQmlJSMetaEnum &metaEnum,
++ const QString &keyName)
++ {
++ using namespace QQmlPrivate::AOTLookupValidation;
++
++ const auto [owner, extensionSpecifier] = QQmlJSScope::ownerOfEnum(base, metaEnum.name());
++ if (base->isScript() || safeBase(base) || cantDesync(owner))
++ return true;
++
++ EnumKeySignature enumSignature;
++ // QTBUG-145053: enums can only hold ints in the Compiler
++ enumSignature.value = quint64(metaEnum.value(keyName));
++ if (metaEnum.isFlag())
++ enumSignature.isFlag = IsFlag::Yes;
++
++ Lookup lookup;
++ CHECKED_OPT_ASSIGN(lookup.base, type(base))
++ lookup.member = keyName;
++ lookup.enumName = metaEnum.name();
++ m_signatures.insert(lookup, enumSignature);
++ return true;
++ }
++
++ QT_END_NAMESPACE