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
qqmlcomponentandaliasresolver_p.h
Go to the documentation of this file.
1// Copyright (C) 2023 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3// Qt-Security score:significant
4
5#ifndef QQMLCOMPONENTANDALIASRESOLVER_P_H
6#define QQMLCOMPONENTANDALIASRESOLVER_P_H
7
8//
9// W A R N I N G
10// -------------
11//
12// This file is not part of the Qt API. It exists purely as an
13// implementation detail. This header file may change from version to
14// version without notice, or even be removed.
15//
16// We mean it.
17//
18
19#include <QtQml/qqmlcomponent.h>
20#include <QtQml/qqmlerror.h>
21
22#include <QtCore/qglobal.h>
23#include <QtCore/qhash.h>
24
25#include <private/qqmltypeloader_p.h>
26#include <private/qqmlpropertyresolver_p.h>
27#include <private/qqmlpropertycachecreator_p.h>
28
30
32
33// This class primarily resolves component boundaries in a document.
34// With the information about boundaries, it then goes on to resolve aliases and generalized
35// group properties. Both rely on IDs as first part of their expressions and the IDs have
36// to be located in surrounding components. That's why we have to do this with the component
37// boundaries in mind.
38
43
44template<typename ObjectContainer>
46{
47public:
48 using CompiledObject = typename ObjectContainer::CompiledObject;
49 using CompiledBinding = typename ObjectContainer::CompiledBinding;
50
52 ObjectContainer *compiler,
53 QQmlPropertyCacheVector *propertyCaches);
54
55 [[nodiscard]] QQmlError resolve(int root = 0);
56
57private:
58 enum AliasResolutionResult {
59 NoAliasResolved,
60 SomeAliasesResolved,
61 AllAliasesResolved
62 };
63
64 // To be specialized for each container
65 void allocateNamedObjects(CompiledObject *object) const;
66 void setObjectId(int index) const;
67 [[nodiscard]] bool markAsComponent(int index) const;
68 [[nodiscard]] AliasResolutionResult resolveAliasesInObject(
69 const CompiledObject &component, int objectIndex,
70 QQmlPropertyCacheAliasCreator<ObjectContainer> *aliasCacheCreator, QQmlError *error);
71 [[nodiscard]] bool appendAliasToPropertyCache(
72 const CompiledObject *component, const QV4::CompiledData::Alias *alias, int objectIndex,
73 int aliasIndex, int encodedPropertyIndex, int resolvedTargetObjectId,
74 QQmlPropertyCacheAliasCreator<ObjectContainer> *aliasCacheCreator, QQmlError *error)
75 {
76 *error = aliasCacheCreator->appendAliasToPropertyCache(
77 *component, *alias, objectIndex, aliasIndex, encodedPropertyIndex,
78 resolvedTargetObjectId);
79 resolvedAliases.insert(alias);
80 return !error->isValid();
81 }
82
83
84 void resolveGeneralizedGroupProperty(const CompiledObject &component, CompiledBinding *binding);
85 [[nodiscard]] bool wrapImplicitComponent(CompiledBinding *binding);
86
87 [[nodiscard]] QQmlError findAndRegisterImplicitComponents(
88 const CompiledObject *obj, const QQmlPropertyCache::ConstPtr &propertyCache);
89 [[nodiscard]] QQmlError collectIdsAndAliases(int objectIndex);
90 [[nodiscard]] QQmlError resolveAliases(int componentIndex);
91 void resolveGeneralizedGroupProperties(int componentIndex);
92 [[nodiscard]] QQmlError resolveComponentsInInlineComponentRoot(int root);
93
94 QString stringAt(int idx) const { return m_compiler->stringAt(idx); }
95 QV4::ResolvedTypeReference *resolvedType(int id) const { return m_compiler->resolvedType(id); }
96
97 [[nodiscard]] QQmlError error(
98 const QV4::CompiledData::Location &location,
99 const QString &description)
100 {
101 QQmlError error;
102 error.setLine(qmlConvertSourceCoordinate<quint32, int>(location.line()));
103 error.setColumn(qmlConvertSourceCoordinate<quint32, int>(location.column()));
104 error.setDescription(description);
105 error.setUrl(m_compiler->url());
106 return error;
107 }
108
109 template<typename Token>
110 [[nodiscard]] QQmlError error(Token token, const QString &description)
111 {
112 return error(token->location, description);
113 }
114
115 static bool isUsableComponent(const QMetaObject *metaObject)
116 {
117 // The metaObject is a component we're interested in if it either is a QQmlComponent itself
118 // or if any of its parents is a QQmlAbstractDelegateComponent. We don't want to include
119 // qqmldelegatecomponent_p.h because it belongs to QtQmlModels.
120
121 if (metaObject == &QQmlComponent::staticMetaObject)
122 return true;
123
124 for (; metaObject; metaObject = metaObject->superClass()) {
125 if (qstrcmp(metaObject->className(), "QQmlAbstractDelegateComponent") == 0)
126 return true;
127 }
128
129 return false;
130 }
131
132 ObjectContainer *m_compiler = nullptr;
133
134 // Implicit component insertion may have added objects and thus we also need
135 // to extend the symmetric propertyCaches. Therefore, non-const propertyCaches.
136 QQmlPropertyCacheVector *m_propertyCaches = nullptr;
137
138 // indices of the objects that are actually Component {}
139 QList<quint32> m_componentRoots;
140 QList<int> m_objectsWithAliases;
141 QList<CompiledBinding *> m_generalizedGroupProperties;
142 QSet<const QV4::CompiledData::Alias *> resolvedAliases;
143 typename ObjectContainer::IdToObjectMap m_idToObjectIndex;
144};
145
146template<typename ObjectContainer>
148 ObjectContainer *compiler,
149 QQmlPropertyCacheVector *propertyCaches)
150 : m_compiler(compiler)
152{
153}
154
155template<typename ObjectContainer>
156QQmlError QQmlComponentAndAliasResolver<ObjectContainer>::findAndRegisterImplicitComponents(
157 const CompiledObject *obj, const QQmlPropertyCache::ConstPtr &propertyCache)
158{
159 QQmlPropertyResolver propertyResolver(propertyCache);
160
161 const QQmlPropertyData *defaultProperty = obj->indexOfDefaultPropertyOrAlias != -1
162 ? propertyCache->parent()->defaultProperty()
163 : propertyCache->defaultProperty();
164
165 for (auto binding = obj->bindingsBegin(), end = obj->bindingsEnd(); binding != end; ++binding) {
166 if (binding->type() != QV4::CompiledData::Binding::Type_Object)
167 continue;
168 if (binding->hasFlag(QV4::CompiledData::Binding::IsSignalHandlerObject))
169 continue;
170
171 auto targetObject = m_compiler->objectAt(binding->value.objectIndex);
172 auto typeReference = resolvedType(targetObject->inheritedTypeNameIndex);
173 Q_ASSERT(typeReference);
174
175 const QMetaObject *firstMetaObject = nullptr;
176 if (const auto propCache = typeReference->typePropertyCache())
177 firstMetaObject = propCache->firstCppMetaObject();
178 if (isUsableComponent(firstMetaObject))
179 continue;
180
181 // if here, not a QQmlComponent, so needs wrapping
182 const QQmlPropertyData *pd = nullptr;
183 if (binding->propertyNameIndex != quint32(0)) {
184 bool notInRevision = false;
185 pd = propertyResolver.property(stringAt(binding->propertyNameIndex), &notInRevision);
186 } else {
187 pd = defaultProperty;
188 }
189 if (!pd || !pd->isQObject())
190 continue;
191
192 // If the version is given, use it and look up by QQmlType.
193 // Otherwise, make sure we look up by metaobject.
194 // TODO: Is this correct?
195 QQmlPropertyCache::ConstPtr pc = pd->typeVersion().hasMinorVersion()
196 ? QQmlMetaType::rawPropertyCacheForType(pd->propType(), pd->typeVersion())
197 : QQmlMetaType::rawPropertyCacheForType(pd->propType());
198 const QMetaObject *mo = pc ? pc->firstCppMetaObject() : nullptr;
199 while (mo) {
200 if (mo == &QQmlComponent::staticMetaObject)
201 break;
202 mo = mo->superClass();
203 }
204
205 if (!mo)
206 continue;
207
208 if (!wrapImplicitComponent(binding))
209 return error(binding, QQmlComponentAndAliasResolverBase::tr("Cannot wrap implicit component"));
210 }
211
212 return QQmlError();
213}
214
215template<typename ObjectContainer>
216QQmlError QQmlComponentAndAliasResolver<ObjectContainer>::resolveComponentsInInlineComponentRoot(
217 int root)
218{
219 // Find implicit components in the inline component itself. Also warn about inline
220 // components being explicit components.
221
222 const auto rootObj = m_compiler->objectAt(root);
223 Q_ASSERT(rootObj->hasFlag(QV4::CompiledData::Object::IsInlineComponentRoot));
224
225 if (const int typeName = rootObj->inheritedTypeNameIndex) {
226 const auto *tref = resolvedType(typeName);
227 Q_ASSERT(tref);
228 if (tref->type().metaObject() == &QQmlComponent::staticMetaObject) {
229 qCWarning(lcQmlTypeCompiler).nospace().noquote()
230 << m_compiler->url().toString() << ":" << rootObj->location.line() << ":"
231 << rootObj->location.column()
232 << ": Using a Component as the root of an inline component is deprecated: "
233 "inline components are "
234 "automatically wrapped into Components when needed.";
235 return QQmlError();
236 }
237 }
238
239 const QQmlPropertyCache::ConstPtr rootCache = m_propertyCaches->at(root);
240 Q_ASSERT(rootCache);
241
242 return findAndRegisterImplicitComponents(rootObj, rootCache);
243}
244
245// Resolve ignores everything relating to inline components, except for implicit components.
246template<typename ObjectContainer>
248{
249 // Detect real Component {} objects as well as implicitly defined components, such as
250 // someItemDelegate: Item {}
251 // In the implicit case Item is surrounded by a synthetic Component {} because the property
252 // on the left hand side is of QQmlComponent type.
253 const int objCountWithoutSynthesizedComponents = m_compiler->objectCount();
254
255 if (root != 0) {
256 const QQmlError error = resolveComponentsInInlineComponentRoot(root);
257 if (error.isValid())
258 return error;
259 }
260
261 // root+1, as ic root is handled at the end
262 const int startObjectIndex = root == 0 ? root : root+1;
263
264 for (int i = startObjectIndex; i < objCountWithoutSynthesizedComponents; ++i) {
265 auto obj = m_compiler->objectAt(i);
266 const bool isInlineComponentRoot
267 = obj->hasFlag(QV4::CompiledData::Object::IsInlineComponentRoot);
268 const bool isPartOfInlineComponent
269 = obj->hasFlag(QV4::CompiledData::Object::IsPartOfInlineComponent);
270 QQmlPropertyCache::ConstPtr cache = m_propertyCaches->at(i);
271
272 if (root == 0) {
273 // normal component root, skip over anything inline component related
274 if (isInlineComponentRoot || isPartOfInlineComponent)
275 continue;
276 } else if (!isPartOfInlineComponent || isInlineComponentRoot) {
277 // When handling an inline component, stop where the inline component ends
278 // Note: We do not support nested inline components. Therefore, isInlineComponentRoot
279 // tells us that the element after the current inline component is again an
280 // inline component
281 break;
282 }
283
284 if (obj->inheritedTypeNameIndex == 0 && !cache)
285 continue;
286
287 bool isExplicitComponent = false;
288 if (obj->inheritedTypeNameIndex) {
289 auto *tref = resolvedType(obj->inheritedTypeNameIndex);
290 Q_ASSERT(tref);
291 if (tref->type().metaObject() == &QQmlComponent::staticMetaObject)
292 isExplicitComponent = true;
293 }
294
295 if (!isExplicitComponent) {
296 if (cache) {
297 const QQmlError error = findAndRegisterImplicitComponents(obj, cache);
298 if (error.isValid())
299 return error;
300 }
301 continue;
302 }
303
304 if (!markAsComponent(i))
305 return error(obj, QQmlComponentAndAliasResolverBase::tr("Cannot mark object as component"));
306
307 // check if this object is the root
308 if (i == 0) {
309 if (isExplicitComponent)
310 qCWarning(lcQmlTypeCompiler).nospace().noquote()
311 << m_compiler->url().toString() << ":" << obj->location.line() << ":"
312 << obj->location.column()
313 << ": Using a Component as the root of a QML document is deprecated: types "
314 "defined in qml documents are "
315 "automatically wrapped into Components when needed.";
316 }
317
318 if (obj->functionCount() > 0)
319 return error(obj, QQmlComponentAndAliasResolverBase::tr("Component objects cannot declare new functions."));
320 if (obj->propertyCount() > 0 || obj->aliasCount() > 0)
321 return error(obj, QQmlComponentAndAliasResolverBase::tr("Component objects cannot declare new properties."));
322 if (obj->signalCount() > 0)
323 return error(obj, QQmlComponentAndAliasResolverBase::tr("Component objects cannot declare new signals."));
324
325 if (obj->bindingCount() == 0)
326 return error(obj, QQmlComponentAndAliasResolverBase::tr("Cannot create empty component specification"));
327
328 const auto rootBinding = obj->bindingsBegin();
329 const auto bindingsEnd = obj->bindingsEnd();
330
331 // Produce the more specific "no properties" error rather than the "invalid body" error
332 // where possible.
333 for (auto b = rootBinding; b != bindingsEnd; ++b) {
334 if (b->propertyNameIndex == 0)
335 continue;
336
337 return error(b, QQmlComponentAndAliasResolverBase::tr("Component elements may not contain properties other than id"));
338 }
339
340 if (auto b = rootBinding;
341 b->type() != QV4::CompiledData::Binding::Type_Object || ++b != bindingsEnd) {
342 return error(obj, QQmlComponentAndAliasResolverBase::tr("Invalid component body specification"));
343 }
344
345 // For the root object, we are going to collect ids/aliases and resolve them for as a
346 // separate last pass.
347 if (i != 0)
348 m_componentRoots.append(i);
349 }
350
351 for (int i = 0; i < m_componentRoots.size(); ++i) {
352 CompiledObject *component = m_compiler->objectAt(m_componentRoots.at(i));
353 const auto rootBinding = component->bindingsBegin();
354
355 m_idToObjectIndex.clear();
356 m_objectsWithAliases.clear();
357 m_generalizedGroupProperties.clear();
358
359 if (const QQmlError error = collectIdsAndAliases(rootBinding->value.objectIndex);
360 error.isValid()) {
361 return error;
362 }
363
364 allocateNamedObjects(component);
365
366 if (const QQmlError error = resolveAliases(m_componentRoots.at(i)); error.isValid())
367 return error;
368
369 resolveGeneralizedGroupProperties(m_componentRoots.at(i));
370 }
371
372 // Collect ids and aliases for root
373 m_idToObjectIndex.clear();
374 m_objectsWithAliases.clear();
375 m_generalizedGroupProperties.clear();
376
377 if (const QQmlError error = collectIdsAndAliases(root); error.isValid())
378 return error;
379
380 allocateNamedObjects(m_compiler->objectAt(root));
381 if (const QQmlError error = resolveAliases(root); error.isValid())
382 return error;
383
384 resolveGeneralizedGroupProperties(root);
385 return QQmlError();
386}
387
388template<typename ObjectContainer>
389QQmlError QQmlComponentAndAliasResolver<ObjectContainer>::collectIdsAndAliases(int objectIndex)
390{
391 auto obj = m_compiler->objectAt(objectIndex);
392
393 if (obj->idNameIndex != 0) {
394 if (m_idToObjectIndex.contains(obj->idNameIndex))
395 return error(obj->locationOfIdProperty, QQmlComponentAndAliasResolverBase::tr("id is not unique"));
396 setObjectId(objectIndex);
397 m_idToObjectIndex.insert(obj->idNameIndex, objectIndex);
398 }
399
400 if (obj->aliasCount() > 0)
401 m_objectsWithAliases.append(objectIndex);
402
403 // Stop at Component boundary
404 if (obj->hasFlag(QV4::CompiledData::Object::IsComponent) && objectIndex != /*root object*/0)
405 return QQmlError();
406
407 for (auto binding = obj->bindingsBegin(), end = obj->bindingsEnd();
408 binding != end; ++binding) {
409 switch (binding->type()) {
410 case QV4::CompiledData::Binding::Type_GroupProperty: {
411 const auto *inner = m_compiler->objectAt(binding->value.objectIndex);
412 if (m_compiler->stringAt(inner->inheritedTypeNameIndex).isEmpty()) {
413 const auto cache = m_propertyCaches->at(objectIndex);
414 if (!cache || !cache->property(
415 m_compiler->stringAt(binding->propertyNameIndex), nullptr, nullptr)) {
416 m_generalizedGroupProperties.append(binding);
417 }
418 }
419 }
420 Q_FALLTHROUGH();
421 case QV4::CompiledData::Binding::Type_Object:
422 case QV4::CompiledData::Binding::Type_AttachedProperty:
423 if (const QQmlError error = collectIdsAndAliases(binding->value.objectIndex);
424 error.isValid()) {
425 return error;
426 }
427 break;
428 default:
429 break;
430 }
431 }
432
433 return QQmlError();
434}
435
436template<typename ObjectContainer>
437QQmlError QQmlComponentAndAliasResolver<ObjectContainer>::resolveAliases(int componentIndex)
438{
439 if (m_objectsWithAliases.isEmpty())
440 return QQmlError();
441
442 QQmlPropertyCacheAliasCreator<ObjectContainer> aliasCacheCreator(m_propertyCaches, m_compiler);
443
444 bool atLeastOneAliasResolved;
445 do {
446 atLeastOneAliasResolved = false;
447 QList<int> pendingObjects;
448
449 for (int objectIndex: std::as_const(m_objectsWithAliases)) {
450
451 QQmlError error;
452 const auto &component = *m_compiler->objectAt(componentIndex);
453 const auto result
454 = resolveAliasesInObject(component, objectIndex, &aliasCacheCreator, &error);
455 if (error.isValid())
456 return error;
457
458 if (result == AllAliasesResolved) {
459 atLeastOneAliasResolved = true;
460 } else if (result == SomeAliasesResolved) {
461 atLeastOneAliasResolved = true;
462 pendingObjects.append(objectIndex);
463 } else {
464 pendingObjects.append(objectIndex);
465 }
466 }
467 qSwap(m_objectsWithAliases, pendingObjects);
468 } while (!m_objectsWithAliases.isEmpty() && atLeastOneAliasResolved);
469
470 if (!atLeastOneAliasResolved && !m_objectsWithAliases.isEmpty()) {
471 const CompiledObject *obj = m_compiler->objectAt(m_objectsWithAliases.first());
472 for (auto alias = obj->aliasesBegin(), end = obj->aliasesEnd(); alias != end; ++alias) {
473 if (!resolvedAliases.contains(alias)) {
474 return error(
475 alias->location(), QQmlComponentAndAliasResolverBase::tr("Cyclic alias"));
476 }
477 }
478 }
479
480 return QQmlError();
481}
482
483template<typename ObjectContainer>
484void QQmlComponentAndAliasResolver<ObjectContainer>::resolveGeneralizedGroupProperties(
485 int componentIndex)
486{
487 const auto &component = *m_compiler->objectAt(componentIndex);
488 for (CompiledBinding *binding : m_generalizedGroupProperties)
489 resolveGeneralizedGroupProperty(component, binding);
490}
491
492QT_END_NAMESPACE
493
494#endif // QQMLCOMPONENTANDALIASRESOLVER_P_H
QQmlComponentAndAliasResolver(ObjectContainer *compiler, QQmlPropertyCacheVector *propertyCaches)
typename ObjectContainer::CompiledObject CompiledObject
typename ObjectContainer::CompiledBinding CompiledBinding
QT_BEGIN_NAMESPACE Q_DECLARE_LOGGING_CATEGORY(lcQIORing)