Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qqmljslookupsignatures.cpp
Go to the documentation of this file.
1// Copyright (C) 2026 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
3// Qt-Security score:significant
4
6
7#include <private/qqmljstyperesolver_p.h>
8
9using namespace Qt::StringLiterals;
10
11QT_BEGIN_NAMESPACE
12
13QQmlJSLookupSignaturesRecorder::QQmlJSLookupSignaturesRecorder(
14 const QString &currentFilePath, const QQmlJSTypeResolver *typeResolver)
15 : m_currentFilePath(currentFilePath), m_typeResolver(typeResolver)
16{
17 Q_ASSERT(!m_currentFilePath.isEmpty());
18 Q_ASSERT(m_typeResolver);
19}
20
21#define CHECKED_OPT_ASSIGN(var, expr)
22 {
23 const auto opt = expr;
24 if (!opt.has_value())
25 return false;
26 var = opt.value();
27 }
28
29std::optional<QQmlPrivate::AOTLookupValidation::Type>
30QQmlJSLookupSignaturesRecorder::type(const QQmlJSScope::ConstPtr &type)
31{
32 using namespace QQmlPrivate::AOTLookupValidation;
33
34 QQmlPrivate::AOTLookupValidation::Type res;
35 if (!type->isComposite()) {
36 if (type->accessSemantics() == QQmlSA::AccessSemantics::Value && type->isSelfExtension()) {
37 res.name = type->internalName();
38 res.icNameOrExtensionTypeName = type->extensionTypeName();
39 } else {
40 res.name = type->internalName();
41 }
42
43 return res;
44 }
45
46 res.isComposite = IsComposite::Yes;
47 if (isUnnamedCompositeType(type)) {
48 // An unnamed type can only be used as the base of a lookup, not the type in a property or
49 // method declaration. Assume the target property/method is declared on a base type, and
50 // record the base type of \a type as the base of the lookup. Otherwise, we would have
51 // discarded the lookup earlier. See recordPropertyLookup.
52 if (!type->baseType()) {
53 const QString msg = "unknown base type for composite object at %1:%2"_L1;
54 m_rejectMessage = msg.arg(type->filePath(), QString::number(type->lineNumber()));
55 return {};
56 }
57 return QQmlJSLookupSignaturesRecorder::type(type->baseType());
58 }
59
60 QQmlJSScope::ConstPtr root = type;
61 while (!root->isFileRootComponent())
62 root = root->parentScope();
63
64 bool isFileBeingCompiled = root->filePath() == m_typeResolver->logger()->filePath();
65
66 Q_ASSERT(!root->internalName().isEmpty());
67 if (isFileBeingCompiled) {
68 res.module = s_thisCuModule;
69 res.name = s_thisCuType;
70 } else {
71 // At run time, the type is looked up by name through an import of its module. Record an
72 // import a document has. Prefer the imports of the document being compiled, since they
73 // are registered first.
74 const auto imports = m_typeResolver->importer()->versionedImports(root->filePath());
75 const auto import = std::find_if(imports.cbegin(), imports.cend(),
76 [&](const QQmlJSImporter::VersionedImport &import) {
77 return import.module == root->moduleName();
78 });
79 if (import == imports.cend()) {
80 m_rejectMessage = "%1 is not imported through its module %2"_L1.arg(root->filePath(),
81 root->moduleName());
82 return {};
83 }
84
85 res.module = import->module;
86 res.name = import->name;
87 res.version = import->version;
88
89 // A C++ type of the same name takes precedence there. Then we cannot find this type that
90 // way.
91 const QQmlJSScope::ConstPtr found =
92 m_typeResolver->importer()
93 ->importModule(res.module, 0, QString(), res.version)
94 .type(res.name)
95 .scope;
96 if (found && !found->isComposite()) {
97 m_rejectMessage = "%1 is shadowed by C++ type %2 in module %3"_L1.arg(
98 root->filePath(), res.name, res.module);
99 return {};
100 }
101 }
102
103 if (type->isInlineComponent()) {
104 res.isInlineComponent = IsIC::Yes;
105 res.icNameOrExtensionTypeName = *type->inlineComponentName();
106 return res;
107 }
108
109 return res;
110}
111
112bool QQmlJSLookupSignaturesRecorder::cantDesync(const QQmlJSScope::ConstPtr &type) const
113{
114 Q_ASSERT(!m_typeResolver->logger()->filePath().isEmpty());
115 // Same file -> If one changes the other also gets recompiled and adapts to the change
116 return type->filePath() == m_typeResolver->logger()->filePath();
117}
118
119bool QQmlJSLookupSignaturesRecorder::safeBase(const QQmlJSScope::ConstPtr &base) const
120{
121 return base == m_typeResolver->varType() || base == m_typeResolver->jsValueType()
122 || base->inherits(m_typeResolver->qmlPropertyMapType());
123}
124
125bool QQmlJSLookupSignaturesRecorder::isUnnamedCompositeType(const QQmlJSScope::ConstPtr &type) const
126{
127 return type->isComposite() && !type->isFileRootComponent() && !type->isInlineComponent();
128}
129
130// On the MetaObject, signals come before regular functions
131static int ownRegularMethodCountBeforeIndex(const QQmlJSScope::ConstPtr &type, int index)
132{
133 const auto &ms = type->ownMethods();
134 return std::count_if(ms.cbegin(), ms.cend(), [&](const QQmlJSMetaMethod &m) {
135 Q_ASSERT(m.isConstructor() || m.methodIndex() != QQmlJSMetaMethod::RelativeFunctionIndex::Invalid);
136 return m.methodType() != QQmlSA::MethodType::Signal && !m.isConstructor()
137 && int(m.methodIndex()) < index;
138 });
139}
140
141// On the MetaObject, signals come before regular functions
142static int ownSignalCountAfterIndex(const QQmlJSScope::ConstPtr &type, int index)
143{
144 const auto &ms = type->ownMethods();
145 return std::count_if(ms.cbegin(), ms.cend(), [&](const QQmlJSMetaMethod &m) {
146 Q_ASSERT(m.isConstructor() || m.methodIndex() != QQmlJSMetaMethod::RelativeFunctionIndex::Invalid);
147 return m.methodType() == QQmlSA::MethodType::Signal && int(m.methodIndex()) > index;
148 });
149}
150
151// On the MetaObject, regular properties come before aliases
152static int ownAliasCountBeforeIndex(const QQmlJSScope::ConstPtr &type, int index)
153{
154 const auto &ps = type->ownProperties();
155 return std::count_if(ps.cbegin(), ps.cend(), [&](const auto &p) {
156 return p.isAlias() && p.index() < index;
157 });
158}
159
160// On the MetaObject, regular properties come before aliases
161static int ownRegularPropertyCountAfterIndex(const QQmlJSScope::ConstPtr &type, int index)
162{
163 const auto &ps = type->ownProperties();
164 return std::count_if(ps.cbegin(), ps.cend(), [&](const auto &p) {
165 return !p.isAlias() && p.index() > index;
166 });
167}
168
169bool QQmlJSLookupSignaturesRecorder::recordPropertyLookup(const QQmlJSScope::ConstPtr &base,
170 const QQmlJSMetaProperty &property)
171{
172 using namespace QQmlPrivate::AOTLookupValidation;
173
174 const QString &name = property.propertyName();
175 const auto [owner, extensionSpecifier] = QQmlJSScope::ownerOfProperty(base, name);
176 if (base->isScript() || safeBase(base) || cantDesync(owner))
177 return true;
178
179 // When performing a lookup on an inner object of unnamed type, one of two things is true:
180 // 1) The target property (or method) is declared within the object. Then, because the object
181 // is unnamed, it must be defined in the same file as the lookup and they cannot desync and
182 // we don't have to record it.
183 // 2) Or the target property is declared on a base type of the inner object. Then we can
184 // simply record the lookup as a being performed on the base type instead. The addition of
185 // a property on the inner object that would shadow the base type's property can only be
186 // introduced by recompiling the lookup as well.
187 if (isUnnamedCompositeType(base) && owner == base)
188 return true;
189
190 PropertySignature propertySignature;
191 CHECKED_OPT_ASSIGN(propertySignature.type, type(property.type()))
192
193 // Compiler indexes follow document order. On the MO, aliases come after regular properties.
194 propertySignature.relativeIndex = property.isAlias()
195 ? property.index() + ownRegularPropertyCountAfterIndex(owner, property.index())
196 : property.index() - ownAliasCountBeforeIndex(owner, property.index());
197
198 Lookup lookup;
199 CHECKED_OPT_ASSIGN(lookup.base, type(base))
200 lookup.member = name;
201
202 m_signatures.insert(lookup, propertySignature);
203 return true;
204}
205
206bool QQmlJSLookupSignaturesRecorder::recordMethodLookup(const QQmlJSScope::ConstPtr &base,
207 const QQmlJSMetaMethod &method)
208{
209 using namespace QQmlPrivate::AOTLookupValidation;
210
211 const QString &name = method.methodName();
212 const auto [owner, extensionSpecifier] = QQmlJSScope::ownerOfMethod(base, name);
213 if (base->isScript() || safeBase(base) || cantDesync(owner))
214 return true;
215
216 // See recordPropertyLookup
217 if (isUnnamedCompositeType(base) && owner == base)
218 return true;
219
220 // destroy and toString are special?
221 if (name == QStringLiteral("destroy") || name == QStringLiteral("toString"))
222 return true;
223
224 MethodSignature methodSignature;
225
226 if (method.methodType() == QQmlSA::MethodType::Signal) {
227 methodSignature.isSignal = IsSignal::Yes;
228 int index = int(method.methodIndex());
229 methodSignature.relativeIndex = index - ownRegularMethodCountBeforeIndex(owner, index);
230 } else {
231 methodSignature.isSignal = IsSignal::No;
232 int index = int(method.methodIndex());
233 methodSignature.relativeIndex = index + ownSignalCountAfterIndex(owner, index);
234 }
235
236 methodSignature.types.push_back({});
237 CHECKED_OPT_ASSIGN(methodSignature.types.back(), type(method.returnType()))
238 for (const auto &param : method.parameters()) {
239 methodSignature.paramNames.push_back(param.name());
240 methodSignature.types.push_back({});
241 CHECKED_OPT_ASSIGN(methodSignature.types.back(), type(param.type()))
242 }
243
244 Lookup lookup;
245 CHECKED_OPT_ASSIGN(lookup.base, type(base))
246 lookup.member = name;
247
248 m_signatures.insert(lookup, methodSignature);
249 return true;
250}
251
252bool QQmlJSLookupSignaturesRecorder::recordEnumKeyLookup(const QQmlJSScope::ConstPtr &base,
253 const QQmlJSMetaEnum &metaEnum,
254 const QString &keyName)
255{
256 using namespace QQmlPrivate::AOTLookupValidation;
257
258 const auto [owner, extensionSpecifier] = QQmlJSScope::ownerOfEnum(base, metaEnum.name());
259 if (base->isScript() || safeBase(base) || cantDesync(owner))
260 return true;
261
262 EnumKeySignature enumSignature;
263 // QTBUG-145053: enums can only hold ints in the Compiler
264 enumSignature.value = quint64(metaEnum.value(keyName));
265 if (metaEnum.isFlag())
266 enumSignature.isFlag = IsFlag::Yes;
267
268 Lookup lookup;
269 CHECKED_OPT_ASSIGN(lookup.base, type(base))
270 lookup.member = keyName;
271 lookup.enumName = metaEnum.name();
272 m_signatures.insert(lookup, enumSignature);
273 return true;
274}
275
276QT_END_NAMESPACE
static int ownAliasCountBeforeIndex(const QQmlJSScope::ConstPtr &type, int index)
static int ownRegularMethodCountBeforeIndex(const QQmlJSScope::ConstPtr &type, int index)
static int ownRegularPropertyCountAfterIndex(const QQmlJSScope::ConstPtr &type, int index)
#define CHECKED_OPT_ASSIGN(var, expr)
static int ownSignalCountAfterIndex(const QQmlJSScope::ConstPtr &type, int index)