Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qqmltypecompiler.cpp
Go to the documentation of this file.
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// Qt-Security score:significant
4
6
7#include <private/qqmlobjectcreator_p.h>
8#include <private/qqmlcustomparser_p.h>
9#include <private/qqmlvmemetaobject_p.h>
10#include <private/qqmlcomponent_p.h>
11#include <private/qqmlpropertyresolver_p.h>
12#include <private/qqmlcomponentandaliasresolver_p.h>
13#include <private/qqmlsignalnames_p.h>
14
15#include <QtCore/qtyperevision.h>
16
17#define COMPILE_EXCEPTION(token, desc)
18 {
19 recordError((token)->location, desc);
20 return false;
21 }
22
24
25DEFINE_BOOL_CONFIG_OPTION(
26 disableInternalDeferredProperties, QML_DISABLE_INTERNAL_DEFERRED_PROPERTIES);
27
28Q_LOGGING_CATEGORY(lcQmlTypeCompiler, "qt.qml.typecompiler");
29
30QQmlTypeCompiler::QQmlTypeCompiler(
31 QQmlTypeLoader *typeLoader, QQmlTypeData *typeData, QmlIR::Document *parsedQML,
32 QV4::CompiledData::ResolvedTypeReferenceMap *resolvedTypeCache,
33 const QV4::CompiledData::DependentTypesHasher &dependencyHasher)
34 : resolvedTypes(resolvedTypeCache)
35 , loader(typeLoader)
36 , dependencyHasher(dependencyHasher)
37 , document(parsedQML)
38 , typeData(typeData)
39{
40}
41
43{
44 // Build property caches and VME meta object data
45
46 for (auto it = resolvedTypes->constBegin(), end = resolvedTypes->constEnd();
47 it != end; ++it) {
48 QQmlCustomParser *customParser = (*it)->type().customParser();
49 if (customParser)
50 customParsers.insert(it.key(), customParser);
51 }
52
53 QQmlPendingGroupPropertyBindings pendingGroupPropertyBindings;
54
55
56 {
57 QQmlPropertyCacheCreator<QQmlTypeCompiler> propertyCacheBuilder(
58 &m_propertyCaches, &pendingGroupPropertyBindings,
59 loader, this, imports(), typeData->typeClassName());
60 QQmlError cycleError = propertyCacheBuilder.verifyNoICCycle();
61 if (cycleError.isValid()) {
62 recordError(cycleError);
63 return nullptr;
64 }
65 QQmlPropertyCacheCreatorBase::IncrementalResult result;
66 do {
67 result = propertyCacheBuilder.buildMetaObjectsIncrementally();
68 const QQmlError &error = result.error;
69 if (error.isValid()) {
70 recordError(error);
71 return nullptr;
72 } else {
73 // Resolve component boundaries and aliases
74
75 QQmlComponentAndAliasResolver resolver(this, &m_propertyCaches);
76 if (QQmlError error = resolver.resolve(result.processedRoot); error.isValid()) {
77 recordError(error);
78 return nullptr;
79 }
80 pendingGroupPropertyBindings.resolveMissingPropertyCaches(&m_propertyCaches);
81 pendingGroupPropertyBindings.clear(); // anything that can be processed is now processed
82 }
83 } while (result.canResume);
84 }
85
86 {
87 SignalHandlerResolver converter(this);
89 return nullptr;
90 }
91
92 {
93 QQmlEnumTypeResolver enumResolver(this);
94 if (!enumResolver.resolveEnumBindings())
95 return nullptr;
96 }
97
98 {
101 }
102
103 {
104 QQmlAliasAnnotator annotator(this);
106 }
107
108 {
109 QQmlDeferredAndCustomParserBindingScanner deferredAndCustomParserBindingScanner(this);
110 if (!deferredAndCustomParserBindingScanner.scanObject())
111 return nullptr;
112 }
113
114 if (!document->javaScriptCompilationUnit || !document->javaScriptCompilationUnit->unitData()) {
115 // Compile JS binding expressions and signal handlers if necessary
116 {
117 // We can compile script strings ahead of time, but they must be compiled
118 // without type optimizations as their scope is always entirely dynamic.
120 sss.scan();
121 }
122
123 Q_ASSERT(document->jsModule.fileName == typeData->urlString());
124 Q_ASSERT(document->jsModule.finalUrl == typeData->finalUrlString());
125 QmlIR::JSCodeGen v4CodeGenerator(document);
126 for (QmlIR::Object *object : std::as_const(document->objects)) {
127 if (!v4CodeGenerator.generateRuntimeFunctions(object)) {
128 Q_ASSERT(v4CodeGenerator.hasError());
129 recordError(v4CodeGenerator.error());
130 return nullptr;
131 }
132 }
133 document->javaScriptCompilationUnit = v4CodeGenerator.generateCompilationUnit(/*generated unit data*/false);
134 }
135
136 // Generate QML compiled type data structures
137
138 QmlIR::QmlUnitGenerator qmlGenerator;
139 qmlGenerator.generate(*document, dependencyHasher);
140
141 if (!errors.isEmpty())
142 return nullptr;
143
144 return std::move(document->javaScriptCompilationUnit);
145}
146
147void QQmlTypeCompiler::recordError(const QV4::CompiledData::Location &location, const QString &description)
148{
149 QQmlError error;
150 error.setLine(qmlConvertSourceCoordinate<quint32, int>(location.line()));
151 error.setColumn(qmlConvertSourceCoordinate<quint32, int>(location.column()));
152 error.setDescription(description);
153 error.setUrl(url());
154 errors << error;
155}
156
157void QQmlTypeCompiler::recordError(const QQmlJS::DiagnosticMessage &message)
158{
159 QQmlError error;
160 error.setDescription(message.message);
161 error.setLine(qmlConvertSourceCoordinate<quint32, int>(message.loc.startLine));
162 error.setColumn(qmlConvertSourceCoordinate<quint32, int>(message.loc.startColumn));
163 error.setUrl(url());
164 errors << error;
165}
166
167void QQmlTypeCompiler::recordError(const QQmlError &e)
168{
169 QQmlError error = e;
170 error.setUrl(url());
171 errors << error;
172}
173
175{
176 return document->stringAt(idx);
177}
178
179int QQmlTypeCompiler::registerString(const QString &str)
180{
181 return document->jsGenerator.registerString(str);
182}
183
184int QQmlTypeCompiler::resolvedIndex(int index) const
185{
186 if (index < objectCount())
187 return index;
188 const auto *p = m_propertyCaches.at(index)->defaultProperty();
189 Q_ASSERT(p && p->isComponentWrapper());
190 return p->wrappedObjectIndex();
191}
192
194{
195 for (int i = objectCount(), end = m_propertyCaches.count(); i < end; ++i) {
196 const auto *p = m_propertyCaches.at(i)->defaultProperty();
197 Q_ASSERT(p && p->isComponentWrapper());
198 if (p->wrappedObjectIndex() == childIndex)
199 return i;
200 }
201 return -1;
202}
203
204int QQmlTypeCompiler::registerConstant(QV4::ReturnedValue v)
205{
206 return document->jsGenerator.registerConstant(v);
207}
208
210{
211 return document->javaScriptCompilationUnit->unitData();
212}
213
214const QQmlImports *QQmlTypeCompiler::imports() const
215{
216 return typeData->imports();
217}
218
220{
221 return &document->objects;
222}
223
225{
226 return &m_propertyCaches;
227}
228
230{
231 return &m_propertyCaches;
232}
233
235{
236 return document->jsParserEngine.pool();
237}
238
240{
241 return document->jsParserEngine.newStringRef(string);
242}
243
245{
246 return &document->jsGenerator.stringTable;
247}
248
249QString QQmlTypeCompiler::bindingAsString(const QmlIR::Object *object, int scriptIndex) const
250{
251 return object->bindingAsString(document, scriptIndex);
252}
253
254void QQmlTypeCompiler::addImport(const QString &module, const QString &qualifier, QTypeRevision version)
255{
256 const quint32 moduleIdx = registerString(module);
257 const quint32 qualifierIdx = registerString(qualifier);
258
259 for (int i = 0, count = document->imports.size(); i < count; ++i) {
260 const QV4::CompiledData::Import *existingImport = document->imports.at(i);
261 if (existingImport->type == QV4::CompiledData::Import::ImportLibrary
262 && existingImport->uriIndex == moduleIdx
263 && existingImport->qualifierIndex == qualifierIdx)
264 return;
265 }
266 auto pool = memoryPool();
267 QV4::CompiledData::Import *import = pool->New<QV4::CompiledData::Import>();
268 import->type = QV4::CompiledData::Import::ImportLibrary;
269 import->version = version;
270 import->uriIndex = moduleIdx;
271 import->qualifierIndex = qualifierIdx;
272 document->imports.append(import);
273}
274
275QQmlType QQmlTypeCompiler::qmlTypeForComponent(const QString &inlineComponentName) const
276{
277 return typeData->qmlType(inlineComponentName);
278}
279
281 : compiler(typeCompiler)
282{
283}
284
285SignalHandlerResolver::SignalHandlerResolver(QQmlTypeCompiler *typeCompiler)
286 : QQmlCompilePass(typeCompiler)
287 , typeLoader(typeCompiler->typeLoader())
288 , qmlObjects(*typeCompiler->qmlObjects())
289 , imports(typeCompiler->imports())
290 , customParsers(typeCompiler->customParserCache())
291 , propertyCaches(typeCompiler->propertyCaches())
292{
293}
294
296{
297 for (int objectIndex = 0; objectIndex < qmlObjects.size(); ++objectIndex) {
298 const QmlIR::Object * const obj = qmlObjects.at(objectIndex);
299 QQmlPropertyCache::ConstPtr cache = propertyCaches->at(objectIndex);
300 if (!cache)
301 continue;
302 if (QQmlCustomParser *customParser = customParsers.value(obj->inheritedTypeNameIndex)) {
303 if (!(customParser->flags() & QQmlCustomParser::AcceptsSignalHandlers))
304 continue;
305 }
306 const QString elementName = stringAt(obj->inheritedTypeNameIndex);
307 if (!resolveSignalHandlerExpressions(obj, elementName, cache))
308 return false;
309 }
310 return true;
311}
312
313bool SignalHandlerResolver::resolveSignalHandlerExpressions(
314 const QmlIR::Object *obj, const QString &typeName,
315 const QQmlPropertyCache::ConstPtr &propertyCache,
316 QQmlPropertyResolver::RevisionCheck checkRevision)
317{
318 // map from signal name defined in qml itself to list of parameters
319 QHash<QString, QStringList> customSignals;
320
321 for (QmlIR::Binding *binding = obj->firstBinding(); binding; binding = binding->next) {
322 const QString bindingPropertyName = stringAt(binding->propertyNameIndex);
323 // Attached property?
324 const QV4::CompiledData::Binding::Type bindingType = binding->type();
325 if (bindingType == QV4::CompiledData::Binding::Type_AttachedProperty) {
326 const QmlIR::Object *attachedObj = qmlObjects.at(binding->value.objectIndex);
327 auto *typeRef = resolvedType(binding->propertyNameIndex);
328 QQmlType type = typeRef ? typeRef->type() : QQmlType();
329 if (!type.isValid())
330 imports->resolveType(typeLoader, bindingPropertyName, &type, nullptr, nullptr);
331
332 const QMetaObject *attachedType = type.attachedPropertiesType(typeLoader);
333 if (!attachedType)
334 COMPILE_EXCEPTION(binding, tr("Non-existent attached object"));
335 QQmlPropertyCache::ConstPtr cache = QQmlMetaType::propertyCache(attachedType);
336
337 // Ignore revisions of signals on attached objects. They are not unqualified.
339 attachedObj, bindingPropertyName, cache,
340 QQmlPropertyResolver::IgnoreRevision)) {
341 return false;
342 }
343
344 continue;
345 }
346
347 QString qPropertyName;
348 QString signalName;
349 if (auto propertyName =
350 QQmlSignalNames::changedHandlerNameToPropertyName(bindingPropertyName)) {
351 qPropertyName = *propertyName;
352 signalName = *QQmlSignalNames::changedHandlerNameToSignalName(bindingPropertyName);
353 } else {
354 signalName = QQmlSignalNames::handlerNameToSignalName(bindingPropertyName)
355 .value_or(QString());
356 }
357 if (signalName.isEmpty())
358 continue;
359
360 QQmlPropertyResolver resolver(propertyCache);
361
362 bool notInRevision = false;
363 const QQmlPropertyData *const signal
364 = resolver.signal(signalName, &notInRevision, checkRevision);
365 const QQmlPropertyData *const signalPropertyData
366 = resolver.property(signalName, /*notInRevision ptr*/nullptr, checkRevision);
367 const QQmlPropertyData *const qPropertyData = !qPropertyName.isEmpty()
368 ? resolver.property(qPropertyName, nullptr, checkRevision)
369 : nullptr;
370 QString finalSignalHandlerPropertyName = signalName;
371 QV4::CompiledData::Binding::Flag flag
372 = QV4::CompiledData::Binding::IsSignalHandlerExpression;
373
374 const bool isPropertyObserver
375 = !signalPropertyData && qPropertyData && qPropertyData->notifiesViaBindable();
376 if (signal && !(qPropertyData && qPropertyData->isAlias() && isPropertyObserver)) {
377 int sigIndex = propertyCache->methodIndexToSignalIndex(signal->coreIndex());
378 sigIndex = propertyCache->originalClone(sigIndex);
379
380 bool unnamedParameter = false;
381
382 QList<QByteArray> parameterNames = propertyCache->signalParameterNames(sigIndex);
383 for (int i = 0; i < parameterNames.size(); ++i) {
384 const QString param = QString::fromUtf8(parameterNames.at(i));
385 if (param.isEmpty())
386 unnamedParameter = true;
387 else if (unnamedParameter) {
388 COMPILE_EXCEPTION(binding, tr("Signal uses unnamed parameter followed by named parameter."));
389 } else if (QV4::Compiler::Codegen::isNameGlobal(param)) {
390 COMPILE_EXCEPTION(binding, tr("Signal parameter \"%1\" hides global variable.").arg(param));
391 }
392 }
393 } else if (isPropertyObserver) {
394 finalSignalHandlerPropertyName = qPropertyName;
395 flag = QV4::CompiledData::Binding::IsPropertyObserver;
396 } else {
397 if (notInRevision) {
398 // Try assinging it as a property later
399 if (signalPropertyData)
400 continue;
401
402 const QString &originalPropertyName = stringAt(binding->propertyNameIndex);
403
404 auto *typeRef = resolvedType(obj->inheritedTypeNameIndex);
405 const QQmlType type = typeRef ? typeRef->type() : QQmlType();
406 if (type.isValid()) {
407 COMPILE_EXCEPTION(binding, tr("\"%1.%2\" is not available in %3 %4.%5.")
408 .arg(typeName, originalPropertyName, type.module())
409 .arg(type.version().majorVersion())
410 .arg(type.version().minorVersion()));
411 } else {
413 binding,
414 tr("\"%1.%2\" is not available due to component versioning.")
415 .arg(typeName, originalPropertyName));
416 }
417 }
418
419 // Try to look up the signal parameter names in the object itself
420
421 // build cache if necessary
422 if (customSignals.isEmpty()) {
423 for (const QmlIR::Signal *signal = obj->firstSignal(); signal; signal = signal->next) {
424 const QString &signalName = stringAt(signal->nameIndex);
425 customSignals.insert(signalName, signal->parameterStringList(compiler->stringPool()));
426 }
427
428 for (const QmlIR::Property *property = obj->firstProperty(); property; property = property->next) {
429 const QString propName = stringAt(property->nameIndex());
430 customSignals.insert(propName, QStringList());
431 }
432 }
433
434 QHash<QString, QStringList>::ConstIterator entry = customSignals.constFind(signalName);
435 if (entry == customSignals.constEnd() && !qPropertyName.isEmpty())
436 entry = customSignals.constFind(qPropertyName);
437
438 if (entry == customSignals.constEnd()) {
439 // Can't find even a custom signal, then just don't do anything and try
440 // keeping the binding as a regular property assignment.
441 continue;
442 }
443 }
444
445 // Binding object to signal means connect the signal to the object's default method.
446 if (bindingType == QV4::CompiledData::Binding::Type_Object) {
447 binding->setFlag(QV4::CompiledData::Binding::IsSignalHandlerObject);
448 continue;
449 }
450
451 if (bindingType != QV4::CompiledData::Binding::Type_Script) {
452 if (bindingType < QV4::CompiledData::Binding::Type_Script) {
453 COMPILE_EXCEPTION(binding, tr("Cannot assign a value to a signal (expecting a script to be run)"));
454 } else {
455 COMPILE_EXCEPTION(binding, tr("Incorrectly specified signal assignment"));
456 }
457 }
458
459 binding->propertyNameIndex = compiler->registerString(finalSignalHandlerPropertyName);
460 binding->setFlag(flag);
461 }
462 return true;
463}
464
465QQmlEnumTypeResolver::QQmlEnumTypeResolver(QQmlTypeCompiler *typeCompiler)
466 : QQmlCompilePass(typeCompiler)
467 , qmlObjects(*typeCompiler->qmlObjects())
468 , propertyCaches(typeCompiler->propertyCaches())
469 , imports(typeCompiler->imports())
470{
471}
472
474{
475 for (int i = 0; i < qmlObjects.size(); ++i) {
476 QQmlPropertyCache::ConstPtr propertyCache = propertyCaches->at(i);
477 if (!propertyCache)
478 continue;
479 const QmlIR::Object *obj = qmlObjects.at(i);
480
481 QQmlPropertyResolver resolver(propertyCache);
482
483 for (QmlIR::Binding *binding = obj->firstBinding(); binding; binding = binding->next) {
484 const QV4::CompiledData::Binding::Flags bindingFlags = binding->flags();
485 if (bindingFlags & QV4::CompiledData::Binding::IsSignalHandlerExpression
486 || bindingFlags & QV4::CompiledData::Binding::IsSignalHandlerObject
487 || bindingFlags & QV4::CompiledData::Binding::IsPropertyObserver)
488 continue;
489
490 if (binding->type() != QV4::CompiledData::Binding::Type_Script)
491 continue;
492
493 const QString propertyName = stringAt(binding->propertyNameIndex);
494 bool notInRevision = false;
495 const QQmlPropertyData *pd = resolver.property(propertyName, &notInRevision);
496 if (!pd || pd->isQList())
497 continue;
498
499 if (!pd->isEnum() && pd->propType().id() != QMetaType::Int)
500 continue;
501
502 if (!tryQualifiedEnumAssignment(obj, propertyCache, pd, binding))
503 return false;
504 }
505 }
506
507 return true;
508}
509
510bool QQmlEnumTypeResolver::assignEnumToBinding(QmlIR::Binding *binding, QStringView, int enumValue)
511{
512 binding->setType(QV4::CompiledData::Binding::Type_Number);
513 // Enum values are integers (for now). This keeps the fold independent of the constant table,
514 // which is not regenerated when an ahead-of-time-compiled unit is loaded and its enum bindings
515 // are folded against the run-time-resolved types.
516 // TODO: Filter out long enums when we get to use them
517 binding->value.resolvedEnumValue = enumValue;
518 binding->setFlag(QV4::CompiledData::Binding::IsResolvedEnum);
519 return true;
520}
521
522bool QQmlEnumTypeResolver::tryQualifiedEnumAssignment(
523 const QmlIR::Object *obj, const QQmlPropertyCache::ConstPtr &propertyCache,
524 const QQmlPropertyData *prop, QmlIR::Binding *binding)
525{
526 bool isIntProp = (prop->propType().id() == QMetaType::Int) && !prop->isEnum();
527 if (!prop->isEnum() && !isIntProp)
528 return true;
529
530 if (!prop->isWritable()
531 && !(binding->hasFlag(QV4::CompiledData::Binding::InitializerForReadOnlyDeclaration))) {
532 COMPILE_EXCEPTION(binding, tr("Invalid property assignment: \"%1\" is a read-only property")
533 .arg(stringAt(binding->propertyNameIndex)));
534 }
535
536 Q_ASSERT(binding->type() == QV4::CompiledData::Binding::Type_Script);
537 const QString string = compiler->bindingAsString(obj, binding->value.compiledScriptIndex);
538 const int dot = QmlIR::qualifiedEnumDot(string);
539 if (dot == -1)
540 return true;
541
542 int dot2 = string.indexOf(QLatin1Char('.'), dot+1);
543 if (dot2 != -1 && dot2 != string.size()-1) {
544 if (!string.at(dot+1).isUpper())
545 return true;
546 if (string.indexOf(QLatin1Char('.'), dot2+1) != -1)
547 return true;
548 }
549
550 QHashedStringRef typeName(string.constData(), dot);
551 const bool isQtObject = (typeName == QLatin1String("Qt"));
552 const QStringView scopedEnumName = (dot2 != -1 ? QStringView{string}.mid(dot + 1, dot2 - dot - 1) : QStringView());
553 // ### consider supporting scoped enums in Qt namespace
554 const QStringView enumValue = QStringView{string}.mid(!isQtObject && dot2 != -1 ? dot2 + 1 : dot + 1);
555
556 if (isIntProp) { // ### C++11 allows enums to be other integral types. Should we support other integral types here?
557 // Allow enum assignment to ints.
558 bool ok;
559 int enumval = evaluateEnum(typeName.toString(), scopedEnumName, enumValue, &ok);
560 if (ok) {
561 if (!assignEnumToBinding(binding, enumValue, enumval))
562 return false;
563 }
564 return true;
565 }
566 QQmlType type;
567 imports->resolveType(compiler->typeLoader(), typeName, &type, nullptr, nullptr);
568
569 if (!type.isValid() && !isQtObject)
570 return true;
571
572 int value = 0;
573 bool ok = false;
574
575 auto *tr = resolvedType(obj->inheritedTypeNameIndex);
576
577 // When these two match, we can short cut the search, unless...
578 bool useFastPath = type.isValid() && tr && tr->type() == type;
579 QMetaProperty mprop;
580 QMetaEnum menum;
581 if (useFastPath) {
582 mprop = propertyCache->firstCppMetaObject()->property(prop->coreIndex());
583 menum = mprop.enumerator();
584 // ...the enumerator merely comes from a related metaobject, but the enum scope does not match
585 // the typename we resolved
586 if (!menum.isScoped() && scopedEnumName.isEmpty() && typeName != QString::fromUtf8(menum.scope()))
587 useFastPath = false;;
588 }
589 if (useFastPath) {
590 QByteArray enumName = enumValue.toUtf8();
591 if (menum.isScoped() && !scopedEnumName.isEmpty() && enumName != scopedEnumName.toUtf8())
592 return true;
593
594 if (mprop.isFlagType()) {
595 value = menum.keysToValue(enumName.constData(), &ok);
596 } else {
597 value = menum.keyToValue(enumName.constData(), &ok);
598 }
599 } else {
600 // Otherwise we have to search the whole type
601 if (type.isValid()) {
602 if (!scopedEnumName.isEmpty()) {
603 value = type.scopedEnumValue(
604 compiler->typeLoader(), scopedEnumName, enumValue, &ok);
605 } else {
606 value = type.enumValue(compiler->typeLoader(), QHashedStringRef(enumValue), &ok);
607 }
608 } else {
609 QByteArray enumName = enumValue.toUtf8();
610 const QMetaObject *metaObject = &Qt::staticMetaObject;
611 for (int ii = metaObject->enumeratorCount() - 1; !ok && ii >= 0; --ii) {
612 QMetaEnum e = metaObject->enumerator(ii);
613 value = e.keyToValue(enumName.constData(), &ok);
614 }
615 }
616 }
617
618 if (!ok)
619 return true;
620
621 return assignEnumToBinding(binding, enumValue, value);
622}
623
624int QQmlEnumTypeResolver::evaluateEnum(const QString &scope, QStringView enumName, QStringView enumValue, bool *ok) const
625{
626 Q_ASSERT_X(ok, "QQmlEnumTypeResolver::evaluateEnum", "ok must not be a null pointer");
627 *ok = false;
628
629 if (scope != QLatin1String("Qt")) {
630 QQmlType type;
631 imports->resolveType(compiler->typeLoader(), scope, &type, nullptr, nullptr);
632 if (!type.isValid())
633 return -1;
634 if (!enumName.isEmpty())
635 return type.scopedEnumValue(compiler->typeLoader(), enumName, enumValue, ok);
636 return type.enumValue(
637 compiler->typeLoader(),
638 QHashedStringRef(enumValue.constData(), enumValue.size()), ok);
639 }
640
641 const QMetaObject *mo = &Qt::staticMetaObject;
642 int i = mo->enumeratorCount();
643 const QByteArray ba = enumValue.toUtf8();
644 while (i--) {
645 int v = mo->enumerator(i).keyToValue(ba.constData(), ok);
646 if (*ok)
647 return v;
648 }
649 return -1;
650}
651
658
660{
661 scanObjectRecursively(/*root object*/0);
662 for (int i = 0; i < qmlObjects.size(); ++i)
663 if (qmlObjects.at(i)->flags & QV4::CompiledData::Object::IsInlineComponentRoot)
664 scanObjectRecursively(i);
665}
666
667void QQmlCustomParserScriptIndexer::scanObjectRecursively(int objectIndex, bool annotateScriptBindings)
668{
669 const QmlIR::Object * const obj = qmlObjects.at(objectIndex);
670 if (!annotateScriptBindings)
671 annotateScriptBindings = customParsers.contains(obj->inheritedTypeNameIndex);
672 for (QmlIR::Binding *binding = obj->firstBinding(); binding; binding = binding->next) {
673 switch (binding->type()) {
674 case QV4::CompiledData::Binding::Type_Script:
675 if (annotateScriptBindings) {
676 binding->stringIndex = compiler->registerString(
677 compiler->bindingAsString(obj, binding->value.compiledScriptIndex));
678 }
679 break;
680 case QV4::CompiledData::Binding::Type_Object:
681 case QV4::CompiledData::Binding::Type_AttachedProperty:
682 case QV4::CompiledData::Binding::Type_GroupProperty:
683 scanObjectRecursively(binding->value.objectIndex, annotateScriptBindings);
684 break;
685 default:
686 break;
687 }
688 }
689}
690
697
699{
700 for (int i = 0; i < qmlObjects.size(); ++i) {
701 QQmlPropertyCache::ConstPtr propertyCache = propertyCaches->at(i);
702 if (!propertyCache)
703 continue;
704
705 const QmlIR::Object *obj = qmlObjects.at(i);
706
707 QQmlPropertyResolver resolver(propertyCache);
708 const QQmlPropertyData *defaultProperty = obj->indexOfDefaultPropertyOrAlias != -1 ? propertyCache->parent()->defaultProperty() : propertyCache->defaultProperty();
709
710 for (QmlIR::Binding *binding = obj->firstBinding(); binding; binding = binding->next) {
711 if (!binding->isValueBinding())
712 continue;
713 bool notInRevision = false;
714 const QQmlPropertyData *pd = binding->propertyNameIndex != quint32(0) ? resolver.property(stringAt(binding->propertyNameIndex), &notInRevision) : defaultProperty;
715 if (pd && pd->isAlias())
716 binding->setFlag(QV4::CompiledData::Binding::IsBindingToAlias);
717 }
718 }
719}
720
728
730{
731 const QMetaType scriptStringMetaType = QMetaType::fromType<QQmlScriptString>();
732 for (int i = 0; i < qmlObjects.size(); ++i) {
733 QQmlPropertyCache::ConstPtr propertyCache = propertyCaches->at(i);
734 if (!propertyCache)
735 continue;
736
737 const QmlIR::Object *obj = qmlObjects.at(i);
738
739 QQmlPropertyResolver resolver(propertyCache);
740 const QQmlPropertyData *defaultProperty = obj->indexOfDefaultPropertyOrAlias != -1 ? propertyCache->parent()->defaultProperty() : propertyCache->defaultProperty();
741
742 for (QmlIR::Binding *binding = obj->firstBinding(); binding; binding = binding->next) {
743 if (binding->type() != QV4::CompiledData::Binding::Type_Script)
744 continue;
745 bool notInRevision = false;
746 const QQmlPropertyData *pd = binding->propertyNameIndex != quint32(0) ? resolver.property(stringAt(binding->propertyNameIndex), &notInRevision) : defaultProperty;
747 if (!pd || pd->propType() != scriptStringMetaType)
748 continue;
749
750 QString script = compiler->bindingAsString(obj, binding->value.compiledScriptIndex);
751 binding->stringIndex = compiler->registerString(script);
752 }
753 }
754}
755
756template<>
757void QQmlComponentAndAliasResolver<QQmlTypeCompiler>::allocateNamedObjects(
758 QmlIR::Object *object) const
759{
760 object->namedObjectsInComponent.allocate(m_compiler->memoryPool(), m_idToObjectIndex);
761}
762
763template<>
764bool QQmlComponentAndAliasResolver<QQmlTypeCompiler>::markAsComponent(int index) const
765{
766 m_compiler->qmlObjects()->at(index)->flags |= QV4::CompiledData::Object::IsComponent;
767 return true;
768}
769
770template<>
771void QQmlComponentAndAliasResolver<QQmlTypeCompiler>::setObjectId(int index) const
772{
773 m_compiler->qmlObjects()->at(index)->id = m_idToObjectIndex.size();
774}
775
776template<>
777void QQmlComponentAndAliasResolver<QQmlTypeCompiler>::resolveGeneralizedGroupProperty(
778 const CompiledObject &component, CompiledBinding *binding)
779{
780 Q_UNUSED(component);
781 // We cannot make it fail here. It might be a custom-parsed property
782 const int targetObjectIndex = m_idToObjectIndex.value(binding->propertyNameIndex, -1);
783 if (targetObjectIndex != -1)
784 m_propertyCaches->set(binding->value.objectIndex, m_propertyCaches->at(targetObjectIndex));
785}
786
787/*!
788 \internal
789
790 Attempts to resolve a "deep alias" — an alias whose sub-property path
791 goes through a QObject property, an inline component binding, or another
792 alias. For example: \c{alias foo: target.groupProp.innerProp}
793
794 First searches the target object's bindings and aliases for \a property,
795 then looks up \a subProperty on the bound/aliased object's property cache.
796 If no binding or alias matches, falls back to looking up \a subProperty
797 on the declared type's property cache.
798
799 On success, updates \a propIdx with the resolved value-type index and
800 returns \c true.
801*/
803 QQmlTypeCompiler *compiler, const QmlIR::Object *targetObject,
804 QStringView property, QStringView subProperty,
805 QQmlPropertyIndex &propIdx, QMetaType targetPropertyType,
806 const QQmlPropertyCacheVector *propertyCaches,
807 const QMap<int, int> &idToObjectIndex)
808{
809 for (auto it = targetObject->bindingsBegin(), end = targetObject->bindingsEnd();
810 it != end; ++it) {
811 auto binding = *it;
812 if (compiler->stringAt(binding.propertyNameIndex) != property)
813 continue;
814 const auto &cache = propertyCaches->at(binding.value.objectIndex);
815 if (!cache)
816 continue;
817 QQmlPropertyResolver resolver(cache);
818 const QQmlPropertyData *pd = resolver.property(subProperty.toString());
819 if (!pd)
820 continue;
821 propIdx = QQmlPropertyIndex(propIdx.coreIndex(), pd->coreIndex());
822 return true;
823 }
824
825 for (auto it = targetObject->aliasesBegin(), end = targetObject->aliasesEnd();
826 it != end; ++it) {
827 auto innerAlias = *it;
828 if (compiler->stringAt(innerAlias.nameIndex()) != property)
829 continue;
830 const int innerObjectIndex = idToObjectIndex.value(innerAlias.idIndex(), -1);
831 if (innerObjectIndex == -1)
832 continue;
833 const auto &cache = propertyCaches->at(innerObjectIndex);
834 if (!cache)
835 continue;
836 QQmlPropertyResolver resolver(cache);
837 const QQmlPropertyData *pd = resolver.property(subProperty.toString());
838 if (!pd)
839 continue;
840 propIdx = QQmlPropertyIndex(propIdx.coreIndex(), pd->coreIndex());
841 return true;
842 }
843
844 const QQmlPropertyCache::ConstPtr typeCache
845 = QQmlMetaType::propertyCacheForType(targetPropertyType);
846 if (typeCache) {
847 const QQmlPropertyResolver resolver(typeCache);
848 const QQmlPropertyData *pd = resolver.property(subProperty.toString());
849 if (pd) {
850 propIdx = QQmlPropertyIndex(propIdx.coreIndex(), pd->coreIndex());
851 return true;
852 }
853 }
854
855 return false;
856}
857
858template<>
859typename QQmlComponentAndAliasResolver<QQmlTypeCompiler>::AliasResolutionResult
860QQmlComponentAndAliasResolver<QQmlTypeCompiler>::resolveAliasesInObject(
861 const CompiledObject &component, int objectIndex,
862 QQmlPropertyCacheAliasCreator<QQmlTypeCompiler> *aliasCacheCreator, QQmlError *error)
863{
864 Q_UNUSED(component);
865
866 const QmlIR::Object *obj = m_compiler->objectAt(objectIndex);
867 if (!obj->aliasCount())
868 return AllAliasesResolved;
869
870 int aliasIndex = 0;
871 int numSkippedAliases = 0;
872 bool hasUnresolvedLocalAliases = false;
873
874 for (const QmlIR::Alias *alias = obj->firstAlias(); alias; alias = alias->next, ++aliasIndex) {
875 if (resolvedAliases.contains(alias)) {
876 ++numSkippedAliases;
877 continue;
878 }
879
880
881 const int idIndex = alias->idIndex();
882 const int targetObjectIndex = m_idToObjectIndex.value(idIndex, -1);
883 if (targetObjectIndex == -1) {
884 *error = qQmlCompileError(
885 alias->referenceLocation(),
886 QQmlComponentAndAliasResolverBase::tr(
887 "Invalid alias reference. Unable to find id \"%1\"")
888 .arg(stringAt(idIndex)));
889 break;
890 }
891
892 const QmlIR::Object *targetObject = m_compiler->objectAt(targetObjectIndex);
893 Q_ASSERT(targetObject->id >= 0);
894 const int resolvedTargetObjectId = targetObject->id;
895
896 const QString aliasPropertyValue = stringAt(alias->propertyNameIndex());
897
898 QStringView property;
899 QStringView subProperty;
900
901 const int propertySeparator = aliasPropertyValue.indexOf(QLatin1Char('.'));
902 if (propertySeparator != -1) {
903 property = QStringView{aliasPropertyValue}.left(propertySeparator);
904 subProperty = QStringView{aliasPropertyValue}.mid(propertySeparator + 1);
905 } else
906 property = QStringView(aliasPropertyValue);
907
908 QQmlPropertyIndex propIdx;
909
910 if (property.isEmpty()) {
911 // Alias points to object. No need to resolve properties.
912 } else {
913 QQmlPropertyCache::ConstPtr targetCache = m_propertyCaches->at(targetObjectIndex);
914 if (!targetCache) {
915 *error = qQmlCompileError(
916 alias->referenceLocation(),
917 QQmlComponentAndAliasResolverBase::tr("Invalid alias target location: %1")
918 .arg(property.toString()));
919 break;
920 }
921
922 QQmlPropertyResolver resolver(targetCache);
923
924 const QQmlPropertyData *targetProperty = resolver.property(
925 property.toString(), nullptr, QQmlPropertyResolver::IgnoreRevision);
926
927 // If it's an alias that we haven't resolved yet, try again later.
928 if (!targetProperty) {
929 bool aliasPointsToOtherAlias = false;
930 auto targetAlias = targetObject->aliasesBegin();
931 for (const auto end = targetObject->aliasesEnd(); targetAlias != end;
932 ++targetAlias) {
933 if (stringAt(targetAlias->nameIndex()) == property) {
934 aliasPointsToOtherAlias = true;
935 break;
936 }
937 }
938 if (aliasPointsToOtherAlias) {
939 if (targetObjectIndex != objectIndex) {
940 // Don't continue, yet. We need to respect the order of objects.
941 return aliasIndex == numSkippedAliases
942 ? NoAliasResolved
943 : SomeAliasesResolved;
944 }
945
946 if (resolvedAliases.contains(targetAlias)) {
947 // Target already resolved. We can set the alias right away.
948 if (!appendAliasToPropertyCache(
949 &component, alias, objectIndex, aliasIndex, -1,
950 resolvedTargetObjectId, aliasCacheCreator, error)) {
951 break;
952 }
953 continue;
954 }
955
956 // Target isn't resolved yet, but it's in the same object.
957 // Continue with the other aliases.
958 // Try again later and resolve the target alias first.
959 ++numSkippedAliases;
960 hasUnresolvedLocalAliases = true;
961 continue;
962 }
963 }
964
965 if (!targetProperty || targetProperty->coreIndex() > 0x0000FFFF) {
966 *error = qQmlCompileError(
967 alias->referenceLocation(),
968 QQmlComponentAndAliasResolverBase::tr("Invalid alias target location: %1")
969 .arg(property.toString()));
970 break;
971 }
972
973 propIdx = QQmlPropertyIndex(targetProperty->coreIndex());
974
975 if (!subProperty.isEmpty()) {
976 const QMetaObject *valueTypeMetaObject = QQmlMetaType::metaObjectForValueType(targetProperty->propType());
977 if (!valueTypeMetaObject) {
978 // could be a deep alias
979 bool isDeepAlias = subProperty.at(0).isLower();
980 if (isDeepAlias) {
981 isDeepAlias = resolveDeepAlias(
982 m_compiler, targetObject, property, subProperty, propIdx,
983 targetProperty->propType(), m_propertyCaches, m_idToObjectIndex);
984 }
985 if (!isDeepAlias) {
986 *error = qQmlCompileError(
987 alias->referenceLocation(),
988 QQmlComponentAndAliasResolverBase::tr(
989 "Invalid alias target location: %1")
990 .arg(subProperty.toString()));
991 break;
992 }
993 } else {
994
995 int valueTypeIndex =
996 valueTypeMetaObject->indexOfProperty(subProperty.toString().toUtf8().constData());
997 if (valueTypeIndex == -1) {
998 *error = qQmlCompileError(
999 alias->referenceLocation(),
1000 QQmlComponentAndAliasResolverBase::tr(
1001 "Invalid alias target location: %1")
1002 .arg(subProperty.toString()));
1003 break;
1004 }
1005 Q_ASSERT(valueTypeIndex <= 0x0000FFFF);
1006
1007 propIdx = QQmlPropertyIndex(propIdx.coreIndex(), valueTypeIndex);
1008 }
1009 }
1010 }
1011
1012 if (!appendAliasToPropertyCache(
1013 &component, alias, objectIndex, aliasIndex, propIdx.toEncoded(),
1014 resolvedTargetObjectId, aliasCacheCreator, error)) {
1015 break;
1016 }
1017 }
1018
1019 if (numSkippedAliases == aliasIndex)
1020 return NoAliasResolved;
1021
1022 if (aliasIndex == obj->aliasCount() && !hasUnresolvedLocalAliases)
1023 return AllAliasesResolved;
1024
1025 return SomeAliasesResolved;
1026}
1027
1028QQmlDeferredAndCustomParserBindingScanner::QQmlDeferredAndCustomParserBindingScanner(QQmlTypeCompiler *typeCompiler)
1029 : QQmlCompilePass(typeCompiler)
1030 , qmlObjects(typeCompiler->qmlObjects())
1031 , propertyCaches(typeCompiler->propertyCaches())
1032 , customParsers(typeCompiler->customParserCache())
1033 , _seenObjectWithId(false)
1034{
1035}
1036
1038{
1039 for (int i = 0; i < qmlObjects->size(); ++i) {
1040 if ((qmlObjects->at(i)->flags & QV4::CompiledData::Object::IsInlineComponentRoot)
1041 && !scanObject(i, ScopeDeferred::False)) {
1042 return false;
1043 }
1044 }
1045 return scanObject(/*root object*/0, ScopeDeferred::False);
1046}
1047
1049 int objectIndex, ScopeDeferred scopeDeferred)
1050{
1051 using namespace QV4::CompiledData;
1052
1053 QmlIR::Object *obj = qmlObjects->at(objectIndex);
1054 if (obj->idNameIndex != 0)
1055 _seenObjectWithId = true;
1056
1057 if (obj->flags & Object::IsComponent) {
1058 Q_ASSERT(obj->bindingCount() == 1);
1059 const Binding *componentBinding = obj->firstBinding();
1060 Q_ASSERT(componentBinding->type() == Binding::Type_Object);
1061 // Components are separate from their surrounding scope. They cannot be deferred.
1062 return scanObject(componentBinding->value.objectIndex, ScopeDeferred::False);
1063 }
1064
1065 QQmlPropertyCache::ConstPtr propertyCache = propertyCaches->at(objectIndex);
1066 if (!propertyCache)
1067 return true;
1068
1069 QString defaultPropertyName;
1070 const QQmlPropertyData *defaultProperty = nullptr;
1071 if (obj->indexOfDefaultPropertyOrAlias != -1) {
1072 const QQmlPropertyCache *cache = propertyCache->parent().data();
1073 defaultPropertyName = cache->defaultPropertyName();
1074 defaultProperty = cache->defaultProperty();
1075 } else {
1076 defaultPropertyName = propertyCache->defaultPropertyName();
1077 defaultProperty = propertyCache->defaultProperty();
1078 }
1079
1080 QQmlCustomParser *customParser = customParsers.value(obj->inheritedTypeNameIndex);
1081
1082 QQmlPropertyResolver propertyResolver(propertyCache);
1083
1084 QStringList deferredPropertyNames;
1085 QStringList immediatePropertyNames;
1086 {
1087 const QMetaObject *mo = propertyCache->firstCppMetaObject();
1088 const int deferredNamesIndex = mo->indexOfClassInfo("DeferredPropertyNames");
1089 const int immediateNamesIndex = mo->indexOfClassInfo("ImmediatePropertyNames");
1090 if (deferredNamesIndex != -1) {
1091 if (immediateNamesIndex != -1) {
1092 COMPILE_EXCEPTION(obj, tr("You cannot define both DeferredPropertyNames and "
1093 "ImmediatePropertyNames on the same type."));
1094 }
1095 const QMetaClassInfo classInfo = mo->classInfo(deferredNamesIndex);
1096 deferredPropertyNames = QString::fromUtf8(classInfo.value()).split(u',');
1097 } else if (immediateNamesIndex != -1) {
1098 const QMetaClassInfo classInfo = mo->classInfo(immediateNamesIndex);
1099 immediatePropertyNames = QString::fromUtf8(classInfo.value()).split(u',');
1100
1101 // If the property contains an empty string, all properties shall be deferred.
1102 if (immediatePropertyNames.isEmpty())
1103 immediatePropertyNames.append(QString());
1104 }
1105 }
1106
1107 for (QmlIR::Binding *binding = obj->firstBinding(); binding; binding = binding->next) {
1108 QString name = stringAt(binding->propertyNameIndex);
1109
1110 if (customParser) {
1111 if (binding->type() == Binding::Type_AttachedProperty) {
1112 if (customParser->flags() & QQmlCustomParser::AcceptsAttachedProperties) {
1113 binding->setFlag(Binding::IsCustomParserBinding);
1114 obj->flags |= Object::HasCustomParserBindings;
1115 continue;
1116 }
1117 } else if (QQmlSignalNames::isHandlerName(name)
1118 && !(customParser->flags() & QQmlCustomParser::AcceptsSignalHandlers)) {
1119 obj->flags |= Object::HasCustomParserBindings;
1120 binding->setFlag(Binding::IsCustomParserBinding);
1121 continue;
1122 }
1123 }
1124
1125 const bool hasPropertyData = [&]() {
1126 if (name.isEmpty()) {
1127 name = defaultPropertyName;
1128 if (defaultProperty)
1129 return true;
1130 } else if (name.constData()->isUpper()) {
1131 // Upper case names cannot be custom-parsed unless they are attached properties
1132 // and the custom parser explicitly accepts them. See above for that case.
1133 return false;
1134 } else {
1135 bool notInRevision = false;
1136 if (propertyResolver.property(
1137 name, &notInRevision, QQmlPropertyResolver::CheckRevision)) {
1138 return true;
1139 }
1140 }
1141
1142 if (!customParser)
1143 return false;
1144
1145 const Binding::Flags bindingFlags = binding->flags();
1146 if (bindingFlags & Binding::IsSignalHandlerExpression
1147 || bindingFlags & Binding::IsSignalHandlerObject
1148 || bindingFlags & Binding::IsPropertyObserver) {
1149 // These signal handlers cannot be custom-parsed. We have already established
1150 // that the signal exists.
1151 return false;
1152 }
1153
1154 // If the property isn't found, we may want to custom-parse the binding.
1155 obj->flags |= Object::HasCustomParserBindings;
1156 binding->setFlag(Binding::IsCustomParserBinding);
1157 return false;
1158 }();
1159
1160 bool seenSubObjectWithId = false;
1161 bool isExternal = false;
1162 if (binding->type() >= Binding::Type_Object) {
1163 const bool isOwnProperty = hasPropertyData || binding->isAttachedProperty();
1164 isExternal = !isOwnProperty && binding->isGroupProperty();
1165 if (isOwnProperty || isExternal) {
1166 qSwap(_seenObjectWithId, seenSubObjectWithId);
1167
1168 // Implicit component wrappers are scope boundaries.
1169 const bool isImplicitComponent
1170 = compiler->implicitComponentForObject(binding->value.objectIndex) != -1;
1171
1172 const ScopeDeferred objectScope =
1173 !isImplicitComponent && (isExternal || scopeDeferred == ScopeDeferred::True)
1174 ? ScopeDeferred::True
1175 : ScopeDeferred::False;
1176 const bool subObjectValid = scanObject(binding->value.objectIndex, objectScope);
1177 qSwap(_seenObjectWithId, seenSubObjectWithId);
1178 if (!subObjectValid)
1179 return false;
1180 _seenObjectWithId |= seenSubObjectWithId;
1181 }
1182 }
1183
1184 bool isDeferred = false;
1185 if (!immediatePropertyNames.isEmpty() && !immediatePropertyNames.contains(name)) {
1186 if (seenSubObjectWithId) {
1187 COMPILE_EXCEPTION(binding, tr("You cannot assign an id to an object assigned "
1188 "to a deferred property."));
1189 }
1190 if (isExternal || !disableInternalDeferredProperties())
1191 isDeferred = true;
1192 } else if (!deferredPropertyNames.isEmpty() && deferredPropertyNames.contains(name)) {
1193 if (!seenSubObjectWithId && binding->type() != Binding::Type_GroupProperty) {
1194 if (isExternal || !disableInternalDeferredProperties())
1195 isDeferred = true;
1196 }
1197 }
1198
1199 if (binding->type() >= Binding::Type_Object) {
1200 if (isExternal && !isDeferred && !customParser) {
1202 binding, tr("Cannot assign to non-existent property \"%1\"").arg(name));
1203 }
1204 }
1205
1206 if (isDeferred) {
1207 binding->setFlag(Binding::IsDeferredBinding);
1208 obj->flags |= Object::HasDeferredBindings;
1209 }
1210 }
1211
1212 return true;
1213}
1214
1215QT_END_NAMESPACE
QQmlAliasAnnotator(QQmlTypeCompiler *typeCompiler)
QQmlCustomParserScriptIndexer(QQmlTypeCompiler *typeCompiler)
QQmlScriptStringScanner(QQmlTypeCompiler *typeCompiler)
\inmodule QtCore
Combined button and popup list for selecting options.
Definition qjsvalue.h:24
Q_LOGGING_CATEGORY(lcEventDispatcher, "qt.eventdispatcher")
#define COMPILE_EXCEPTION(location, desc)
static bool resolveDeepAlias(QQmlTypeCompiler *compiler, const QmlIR::Object *targetObject, QStringView property, QStringView subProperty, QQmlPropertyIndex &propIdx, QMetaType targetPropertyType, const QQmlPropertyCacheVector *propertyCaches, const QMap< int, int > &idToObjectIndex)
QQmlCompilePass(QQmlTypeCompiler *typeCompiler)
QQmlTypeCompiler * compiler
int implicitComponentForObject(int childIndex) const
int registerConstant(QV4::ReturnedValue v)
void recordError(const QQmlJS::DiagnosticMessage &message)
const QV4::CompiledData::Unit * qmlUnit() const
QQmlType qmlTypeForComponent(const QString &inlineComponentName=QString()) const
QList< QmlIR::Object * > * qmlObjects() const
QQmlPropertyCacheVector * propertyCaches()
QQmlJS::MemoryPool * memoryPool()
void recordError(const QQmlError &e)
QString bindingAsString(const QmlIR::Object *object, int scriptIndex) const
const QQmlPropertyCacheVector * propertyCaches() const
int registerString(const QString &str)
const QV4::Compiler::StringTableGenerator * stringPool() const
void recordError(const QV4::CompiledData::Location &location, const QString &description)
QStringView newStringRef(const QString &string)
int resolvedIndex(int index) const
void addImport(const QString &module, const QString &qualifier, QTypeRevision version)
QQmlRefPointer< QV4::CompiledData::CompilationUnit > compile()
QString stringAt(int idx) const
const QQmlImports * imports() const