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