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

Provided by KDAB

Privacy Policy
Start learning QML with our Intro Training
Find out more

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