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 const int childObjectIndex = binding->value.objectIndex;
88
89 // Create an implicit component wrapper as an additional property cache entry.
90 // The CU binary is read-only so we cannot patch the binding.
91 QQmlPropertyCache::ConstPtr baseComponentCache
92 = QQmlMetaType::propertyCache(&QQmlComponent::staticMetaObject);
93 QQmlPropertyCache::Ptr wrapperCache = baseComponentCache->copyAndReserve(1, 0, 0, 0);
94 wrapperCache->appendComponentWrapper(
95 wrapperCache->propertyCount(), childObjectIndex);
96
97 const uint wrapperIndex = m_propertyCaches->count();
98 m_propertyCaches->append(std::move(wrapperCache));
99
100 m_componentRoots.append(wrapperIndex);
101 return true;
102 }
103
104 [[nodiscard]] QQmlError findAndRegisterImplicitComponents(
105 const CompiledObject *obj, const QQmlPropertyCache::ConstPtr &propertyCache);
106 [[nodiscard]] QQmlError collectIdsAndAliases(int objectIndex);
107 [[nodiscard]] QQmlError resolveAliases(int componentIndex);
108 void resolveGeneralizedGroupProperties(int componentIndex);
109 [[nodiscard]] QQmlError resolveComponentsInInlineComponentRoot(int root);
110
111 QString stringAt(int idx) const { return m_compiler->stringAt(idx); }
112 QV4::ResolvedTypeReference *resolvedType(int id) const { return m_compiler->resolvedType(id); }
113
114 [[nodiscard]] QQmlError error(
115 const QV4::CompiledData::Location &location,
116 const QString &description)
117 {
118 QQmlError error;
119 error.setLine(qmlConvertSourceCoordinate<quint32, int>(location.line()));
120 error.setColumn(qmlConvertSourceCoordinate<quint32, int>(location.column()));
121 error.setDescription(description);
122 error.setUrl(m_compiler->url());
123 return error;
124 }
125
126 template<typename Token>
127 [[nodiscard]] QQmlError error(Token token, const QString &description)
128 {
129 return error(token->location, description);
130 }
131
132 static bool isUsableComponent(const QMetaObject *metaObject)
133 {
134 // The metaObject is a component we're interested in if it either is a QQmlComponent itself
135 // or if any of its parents is a QQmlAbstractDelegateComponent. We don't want to include
136 // qqmldelegatecomponent_p.h because it belongs to QtQmlModels.
137
138 if (metaObject == &QQmlComponent::staticMetaObject)
139 return true;
140
141 for (; metaObject; metaObject = metaObject->superClass()) {
142 if (qstrcmp(metaObject->className(), "QQmlAbstractDelegateComponent") == 0)
143 return true;
144 }
145
146 return false;
147 }
148
149 ObjectContainer *m_compiler = nullptr;
150
151 // Implicit component insertion may have added objects and thus we also need
152 // to extend the symmetric propertyCaches. Therefore, non-const propertyCaches.
153 QQmlPropertyCacheVector *m_propertyCaches = nullptr;
154
155 // indices of the objects that are actually Component {}
156 QList<quint32> m_componentRoots;
157 QList<int> m_objectsWithAliases;
158 QList<CompiledBinding *> m_generalizedGroupProperties;
159 QSet<const QV4::CompiledData::Alias *> resolvedAliases;
160 typename ObjectContainer::IdToObjectMap m_idToObjectIndex;
161};
162
163template<typename ObjectContainer>
165 ObjectContainer *compiler,
166 QQmlPropertyCacheVector *propertyCaches)
167 : m_compiler(compiler)
169{
170}
171
172template<typename ObjectContainer>
173QQmlError QQmlComponentAndAliasResolver<ObjectContainer>::findAndRegisterImplicitComponents(
174 const CompiledObject *obj, const QQmlPropertyCache::ConstPtr &propertyCache)
175{
176 QQmlPropertyResolver propertyResolver(propertyCache);
177
178 const QQmlPropertyData *defaultProperty = obj->indexOfDefaultPropertyOrAlias != -1
179 ? propertyCache->parent()->defaultProperty()
180 : propertyCache->defaultProperty();
181
182 for (auto binding = obj->bindingsBegin(), end = obj->bindingsEnd(); binding != end; ++binding) {
183 if (binding->type() != QV4::CompiledData::Binding::Type_Object)
184 continue;
185 if (binding->hasFlag(QV4::CompiledData::Binding::IsSignalHandlerObject))
186 continue;
187
188 auto targetObject = m_compiler->objectAt(binding->value.objectIndex);
189 auto typeReference = resolvedType(targetObject->inheritedTypeNameIndex);
190 Q_ASSERT(typeReference);
191
192 const QMetaObject *firstMetaObject = nullptr;
193 if (const auto propCache = typeReference->typePropertyCache())
194 firstMetaObject = propCache->firstCppMetaObject();
195 if (isUsableComponent(firstMetaObject))
196 continue;
197
198 // if here, not a QQmlComponent, so needs wrapping
199 const QQmlPropertyData *pd = nullptr;
200 if (binding->propertyNameIndex != quint32(0)) {
201 bool notInRevision = false;
202 pd = propertyResolver.property(stringAt(binding->propertyNameIndex), &notInRevision);
203 } else {
204 pd = defaultProperty;
205 }
206 if (!pd || !pd->isQObject())
207 continue;
208
209 // If the version is given, use it and look up by QQmlType.
210 // Otherwise, make sure we look up by metaobject.
211 // TODO: Is this correct?
212 QQmlPropertyCache::ConstPtr pc = pd->typeVersion().hasMinorVersion()
213 ? QQmlMetaType::rawPropertyCacheForType(pd->propType(), pd->typeVersion())
214 : QQmlMetaType::rawPropertyCacheForType(pd->propType());
215 const QMetaObject *mo = pc ? pc->firstCppMetaObject() : nullptr;
216 while (mo) {
217 if (mo == &QQmlComponent::staticMetaObject)
218 break;
219 mo = mo->superClass();
220 }
221
222 if (!mo)
223 continue;
224
225 if (!wrapImplicitComponent(binding))
226 return error(binding, QQmlComponentAndAliasResolverBase::tr("Cannot wrap implicit component"));
227 }
228
229 return QQmlError();
230}
231
232template<typename ObjectContainer>
233QQmlError QQmlComponentAndAliasResolver<ObjectContainer>::resolveComponentsInInlineComponentRoot(
234 int root)
235{
236 // Find implicit components in the inline component itself. Also warn about inline
237 // components being explicit components.
238
239 const auto rootObj = m_compiler->objectAt(root);
240 Q_ASSERT(rootObj->hasFlag(QV4::CompiledData::Object::IsInlineComponentRoot));
241
242 if (const int typeName = rootObj->inheritedTypeNameIndex) {
243 const auto *tref = resolvedType(typeName);
244 Q_ASSERT(tref);
245 if (tref->type().metaObject() == &QQmlComponent::staticMetaObject) {
246 qCWarning(lcQmlTypeCompiler).nospace().noquote()
247 << m_compiler->url().toString() << ":" << rootObj->location.line() << ":"
248 << rootObj->location.column()
249 << ": Using a Component as the root of an inline component is deprecated: "
250 "inline components are "
251 "automatically wrapped into Components when needed.";
252 return QQmlError();
253 }
254 }
255
256 const QQmlPropertyCache::ConstPtr rootCache = m_propertyCaches->at(root);
257 Q_ASSERT(rootCache);
258
259 return findAndRegisterImplicitComponents(rootObj, rootCache);
260}
261
262// Resolve ignores everything relating to inline components, except for implicit components.
263template<typename ObjectContainer>
265{
266 // Detect real Component {} objects as well as implicitly defined components, such as
267 // someItemDelegate: Item {}
268 // In the implicit case Item is surrounded by a synthetic Component {} because the property
269 // on the left hand side is of QQmlComponent type.
270 const int objCountWithoutSynthesizedComponents = m_compiler->objectCount();
271
272 if (root != 0) {
273 const QQmlError error = resolveComponentsInInlineComponentRoot(root);
274 if (error.isValid())
275 return error;
276 }
277
278 // root+1, as ic root is handled at the end
279 const int startObjectIndex = root == 0 ? root : root+1;
280
281 for (int i = startObjectIndex; i < objCountWithoutSynthesizedComponents; ++i) {
282 auto obj = m_compiler->objectAt(i);
283 const bool isInlineComponentRoot
284 = obj->hasFlag(QV4::CompiledData::Object::IsInlineComponentRoot);
285 const bool isPartOfInlineComponent
286 = obj->hasFlag(QV4::CompiledData::Object::IsPartOfInlineComponent);
287 QQmlPropertyCache::ConstPtr cache = m_propertyCaches->at(i);
288
289 if (root == 0) {
290 // normal component root, skip over anything inline component related
291 if (isInlineComponentRoot || isPartOfInlineComponent)
292 continue;
293 } else if (!isPartOfInlineComponent || isInlineComponentRoot) {
294 // When handling an inline component, stop where the inline component ends
295 // Note: We do not support nested inline components. Therefore, isInlineComponentRoot
296 // tells us that the element after the current inline component is again an
297 // inline component
298 break;
299 }
300
301 if (obj->inheritedTypeNameIndex == 0 && !cache)
302 continue;
303
304 bool isExplicitComponent = false;
305 if (obj->inheritedTypeNameIndex) {
306 auto *tref = resolvedType(obj->inheritedTypeNameIndex);
307 Q_ASSERT(tref);
308 if (tref->type().metaObject() == &QQmlComponent::staticMetaObject)
309 isExplicitComponent = true;
310 }
311
312 if (!isExplicitComponent) {
313 if (cache) {
314 const QQmlError error = findAndRegisterImplicitComponents(obj, cache);
315 if (error.isValid())
316 return error;
317 }
318 continue;
319 }
320
321 if (!markAsComponent(i))
322 return error(obj, QQmlComponentAndAliasResolverBase::tr("Cannot mark object as component"));
323
324 // check if this object is the root
325 if (i == 0) {
326 if (isExplicitComponent)
327 qCWarning(lcQmlTypeCompiler).nospace().noquote()
328 << m_compiler->url().toString() << ":" << obj->location.line() << ":"
329 << obj->location.column()
330 << ": Using a Component as the root of a QML document is deprecated: types "
331 "defined in qml documents are "
332 "automatically wrapped into Components when needed.";
333 }
334
335 if (obj->functionCount() > 0)
336 return error(obj, QQmlComponentAndAliasResolverBase::tr("Component objects cannot declare new functions."));
337 if (obj->propertyCount() > 0 || obj->aliasCount() > 0)
338 return error(obj, QQmlComponentAndAliasResolverBase::tr("Component objects cannot declare new properties."));
339 if (obj->signalCount() > 0)
340 return error(obj, QQmlComponentAndAliasResolverBase::tr("Component objects cannot declare new signals."));
341
342 if (obj->bindingCount() == 0)
343 return error(obj, QQmlComponentAndAliasResolverBase::tr("Cannot create empty component specification"));
344
345 const auto rootBinding = obj->bindingsBegin();
346 const auto bindingsEnd = obj->bindingsEnd();
347
348 // Produce the more specific "no properties" error rather than the "invalid body" error
349 // where possible.
350 for (auto b = rootBinding; b != bindingsEnd; ++b) {
351 if (b->propertyNameIndex == 0)
352 continue;
353
354 return error(b, QQmlComponentAndAliasResolverBase::tr("Component elements may not contain properties other than id"));
355 }
356
357 if (auto b = rootBinding;
358 b->type() != QV4::CompiledData::Binding::Type_Object || ++b != bindingsEnd) {
359 return error(obj, QQmlComponentAndAliasResolverBase::tr("Invalid component body specification"));
360 }
361
362 // For the root object, we are going to collect ids/aliases and resolve them for as a
363 // separate last pass.
364 if (i != 0)
365 m_componentRoots.append(i);
366 }
367
368 for (int i = 0; i < m_componentRoots.size(); ++i) {
369 const int componentRoot = m_componentRoots.at(i);
370 const int childObjectIndex = componentRoot < m_compiler->objectCount()
371 ? m_compiler->objectAt(componentRoot)->bindingsBegin()->value.objectIndex
372 : m_compiler->resolvedIndex(componentRoot);
373
374 m_idToObjectIndex.clear();
375 m_objectsWithAliases.clear();
376 m_generalizedGroupProperties.clear();
377
378 if (const QQmlError error = collectIdsAndAliases(childObjectIndex);
379 error.isValid()) {
380 return error;
381 }
382
383 allocateNamedObjects(m_compiler->objectAt(componentRoot));
384
385 if (const QQmlError error = resolveAliases(componentRoot); error.isValid())
386 return error;
387
388 resolveGeneralizedGroupProperties(componentRoot);
389 }
390
391 // Collect ids and aliases for root
392 m_idToObjectIndex.clear();
393 m_objectsWithAliases.clear();
394 m_generalizedGroupProperties.clear();
395
396 if (const QQmlError error = collectIdsAndAliases(root); error.isValid())
397 return error;
398
399 allocateNamedObjects(m_compiler->objectAt(root));
400 if (const QQmlError error = resolveAliases(root); error.isValid())
401 return error;
402
403 resolveGeneralizedGroupProperties(root);
404 return QQmlError();
405}
406
407template<typename ObjectContainer>
408QQmlError QQmlComponentAndAliasResolver<ObjectContainer>::collectIdsAndAliases(int objectIndex)
409{
410 auto obj = m_compiler->objectAt(objectIndex);
411
412 if (obj->idNameIndex != 0) {
413 if (m_idToObjectIndex.contains(obj->idNameIndex))
414 return error(obj->locationOfIdProperty, QQmlComponentAndAliasResolverBase::tr("id is not unique"));
415 setObjectId(objectIndex);
416 m_idToObjectIndex.insert(obj->idNameIndex, objectIndex);
417 }
418
419 if (obj->aliasCount() > 0)
420 m_objectsWithAliases.append(objectIndex);
421
422 // Stop at Component boundary
423 if (obj->hasFlag(QV4::CompiledData::Object::IsComponent) && objectIndex != /*root object*/0)
424 return QQmlError();
425
426 for (auto binding = obj->bindingsBegin(), end = obj->bindingsEnd();
427 binding != end; ++binding) {
428 switch (binding->type()) {
429 case QV4::CompiledData::Binding::Type_GroupProperty: {
430 const auto *inner = m_compiler->objectAt(binding->value.objectIndex);
431 if (m_compiler->stringAt(inner->inheritedTypeNameIndex).isEmpty()) {
432 const auto cache = m_propertyCaches->at(objectIndex);
433 if (!cache || !cache->property(
434 m_compiler->stringAt(binding->propertyNameIndex), nullptr, nullptr)) {
435 m_generalizedGroupProperties.append(binding);
436 }
437 }
438 }
439 Q_FALLTHROUGH();
440 case QV4::CompiledData::Binding::Type_Object:
441 case QV4::CompiledData::Binding::Type_AttachedProperty:
442 // Implicit component wrappers are component boundaries.
443 // Their children are collected separately via the componentRoots loop.
444 if (m_compiler->implicitComponentForObject(binding->value.objectIndex) != -1)
445 break;
446 if (const QQmlError error = collectIdsAndAliases(binding->value.objectIndex);
447 error.isValid()) {
448 return error;
449 }
450 break;
451 default:
452 break;
453 }
454 }
455
456 return QQmlError();
457}
458
459template<typename ObjectContainer>
460QQmlError QQmlComponentAndAliasResolver<ObjectContainer>::resolveAliases(int componentIndex)
461{
462 if (m_objectsWithAliases.isEmpty())
463 return QQmlError();
464
465 QQmlPropertyCacheAliasCreator<ObjectContainer> aliasCacheCreator(m_propertyCaches, m_compiler);
466
467 bool atLeastOneAliasResolved;
468 do {
469 atLeastOneAliasResolved = false;
470 QList<int> pendingObjects;
471
472 for (int objectIndex: std::as_const(m_objectsWithAliases)) {
473
474 QQmlError error;
475 const auto &component = *m_compiler->objectAt(componentIndex);
476 const auto result
477 = resolveAliasesInObject(component, objectIndex, &aliasCacheCreator, &error);
478 if (error.isValid())
479 return error;
480
481 if (result == AllAliasesResolved) {
482 atLeastOneAliasResolved = true;
483 } else if (result == SomeAliasesResolved) {
484 atLeastOneAliasResolved = true;
485 pendingObjects.append(objectIndex);
486 } else {
487 pendingObjects.append(objectIndex);
488 }
489 }
490 qSwap(m_objectsWithAliases, pendingObjects);
491 } while (!m_objectsWithAliases.isEmpty() && atLeastOneAliasResolved);
492
493 if (!atLeastOneAliasResolved && !m_objectsWithAliases.isEmpty()) {
494 const CompiledObject *obj = m_compiler->objectAt(m_objectsWithAliases.first());
495 for (auto alias = obj->aliasesBegin(), end = obj->aliasesEnd(); alias != end; ++alias) {
496 if (!resolvedAliases.contains(alias)) {
497 return error(
498 alias->location(), QQmlComponentAndAliasResolverBase::tr("Cyclic alias"));
499 }
500 }
501 }
502
503 return QQmlError();
504}
505
506template<typename ObjectContainer>
507void QQmlComponentAndAliasResolver<ObjectContainer>::resolveGeneralizedGroupProperties(
508 int componentIndex)
509{
510 const auto &component = *m_compiler->objectAt(componentIndex);
511 for (CompiledBinding *binding : m_generalizedGroupProperties)
512 resolveGeneralizedGroupProperty(component, binding);
513}
514
515QT_END_NAMESPACE
516
517#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)