1// Copyright (C) 2016 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#ifndef QQMLPROPERTYCACHECREATOR_P_H
4#define QQMLPROPERTYCACHECREATOR_P_H
5
6//
7// W A R N I N G
8// -------------
9//
10// This file is not part of the Qt API. It exists purely as an
11// implementation detail. This header file may change from version to
12// version without notice, or even be removed.
13//
14// We mean it.
15//
16
17#include <private/qqmlvaluetype_p.h>
18#include <private/qqmlengine_p.h>
19#include <private/qqmlmetaobject_p.h>
20#include <private/qqmlpropertyresolver_p.h>
21#include <private/qqmltypedata_p.h>
22#include <private/inlinecomponentutils_p.h>
23#include <private/qqmlsourcecoordinate_p.h>
24#include <private/qqmlsignalnames_p.h>
25
26#include <QtCore/qloggingcategory.h>
27#include <QtCore/qscopedvaluerollback.h>
28
29#if QT_CONFIG(regularexpression)
30#include <QtCore/qregularexpression.h>
31#endif
32
33#include <vector>
34
35QT_BEGIN_NAMESPACE
36
37Q_DECLARE_LOGGING_CATEGORY(invalidOverride);
38
39inline QQmlError qQmlCompileError(const QV4::CompiledData::Location &location,
40 const QString &description)
41{
42 QQmlError error;
43 error.setLine(qmlConvertSourceCoordinate<quint32, int>(n: location.line()));
44 error.setColumn(qmlConvertSourceCoordinate<quint32, int>(n: location.column()));
45 error.setDescription(description);
46 return error;
47}
48
49struct QQmlBindingInstantiationContext {
50 QQmlBindingInstantiationContext() {}
51 QQmlBindingInstantiationContext(
52 int referencingObjectIndex, const QV4::CompiledData::Binding *instantiatingBinding,
53 const QString &instantiatingPropertyName,
54 const QQmlPropertyCache::ConstPtr &referencingObjectPropertyCache);
55
56 bool resolveInstantiatingProperty();
57 QQmlPropertyCache::ConstPtr instantiatingPropertyCache() const;
58
59 int referencingObjectIndex = -1;
60 const QV4::CompiledData::Binding *instantiatingBinding = nullptr;
61 QString instantiatingPropertyName;
62 QQmlPropertyCache::ConstPtr referencingObjectPropertyCache;
63 const QQmlPropertyData *instantiatingProperty = nullptr;
64};
65
66struct QQmlPendingGroupPropertyBindings : public QVector<QQmlBindingInstantiationContext>
67{
68 void resolveMissingPropertyCaches(
69 QQmlPropertyCacheVector *propertyCaches) const;
70};
71
72struct QQmlPropertyCacheCreatorBase
73{
74 Q_DECLARE_TR_FUNCTIONS(QQmlPropertyCacheCreatorBase)
75public:
76 static QAtomicInt Q_AUTOTEST_EXPORT classIndexCounter;
77
78 static QMetaType metaTypeForPropertyType(QV4::CompiledData::CommonType type)
79 {
80 switch (type) {
81 case QV4::CompiledData::CommonType::Void: return QMetaType();
82 case QV4::CompiledData::CommonType::Var: return QMetaType::fromType<QVariant>();
83 case QV4::CompiledData::CommonType::Int: return QMetaType::fromType<int>();
84 case QV4::CompiledData::CommonType::Bool: return QMetaType::fromType<bool>();
85 case QV4::CompiledData::CommonType::Real: return QMetaType::fromType<qreal>();
86 case QV4::CompiledData::CommonType::String: return QMetaType::fromType<QString>();
87 case QV4::CompiledData::CommonType::Url: return QMetaType::fromType<QUrl>();
88 case QV4::CompiledData::CommonType::Time: return QMetaType::fromType<QTime>();
89 case QV4::CompiledData::CommonType::Date: return QMetaType::fromType<QDate>();
90 case QV4::CompiledData::CommonType::DateTime: return QMetaType::fromType<QDateTime>();
91#if QT_CONFIG(regularexpression)
92 case QV4::CompiledData::CommonType::RegExp: return QMetaType::fromType<QRegularExpression>();
93#else
94 case QV4::CompiledData::CommonType::RegExp: return QMetaType();
95#endif
96 case QV4::CompiledData::CommonType::Rect: return QMetaType::fromType<QRectF>();
97 case QV4::CompiledData::CommonType::Point: return QMetaType::fromType<QPointF>();
98 case QV4::CompiledData::CommonType::Size: return QMetaType::fromType<QSizeF>();
99 case QV4::CompiledData::CommonType::Invalid: break;
100 };
101 return QMetaType {};
102 }
103
104 static QMetaType listTypeForPropertyType(QV4::CompiledData::CommonType type)
105 {
106 switch (type) {
107 case QV4::CompiledData::CommonType::Void: return QMetaType();
108 case QV4::CompiledData::CommonType::Var: return QMetaType::fromType<QList<QVariant>>();
109 case QV4::CompiledData::CommonType::Int: return QMetaType::fromType<QList<int>>();
110 case QV4::CompiledData::CommonType::Bool: return QMetaType::fromType<QList<bool>>();
111 case QV4::CompiledData::CommonType::Real: return QMetaType::fromType<QList<qreal>>();
112 case QV4::CompiledData::CommonType::String: return QMetaType::fromType<QList<QString>>();
113 case QV4::CompiledData::CommonType::Url: return QMetaType::fromType<QList<QUrl>>();
114 case QV4::CompiledData::CommonType::Time: return QMetaType::fromType<QList<QTime>>();
115 case QV4::CompiledData::CommonType::Date: return QMetaType::fromType<QList<QDate>>();
116 case QV4::CompiledData::CommonType::DateTime: return QMetaType::fromType<QList<QDateTime>>();
117#if QT_CONFIG(regularexpression)
118 case QV4::CompiledData::CommonType::RegExp: return QMetaType::fromType<QList<QRegularExpression>>();
119#else
120 case QV4::CompiledData::CommonType::RegExp: return QMetaType();
121#endif
122 case QV4::CompiledData::CommonType::Rect: return QMetaType::fromType<QList<QRectF>>();
123 case QV4::CompiledData::CommonType::Point: return QMetaType::fromType<QList<QPointF>>();
124 case QV4::CompiledData::CommonType::Size: return QMetaType::fromType<QList<QSizeF>>();
125 case QV4::CompiledData::CommonType::Invalid: break;
126 };
127 return QMetaType {};
128 }
129
130 static QV4::CompiledData::CommonType propertyTypeForMetaType(QMetaType metaType) {
131 if (!metaType.isValid())
132 return QV4::CompiledData::CommonType::Void;
133
134 switch (metaType.id()) {
135 case QMetaType::QVariant:
136 return QV4::CompiledData::CommonType::Var;
137 case QMetaType::Int:
138 return QV4::CompiledData::CommonType::Int;
139 case QMetaType::Bool:
140 return QV4::CompiledData::CommonType::Bool;
141 case QMetaType::QReal:
142 return QV4::CompiledData::CommonType::Real;
143 case QMetaType::QString:
144 return QV4::CompiledData::CommonType::String;
145 case QMetaType::QUrl:
146 return QV4::CompiledData::CommonType::Url;
147 case QMetaType::QTime:
148 return QV4::CompiledData::CommonType::Time;
149 case QMetaType::QDate:
150 return QV4::CompiledData::CommonType::Date;
151 case QMetaType::QDateTime:
152 return QV4::CompiledData::CommonType::DateTime;
153#if QT_CONFIG(regularexpression)
154 case QMetaType::QRegularExpression:
155 return QV4::CompiledData::CommonType::RegExp;
156#endif
157 case QMetaType::QRectF:
158 return QV4::CompiledData::CommonType::Rect;
159 case QMetaType::QPointF:
160 return QV4::CompiledData::CommonType::Point;
161 case QMetaType::QSizeF:
162 return QV4::CompiledData::CommonType::Size;
163 default:
164 break;
165 }
166 return QV4::CompiledData::CommonType::Invalid;
167 }
168
169 static bool canCreateClassNameTypeByUrl(const QUrl &url);
170 static QByteArray createClassNameTypeByUrl(const QUrl &url);
171 static QByteArray createClassNameForInlineComponent(const QUrl &url);
172
173 struct IncrementalResult {
174 // valid if and only if an error occurred
175 QQmlError error;
176 // true if there was no error and there are still components left to process
177 bool canResume = false;
178 // the object index of the last processed (inline) component root.
179 int processedRoot = 0;
180 };
181};
182
183template <typename ObjectContainer>
184class QQmlPropertyCacheCreator : public QQmlPropertyCacheCreatorBase
185{
186public:
187 using CompiledObject = typename ObjectContainer::CompiledObject;
188 using InlineComponent = typename std::remove_reference<decltype (*(std::declval<CompiledObject>().inlineComponentsBegin()))>::type;
189
190 QQmlPropertyCacheCreator(QQmlPropertyCacheVector *propertyCaches,
191 QQmlPendingGroupPropertyBindings *pendingGroupPropertyBindings,
192 QQmlTypeLoader *typeLoader,
193 const ObjectContainer *objectContainer, const QQmlImports *imports,
194 const QByteArray &typeClassName);
195 ~QQmlPropertyCacheCreator() { propertyCaches->seal(); }
196
197
198 /*!
199 \internal
200 Creates the property cache for the CompiledObjects of objectContainer,
201 one (inline) root component at a time.
202
203 \note Later compiler passes might modify those property caches. Therefore,
204 the actual metaobjects are not created yet.
205 */
206 IncrementalResult buildMetaObjectsIncrementally();
207
208 /*!
209 \internal
210 Returns a valid error if the inline components of the objectContainer
211 form a cycle. Otherwise an invalid error is returned
212 */
213 QQmlError verifyNoICCycle();
214
215 enum class VMEMetaObjectIsRequired {
216 Maybe,
217 Always
218 };
219protected:
220 QQmlError buildMetaObjectRecursively(int objectIndex, const QQmlBindingInstantiationContext &context, VMEMetaObjectIsRequired isVMERequired);
221 QQmlPropertyCache::ConstPtr propertyCacheForObject(const CompiledObject *obj, const QQmlBindingInstantiationContext &context, QQmlError *error) const;
222 QQmlError createMetaObject(int objectIndex, const CompiledObject *obj, const QQmlPropertyCache::ConstPtr &baseTypeCache);
223
224 QMetaType metaTypeForParameter(const QV4::CompiledData::ParameterType &param, QString *customTypeName = nullptr);
225
226 QString stringAt(int index) const { return objectContainer->stringAt(index); }
227
228 QQmlTypeLoader *const typeLoader;
229 const ObjectContainer * const objectContainer;
230 const QQmlImports * const imports;
231 QQmlPropertyCacheVector *propertyCaches;
232 QQmlPendingGroupPropertyBindings *pendingGroupPropertyBindings;
233 QByteArray typeClassName; // not const as we temporarily chang it for inline components
234 unsigned int currentRoot; // set to objectID of inline component root when handling inline components
235
236 QQmlBindingInstantiationContext m_context;
237 std::vector<InlineComponent> allICs;
238 std::vector<icutils::Node> nodesSorted;
239 std::vector<icutils::Node>::reverse_iterator nodeIt = nodesSorted.rbegin();
240 bool hasCycle = false;
241};
242
243template <typename ObjectContainer>
244inline QQmlPropertyCacheCreator<ObjectContainer>::QQmlPropertyCacheCreator(
245 QQmlPropertyCacheVector *propertyCaches,
246 QQmlPendingGroupPropertyBindings *pendingGroupPropertyBindings,
247 QQmlTypeLoader *typeLoader,
248 const ObjectContainer *objectContainer, const QQmlImports *imports,
249 const QByteArray &typeClassName)
250 : typeLoader(typeLoader)
251 , objectContainer(objectContainer)
252 , imports(imports)
253 , propertyCaches(propertyCaches)
254 , pendingGroupPropertyBindings(pendingGroupPropertyBindings)
255 , typeClassName(typeClassName)
256 , currentRoot(-1)
257{
258 propertyCaches->resetAndResize(size: objectContainer->objectCount());
259
260 using namespace icutils;
261
262 // get a list of all inline components
263
264 for (int i=0; i != objectContainer->objectCount(); ++i) {
265 const CompiledObject *obj = objectContainer->objectAt(i);
266 for (auto it = obj->inlineComponentsBegin(); it != obj->inlineComponentsEnd(); ++it) {
267 allICs.push_back(*it);
268 }
269 }
270
271 // create a graph on inline components referencing inline components
272 std::vector<icutils::Node> nodes;
273 nodes.resize(allICs.size());
274 std::iota(first: nodes.begin(), last: nodes.end(), value: 0);
275 AdjacencyList adjacencyList;
276 adjacencyList.resize(new_size: nodes.size());
277 fillAdjacencyListForInlineComponents(objectContainer, adjacencyList, nodes, allICs);
278
279 nodesSorted = topoSort(nodes, adjacencyList, hasCycle);
280 nodeIt = nodesSorted.rbegin();
281}
282
283template <typename ObjectContainer>
284inline QQmlError QQmlPropertyCacheCreator<ObjectContainer>::verifyNoICCycle()
285{
286 if (hasCycle) {
287 QQmlError diag;
288 diag.setDescription(QLatin1String("Inline components form a cycle!"));
289 return diag;
290 }
291 return {};
292}
293
294template <typename ObjectContainer>
295inline QQmlPropertyCacheCreatorBase::IncrementalResult
296QQmlPropertyCacheCreator<ObjectContainer>::buildMetaObjectsIncrementally()
297{
298 // needs to be checked with verifyNoICCycle before this function is called
299 Q_ASSERT(!hasCycle);
300
301 // create meta objects for inline components before compiling actual root component
302 if (nodeIt != nodesSorted.rend()) {
303 const auto &ic = allICs[nodeIt->index()];
304 QV4::ResolvedTypeReference *typeRef = objectContainer->resolvedType(ic.nameIndex);
305 Q_ASSERT(propertyCaches->at(ic.objectIndex).isNull());
306 Q_ASSERT(typeRef->typePropertyCache().isNull()); // not set yet
307
308 QByteArray icTypeName { objectContainer->stringAt(ic.nameIndex).toUtf8() };
309 QScopedValueRollback<QByteArray> nameChange {typeClassName, icTypeName};
310 QScopedValueRollback<unsigned int> rootChange {currentRoot, ic.objectIndex};
311 ++nodeIt;
312 QQmlError diag = buildMetaObjectRecursively(objectIndex: ic.objectIndex, context: m_context, isVMERequired: VMEMetaObjectIsRequired::Always);
313 if (diag.isValid()) {
314 return {.error: diag, .canResume: false, .processedRoot: 0};
315 }
316 typeRef->setTypePropertyCache(propertyCaches->at(index: ic.objectIndex));
317 Q_ASSERT(!typeRef->typePropertyCache().isNull());
318 return { .error: QQmlError(), .canResume: true, .processedRoot: int(ic.objectIndex) };
319 }
320
321 auto diag = buildMetaObjectRecursively(/*root object*/objectIndex: 0, context: m_context, isVMERequired: VMEMetaObjectIsRequired::Maybe);
322 return {diag, false, 0};
323}
324
325template <typename ObjectContainer>
326inline QQmlError QQmlPropertyCacheCreator<ObjectContainer>::buildMetaObjectRecursively(int objectIndex, const QQmlBindingInstantiationContext &context, VMEMetaObjectIsRequired isVMERequired)
327{
328 auto isAddressable = [](const QUrl &url) {
329 const QString fileName = url.fileName();
330 return !fileName.isEmpty() && fileName.front().isUpper();
331 };
332
333 const CompiledObject *obj = objectContainer->objectAt(objectIndex);
334 bool needVMEMetaObject = isVMERequired == VMEMetaObjectIsRequired::Always
335 || obj->propertyCount() != 0 || obj->aliasCount() != 0 || obj->signalCount() != 0
336 || obj->functionCount() != 0 || obj->enumCount() != 0
337 || obj->inlineComponentCount() != 0
338 || ((obj->hasFlag(QV4::CompiledData::Object::IsComponent)
339 || (objectIndex == 0 && isAddressable(objectContainer->url())))
340 && !objectContainer->resolvedType(obj->inheritedTypeNameIndex)->isFullyDynamicType());
341
342 if (!needVMEMetaObject) {
343 auto binding = obj->bindingsBegin();
344 auto end = obj->bindingsEnd();
345 for ( ; binding != end; ++binding) {
346 if (binding->type() == QV4::CompiledData::Binding::Type_Object
347 && (binding->flags() & QV4::CompiledData::Binding::IsOnAssignment)) {
348 // If the on assignment is inside a group property, we need to distinguish between QObject based
349 // group properties and value type group properties. For the former the base type is derived from
350 // the property that references us, for the latter we only need a meta-object on the referencing object
351 // because interceptors can't go to the shared value type instances.
352 if (context.instantiatingProperty && QQmlMetaType::isValueType(type: context.instantiatingProperty->propType())) {
353 if (!propertyCaches->needsVMEMetaObject(index: context.referencingObjectIndex)) {
354 const CompiledObject *obj = objectContainer->objectAt(context.referencingObjectIndex);
355 auto *typeRef = objectContainer->resolvedType(obj->inheritedTypeNameIndex);
356 Q_ASSERT(typeRef);
357 QQmlPropertyCache::ConstPtr baseTypeCache = typeRef->createPropertyCache();
358 QQmlError error = baseTypeCache
359 ? createMetaObject(objectIndex: context.referencingObjectIndex, obj, baseTypeCache)
360 : qQmlCompileError(binding->location, QQmlPropertyCacheCreatorBase::tr(
361 sourceText: "Type cannot be used for 'on' assignment"));
362 if (error.isValid())
363 return error;
364 }
365 } else {
366 // On assignments are implemented using value interceptors, which require a VME meta object.
367 needVMEMetaObject = true;
368 }
369 break;
370 }
371 }
372 }
373
374 QQmlPropertyCache::ConstPtr baseTypeCache;
375 {
376 QQmlError error;
377 baseTypeCache = propertyCacheForObject(obj, context, error: &error);
378 if (error.isValid())
379 return error;
380 }
381
382 if (baseTypeCache) {
383 if (needVMEMetaObject) {
384 QQmlError error = createMetaObject(objectIndex, obj, baseTypeCache);
385 if (error.isValid())
386 return error;
387 } else {
388 propertyCaches->set(index: objectIndex, replacement: baseTypeCache);
389 }
390 }
391
392 QQmlPropertyCache::ConstPtr thisCache = propertyCaches->at(index: objectIndex);
393 auto binding = obj->bindingsBegin();
394 auto end = obj->bindingsEnd();
395 for (; binding != end; ++binding) {
396 switch (binding->type()) {
397 case QV4::CompiledData::Binding::Type_Object:
398 case QV4::CompiledData::Binding::Type_GroupProperty:
399 case QV4::CompiledData::Binding::Type_AttachedProperty:
400 // We can always resolve object, group, and attached properties.
401 break;
402 default:
403 // Everything else is of no interest here.
404 continue;
405 }
406
407 QQmlBindingInstantiationContext context(
408 objectIndex, &(*binding), stringAt(index: binding->propertyNameIndex), thisCache);
409
410 // Binding to group property where we failed to look up the type of the
411 // property? Possibly a group property that is an alias that's not resolved yet.
412 // Let's attempt to resolve it after we're done with the aliases and fill in the
413 // propertyCaches entry then.
414 if (!thisCache || !context.resolveInstantiatingProperty())
415 pendingGroupPropertyBindings->append(t: context);
416
417 QQmlError error = buildMetaObjectRecursively(
418 objectIndex: binding->value.objectIndex, context, isVMERequired: VMEMetaObjectIsRequired::Maybe);
419 if (error.isValid())
420 return error;
421 }
422
423 QQmlError noError;
424 return noError;
425}
426
427template <typename ObjectContainer>
428inline QQmlPropertyCache::ConstPtr QQmlPropertyCacheCreator<ObjectContainer>::propertyCacheForObject(const CompiledObject *obj, const QQmlBindingInstantiationContext &context, QQmlError *error) const
429{
430 if (context.instantiatingProperty) {
431 return context.instantiatingPropertyCache();
432 } else if (obj->inheritedTypeNameIndex != 0) {
433 auto *typeRef = objectContainer->resolvedType(obj->inheritedTypeNameIndex);
434 Q_ASSERT(typeRef);
435
436 if (typeRef->isFullyDynamicType()) {
437 if (obj->propertyCount() > 0 || obj->aliasCount() > 0) {
438 *error = qQmlCompileError(obj->location, QQmlPropertyCacheCreatorBase::tr(sourceText: "Fully dynamic types cannot declare new properties."));
439 return nullptr;
440 }
441 if (obj->signalCount() > 0) {
442 *error = qQmlCompileError(obj->location, QQmlPropertyCacheCreatorBase::tr(sourceText: "Fully dynamic types cannot declare new signals."));
443 return nullptr;
444 }
445 if (obj->functionCount() > 0) {
446 *error = qQmlCompileError(obj->location, QQmlPropertyCacheCreatorBase::tr(sourceText: "Fully Dynamic types cannot declare new functions."));
447 return nullptr;
448 }
449 }
450
451 if (QQmlPropertyCache::ConstPtr propertyCache = typeRef->createPropertyCache())
452 return propertyCache;
453 *error = qQmlCompileError(
454 obj->location,
455 QQmlPropertyCacheCreatorBase::tr(sourceText: "Type '%1' cannot declare new members.")
456 .arg(stringAt(index: obj->inheritedTypeNameIndex)));
457 return nullptr;
458 } else if (const QV4::CompiledData::Binding *binding = context.instantiatingBinding) {
459 if (binding->isAttachedProperty()) {
460 auto *typeRef = objectContainer->resolvedType(
461 binding->propertyNameIndex);
462 Q_ASSERT(typeRef);
463 QQmlType qmltype = typeRef->type();
464 if (!qmltype.isValid()) {
465 imports->resolveType(
466 typeLoader, stringAt(index: binding->propertyNameIndex),
467 &qmltype, nullptr, nullptr);
468 }
469
470 const QMetaObject *attachedMo = qmltype.attachedPropertiesType(typeLoader);
471 if (!attachedMo) {
472 *error = qQmlCompileError(location: binding->location, description: QQmlPropertyCacheCreatorBase::tr(sourceText: "Non-existent attached object"));
473 return nullptr;
474 }
475 return QQmlMetaType::propertyCache(metaObject: attachedMo);
476 }
477 }
478 return nullptr;
479}
480
481template <typename ObjectContainer>
482inline QQmlError QQmlPropertyCacheCreator<ObjectContainer>::createMetaObject(
483 int objectIndex, const CompiledObject *obj,
484 const QQmlPropertyCache::ConstPtr &baseTypeCache)
485{
486 QQmlPropertyCache::Ptr cache = baseTypeCache->copyAndReserve(
487 propertyCount: obj->propertyCount() + obj->aliasCount(),
488 methodCount: obj->functionCount() + obj->propertyCount() + obj->aliasCount() + obj->signalCount(),
489 signalCount: obj->signalCount() + obj->propertyCount() + obj->aliasCount(),
490 enumCount: obj->enumCount());
491
492 propertyCaches->setOwn(index: objectIndex, replacement: cache);
493 propertyCaches->setNeedsVMEMetaObject(objectIndex);
494
495 QByteArray newClassName;
496
497 if (objectIndex == /*root object*/0 || int(currentRoot) == objectIndex) {
498 newClassName = typeClassName;
499 }
500 if (newClassName.isEmpty()) {
501 newClassName = QQmlMetaObject(baseTypeCache).className();
502 newClassName.append(s: "_QML_");
503 newClassName.append(a: QByteArray::number(classIndexCounter.fetchAndAddRelaxed(valueToAdd: 1)));
504 }
505
506 cache->_dynamicClassName = newClassName;
507
508 using ListPropertyAssignBehavior = typename ObjectContainer::ListPropertyAssignBehavior;
509 switch (objectContainer->listPropertyAssignBehavior()) {
510 case ListPropertyAssignBehavior::ReplaceIfNotDefault:
511 cache->_listPropertyAssignBehavior = "ReplaceIfNotDefault";
512 break;
513 case ListPropertyAssignBehavior::Replace:
514 cache->_listPropertyAssignBehavior = "Replace";
515 break;
516 case ListPropertyAssignBehavior::Append:
517 break;
518 }
519
520 QQmlPropertyResolver resolver(baseTypeCache);
521
522 auto p = obj->propertiesBegin();
523 auto pend = obj->propertiesEnd();
524 for ( ; p != pend; ++p) {
525 bool notInRevision = false;
526 const QQmlPropertyData *d = resolver.property(stringAt(index: p->nameIndex()), &notInRevision);
527 if (d && d->isFinal())
528 return qQmlCompileError(p->location, QQmlPropertyCacheCreatorBase::tr(sourceText: "Cannot override FINAL property"));
529 }
530
531 auto a = obj->aliasesBegin();
532 auto aend = obj->aliasesEnd();
533 for ( ; a != aend; ++a) {
534 bool notInRevision = false;
535 const QQmlPropertyData *d = resolver.property(stringAt(index: a->nameIndex()), &notInRevision);
536 if (d && d->isFinal())
537 return qQmlCompileError(a->location, QQmlPropertyCacheCreatorBase::tr(sourceText: "Cannot override FINAL property"));
538 }
539
540 int effectivePropertyIndex = cache->propertyIndexCacheStart;
541 int effectiveMethodIndex = cache->methodIndexCacheStart;
542
543 // For property change signal override detection.
544 // We prepopulate a set of signal names which already exist in the object,
545 // and throw an error if there is a signal/method defined as an override.
546 // TODO: Remove AllowOverride once we can. No override should be allowed.
547 enum class AllowOverride { No, Yes };
548 QHash<QString, AllowOverride> seenSignals {
549 { QStringLiteral("destroyed"), AllowOverride::No },
550 { QStringLiteral("parentChanged"), AllowOverride::No },
551 { QStringLiteral("objectNameChanged"), AllowOverride::No }
552 };
553 const QQmlPropertyCache *parentCache = cache.data();
554 while ((parentCache = parentCache->parent().data())) {
555 if (int pSigCount = parentCache->signalCount()) {
556 int pSigOffset = parentCache->signalOffset();
557 for (int i = pSigOffset; i < pSigCount; ++i) {
558 const QQmlPropertyData *currPSig = parentCache->signal(index: i);
559 // XXX TODO: find a better way to get signal name from the property data :-/
560 for (QQmlPropertyCache::StringCache::ConstIterator iter = parentCache->stringCache.begin();
561 iter != parentCache->stringCache.end(); ++iter) {
562 if (currPSig == (*iter).second) {
563 if (currPSig->isOverridableSignal()) {
564 const qsizetype oldSize = seenSignals.size();
565 AllowOverride &entry = seenSignals[iter.key()];
566 if (seenSignals.size() != oldSize)
567 entry = AllowOverride::Yes;
568 } else {
569 seenSignals[iter.key()] = AllowOverride::No;
570 }
571
572 break;
573 }
574 }
575 }
576 }
577 }
578
579 // Set up notify signals for properties - first normal, then alias
580 p = obj->propertiesBegin();
581 pend = obj->propertiesEnd();
582 for ( ; p != pend; ++p) {
583 auto flags = QQmlPropertyData::defaultSignalFlags();
584
585 const QString changedSigName =
586 QQmlSignalNames::propertyNameToChangedSignalName(stringAt(index: p->nameIndex()));
587 seenSignals[changedSigName] = AllowOverride::No;
588
589 cache->appendSignal(changedSigName, flags, coreIndex: effectiveMethodIndex++);
590 }
591
592 a = obj->aliasesBegin();
593 aend = obj->aliasesEnd();
594 for ( ; a != aend; ++a) {
595 auto flags = QQmlPropertyData::defaultSignalFlags();
596
597 const QString changedSigName =
598 QQmlSignalNames::propertyNameToChangedSignalName(stringAt(index: a->nameIndex()));
599 seenSignals[changedSigName] = AllowOverride::No;
600
601 cache->appendSignal(changedSigName, flags, coreIndex: effectiveMethodIndex++);
602 }
603
604 auto e = obj->enumsBegin();
605 auto eend = obj->enumsEnd();
606 for ( ; e != eend; ++e) {
607 const int enumValueCount = e->enumValueCount();
608 QVector<QQmlEnumValue> values;
609 values.reserve(size: enumValueCount);
610
611 auto enumValue = e->enumValuesBegin();
612 auto end = e->enumValuesEnd();
613 for ( ; enumValue != end; ++enumValue)
614 values.append(t: QQmlEnumValue(stringAt(index: enumValue->nameIndex), enumValue->value));
615
616 cache->appendEnum(stringAt(index: e->nameIndex), values);
617 }
618
619 // Dynamic signals
620 auto s = obj->signalsBegin();
621 auto send = obj->signalsEnd();
622 for ( ; s != send; ++s) {
623 const int paramCount = s->parameterCount();
624
625 QList<QByteArray> names;
626 names.reserve(size: paramCount);
627 QVarLengthArray<QMetaType, 10> paramTypes(paramCount);
628
629 if (paramCount) {
630
631 int i = 0;
632 auto param = s->parametersBegin();
633 auto end = s->parametersEnd();
634 for ( ; param != end; ++param, ++i) {
635 names.append(stringAt(index: param->nameIndex).toUtf8());
636
637 QString customTypeName;
638 QMetaType type = metaTypeForParameter(param: param->type, customTypeName: &customTypeName);
639 if (!type.isValid())
640 return qQmlCompileError(s->location, QQmlPropertyCacheCreatorBase::tr(sourceText: "Invalid signal parameter type: %1").arg(a: customTypeName));
641
642 paramTypes[i] = type;
643 }
644 }
645
646 auto flags = QQmlPropertyData::defaultSignalFlags();
647 if (paramCount)
648 flags.setHasArguments(true);
649
650 QString signalName = stringAt(index: s->nameIndex);
651 const auto it = seenSignals.find(signalName);
652 if (it == seenSignals.end()) {
653 seenSignals[signalName] = AllowOverride::No;
654 } else {
655 // TODO: Remove the AllowOverride::Yes branch once we can.
656 QQmlError message = qQmlCompileError(
657 s->location,
658 QQmlPropertyCacheCreatorBase::tr(
659 sourceText: "Duplicate signal name: "
660 "invalid override of property change signal or superclass signal"));
661 switch (*it) {
662 case AllowOverride::No:
663 return message;
664 case AllowOverride::Yes:
665 message.setUrl(objectContainer->url());
666 qCWarning(invalidOverride).noquote() << message.toString();
667 *it = AllowOverride::No; // No further overriding allowed.
668 break;
669 }
670 }
671 cache->appendSignal(signalName, flags, coreIndex: effectiveMethodIndex++,
672 types: paramCount?paramTypes.constData():nullptr, names);
673 }
674
675
676 // Dynamic slots
677 auto function = objectContainer->objectFunctionsBegin(obj);
678 auto fend = objectContainer->objectFunctionsEnd(obj);
679 for ( ; function != fend; ++function) {
680 auto flags = QQmlPropertyData::defaultSlotFlags();
681
682 const QString slotName = stringAt(index: function->nameIndex);
683 const auto it = seenSignals.constFind(slotName);
684 if (it != seenSignals.constEnd()) {
685 // TODO: Remove the AllowOverride::Yes branch once we can.
686 QQmlError message = qQmlCompileError(
687 function->location,
688 QQmlPropertyCacheCreatorBase::tr(
689 sourceText: "Duplicate method name: "
690 "invalid override of property change signal or superclass signal"));
691 switch (*it) {
692 case AllowOverride::No:
693 return message;
694 case AllowOverride::Yes:
695 message.setUrl(objectContainer->url());
696 qCWarning(invalidOverride).noquote() << message.toString();
697 break;
698 }
699 }
700 // Note: we don't append slotName to the seenSignals list, since we don't
701 // protect against overriding change signals or methods with properties.
702
703 QList<QByteArray> parameterNames;
704 QVector<QMetaType> parameterTypes;
705 auto formal = function->formalsBegin();
706 auto end = function->formalsEnd();
707 for ( ; formal != end; ++formal) {
708 flags.setHasArguments(true);
709 parameterNames << stringAt(index: formal->nameIndex).toUtf8();
710 QMetaType type = metaTypeForParameter(param: formal->type);
711 if (!type.isValid())
712 type = QMetaType::fromType<QVariant>();
713 parameterTypes << type;
714 }
715
716 QMetaType returnType = metaTypeForParameter(param: function->returnType);
717 if (!returnType.isValid())
718 returnType = QMetaType::fromType<QVariant>();
719
720 cache->appendMethod(slotName, flags, coreIndex: effectiveMethodIndex++, returnType, names: parameterNames, parameterTypes);
721 }
722
723
724 // Dynamic properties
725 int effectiveSignalIndex = cache->signalHandlerIndexCacheStart;
726 int propertyIdx = 0;
727 p = obj->propertiesBegin();
728 pend = obj->propertiesEnd();
729 for ( ; p != pend; ++p, ++propertyIdx) {
730 QMetaType propertyType;
731 QTypeRevision propertyTypeVersion = QTypeRevision::zero();
732 QQmlPropertyData::Flags propertyFlags;
733
734 const QV4::CompiledData::CommonType type = p->commonType();
735
736 if (p->isList())
737 propertyFlags.setType(QQmlPropertyData::Flags::QListType);
738 else if (type == QV4::CompiledData::CommonType::Var)
739 propertyFlags.setType(QQmlPropertyData::Flags::VarPropertyType);
740
741 if (type != QV4::CompiledData::CommonType::Invalid) {
742 propertyType = p->isList()
743 ? listTypeForPropertyType(type)
744 : metaTypeForPropertyType(type);
745 } else {
746 Q_ASSERT(!p->isCommonType());
747
748 QQmlType qmltype;
749 bool selfReference = false;
750 if (!imports->resolveType(
751 typeLoader,
752 stringAt(index: p->commonTypeOrTypeNameIndex()), &qmltype, nullptr, nullptr,
753 nullptr, QQmlType::AnyRegistrationType, &selfReference)) {
754 return qQmlCompileError(p->location, QQmlPropertyCacheCreatorBase::tr(sourceText: "Invalid property type"));
755 }
756
757 // inline components are not necessarily valid yet
758 Q_ASSERT(qmltype.isValid());
759 if (qmltype.isComposite() || qmltype.isInlineComponentType()) {
760 QQmlType compositeType;
761 if (qmltype.isInlineComponentType()) {
762 compositeType = qmltype;
763 Q_ASSERT(compositeType.isValid());
764 } else if (selfReference) {
765 compositeType = objectContainer->qmlTypeForComponent();
766 } else {
767 // compositeType may not be the same type as qmlType because multiple engines
768 // may load different types for the same document. Therefore we have to ask
769 // our engine's type loader here.
770 QQmlRefPointer<QQmlTypeData> tdata = typeLoader->getType(unNormalizedUrl: qmltype.sourceUrl());
771 Q_ASSERT(tdata);
772 Q_ASSERT(tdata->isComplete());
773 compositeType = tdata->compilationUnit()->qmlTypeForComponent();
774 }
775
776 if (p->isList()) {
777 propertyType = compositeType.qListTypeId();
778 } else {
779 propertyType = compositeType.typeId();
780 }
781 } else {
782 if (p->isList())
783 propertyType = qmltype.qListTypeId();
784 else
785 propertyType = qmltype.typeId();
786 propertyTypeVersion = qmltype.version();
787 }
788
789 if (p->isList())
790 propertyFlags.setType(QQmlPropertyData::Flags::QListType);
791 else if (propertyType.flags().testFlag(flag: QMetaType::PointerToQObject))
792 propertyFlags.setType(QQmlPropertyData::Flags::QObjectDerivedType);
793 }
794
795 if (!p->isReadOnly() && !propertyType.flags().testFlag(flag: QMetaType::IsQmlList))
796 propertyFlags.setIsWritable(true);
797 if (p->isFinal())
798 propertyFlags.setIsFinal(true);
799
800
801 QString propertyName = stringAt(index: p->nameIndex());
802 if (!obj->hasAliasAsDefaultProperty() && propertyIdx == obj->indexOfDefaultPropertyOrAlias)
803 cache->_defaultPropertyName = propertyName;
804 cache->appendProperty(propertyName, flags: propertyFlags, coreIndex: effectivePropertyIndex++,
805 propType: propertyType, revision: propertyTypeVersion, notifyIndex: effectiveSignalIndex);
806
807 effectiveSignalIndex++;
808 }
809
810 QQmlError noError;
811 return noError;
812}
813
814template <typename ObjectContainer>
815inline QMetaType QQmlPropertyCacheCreator<ObjectContainer>::metaTypeForParameter(
816 const QV4::CompiledData::ParameterType &param, QString *customTypeName)
817{
818 const quint32 typeId = param.typeNameIndexOrCommonType();
819 if (param.indexIsCommonType()) {
820 // built-in type
821 if (param.isList())
822 return listTypeForPropertyType(type: QV4::CompiledData::CommonType(typeId));
823 return metaTypeForPropertyType(type: QV4::CompiledData::CommonType(typeId));
824 }
825
826 // lazily resolved type
827 const QString typeName = stringAt(index: param.typeNameIndexOrCommonType());
828 if (customTypeName)
829 *customTypeName = typeName;
830 QQmlType qmltype;
831 bool selfReference = false;
832 if (!imports->resolveType(
833 typeLoader, type: typeName, type_return: &qmltype, version_return: nullptr, ns_return: nullptr, errors: nullptr,
834 registrationType: QQmlType::AnyRegistrationType, typeRecursionDetected: &selfReference))
835 return QMetaType();
836
837 if (!qmltype.isComposite()) {
838 const QMetaType typeId = param.isList() ? qmltype.qListTypeId() : qmltype.typeId();
839 if (!typeId.isValid() && qmltype.isInlineComponentType()) {
840 const QQmlType qmlType = objectContainer->qmlTypeForComponent(qmltype.elementName());
841 return param.isList() ? qmlType.qListTypeId() : qmlType.typeId();
842 } else {
843 return typeId;
844 }
845 }
846
847 if (selfReference) {
848 const QQmlType qmlType = objectContainer->qmlTypeForComponent();
849 return param.isList() ? qmlType.qListTypeId() : qmlType.typeId();
850 }
851
852 return param.isList() ? qmltype.qListTypeId() : qmltype.typeId();
853}
854
855template <typename ObjectContainer, typename CompiledObject>
856int objectForId(const ObjectContainer *objectContainer, const CompiledObject &component, int id)
857{
858 for (quint32 i = 0, count = component.namedObjectsInComponentCount(); i < count; ++i) {
859 const int candidateIndex = component.namedObjectsInComponentTable()[i];
860 const CompiledObject &candidate = *objectContainer->objectAt(candidateIndex);
861 if (candidate.objectId() == id)
862 return candidateIndex;
863 }
864 return -1;
865}
866
867template <typename ObjectContainer>
868class QQmlPropertyCacheAliasCreator
869{
870public:
871 typedef typename ObjectContainer::CompiledObject CompiledObject;
872
873 QQmlPropertyCacheAliasCreator(
874 QQmlPropertyCacheVector *propertyCaches, const ObjectContainer *objectContainer);
875 QQmlError appendAliasToPropertyCache(
876 const CompiledObject &component, const QV4::CompiledData::Alias &alias, int objectIndex,
877 int aliasIndex, int encodedMetaPropertyIndex);
878
879private:
880 QQmlError propertyDataForAlias(
881 const CompiledObject &component, const QV4::CompiledData::Alias &alias, QMetaType *type,
882 QTypeRevision *version, QQmlPropertyData::Flags *propertyFlags,
883 int targetPropertyIndex);
884
885 QQmlPropertyCacheVector *propertyCaches;
886 const ObjectContainer *objectContainer;
887};
888
889template <typename ObjectContainer>
890inline QQmlPropertyCacheAliasCreator<ObjectContainer>::QQmlPropertyCacheAliasCreator(
891 QQmlPropertyCacheVector *propertyCaches, const ObjectContainer *objectContainer)
892 : propertyCaches(propertyCaches)
893 , objectContainer(objectContainer)
894{
895}
896
897template <typename ObjectContainer>
898inline QQmlError QQmlPropertyCacheAliasCreator<ObjectContainer>::propertyDataForAlias(
899 const CompiledObject &component, const QV4::CompiledData::Alias &alias, QMetaType *type,
900 QTypeRevision *version, QQmlPropertyData::Flags *propertyFlags, int targetPropertyIndex)
901{
902 *type = QMetaType();
903 bool writable = false;
904 bool resettable = false;
905 bool notifiesViaBindable = false;
906
907 propertyFlags->setIsAlias(true);
908
909 if (alias.isAliasToLocalAlias()) {
910 const QV4::CompiledData::Alias *lastAlias = &alias;
911 QVarLengthArray<const QV4::CompiledData::Alias *, 4> seenAliases({lastAlias});
912
913 do {
914 const int targetObjectIndex = objectForId(
915 objectContainer, component, lastAlias->targetObjectId());
916 Q_ASSERT(targetObjectIndex >= 0);
917 const CompiledObject *targetObject = objectContainer->objectAt(targetObjectIndex);
918 Q_ASSERT(targetObject);
919
920 auto nextAlias = targetObject->aliasesBegin();
921 for (uint i = 0; i < lastAlias->localAliasIndex; ++i)
922 ++nextAlias;
923
924 const QV4::CompiledData::Alias *targetAlias = &(*nextAlias);
925 if (seenAliases.contains(t: targetAlias)) {
926 return qQmlCompileError(location: targetAlias->location,
927 description: QQmlPropertyCacheCreatorBase::tr(sourceText: "Cyclic alias"));
928 }
929
930 seenAliases.append(t: targetAlias);
931 lastAlias = targetAlias;
932 } while (lastAlias->isAliasToLocalAlias());
933
934 return propertyDataForAlias(
935 component, alias: *lastAlias, type, version, propertyFlags, targetPropertyIndex);
936 }
937
938 const int targetObjectIndex = objectForId(objectContainer, component, alias.targetObjectId());
939 Q_ASSERT(targetObjectIndex >= 0);
940 const CompiledObject &targetObject = *objectContainer->objectAt(targetObjectIndex);
941
942 if (targetPropertyIndex == -1) {
943 Q_ASSERT(alias.hasFlag(QV4::CompiledData::Alias::AliasPointsToPointerObject));
944 auto *typeRef = objectContainer->resolvedType(targetObject.inheritedTypeNameIndex);
945 if (!typeRef) {
946 // Can be caused by the alias target not being a valid id or property. E.g.:
947 // property alias dataValue: dataVal
948 // invalidAliasComponent { id: dataVal }
949 return qQmlCompileError(targetObject.location,
950 QQmlPropertyCacheCreatorBase::tr(sourceText: "Invalid alias target"));
951 }
952
953 const auto referencedType = typeRef->type();
954 if (referencedType.isValid()) {
955 *type = referencedType.typeId();
956 if (!type->isValid() && referencedType.isInlineComponentType()) {
957 *type = objectContainer->qmlTypeForComponent(referencedType.elementName()).typeId();
958 Q_ASSERT(type->isValid());
959 }
960 } else {
961 *type = typeRef->compilationUnit()->metaType();
962 }
963
964 *version = typeRef->version();
965
966 propertyFlags->setType(QQmlPropertyData::Flags::QObjectDerivedType);
967 } else {
968 int coreIndex = QQmlPropertyIndex::fromEncoded(encodedIndex: targetPropertyIndex).coreIndex();
969 int valueTypeIndex
970 = QQmlPropertyIndex::fromEncoded(encodedIndex: targetPropertyIndex).valueTypeIndex();
971
972 QQmlPropertyCache::ConstPtr targetCache = propertyCaches->at(index: targetObjectIndex);
973 Q_ASSERT(targetCache);
974
975 const QQmlPropertyData *targetProperty = targetCache->property(index: coreIndex);
976 Q_ASSERT(targetProperty);
977
978 const QMetaType targetPropType = targetProperty->propType();
979
980 const auto populateWithPropertyData = [&](const QQmlPropertyData *property) {
981 *type = property->propType();
982 writable = property->isWritable();
983 resettable = property->isResettable();
984 notifiesViaBindable = property->notifiesViaBindable();
985
986 if (property->isVarProperty())
987 propertyFlags->setType(QQmlPropertyData::Flags::QVariantType);
988 else
989 propertyFlags->copyPropertyTypeFlags(from: property->flags());
990 };
991
992 // for deep aliases, valueTypeIndex is always set
993 if (!QQmlMetaType::isValueType(type: targetPropType) && valueTypeIndex != -1) {
994 // deep alias property
995
996 QQmlPropertyCache::ConstPtr typeCache
997 = QQmlMetaType::propertyCacheForType(metaType: targetPropType);
998
999 if (!typeCache) {
1000 // See if it's a half-resolved composite type
1001 if (const QV4::ResolvedTypeReference *typeRef
1002 = objectContainer->resolvedType(targetPropType)) {
1003 typeCache = typeRef->typePropertyCache();
1004 }
1005 }
1006
1007 const QQmlPropertyData *typeProperty = typeCache
1008 ? typeCache->property(index: valueTypeIndex)
1009 : nullptr;
1010 if (typeProperty == nullptr) {
1011 return qQmlCompileError(
1012 location: alias.referenceLocation,
1013 description: QQmlPropertyCacheCreatorBase::tr(sourceText: "Invalid alias target"));
1014 }
1015 populateWithPropertyData(typeProperty);
1016 } else {
1017 // value type or primitive type or enum
1018 populateWithPropertyData(targetProperty);
1019
1020 if (valueTypeIndex != -1) {
1021 const QMetaObject *valueTypeMetaObject
1022 = QQmlMetaType::metaObjectForValueType(type: *type);
1023 const QMetaProperty valueTypeMetaProperty
1024 = valueTypeMetaObject->property(index: valueTypeIndex);
1025 *type = valueTypeMetaProperty.metaType();
1026
1027 // We can only write or reset the value type property if we can write
1028 // the value type itself.
1029 resettable = writable && valueTypeMetaProperty.isResettable();
1030 writable = writable && valueTypeMetaProperty.isWritable();
1031
1032 // Do not update notifiesViaBindable. The core property counts for notifications.
1033 propertyFlags->setIsDeepAlias(true);
1034 }
1035 }
1036 }
1037
1038 propertyFlags->setIsWritable(
1039 writable && !alias.hasFlag(flag: QV4::CompiledData::Alias::IsReadOnly));
1040 propertyFlags->setIsResettable(resettable);
1041 propertyFlags->setIsBindable(notifiesViaBindable);
1042 return QQmlError();
1043}
1044
1045template <typename ObjectContainer>
1046inline QQmlError QQmlPropertyCacheAliasCreator<ObjectContainer>::appendAliasToPropertyCache(
1047 const CompiledObject &component, const QV4::CompiledData::Alias &alias, int objectIndex,
1048 int aliasIndex, int encodedMetaPropertyIndex)
1049{
1050 const CompiledObject &object = *objectContainer->objectAt(objectIndex);
1051
1052 Q_ASSERT(object.aliasCount() > aliasIndex);
1053 QMetaType type;
1054 QTypeRevision version = QTypeRevision::zero();
1055 QQmlPropertyData::Flags propertyFlags;
1056 QQmlError error = propertyDataForAlias(
1057 component, alias, type: &type, version: &version, propertyFlags: &propertyFlags, targetPropertyIndex: encodedMetaPropertyIndex);
1058 if (error.isValid())
1059 return error;
1060
1061 const QString propertyName = objectContainer->stringAt(alias.nameIndex());
1062
1063 const QQmlPropertyCache::Ptr propertyCache = propertyCaches->ownAt(index: objectIndex);
1064 Q_ASSERT(propertyCache);
1065
1066 const int effectiveSignalIndex
1067 = propertyCache->signalHandlerIndexCacheStart + propertyCache->propertyIndexCache.size();
1068 const int effectivePropertyIndex
1069 = propertyCache->propertyIndexCacheStart + propertyCache->propertyIndexCache.size();
1070
1071 if (object.hasAliasAsDefaultProperty() && aliasIndex == object.indexOfDefaultPropertyOrAlias)
1072 propertyCache->_defaultPropertyName = propertyName;
1073
1074 propertyCache->appendAlias(propertyName, flags: propertyFlags, coreIndex: effectivePropertyIndex,
1075 propType: type, version, notifyIndex: effectiveSignalIndex, encodedTargetIndex: encodedMetaPropertyIndex);
1076 return QQmlError();
1077}
1078
1079QT_END_NAMESPACE
1080
1081#endif // QQMLPROPERTYCACHECREATOR_P_H
1082

source code of qtdeclarative/src/qml/qml/qqmlpropertycachecreator_p.h