1 | /**************************************************************************** |
2 | ** |
3 | ** Copyright (C) 2016 The Qt Company Ltd. |
4 | ** Contact: https://www.qt.io/licensing/ |
5 | ** |
6 | ** This file is part of the tools applications of the Qt Toolkit. |
7 | ** |
8 | ** $QT_BEGIN_LICENSE:LGPL$ |
9 | ** Commercial License Usage |
10 | ** Licensees holding valid commercial Qt licenses may use this file in |
11 | ** accordance with the commercial license agreement provided with the |
12 | ** Software or, alternatively, in accordance with the terms contained in |
13 | ** a written agreement between you and The Qt Company. For licensing terms |
14 | ** and conditions see https://www.qt.io/terms-conditions. For further |
15 | ** information use the contact form at https://www.qt.io/contact-us. |
16 | ** |
17 | ** GNU Lesser General Public License Usage |
18 | ** Alternatively, this file may be used under the terms of the GNU Lesser |
19 | ** General Public License version 3 as published by the Free Software |
20 | ** Foundation and appearing in the file LICENSE.LGPL3 included in the |
21 | ** packaging of this file. Please review the following information to |
22 | ** ensure the GNU Lesser General Public License version 3 requirements |
23 | ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. |
24 | ** |
25 | ** GNU General Public License Usage |
26 | ** Alternatively, this file may be used under the terms of the GNU |
27 | ** General Public License version 2.0 or (at your option) the GNU General |
28 | ** Public license version 3 or any later version approved by the KDE Free |
29 | ** Qt Foundation. The licenses are as published by the Free Software |
30 | ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 |
31 | ** included in the packaging of this file. Please review the following |
32 | ** information to ensure the GNU General Public License requirements will |
33 | ** be met: https://www.gnu.org/licenses/gpl-2.0.html and |
34 | ** https://www.gnu.org/licenses/gpl-3.0.html. |
35 | ** |
36 | ** $QT_END_LICENSE$ |
37 | ** |
38 | ****************************************************************************/ |
39 | #ifndef QQMLPROPERTYCACHECREATOR_P_H |
40 | #define QQMLPROPERTYCACHECREATOR_P_H |
41 | |
42 | // |
43 | // W A R N I N G |
44 | // ------------- |
45 | // |
46 | // This file is not part of the Qt API. It exists purely as an |
47 | // implementation detail. This header file may change from version to |
48 | // version without notice, or even be removed. |
49 | // |
50 | // We mean it. |
51 | // |
52 | |
53 | #include <private/qqmlvaluetype_p.h> |
54 | #include <private/qqmlengine_p.h> |
55 | #include <private/qqmlmetaobject_p.h> |
56 | #include <private/qqmlpropertyresolver_p.h> |
57 | #include <private/qqmltypedata_p.h> |
58 | |
59 | QT_BEGIN_NAMESPACE |
60 | |
61 | inline QQmlJS::DiagnosticMessage qQmlCompileError(const QV4::CompiledData::Location &location, |
62 | const QString &description) |
63 | { |
64 | QQmlJS::DiagnosticMessage error; |
65 | error.line = location.line; |
66 | error.column = location.column; |
67 | error.message = description; |
68 | return error; |
69 | } |
70 | |
71 | struct QQmlBindingInstantiationContext { |
72 | QQmlBindingInstantiationContext() {} |
73 | QQmlBindingInstantiationContext(int referencingObjectIndex, |
74 | const QV4::CompiledData::Binding *instantiatingBinding, |
75 | const QString &instantiatingPropertyName, |
76 | QQmlPropertyCache *referencingObjectPropertyCache); |
77 | |
78 | bool resolveInstantiatingProperty(); |
79 | QQmlRefPointer<QQmlPropertyCache> instantiatingPropertyCache(QQmlEnginePrivate *enginePrivate) const; |
80 | |
81 | int referencingObjectIndex = -1; |
82 | const QV4::CompiledData::Binding *instantiatingBinding = nullptr; |
83 | QString instantiatingPropertyName; |
84 | QQmlRefPointer<QQmlPropertyCache> referencingObjectPropertyCache; |
85 | QQmlPropertyData *instantiatingProperty = nullptr; |
86 | }; |
87 | |
88 | struct QQmlPendingGroupPropertyBindings : public QVector<QQmlBindingInstantiationContext> |
89 | { |
90 | void resolveMissingPropertyCaches(QQmlEnginePrivate *enginePrivate, QQmlPropertyCacheVector *propertyCaches) const; |
91 | }; |
92 | |
93 | struct QQmlPropertyCacheCreatorBase |
94 | { |
95 | Q_DECLARE_TR_FUNCTIONS(QQmlPropertyCacheCreatorBase) |
96 | public: |
97 | static QAtomicInt classIndexCounter; |
98 | |
99 | static int metaTypeForPropertyType(QV4::CompiledData::BuiltinType type); |
100 | }; |
101 | |
102 | template <typename ObjectContainer> |
103 | class QQmlPropertyCacheCreator : public QQmlPropertyCacheCreatorBase |
104 | { |
105 | public: |
106 | typedef typename ObjectContainer::CompiledObject CompiledObject; |
107 | |
108 | QQmlPropertyCacheCreator(QQmlPropertyCacheVector *propertyCaches, |
109 | QQmlPendingGroupPropertyBindings *pendingGroupPropertyBindings, |
110 | QQmlEnginePrivate *enginePrivate, |
111 | const ObjectContainer *objectContainer, const QQmlImports *imports); |
112 | |
113 | QQmlJS::DiagnosticMessage buildMetaObjects(); |
114 | |
115 | protected: |
116 | QQmlJS::DiagnosticMessage buildMetaObjectRecursively(int objectIndex, const QQmlBindingInstantiationContext &context); |
117 | QQmlRefPointer<QQmlPropertyCache> propertyCacheForObject(const CompiledObject *obj, const QQmlBindingInstantiationContext &context, QQmlJS::DiagnosticMessage *error) const; |
118 | QQmlJS::DiagnosticMessage createMetaObject(int objectIndex, const CompiledObject *obj, const QQmlRefPointer<QQmlPropertyCache> &baseTypeCache); |
119 | |
120 | int metaTypeForParameter(const QV4::CompiledData::ParameterType ¶m, QString *customTypeName = nullptr); |
121 | |
122 | QString stringAt(int index) const { return objectContainer->stringAt(index); } |
123 | |
124 | QQmlEnginePrivate * const enginePrivate; |
125 | const ObjectContainer * const objectContainer; |
126 | const QQmlImports * const imports; |
127 | QQmlPropertyCacheVector *propertyCaches; |
128 | QQmlPendingGroupPropertyBindings *pendingGroupPropertyBindings; |
129 | }; |
130 | |
131 | template <typename ObjectContainer> |
132 | inline QQmlPropertyCacheCreator<ObjectContainer>::QQmlPropertyCacheCreator(QQmlPropertyCacheVector *propertyCaches, |
133 | QQmlPendingGroupPropertyBindings *pendingGroupPropertyBindings, |
134 | QQmlEnginePrivate *enginePrivate, |
135 | const ObjectContainer *objectContainer, const QQmlImports *imports) |
136 | : enginePrivate(enginePrivate) |
137 | , objectContainer(objectContainer) |
138 | , imports(imports) |
139 | , propertyCaches(propertyCaches) |
140 | , pendingGroupPropertyBindings(pendingGroupPropertyBindings) |
141 | { |
142 | propertyCaches->resize(objectContainer->objectCount()); |
143 | } |
144 | |
145 | template <typename ObjectContainer> |
146 | inline QQmlJS::DiagnosticMessage QQmlPropertyCacheCreator<ObjectContainer>::buildMetaObjects() |
147 | { |
148 | QQmlBindingInstantiationContext context; |
149 | return buildMetaObjectRecursively(/*root object*/0, context); |
150 | } |
151 | |
152 | template <typename ObjectContainer> |
153 | inline QQmlJS::DiagnosticMessage QQmlPropertyCacheCreator<ObjectContainer>::buildMetaObjectRecursively(int objectIndex, const QQmlBindingInstantiationContext &context) |
154 | { |
155 | const CompiledObject *obj = objectContainer->objectAt(objectIndex); |
156 | |
157 | bool needVMEMetaObject = obj->propertyCount() != 0 || obj->aliasCount() != 0 || obj->signalCount() != 0 || obj->functionCount() != 0 || obj->enumCount() != 0; |
158 | if (!needVMEMetaObject) { |
159 | auto binding = obj->bindingsBegin(); |
160 | auto end = obj->bindingsEnd(); |
161 | for ( ; binding != end; ++binding) { |
162 | if (binding->type == QV4::CompiledData::Binding::Type_Object && (binding->flags & QV4::CompiledData::Binding::IsOnAssignment)) { |
163 | // If the on assignment is inside a group property, we need to distinguish between QObject based |
164 | // group properties and value type group properties. For the former the base type is derived from |
165 | // the property that references us, for the latter we only need a meta-object on the referencing object |
166 | // because interceptors can't go to the shared value type instances. |
167 | if (context.instantiatingProperty && QQmlValueTypeFactory::isValueType(context.instantiatingProperty->propType())) { |
168 | if (!propertyCaches->needsVMEMetaObject(context.referencingObjectIndex)) { |
169 | const CompiledObject *obj = objectContainer->objectAt(context.referencingObjectIndex); |
170 | auto *typeRef = objectContainer->resolvedType(obj->inheritedTypeNameIndex); |
171 | Q_ASSERT(typeRef); |
172 | QQmlRefPointer<QQmlPropertyCache> baseTypeCache = typeRef->createPropertyCache(QQmlEnginePrivate::get(enginePrivate)); |
173 | QQmlJS::DiagnosticMessage error = createMetaObject(context.referencingObjectIndex, obj, baseTypeCache); |
174 | if (error.isValid()) |
175 | return error; |
176 | } |
177 | } else { |
178 | // On assignments are implemented using value interceptors, which require a VME meta object. |
179 | needVMEMetaObject = true; |
180 | } |
181 | break; |
182 | } |
183 | } |
184 | } |
185 | |
186 | QQmlRefPointer<QQmlPropertyCache> baseTypeCache; |
187 | { |
188 | QQmlJS::DiagnosticMessage error; |
189 | baseTypeCache = propertyCacheForObject(obj, context, &error); |
190 | if (error.isValid()) |
191 | return error; |
192 | } |
193 | |
194 | if (baseTypeCache) { |
195 | if (needVMEMetaObject) { |
196 | QQmlJS::DiagnosticMessage error = createMetaObject(objectIndex, obj, baseTypeCache); |
197 | if (error.isValid()) |
198 | return error; |
199 | } else { |
200 | propertyCaches->set(objectIndex, baseTypeCache); |
201 | } |
202 | } |
203 | |
204 | if (QQmlPropertyCache *thisCache = propertyCaches->at(objectIndex)) { |
205 | auto binding = obj->bindingsBegin(); |
206 | auto end = obj->bindingsEnd(); |
207 | for ( ; binding != end; ++binding) |
208 | if (binding->type >= QV4::CompiledData::Binding::Type_Object) { |
209 | QQmlBindingInstantiationContext context(objectIndex, &(*binding), stringAt(binding->propertyNameIndex), thisCache); |
210 | |
211 | // Binding to group property where we failed to look up the type of the |
212 | // property? Possibly a group property that is an alias that's not resolved yet. |
213 | // Let's attempt to resolve it after we're done with the aliases and fill in the |
214 | // propertyCaches entry then. |
215 | if (!context.resolveInstantiatingProperty()) |
216 | pendingGroupPropertyBindings->append(context); |
217 | |
218 | QQmlJS::DiagnosticMessage error = buildMetaObjectRecursively(binding->value.objectIndex, context); |
219 | if (error.isValid()) |
220 | return error; |
221 | } |
222 | } |
223 | |
224 | QQmlJS::DiagnosticMessage noError; |
225 | return noError; |
226 | } |
227 | |
228 | template <typename ObjectContainer> |
229 | inline QQmlRefPointer<QQmlPropertyCache> QQmlPropertyCacheCreator<ObjectContainer>::propertyCacheForObject(const CompiledObject *obj, const QQmlBindingInstantiationContext &context, QQmlJS::DiagnosticMessage *error) const |
230 | { |
231 | if (context.instantiatingProperty) { |
232 | return context.instantiatingPropertyCache(enginePrivate); |
233 | } else if (obj->inheritedTypeNameIndex != 0) { |
234 | auto *typeRef = objectContainer->resolvedType(obj->inheritedTypeNameIndex); |
235 | Q_ASSERT(typeRef); |
236 | |
237 | if (typeRef->isFullyDynamicType) { |
238 | if (obj->propertyCount() > 0 || obj->aliasCount() > 0) { |
239 | *error = qQmlCompileError(obj->location, QQmlPropertyCacheCreatorBase::tr("Fully dynamic types cannot declare new properties." )); |
240 | return nullptr; |
241 | } |
242 | if (obj->signalCount() > 0) { |
243 | *error = qQmlCompileError(obj->location, QQmlPropertyCacheCreatorBase::tr("Fully dynamic types cannot declare new signals." )); |
244 | return nullptr; |
245 | } |
246 | if (obj->functionCount() > 0) { |
247 | *error = qQmlCompileError(obj->location, QQmlPropertyCacheCreatorBase::tr("Fully Dynamic types cannot declare new functions." )); |
248 | return nullptr; |
249 | } |
250 | } |
251 | |
252 | return typeRef->createPropertyCache(QQmlEnginePrivate::get(enginePrivate)); |
253 | } else if (context.instantiatingBinding && context.instantiatingBinding->isAttachedProperty()) { |
254 | auto *typeRef = objectContainer->resolvedType( |
255 | context.instantiatingBinding->propertyNameIndex); |
256 | Q_ASSERT(typeRef); |
257 | QQmlType qmltype = typeRef->type; |
258 | if (!qmltype.isValid()) { |
259 | QString propertyName = stringAt(context.instantiatingBinding->propertyNameIndex); |
260 | if (imports->resolveType(propertyName, &qmltype, nullptr, nullptr, nullptr)) { |
261 | if (qmltype.isComposite()) { |
262 | QQmlRefPointer<QQmlTypeData> tdata = enginePrivate->typeLoader.getType(qmltype.sourceUrl()); |
263 | Q_ASSERT(tdata); |
264 | Q_ASSERT(tdata->isComplete()); |
265 | |
266 | auto compilationUnit = tdata->compilationUnit(); |
267 | qmltype = QQmlMetaType::qmlType(compilationUnit->metaTypeId); |
268 | } |
269 | } |
270 | } |
271 | |
272 | const QMetaObject *attachedMo = qmltype.attachedPropertiesType(enginePrivate); |
273 | if (!attachedMo) { |
274 | *error = qQmlCompileError(context.instantiatingBinding->location, QQmlPropertyCacheCreatorBase::tr("Non-existent attached object" )); |
275 | return nullptr; |
276 | } |
277 | return enginePrivate->cache(attachedMo); |
278 | } |
279 | return nullptr; |
280 | } |
281 | |
282 | template <typename ObjectContainer> |
283 | inline QQmlJS::DiagnosticMessage QQmlPropertyCacheCreator<ObjectContainer>::createMetaObject(int objectIndex, const CompiledObject *obj, const QQmlRefPointer<QQmlPropertyCache> &baseTypeCache) |
284 | { |
285 | QQmlRefPointer<QQmlPropertyCache> cache; |
286 | cache.adopt(baseTypeCache->copyAndReserve(obj->propertyCount() + obj->aliasCount(), |
287 | obj->functionCount() + obj->propertyCount() + obj->aliasCount() + obj->signalCount(), |
288 | obj->signalCount() + obj->propertyCount() + obj->aliasCount(), obj->enumCount())); |
289 | |
290 | propertyCaches->set(objectIndex, cache); |
291 | propertyCaches->setNeedsVMEMetaObject(objectIndex); |
292 | |
293 | QByteArray newClassName; |
294 | |
295 | if (objectIndex == /*root object*/0) { |
296 | const QString path = objectContainer->url().path(); |
297 | int lastSlash = path.lastIndexOf(QLatin1Char('/')); |
298 | if (lastSlash > -1) { |
299 | const QStringRef nameBase = path.midRef(lastSlash + 1, path.length() - lastSlash - 5); |
300 | if (!nameBase.isEmpty() && nameBase.at(0).isUpper()) |
301 | newClassName = nameBase.toUtf8() + "_QMLTYPE_" + |
302 | QByteArray::number(classIndexCounter.fetchAndAddRelaxed(1)); |
303 | } |
304 | } |
305 | if (newClassName.isEmpty()) { |
306 | newClassName = QQmlMetaObject(baseTypeCache.data()).className(); |
307 | newClassName.append("_QML_" ); |
308 | newClassName.append(QByteArray::number(classIndexCounter.fetchAndAddRelaxed(1))); |
309 | } |
310 | |
311 | cache->_dynamicClassName = newClassName; |
312 | |
313 | int varPropCount = 0; |
314 | |
315 | QQmlPropertyResolver resolver(baseTypeCache); |
316 | |
317 | auto p = obj->propertiesBegin(); |
318 | auto pend = obj->propertiesEnd(); |
319 | for ( ; p != pend; ++p) { |
320 | if (p->builtinType() == QV4::CompiledData::BuiltinType::Var) |
321 | varPropCount++; |
322 | |
323 | bool notInRevision = false; |
324 | QQmlPropertyData *d = resolver.property(stringAt(p->nameIndex), ¬InRevision); |
325 | if (d && d->isFinal()) |
326 | return qQmlCompileError(p->location, QQmlPropertyCacheCreatorBase::tr("Cannot override FINAL property" )); |
327 | } |
328 | |
329 | auto a = obj->aliasesBegin(); |
330 | auto aend = obj->aliasesEnd(); |
331 | for ( ; a != aend; ++a) { |
332 | bool notInRevision = false; |
333 | QQmlPropertyData *d = resolver.property(stringAt(a->nameIndex), ¬InRevision); |
334 | if (d && d->isFinal()) |
335 | return qQmlCompileError(a->location, QQmlPropertyCacheCreatorBase::tr("Cannot override FINAL property" )); |
336 | } |
337 | |
338 | int effectivePropertyIndex = cache->propertyIndexCacheStart; |
339 | int effectiveMethodIndex = cache->methodIndexCacheStart; |
340 | |
341 | // For property change signal override detection. |
342 | // We prepopulate a set of signal names which already exist in the object, |
343 | // and throw an error if there is a signal/method defined as an override. |
344 | QSet<QString> seenSignals; |
345 | seenSignals << QStringLiteral("destroyed" ) << QStringLiteral("parentChanged" ) << QStringLiteral("objectNameChanged" ); |
346 | QQmlPropertyCache *parentCache = cache.data(); |
347 | while ((parentCache = parentCache->parent())) { |
348 | if (int pSigCount = parentCache->signalCount()) { |
349 | int pSigOffset = parentCache->signalOffset(); |
350 | for (int i = pSigOffset; i < pSigCount; ++i) { |
351 | QQmlPropertyData *currPSig = parentCache->signal(i); |
352 | // XXX TODO: find a better way to get signal name from the property data :-/ |
353 | for (QQmlPropertyCache::StringCache::ConstIterator iter = parentCache->stringCache.begin(); |
354 | iter != parentCache->stringCache.end(); ++iter) { |
355 | if (currPSig == (*iter).second) { |
356 | seenSignals.insert(iter.key()); |
357 | break; |
358 | } |
359 | } |
360 | } |
361 | } |
362 | } |
363 | |
364 | // Set up notify signals for properties - first normal, then alias |
365 | p = obj->propertiesBegin(); |
366 | pend = obj->propertiesEnd(); |
367 | for ( ; p != pend; ++p) { |
368 | auto flags = QQmlPropertyData::defaultSignalFlags(); |
369 | |
370 | QString changedSigName = stringAt(p->nameIndex) + QLatin1String("Changed" ); |
371 | seenSignals.insert(changedSigName); |
372 | |
373 | cache->appendSignal(changedSigName, flags, effectiveMethodIndex++); |
374 | } |
375 | |
376 | a = obj->aliasesBegin(); |
377 | aend = obj->aliasesEnd(); |
378 | for ( ; a != aend; ++a) { |
379 | auto flags = QQmlPropertyData::defaultSignalFlags(); |
380 | |
381 | QString changedSigName = stringAt(a->nameIndex) + QLatin1String("Changed" ); |
382 | seenSignals.insert(changedSigName); |
383 | |
384 | cache->appendSignal(changedSigName, flags, effectiveMethodIndex++); |
385 | } |
386 | |
387 | auto e = obj->enumsBegin(); |
388 | auto eend = obj->enumsEnd(); |
389 | for ( ; e != eend; ++e) { |
390 | const int enumValueCount = e->enumValueCount(); |
391 | QVector<QQmlEnumValue> values; |
392 | values.reserve(enumValueCount); |
393 | |
394 | auto enumValue = e->enumValuesBegin(); |
395 | auto end = e->enumValuesEnd(); |
396 | for ( ; enumValue != end; ++enumValue) |
397 | values.append(QQmlEnumValue(stringAt(enumValue->nameIndex), enumValue->value)); |
398 | |
399 | cache->appendEnum(stringAt(e->nameIndex), values); |
400 | } |
401 | |
402 | // Dynamic signals |
403 | auto s = obj->signalsBegin(); |
404 | auto send = obj->signalsEnd(); |
405 | for ( ; s != send; ++s) { |
406 | const int paramCount = s->parameterCount(); |
407 | |
408 | QList<QByteArray> names; |
409 | names.reserve(paramCount); |
410 | QVarLengthArray<int, 10> paramTypes(paramCount?(paramCount + 1):0); |
411 | |
412 | if (paramCount) { |
413 | paramTypes[0] = paramCount; |
414 | |
415 | int i = 0; |
416 | auto param = s->parametersBegin(); |
417 | auto end = s->parametersEnd(); |
418 | for ( ; param != end; ++param, ++i) { |
419 | names.append(stringAt(param->nameIndex).toUtf8()); |
420 | |
421 | QString customTypeName; |
422 | auto type = metaTypeForParameter(param->type, &customTypeName); |
423 | if (type == QMetaType::UnknownType) |
424 | return qQmlCompileError(s->location, QQmlPropertyCacheCreatorBase::tr("Invalid signal parameter type: %1" ).arg(customTypeName)); |
425 | |
426 | paramTypes[i + 1] = type; |
427 | } |
428 | } |
429 | |
430 | auto flags = QQmlPropertyData::defaultSignalFlags(); |
431 | if (paramCount) |
432 | flags.hasArguments = true; |
433 | |
434 | QString signalName = stringAt(s->nameIndex); |
435 | if (seenSignals.contains(signalName)) |
436 | return qQmlCompileError(s->location, QQmlPropertyCacheCreatorBase::tr("Duplicate signal name: invalid override of property change signal or superclass signal" )); |
437 | seenSignals.insert(signalName); |
438 | |
439 | cache->appendSignal(signalName, flags, effectiveMethodIndex++, |
440 | paramCount?paramTypes.constData():nullptr, names); |
441 | } |
442 | |
443 | |
444 | // Dynamic slots |
445 | auto function = objectContainer->objectFunctionsBegin(obj); |
446 | auto fend = objectContainer->objectFunctionsEnd(obj); |
447 | for ( ; function != fend; ++function) { |
448 | auto flags = QQmlPropertyData::defaultSlotFlags(); |
449 | |
450 | const QString slotName = stringAt(function->nameIndex); |
451 | if (seenSignals.contains(slotName)) |
452 | return qQmlCompileError(function->location, QQmlPropertyCacheCreatorBase::tr("Duplicate method name: invalid override of property change signal or superclass signal" )); |
453 | // Note: we don't append slotName to the seenSignals list, since we don't |
454 | // protect against overriding change signals or methods with properties. |
455 | |
456 | QList<QByteArray> parameterNames; |
457 | QVector<int> parameterTypes; |
458 | auto formal = function->formalsBegin(); |
459 | auto end = function->formalsEnd(); |
460 | for ( ; formal != end; ++formal) { |
461 | flags.hasArguments = true; |
462 | parameterNames << stringAt(formal->nameIndex).toUtf8(); |
463 | int type = metaTypeForParameter(formal->type); |
464 | if (type == QMetaType::UnknownType) |
465 | type = QMetaType::QVariant; |
466 | parameterTypes << type; |
467 | } |
468 | |
469 | int returnType = metaTypeForParameter(function->returnType); |
470 | if (returnType == QMetaType::UnknownType) |
471 | returnType = QMetaType::QVariant; |
472 | |
473 | cache->appendMethod(slotName, flags, effectiveMethodIndex++, returnType, parameterNames, parameterTypes); |
474 | } |
475 | |
476 | |
477 | // Dynamic properties |
478 | int effectiveSignalIndex = cache->signalHandlerIndexCacheStart; |
479 | int propertyIdx = 0; |
480 | p = obj->propertiesBegin(); |
481 | pend = obj->propertiesEnd(); |
482 | for ( ; p != pend; ++p, ++propertyIdx) { |
483 | int propertyType = 0; |
484 | int propertTypeMinorVersion = 0; |
485 | QQmlPropertyData::Flags propertyFlags; |
486 | |
487 | const QV4::CompiledData::BuiltinType type = p->builtinType(); |
488 | |
489 | if (type == QV4::CompiledData::BuiltinType::Var) |
490 | propertyFlags.type = QQmlPropertyData::Flags::VarPropertyType; |
491 | |
492 | |
493 | if (type != QV4::CompiledData::BuiltinType::InvalidBuiltin) { |
494 | propertyType = metaTypeForPropertyType(type); |
495 | |
496 | if (type == QV4::CompiledData::BuiltinType::Variant) |
497 | propertyFlags.type = QQmlPropertyData::Flags::QVariantType; |
498 | } else { |
499 | Q_ASSERT(!p->isBuiltinType); |
500 | |
501 | QQmlType qmltype; |
502 | if (!imports->resolveType(stringAt(p->builtinTypeOrTypeNameIndex), &qmltype, nullptr, nullptr, nullptr)) { |
503 | return qQmlCompileError(p->location, QQmlPropertyCacheCreatorBase::tr("Invalid property type" )); |
504 | } |
505 | |
506 | Q_ASSERT(qmltype.isValid()); |
507 | if (qmltype.isComposite()) { |
508 | QQmlRefPointer<QQmlTypeData> tdata = enginePrivate->typeLoader.getType(qmltype.sourceUrl()); |
509 | Q_ASSERT(tdata); |
510 | Q_ASSERT(tdata->isComplete()); |
511 | |
512 | auto compilationUnit = tdata->compilationUnit(); |
513 | |
514 | if (p->isList) { |
515 | propertyType = compilationUnit->listMetaTypeId; |
516 | } else { |
517 | propertyType = compilationUnit->metaTypeId; |
518 | } |
519 | } else { |
520 | if (p->isList) { |
521 | propertyType = qmltype.qListTypeId(); |
522 | } else { |
523 | propertyType = qmltype.typeId(); |
524 | propertTypeMinorVersion = qmltype.minorVersion(); |
525 | } |
526 | } |
527 | |
528 | if (p->isList) |
529 | propertyFlags.type = QQmlPropertyData::Flags::QListType; |
530 | else |
531 | propertyFlags.type = QQmlPropertyData::Flags::QObjectDerivedType; |
532 | } |
533 | |
534 | if (!p->isReadOnly && !p->isList) |
535 | propertyFlags.isWritable = true; |
536 | |
537 | |
538 | QString propertyName = stringAt(p->nameIndex); |
539 | if (!obj->defaultPropertyIsAlias && propertyIdx == obj->indexOfDefaultPropertyOrAlias) |
540 | cache->_defaultPropertyName = propertyName; |
541 | cache->appendProperty(propertyName, propertyFlags, effectivePropertyIndex++, |
542 | propertyType, propertTypeMinorVersion, effectiveSignalIndex); |
543 | |
544 | effectiveSignalIndex++; |
545 | } |
546 | |
547 | QQmlJS::DiagnosticMessage noError; |
548 | return noError; |
549 | } |
550 | |
551 | template <typename ObjectContainer> |
552 | inline int QQmlPropertyCacheCreator<ObjectContainer>::metaTypeForParameter(const QV4::CompiledData::ParameterType ¶m, |
553 | QString *customTypeName) |
554 | { |
555 | if (param.indexIsBuiltinType) { |
556 | // built-in type |
557 | return metaTypeForPropertyType(static_cast<QV4::CompiledData::BuiltinType>(int(param.typeNameIndexOrBuiltinType))); |
558 | } |
559 | |
560 | // lazily resolved type |
561 | const QString typeName = stringAt(param.typeNameIndexOrBuiltinType); |
562 | if (customTypeName) |
563 | *customTypeName = typeName; |
564 | QQmlType qmltype; |
565 | if (!imports->resolveType(typeName, &qmltype, nullptr, nullptr, nullptr)) |
566 | return QMetaType::UnknownType; |
567 | |
568 | if (!qmltype.isComposite()) |
569 | return qmltype.typeId(); |
570 | |
571 | QQmlRefPointer<QQmlTypeData> tdata = enginePrivate->typeLoader.getType(qmltype.sourceUrl()); |
572 | Q_ASSERT(tdata); |
573 | Q_ASSERT(tdata->isComplete()); |
574 | |
575 | auto compilationUnit = tdata->compilationUnit(); |
576 | |
577 | return compilationUnit->metaTypeId; |
578 | } |
579 | |
580 | template <typename ObjectContainer> |
581 | class QQmlPropertyCacheAliasCreator |
582 | { |
583 | public: |
584 | typedef typename ObjectContainer::CompiledObject CompiledObject; |
585 | |
586 | QQmlPropertyCacheAliasCreator(QQmlPropertyCacheVector *propertyCaches, const ObjectContainer *objectContainer); |
587 | |
588 | void appendAliasPropertiesToMetaObjects(); |
589 | |
590 | QQmlJS::DiagnosticMessage appendAliasesToPropertyCache(const CompiledObject &component, int objectIndex); |
591 | |
592 | private: |
593 | void appendAliasPropertiesInMetaObjectsWithinComponent(const CompiledObject &component, int firstObjectIndex); |
594 | QQmlJS::DiagnosticMessage propertyDataForAlias(const CompiledObject &component, const QV4::CompiledData::Alias &alias, int *type, int *rev, QQmlPropertyData::Flags *propertyFlags); |
595 | |
596 | void collectObjectsWithAliasesRecursively(int objectIndex, QVector<int> *objectsWithAliases) const; |
597 | |
598 | int objectForId(const CompiledObject &component, int id) const; |
599 | |
600 | QQmlPropertyCacheVector *propertyCaches; |
601 | const ObjectContainer *objectContainer; |
602 | }; |
603 | |
604 | template <typename ObjectContainer> |
605 | inline QQmlPropertyCacheAliasCreator<ObjectContainer>::QQmlPropertyCacheAliasCreator(QQmlPropertyCacheVector *propertyCaches, const ObjectContainer *objectContainer) |
606 | : propertyCaches(propertyCaches) |
607 | , objectContainer(objectContainer) |
608 | { |
609 | |
610 | } |
611 | |
612 | template <typename ObjectContainer> |
613 | inline void QQmlPropertyCacheAliasCreator<ObjectContainer>::appendAliasPropertiesToMetaObjects() |
614 | { |
615 | // skip the root object (index 0) as that one does not have a first object index originating |
616 | // from a binding. |
617 | for (int i = 1; i < objectContainer->objectCount(); ++i) { |
618 | const CompiledObject &component = *objectContainer->objectAt(i); |
619 | if (!(component.flags & QV4::CompiledData::Object::IsComponent)) |
620 | continue; |
621 | |
622 | const auto rootBinding = component.bindingsBegin(); |
623 | appendAliasPropertiesInMetaObjectsWithinComponent(component, rootBinding->value.objectIndex); |
624 | } |
625 | |
626 | const int rootObjectIndex = 0; |
627 | appendAliasPropertiesInMetaObjectsWithinComponent(*objectContainer->objectAt(rootObjectIndex), rootObjectIndex); |
628 | } |
629 | |
630 | template <typename ObjectContainer> |
631 | inline void QQmlPropertyCacheAliasCreator<ObjectContainer>::appendAliasPropertiesInMetaObjectsWithinComponent(const CompiledObject &component, int firstObjectIndex) |
632 | { |
633 | QVector<int> objectsWithAliases; |
634 | collectObjectsWithAliasesRecursively(firstObjectIndex, &objectsWithAliases); |
635 | if (objectsWithAliases.isEmpty()) |
636 | return; |
637 | |
638 | const auto allAliasTargetsExist = [this, &component](const CompiledObject &object) { |
639 | auto alias = object.aliasesBegin(); |
640 | auto end = object.aliasesEnd(); |
641 | for ( ; alias != end; ++alias) { |
642 | Q_ASSERT(alias->flags & QV4::CompiledData::Alias::Resolved); |
643 | |
644 | const int targetObjectIndex = objectForId(component, alias->targetObjectId); |
645 | Q_ASSERT(targetObjectIndex >= 0); |
646 | |
647 | if (alias->aliasToLocalAlias) |
648 | continue; |
649 | |
650 | if (alias->encodedMetaPropertyIndex == -1) |
651 | continue; |
652 | |
653 | const QQmlPropertyCache *targetCache = propertyCaches->at(targetObjectIndex); |
654 | Q_ASSERT(targetCache); |
655 | |
656 | int coreIndex = QQmlPropertyIndex::fromEncoded(alias->encodedMetaPropertyIndex).coreIndex(); |
657 | QQmlPropertyData *targetProperty = targetCache->property(coreIndex); |
658 | if (!targetProperty) |
659 | return false; |
660 | } |
661 | return true; |
662 | }; |
663 | |
664 | do { |
665 | QVector<int> pendingObjects; |
666 | |
667 | for (int objectIndex: qAsConst(objectsWithAliases)) { |
668 | const CompiledObject &object = *objectContainer->objectAt(objectIndex); |
669 | |
670 | if (allAliasTargetsExist(object)) { |
671 | appendAliasesToPropertyCache(component, objectIndex); |
672 | } else { |
673 | pendingObjects.append(objectIndex); |
674 | } |
675 | |
676 | } |
677 | qSwap(objectsWithAliases, pendingObjects); |
678 | } while (!objectsWithAliases.isEmpty()); |
679 | } |
680 | |
681 | template <typename ObjectContainer> |
682 | inline void QQmlPropertyCacheAliasCreator<ObjectContainer>::collectObjectsWithAliasesRecursively(int objectIndex, QVector<int> *objectsWithAliases) const |
683 | { |
684 | const CompiledObject &object = *objectContainer->objectAt(objectIndex); |
685 | if (object.aliasCount() > 0) |
686 | objectsWithAliases->append(objectIndex); |
687 | |
688 | // Stop at Component boundary |
689 | if (object.flags & QV4::CompiledData::Object::IsComponent && objectIndex != /*root object*/0) |
690 | return; |
691 | |
692 | auto binding = object.bindingsBegin(); |
693 | auto end = object.bindingsEnd(); |
694 | for (; binding != end; ++binding) { |
695 | if (binding->type != QV4::CompiledData::Binding::Type_Object |
696 | && binding->type != QV4::CompiledData::Binding::Type_AttachedProperty |
697 | && binding->type != QV4::CompiledData::Binding::Type_GroupProperty) |
698 | continue; |
699 | |
700 | collectObjectsWithAliasesRecursively(binding->value.objectIndex, objectsWithAliases); |
701 | } |
702 | } |
703 | |
704 | template <typename ObjectContainer> |
705 | inline QQmlJS::DiagnosticMessage QQmlPropertyCacheAliasCreator<ObjectContainer>::propertyDataForAlias( |
706 | const CompiledObject &component, const QV4::CompiledData::Alias &alias, int *type, int *minorVersion, |
707 | QQmlPropertyData::Flags *propertyFlags) |
708 | { |
709 | *type = 0; |
710 | bool writable = false; |
711 | bool resettable = false; |
712 | |
713 | propertyFlags->isAlias = true; |
714 | |
715 | if (alias.aliasToLocalAlias) { |
716 | const QV4::CompiledData::Alias *lastAlias = &alias; |
717 | QVarLengthArray<const QV4::CompiledData::Alias *, 4> seenAliases({lastAlias}); |
718 | |
719 | do { |
720 | const int targetObjectIndex = objectForId(component, lastAlias->targetObjectId); |
721 | Q_ASSERT(targetObjectIndex >= 0); |
722 | const CompiledObject *targetObject = objectContainer->objectAt(targetObjectIndex); |
723 | Q_ASSERT(targetObject); |
724 | |
725 | auto nextAlias = targetObject->aliasesBegin(); |
726 | for (uint i = 0; i < lastAlias->localAliasIndex; ++i) |
727 | ++nextAlias; |
728 | |
729 | const QV4::CompiledData::Alias *targetAlias = &(*nextAlias); |
730 | if (seenAliases.contains(targetAlias)) { |
731 | return qQmlCompileError(targetAlias->location, |
732 | QQmlPropertyCacheCreatorBase::tr("Cyclic alias" )); |
733 | } |
734 | |
735 | seenAliases.append(targetAlias); |
736 | lastAlias = targetAlias; |
737 | } while (lastAlias->aliasToLocalAlias); |
738 | |
739 | return propertyDataForAlias(component, *lastAlias, type, minorVersion, propertyFlags); |
740 | } |
741 | |
742 | const int targetObjectIndex = objectForId(component, alias.targetObjectId); |
743 | Q_ASSERT(targetObjectIndex >= 0); |
744 | const CompiledObject &targetObject = *objectContainer->objectAt(targetObjectIndex); |
745 | |
746 | if (alias.encodedMetaPropertyIndex == -1) { |
747 | Q_ASSERT(alias.flags & QV4::CompiledData::Alias::AliasPointsToPointerObject); |
748 | auto *typeRef = objectContainer->resolvedType(targetObject.inheritedTypeNameIndex); |
749 | if (!typeRef) { |
750 | // Can be caused by the alias target not being a valid id or property. E.g.: |
751 | // property alias dataValue: dataVal |
752 | // invalidAliasComponent { id: dataVal } |
753 | return qQmlCompileError(targetObject.location, |
754 | QQmlPropertyCacheCreatorBase::tr("Invalid alias target" )); |
755 | } |
756 | |
757 | if (typeRef->type.isValid()) |
758 | *type = typeRef->type.typeId(); |
759 | else |
760 | *type = typeRef->compilationUnit->metaTypeId; |
761 | |
762 | *minorVersion = typeRef->minorVersion; |
763 | |
764 | propertyFlags->type = QQmlPropertyData::Flags::QObjectDerivedType; |
765 | } else { |
766 | int coreIndex = QQmlPropertyIndex::fromEncoded(alias.encodedMetaPropertyIndex).coreIndex(); |
767 | int valueTypeIndex = QQmlPropertyIndex::fromEncoded(alias.encodedMetaPropertyIndex).valueTypeIndex(); |
768 | |
769 | QQmlPropertyCache *targetCache = propertyCaches->at(targetObjectIndex); |
770 | Q_ASSERT(targetCache); |
771 | QQmlPropertyData *targetProperty = targetCache->property(coreIndex); |
772 | Q_ASSERT(targetProperty); |
773 | |
774 | *type = targetProperty->propType(); |
775 | |
776 | writable = targetProperty->isWritable(); |
777 | resettable = targetProperty->isResettable(); |
778 | |
779 | if (valueTypeIndex != -1) { |
780 | const QMetaObject *valueTypeMetaObject = QQmlValueTypeFactory::metaObjectForMetaType(*type); |
781 | if (valueTypeMetaObject->property(valueTypeIndex).isEnumType()) |
782 | *type = QVariant::Int; |
783 | else |
784 | *type = valueTypeMetaObject->property(valueTypeIndex).userType(); |
785 | } else { |
786 | if (targetProperty->isEnum()) { |
787 | *type = QVariant::Int; |
788 | } else { |
789 | // Copy type flags |
790 | propertyFlags->copyPropertyTypeFlags(targetProperty->flags()); |
791 | |
792 | if (targetProperty->isVarProperty()) |
793 | propertyFlags->type = QQmlPropertyData::Flags::QVariantType; |
794 | } |
795 | } |
796 | } |
797 | |
798 | propertyFlags->isWritable = !(alias.flags & QV4::CompiledData::Alias::IsReadOnly) && writable; |
799 | propertyFlags->isResettable = resettable; |
800 | return QQmlJS::DiagnosticMessage(); |
801 | } |
802 | |
803 | template <typename ObjectContainer> |
804 | inline QQmlJS::DiagnosticMessage QQmlPropertyCacheAliasCreator<ObjectContainer>::appendAliasesToPropertyCache( |
805 | const CompiledObject &component, int objectIndex) |
806 | { |
807 | const CompiledObject &object = *objectContainer->objectAt(objectIndex); |
808 | if (!object.aliasCount()) |
809 | return QQmlJS::DiagnosticMessage(); |
810 | |
811 | QQmlPropertyCache *propertyCache = propertyCaches->at(objectIndex); |
812 | Q_ASSERT(propertyCache); |
813 | |
814 | int effectiveSignalIndex = propertyCache->signalHandlerIndexCacheStart + propertyCache->propertyIndexCache.count(); |
815 | int effectivePropertyIndex = propertyCache->propertyIndexCacheStart + propertyCache->propertyIndexCache.count(); |
816 | |
817 | int aliasIndex = 0; |
818 | auto alias = object.aliasesBegin(); |
819 | auto end = object.aliasesEnd(); |
820 | for ( ; alias != end; ++alias, ++aliasIndex) { |
821 | Q_ASSERT(alias->flags & QV4::CompiledData::Alias::Resolved); |
822 | |
823 | int type = 0; |
824 | int minorVersion = 0; |
825 | QQmlPropertyData::Flags propertyFlags; |
826 | QQmlJS::DiagnosticMessage error = propertyDataForAlias(component, *alias, &type, &minorVersion, &propertyFlags); |
827 | if (error.isValid()) |
828 | return error; |
829 | |
830 | const QString propertyName = objectContainer->stringAt(alias->nameIndex); |
831 | |
832 | if (object.defaultPropertyIsAlias && aliasIndex == object.indexOfDefaultPropertyOrAlias) |
833 | propertyCache->_defaultPropertyName = propertyName; |
834 | |
835 | propertyCache->appendProperty(propertyName, propertyFlags, effectivePropertyIndex++, |
836 | type, minorVersion, effectiveSignalIndex++); |
837 | } |
838 | |
839 | return QQmlJS::DiagnosticMessage(); |
840 | } |
841 | |
842 | template <typename ObjectContainer> |
843 | inline int QQmlPropertyCacheAliasCreator<ObjectContainer>::objectForId(const CompiledObject &component, int id) const |
844 | { |
845 | for (quint32 i = 0, count = component.namedObjectsInComponentCount(); i < count; ++i) { |
846 | const int candidateIndex = component.namedObjectsInComponentTable()[i]; |
847 | const CompiledObject &candidate = *objectContainer->objectAt(candidateIndex); |
848 | if (candidate.id == id) |
849 | return candidateIndex; |
850 | } |
851 | return -1; |
852 | } |
853 | |
854 | QT_END_NAMESPACE |
855 | |
856 | #endif // QQMLPROPERTYCACHECREATOR_P_H |
857 | |