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

Provided by KDAB

Privacy Policy
Learn to use CMake with our Intro Training
Find out more

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