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