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 QQmlPendingGroupedPropertyBindings pendingGroupedPropertyBindings;
54
55
56 {
57 QQmlPropertyCacheCreator<QQmlTypeCompiler> propertyCacheBuilder(
58 &m_propertyCaches, &pendingGroupedPropertyBindings,
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 pendingGroupedPropertyBindings.resolveMissingPropertyCaches(&m_propertyCaches);
81 pendingGroupedPropertyBindings.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 // Attached objects are visited twice: Once via the attached property and once on their own.
323 if (binding->hasSignalHandlerBindingFlag())
324 continue;
325
326 const QString bindingPropertyName = stringAt(binding->propertyNameIndex);
327 // Attached property?
328 const QV4::CompiledData::Binding::Type bindingType = binding->type();
329 if (bindingType == QV4::CompiledData::Binding::Type_AttachedProperty) {
330 const QmlIR::Object *attachedObj = qmlObjects.at(binding->value.objectIndex);
331 auto *typeRef = resolvedType(binding->propertyNameIndex);
332 QQmlType type = typeRef ? typeRef->type() : QQmlType();
333 if (!type.isValid())
334 imports->resolveType(typeLoader, bindingPropertyName, &type, nullptr, nullptr);
335
336 const QMetaObject *attachedType = type.attachedPropertiesType(typeLoader);
337 if (!attachedType)
338 COMPILE_EXCEPTION(binding, tr("Non-existent attached object"));
339 QQmlPropertyCache::ConstPtr cache = QQmlMetaType::propertyCache(attachedType);
340
341 // Ignore revisions of signals on attached objects. They are not unqualified.
343 attachedObj, bindingPropertyName, cache,
344 QQmlPropertyResolver::IgnoreRevision)) {
345 return false;
346 }
347
348 continue;
349 }
350
351 QString qPropertyName;
352 QString signalName;
353 if (auto propertyName =
354 QQmlSignalNames::changedHandlerNameToPropertyName(bindingPropertyName)) {
355 qPropertyName = *propertyName;
356 signalName = *QQmlSignalNames::changedHandlerNameToSignalName(bindingPropertyName);
357 } else {
358 signalName = QQmlSignalNames::handlerNameToSignalName(bindingPropertyName)
359 .value_or(QString());
360 }
361 if (signalName.isEmpty())
362 continue;
363
364 QQmlPropertyResolver resolver(propertyCache);
365
366 bool notInRevision = false;
367 const QQmlPropertyData *const signal
368 = resolver.signal(signalName, &notInRevision, checkRevision);
369 const QQmlPropertyData *const signalPropertyData
370 = resolver.property(signalName, /*notInRevision ptr*/nullptr, checkRevision);
371 const QQmlPropertyData *const qPropertyData = !qPropertyName.isEmpty()
372 ? resolver.property(qPropertyName, nullptr, checkRevision)
373 : nullptr;
374 QV4::CompiledData::Binding::Flag flag
375 = QV4::CompiledData::Binding::IsSignalHandlerExpression;
376
377 const bool isPropertyObserver
378 = !signalPropertyData && qPropertyData && qPropertyData->notifiesViaBindable();
379 if (signal && !(qPropertyData && qPropertyData->isAlias() && isPropertyObserver)) {
380 int sigIndex = propertyCache->methodIndexToSignalIndex(signal->coreIndex());
381 sigIndex = propertyCache->originalClone(sigIndex);
382
383 bool unnamedParameter = false;
384
385 QList<QByteArray> parameterNames = propertyCache->signalParameterNames(sigIndex);
386 for (int i = 0; i < parameterNames.size(); ++i) {
387 const QString param = QString::fromUtf8(parameterNames.at(i));
388 if (param.isEmpty())
389 unnamedParameter = true;
390 else if (unnamedParameter) {
391 COMPILE_EXCEPTION(binding, tr("Signal uses unnamed parameter followed by named parameter."));
392 } else if (QV4::Compiler::Codegen::isNameGlobal(param)) {
393 COMPILE_EXCEPTION(binding, tr("Signal parameter \"%1\" hides global variable.").arg(param));
394 }
395 }
396 } else if (isPropertyObserver) {
397 flag = QV4::CompiledData::Binding::IsPropertyObserver;
398 } else {
399 if (notInRevision) {
400 // Try assinging it as a property later
401 if (signalPropertyData)
402 continue;
403
404 const QString &originalPropertyName = stringAt(binding->propertyNameIndex);
405
406 auto *typeRef = resolvedType(obj->inheritedTypeNameIndex);
407 const QQmlType type = typeRef ? typeRef->type() : QQmlType();
408 if (type.isValid()) {
409 COMPILE_EXCEPTION(binding, tr("\"%1.%2\" is not available in %3 %4.%5.")
410 .arg(typeName, originalPropertyName, type.module())
411 .arg(type.version().majorVersion())
412 .arg(type.version().minorVersion()));
413 } else {
415 binding,
416 tr("\"%1.%2\" is not available due to component versioning.")
417 .arg(typeName, originalPropertyName));
418 }
419 }
420
421 // Try to look up the signal parameter names in the object itself
422
423 // build cache if necessary
424 if (customSignals.isEmpty()) {
425 for (const QmlIR::Signal *signal = obj->firstSignal(); signal; signal = signal->next) {
426 const QString &signalName = stringAt(signal->nameIndex);
427 customSignals.insert(signalName, signal->parameterStringList(compiler->stringPool()));
428 }
429
430 for (const QmlIR::Property *property = obj->firstProperty(); property; property = property->next) {
431 const QString propName = stringAt(property->nameIndex());
432 customSignals.insert(propName, QStringList());
433 }
434 }
435
436 QHash<QString, QStringList>::ConstIterator entry = customSignals.constFind(signalName);
437 if (entry == customSignals.constEnd() && !qPropertyName.isEmpty())
438 entry = customSignals.constFind(qPropertyName);
439
440 if (entry == customSignals.constEnd()) {
441 // Can't find even a custom signal, then just don't do anything and try
442 // keeping the binding as a regular property assignment.
443 continue;
444 }
445 }
446
447 // Binding object to signal means connect the signal to the object's default method.
448 if (bindingType == QV4::CompiledData::Binding::Type_Object) {
449 binding->setFlag(QV4::CompiledData::Binding::IsSignalHandlerObject);
450 continue;
451 }
452
453 if (bindingType != QV4::CompiledData::Binding::Type_Script) {
454 if (bindingType < QV4::CompiledData::Binding::Type_Script) {
455 COMPILE_EXCEPTION(binding, tr("Cannot assign a value to a signal (expecting a script to be run)"));
456 } else {
457 COMPILE_EXCEPTION(binding, tr("Incorrectly specified signal assignment"));
458 }
459 }
460
461 binding->setFlag(flag);
462 }
463 return true;
464}
465
466QQmlEnumTypeResolver::QQmlEnumTypeResolver(QQmlTypeCompiler *typeCompiler)
467 : QQmlCompilePass(typeCompiler)
468 , qmlObjects(*typeCompiler->qmlObjects())
469 , propertyCaches(typeCompiler->propertyCaches())
470 , imports(typeCompiler->imports())
471{
472}
473
475{
476 for (int i = 0; i < qmlObjects.size(); ++i) {
477 QQmlPropertyCache::ConstPtr propertyCache = propertyCaches->at(i);
478 if (!propertyCache)
479 continue;
480 const QmlIR::Object *obj = qmlObjects.at(i);
481
482 QQmlPropertyResolver resolver(propertyCache);
483
484 for (QmlIR::Binding *binding = obj->firstBinding(); binding; binding = binding->next) {
485 const QV4::CompiledData::Binding::Flags bindingFlags = binding->flags();
486 if (bindingFlags & QV4::CompiledData::Binding::IsSignalHandlerExpression
487 || bindingFlags & QV4::CompiledData::Binding::IsSignalHandlerObject
488 || bindingFlags & QV4::CompiledData::Binding::IsPropertyObserver)
489 continue;
490
491 if (binding->type() != QV4::CompiledData::Binding::Type_Script)
492 continue;
493
494 const QString propertyName = stringAt(binding->propertyNameIndex);
495 bool notInRevision = false;
496 const QQmlPropertyData *pd = resolver.property(propertyName, &notInRevision);
497 if (!pd || pd->isQList())
498 continue;
499
500 if (!pd->isEnum() && pd->propType().id() != QMetaType::Int)
501 continue;
502
503 if (!tryQualifiedEnumAssignment(obj, propertyCache, pd, binding))
504 return false;
505 }
506 }
507
508 return true;
509}
510
511bool QQmlEnumTypeResolver::assignEnumToBinding(QmlIR::Binding *binding, QStringView, int enumValue)
512{
513 binding->setType(QV4::CompiledData::Binding::Type_Number);
514 // Enum values are integers (for now). This keeps the fold independent of the constant table,
515 // which is not regenerated when an ahead-of-time-compiled unit is loaded and its enum bindings
516 // are folded against the run-time-resolved types.
517 // TODO: Filter out long enums when we get to use them
518 binding->value.resolvedEnumValue = enumValue;
519 binding->setFlag(QV4::CompiledData::Binding::IsResolvedEnum);
520 return true;
521}
522
523bool QQmlEnumTypeResolver::tryQualifiedEnumAssignment(
524 const QmlIR::Object *obj, const QQmlPropertyCache::ConstPtr &propertyCache,
525 const QQmlPropertyData *prop, QmlIR::Binding *binding)
526{
527 bool isIntProp = (prop->propType().id() == QMetaType::Int) && !prop->isEnum();
528 if (!prop->isEnum() && !isIntProp)
529 return true;
530
531 if (!prop->isWritable()
532 && !(binding->hasFlag(QV4::CompiledData::Binding::InitializerForReadOnlyDeclaration))) {
533 COMPILE_EXCEPTION(binding, tr("Invalid property assignment: \"%1\" is a read-only property")
534 .arg(stringAt(binding->propertyNameIndex)));
535 }
536
537 Q_ASSERT(binding->type() == QV4::CompiledData::Binding::Type_Script);
538 const QString string = compiler->bindingAsString(obj, binding->value.compiledScriptIndex);
539 const int dot = QmlIR::qualifiedEnumDot(string);
540 if (dot == -1)
541 return true;
542
543 int dot2 = string.indexOf(QLatin1Char('.'), dot+1);
544 if (dot2 != -1 && dot2 != string.size()-1) {
545 if (!string.at(dot+1).isUpper())
546 return true;
547 if (string.indexOf(QLatin1Char('.'), dot2+1) != -1)
548 return true;
549 }
550
551 QHashedStringRef typeName(string.constData(), dot);
552 const bool isQtObject = (typeName == QLatin1String("Qt"));
553 const QStringView scopedEnumName = (dot2 != -1 ? QStringView{string}.mid(dot + 1, dot2 - dot - 1) : QStringView());
554 // ### consider supporting scoped enums in Qt namespace
555 const QStringView enumValue = QStringView{string}.mid(!isQtObject && dot2 != -1 ? dot2 + 1 : dot + 1);
556
557 if (isIntProp) { // ### C++11 allows enums to be other integral types. Should we support other integral types here?
558 // Allow enum assignment to ints.
559 bool ok;
560 int enumval = evaluateEnum(typeName.toString(), scopedEnumName, enumValue, &ok);
561 if (ok) {
562 if (!assignEnumToBinding(binding, enumValue, enumval))
563 return false;
564 }
565 return true;
566 }
567 QQmlType type;
568 imports->resolveType(compiler->typeLoader(), typeName, &type, nullptr, nullptr);
569
570 if (!type.isValid() && !isQtObject)
571 return true;
572
573 int value = 0;
574 bool ok = false;
575
576 auto *tr = resolvedType(obj->inheritedTypeNameIndex);
577
578 // When these two match, we can short cut the search, unless...
579 bool useFastPath = type.isValid() && tr && tr->type() == type;
580 QMetaProperty mprop;
581 QMetaEnum menum;
582 if (useFastPath) {
583 mprop = propertyCache->firstCppMetaObject()->property(prop->coreIndex());
584 menum = mprop.enumerator();
585 // ...the enumerator merely comes from a related metaobject, but the enum scope does not match
586 // the typename we resolved
587 if (!menum.isScoped() && scopedEnumName.isEmpty() && typeName != QString::fromUtf8(menum.scope()))
588 useFastPath = false;;
589 }
590 if (useFastPath) {
591 QByteArray enumName = enumValue.toUtf8();
592 if (menum.isScoped() && !scopedEnumName.isEmpty() && enumName != scopedEnumName.toUtf8())
593 return true;
594
595 if (mprop.isFlagType()) {
596 value = menum.keysToValue(enumName.constData(), &ok);
597 } else {
598 value = menum.keyToValue(enumName.constData(), &ok);
599 }
600 } else {
601 // Otherwise we have to search the whole type
602 if (type.isValid()) {
603 if (!scopedEnumName.isEmpty()) {
604 value = type.scopedEnumValue(
605 compiler->typeLoader(), scopedEnumName, enumValue, &ok);
606 } else {
607 value = type.enumValue(compiler->typeLoader(), QHashedStringRef(enumValue), &ok);
608 }
609 } else {
610 QByteArray enumName = enumValue.toUtf8();
611 const QMetaObject *metaObject = &Qt::staticMetaObject;
612 for (int ii = metaObject->enumeratorCount() - 1; !ok && ii >= 0; --ii) {
613 QMetaEnum e = metaObject->enumerator(ii);
614 value = e.keyToValue(enumName.constData(), &ok);
615 }
616 }
617 }
618
619 if (!ok)
620 return true;
621
622 return assignEnumToBinding(binding, enumValue, value);
623}
624
625int QQmlEnumTypeResolver::evaluateEnum(const QString &scope, QStringView enumName, QStringView enumValue, bool *ok) const
626{
627 Q_ASSERT_X(ok, "QQmlEnumTypeResolver::evaluateEnum", "ok must not be a null pointer");
628 *ok = false;
629
630 if (scope != QLatin1String("Qt")) {
631 QQmlType type;
632 imports->resolveType(compiler->typeLoader(), scope, &type, nullptr, nullptr);
633 if (!type.isValid())
634 return -1;
635 if (!enumName.isEmpty())
636 return type.scopedEnumValue(compiler->typeLoader(), enumName, enumValue, ok);
637 return type.enumValue(
638 compiler->typeLoader(),
639 QHashedStringRef(enumValue.constData(), enumValue.size()), ok);
640 }
641
642 const QMetaObject *mo = &Qt::staticMetaObject;
643 int i = mo->enumeratorCount();
644 const QByteArray ba = enumValue.toUtf8();
645 while (i--) {
646 int v = mo->enumerator(i).keyToValue(ba.constData(), ok);
647 if (*ok)
648 return v;
649 }
650 return -1;
651}
652
659
661{
662 scanObjectRecursively(/*root object*/0);
663 for (int i = 0; i < qmlObjects.size(); ++i)
664 if (qmlObjects.at(i)->flags & QV4::CompiledData::Object::IsInlineComponentRoot)
665 scanObjectRecursively(i);
666}
667
668void QQmlCustomParserScriptIndexer::scanObjectRecursively(int objectIndex, bool annotateScriptBindings)
669{
670 const QmlIR::Object * const obj = qmlObjects.at(objectIndex);
671 if (!annotateScriptBindings)
672 annotateScriptBindings = customParsers.contains(obj->inheritedTypeNameIndex);
673 for (QmlIR::Binding *binding = obj->firstBinding(); binding; binding = binding->next) {
674 switch (binding->type()) {
675 case QV4::CompiledData::Binding::Type_Script:
676 if (annotateScriptBindings) {
677 binding->stringIndex = compiler->registerString(
678 compiler->bindingAsString(obj, binding->value.compiledScriptIndex));
679 }
680 break;
681 case QV4::CompiledData::Binding::Type_Object:
682 case QV4::CompiledData::Binding::Type_AttachedProperty:
683 case QV4::CompiledData::Binding::Type_GroupedProperty:
684 scanObjectRecursively(binding->value.objectIndex, annotateScriptBindings);
685 break;
686 default:
687 break;
688 }
689 }
690}
691
698
700{
701 for (int i = 0; i < qmlObjects.size(); ++i) {
702 QQmlPropertyCache::ConstPtr propertyCache = propertyCaches->at(i);
703 if (!propertyCache)
704 continue;
705
706 const QmlIR::Object *obj = qmlObjects.at(i);
707
708 QQmlPropertyResolver resolver(propertyCache);
709 const QQmlPropertyData *defaultProperty = obj->indexOfDefaultPropertyOrAlias != -1 ? propertyCache->parent()->defaultProperty() : propertyCache->defaultProperty();
710
711 for (QmlIR::Binding *binding = obj->firstBinding(); binding; binding = binding->next) {
712 if (!binding->isValueBinding())
713 continue;
714 bool notInRevision = false;
715 const QQmlPropertyData *pd = binding->propertyNameIndex != quint32(0) ? resolver.property(stringAt(binding->propertyNameIndex), &notInRevision) : defaultProperty;
716 if (pd && pd->isAlias())
717 binding->setFlag(QV4::CompiledData::Binding::IsBindingToAlias);
718 }
719 }
720}
721
729
731{
732 const QMetaType scriptStringMetaType = QMetaType::fromType<QQmlScriptString>();
733 for (int i = 0; i < qmlObjects.size(); ++i) {
734 QQmlPropertyCache::ConstPtr propertyCache = propertyCaches->at(i);
735 if (!propertyCache)
736 continue;
737
738 const QmlIR::Object *obj = qmlObjects.at(i);
739
740 QQmlPropertyResolver resolver(propertyCache);
741 const QQmlPropertyData *defaultProperty = obj->indexOfDefaultPropertyOrAlias != -1 ? propertyCache->parent()->defaultProperty() : propertyCache->defaultProperty();
742
743 for (QmlIR::Binding *binding = obj->firstBinding(); binding; binding = binding->next) {
744 if (binding->type() != QV4::CompiledData::Binding::Type_Script)
745 continue;
746 bool notInRevision = false;
747 const QQmlPropertyData *pd = binding->propertyNameIndex != quint32(0) ? resolver.property(stringAt(binding->propertyNameIndex), &notInRevision) : defaultProperty;
748 if (!pd || pd->propType() != scriptStringMetaType)
749 continue;
750
751 QString script = compiler->bindingAsString(obj, binding->value.compiledScriptIndex);
752 binding->stringIndex = compiler->registerString(script);
753 }
754 }
755}
756
757template<>
758void QQmlComponentAndAliasResolver<QQmlTypeCompiler>::allocateNamedObjects(
759 QmlIR::Object *object) const
760{
761 object->namedObjectsInComponent.allocate(m_compiler->memoryPool(), m_idToObjectIndex);
762}
763
764template<>
765bool QQmlComponentAndAliasResolver<QQmlTypeCompiler>::markAsComponent(int index) const
766{
767 m_compiler->qmlObjects()->at(index)->flags |= QV4::CompiledData::Object::IsComponent;
768 return true;
769}
770
771template<>
772void QQmlComponentAndAliasResolver<QQmlTypeCompiler>::setObjectId(int index) const
773{
774 m_compiler->qmlObjects()->at(index)->id = m_idToObjectIndex.size();
775}
776
777template<>
778void QQmlComponentAndAliasResolver<QQmlTypeCompiler>::resolveGeneralizedGroupedProperty(
779 const CompiledObject &component, CompiledBinding *binding)
780{
781 Q_UNUSED(component);
782 // We cannot make it fail here. It might be a custom-parsed property
783 const int targetObjectIndex = m_idToObjectIndex.value(binding->propertyNameIndex, -1);
784 if (targetObjectIndex != -1)
785 m_propertyCaches->set(binding->value.objectIndex, m_propertyCaches->at(targetObjectIndex));
786}
787
788/*!
789 \internal
790
791 Attempts to resolve a "deep alias" — an alias whose sub-property path
792 goes through a QObject property, an inline component binding, or another
793 alias. For example: \c{alias foo: target.groupedProp.innerProp}
794
795 First searches the target object's bindings and aliases for \a property,
796 then looks up \a subProperty on the bound/aliased object's property cache.
797 If no binding or alias matches, falls back to looking up \a subProperty
798 on the declared type's property cache.
799
800 On success, updates \a propIdx with the resolved value-type index and
801 returns \c true.
802*/
804 QQmlTypeCompiler *compiler, const QmlIR::Object *targetObject,
805 QStringView property, QStringView subProperty,
806 QQmlPropertyIndex &propIdx, QMetaType targetPropertyType,
807 const QQmlPropertyCacheVector *propertyCaches,
808 const QMap<int, int> &idToObjectIndex)
809{
810 for (auto it = targetObject->bindingsBegin(), end = targetObject->bindingsEnd();
811 it != end; ++it) {
812 auto binding = *it;
813 if (compiler->stringAt(binding.propertyNameIndex) != property)
814 continue;
815 const auto &cache = propertyCaches->at(binding.value.objectIndex);
816 if (!cache)
817 continue;
818 QQmlPropertyResolver resolver(cache);
819 const QQmlPropertyData *pd = resolver.property(subProperty.toString());
820 if (!pd)
821 continue;
822 propIdx = QQmlPropertyIndex(propIdx.coreIndex(), pd->coreIndex());
823 return true;
824 }
825
826 for (auto it = targetObject->aliasesBegin(), end = targetObject->aliasesEnd();
827 it != end; ++it) {
828 auto innerAlias = *it;
829 if (compiler->stringAt(innerAlias.nameIndex()) != property)
830 continue;
831 const int innerObjectIndex = idToObjectIndex.value(innerAlias.idIndex(), -1);
832 if (innerObjectIndex == -1)
833 continue;
834 const auto &cache = propertyCaches->at(innerObjectIndex);
835 if (!cache)
836 continue;
837 QQmlPropertyResolver resolver(cache);
838 const QQmlPropertyData *pd = resolver.property(subProperty.toString());
839 if (!pd)
840 continue;
841 propIdx = QQmlPropertyIndex(propIdx.coreIndex(), pd->coreIndex());
842 return true;
843 }
844
845 const QQmlPropertyCache::ConstPtr typeCache
846 = QQmlMetaType::propertyCacheForType(targetPropertyType);
847 if (typeCache) {
848 const QQmlPropertyResolver resolver(typeCache);
849 const QQmlPropertyData *pd = resolver.property(subProperty.toString());
850 if (pd) {
851 propIdx = QQmlPropertyIndex(propIdx.coreIndex(), pd->coreIndex());
852 return true;
853 }
854 }
855
856 return false;
857}
858
859template<>
860typename QQmlComponentAndAliasResolver<QQmlTypeCompiler>::AliasResolutionResult
861QQmlComponentAndAliasResolver<QQmlTypeCompiler>::resolveAliasesInObject(
862 const CompiledObject &component, int objectIndex,
863 QQmlPropertyCacheAliasCreator<QQmlTypeCompiler> *aliasCacheCreator, QQmlError *error)
864{
865 Q_UNUSED(component);
866
867 const QmlIR::Object *obj = m_compiler->objectAt(objectIndex);
868 if (!obj->aliasCount())
869 return AllAliasesResolved;
870
871 int aliasIndex = 0;
872 int numSkippedAliases = 0;
873 bool hasUnresolvedLocalAliases = false;
874
875 for (const QmlIR::Alias *alias = obj->firstAlias(); alias; alias = alias->next, ++aliasIndex) {
876 if (resolvedAliases.contains(alias)) {
877 ++numSkippedAliases;
878 continue;
879 }
880
881
882 const int idIndex = alias->idIndex();
883 const int targetObjectIndex = m_idToObjectIndex.value(idIndex, -1);
884 if (targetObjectIndex == -1) {
885 *error = qQmlCompileError(
886 alias->referenceLocation(),
887 QQmlComponentAndAliasResolverBase::tr(
888 "Invalid alias reference. Unable to find id \"%1\"")
889 .arg(stringAt(idIndex)));
890 break;
891 }
892
893 const QmlIR::Object *targetObject = m_compiler->objectAt(targetObjectIndex);
894 Q_ASSERT(targetObject->id >= 0);
895 const int resolvedTargetObjectId = targetObject->id;
896
897 const QString aliasPropertyValue = stringAt(alias->propertyNameIndex());
898
899 QStringView property;
900 QStringView subProperty;
901
902 const int propertySeparator = aliasPropertyValue.indexOf(QLatin1Char('.'));
903 if (propertySeparator != -1) {
904 property = QStringView{aliasPropertyValue}.left(propertySeparator);
905 subProperty = QStringView{aliasPropertyValue}.mid(propertySeparator + 1);
906 } else
907 property = QStringView(aliasPropertyValue);
908
909 QQmlPropertyIndex propIdx;
910
911 if (property.isEmpty()) {
912 // Alias points to object. No need to resolve properties.
913 } else {
914 QQmlPropertyCache::ConstPtr targetCache = m_propertyCaches->at(targetObjectIndex);
915 if (!targetCache) {
916 *error = qQmlCompileError(
917 alias->referenceLocation(),
918 QQmlComponentAndAliasResolverBase::tr("Invalid alias target location: %1")
919 .arg(property.toString()));
920 break;
921 }
922
923 QQmlPropertyResolver resolver(targetCache);
924
925 const QQmlPropertyData *targetProperty = resolver.property(
926 property.toString(), nullptr, QQmlPropertyResolver::IgnoreRevision);
927
928 // If it's an alias that we haven't resolved yet, try again later.
929 if (!targetProperty) {
930 bool aliasPointsToOtherAlias = false;
931 auto targetAlias = targetObject->aliasesBegin();
932 for (const auto end = targetObject->aliasesEnd(); targetAlias != end;
933 ++targetAlias) {
934 if (stringAt(targetAlias->nameIndex()) == property) {
935 aliasPointsToOtherAlias = true;
936 break;
937 }
938 }
939 if (aliasPointsToOtherAlias) {
940 if (targetObjectIndex != objectIndex) {
941 // Don't continue, yet. We need to respect the order of objects.
942 return aliasIndex == numSkippedAliases
943 ? NoAliasResolved
944 : SomeAliasesResolved;
945 }
946
947 if (resolvedAliases.contains(targetAlias)) {
948 // Target already resolved. We can set the alias right away.
949 if (!appendAliasToPropertyCache(
950 alias, objectIndex, aliasIndex, -1,
951 resolvedTargetObjectId, targetObjectIndex, aliasCacheCreator,
952 error)) {
953 break;
954 }
955 continue;
956 }
957
958 // Target isn't resolved yet, but it's in the same object.
959 // Continue with the other aliases.
960 // Try again later and resolve the target alias first.
961 ++numSkippedAliases;
962 hasUnresolvedLocalAliases = true;
963 continue;
964 }
965 }
966
967 if (!targetProperty || targetProperty->coreIndex() > 0x0000FFFF) {
968 *error = qQmlCompileError(
969 alias->referenceLocation(),
970 QQmlComponentAndAliasResolverBase::tr("Invalid alias target location: %1")
971 .arg(property.toString()));
972 break;
973 }
974
975 propIdx = QQmlPropertyIndex(targetProperty->coreIndex());
976
977 if (!subProperty.isEmpty()) {
978 const QMetaObject *valueTypeMetaObject = QQmlMetaType::metaObjectForValueType(targetProperty->propType());
979 if (!valueTypeMetaObject) {
980 // could be a deep alias
981 bool isDeepAlias = subProperty.at(0).isLower();
982 if (isDeepAlias) {
983 isDeepAlias = resolveDeepAlias(
984 m_compiler, targetObject, property, subProperty, propIdx,
985 targetProperty->propType(), m_propertyCaches, m_idToObjectIndex);
986 }
987 if (!isDeepAlias) {
988 *error = qQmlCompileError(
989 alias->referenceLocation(),
990 QQmlComponentAndAliasResolverBase::tr(
991 "Invalid alias target location: %1")
992 .arg(subProperty.toString()));
993 break;
994 }
995 } else {
996
997 int valueTypeIndex =
998 valueTypeMetaObject->indexOfProperty(subProperty.toString().toUtf8().constData());
999 if (valueTypeIndex == -1) {
1000 *error = qQmlCompileError(
1001 alias->referenceLocation(),
1002 QQmlComponentAndAliasResolverBase::tr(
1003 "Invalid alias target location: %1")
1004 .arg(subProperty.toString()));
1005 break;
1006 }
1007 Q_ASSERT(valueTypeIndex <= 0x0000FFFF);
1008
1009 propIdx = QQmlPropertyIndex(propIdx.coreIndex(), valueTypeIndex);
1010 }
1011 }
1012 }
1013
1014 if (!appendAliasToPropertyCache(
1015 alias, objectIndex, aliasIndex, propIdx.toEncoded(),
1016 resolvedTargetObjectId, targetObjectIndex, aliasCacheCreator, error)) {
1017 break;
1018 }
1019 }
1020
1021 if (numSkippedAliases == aliasIndex)
1022 return NoAliasResolved;
1023
1024 if (aliasIndex == obj->aliasCount() && !hasUnresolvedLocalAliases)
1025 return AllAliasesResolved;
1026
1027 return SomeAliasesResolved;
1028}
1029
1030QQmlDeferredAndCustomParserBindingScanner::QQmlDeferredAndCustomParserBindingScanner(QQmlTypeCompiler *typeCompiler)
1031 : QQmlCompilePass(typeCompiler)
1032 , qmlObjects(typeCompiler->qmlObjects())
1033 , propertyCaches(typeCompiler->propertyCaches())
1034 , customParsers(typeCompiler->customParserCache())
1035 , _seenObjectWithId(false)
1036{
1037}
1038
1040{
1041 for (int i = 0; i < qmlObjects->size(); ++i) {
1042 if ((qmlObjects->at(i)->flags & QV4::CompiledData::Object::IsInlineComponentRoot)
1043 && !scanObject(i, ScopeDeferred::False)) {
1044 return false;
1045 }
1046 }
1047 return scanObject(/*root object*/0, ScopeDeferred::False);
1048}
1049
1051 int objectIndex, ScopeDeferred scopeDeferred)
1052{
1053 using namespace QV4::CompiledData;
1054
1055 QmlIR::Object *obj = qmlObjects->at(objectIndex);
1056 if (obj->idNameIndex != 0)
1057 _seenObjectWithId = true;
1058
1059 if (obj->flags & Object::IsComponent) {
1060 Q_ASSERT(obj->bindingCount() == 1);
1061 const Binding *componentBinding = obj->firstBinding();
1062 Q_ASSERT(componentBinding->type() == Binding::Type_Object);
1063 // Components are separate from their surrounding scope. They cannot be deferred.
1064 return scanObject(componentBinding->value.objectIndex, ScopeDeferred::False);
1065 }
1066
1067 QQmlPropertyCache::ConstPtr propertyCache = propertyCaches->at(objectIndex);
1068 if (!propertyCache)
1069 return true;
1070
1071 QString defaultPropertyName;
1072 const QQmlPropertyData *defaultProperty = nullptr;
1073 if (obj->indexOfDefaultPropertyOrAlias != -1) {
1074 const QQmlPropertyCache *cache = propertyCache->parent().data();
1075 defaultPropertyName = cache->defaultPropertyName();
1076 defaultProperty = cache->defaultProperty();
1077 } else {
1078 defaultPropertyName = propertyCache->defaultPropertyName();
1079 defaultProperty = propertyCache->defaultProperty();
1080 }
1081
1082 QQmlCustomParser *customParser = customParsers.value(obj->inheritedTypeNameIndex);
1083
1084 QQmlPropertyResolver propertyResolver(propertyCache);
1085
1086 QStringList deferredPropertyNames;
1087 QStringList immediatePropertyNames;
1088 {
1089 const QMetaObject *mo = propertyCache->firstCppMetaObject();
1090 const int deferredNamesIndex = mo->indexOfClassInfo("DeferredPropertyNames");
1091 const int immediateNamesIndex = mo->indexOfClassInfo("ImmediatePropertyNames");
1092 if (deferredNamesIndex != -1) {
1093 if (immediateNamesIndex != -1) {
1094 COMPILE_EXCEPTION(obj, tr("You cannot define both DeferredPropertyNames and "
1095 "ImmediatePropertyNames on the same type."));
1096 }
1097 const QMetaClassInfo classInfo = mo->classInfo(deferredNamesIndex);
1098 deferredPropertyNames = QString::fromUtf8(classInfo.value()).split(u',');
1099 } else if (immediateNamesIndex != -1) {
1100 const QMetaClassInfo classInfo = mo->classInfo(immediateNamesIndex);
1101 immediatePropertyNames = QString::fromUtf8(classInfo.value()).split(u',');
1102
1103 // If the property contains an empty string, all properties shall be deferred.
1104 if (immediatePropertyNames.isEmpty())
1105 immediatePropertyNames.append(QString());
1106 }
1107 }
1108
1109 for (QmlIR::Binding *binding = obj->firstBinding(); binding; binding = binding->next) {
1110 QString name = stringAt(binding->propertyNameIndex);
1111
1112 if (customParser) {
1113 if (binding->type() == Binding::Type_AttachedProperty) {
1114 if (customParser->flags() & QQmlCustomParser::AcceptsAttachedProperties) {
1115 binding->setFlag(Binding::IsCustomParserBinding);
1116 obj->flags |= Object::HasCustomParserBindings;
1117 continue;
1118 }
1119 } else if (QQmlSignalNames::isHandlerName(name)
1120 && !(customParser->flags() & QQmlCustomParser::AcceptsSignalHandlers)) {
1121 obj->flags |= Object::HasCustomParserBindings;
1122 binding->setFlag(Binding::IsCustomParserBinding);
1123 continue;
1124 }
1125 }
1126
1127 const bool hasPropertyData = [&]() {
1128 if (name.isEmpty()) {
1129 name = defaultPropertyName;
1130 if (defaultProperty)
1131 return true;
1132 } else if (name.constData()->isUpper()) {
1133 // Upper case names cannot be custom-parsed unless they are attached properties
1134 // and the custom parser explicitly accepts them. See above for that case.
1135 return false;
1136 } else {
1137 bool notInRevision = false;
1138 if (propertyResolver.property(
1139 name, &notInRevision, QQmlPropertyResolver::CheckRevision)) {
1140 return true;
1141 }
1142 }
1143
1144 if (!customParser)
1145 return false;
1146
1147 const Binding::Flags bindingFlags = binding->flags();
1148 if (bindingFlags & Binding::IsSignalHandlerExpression
1149 || bindingFlags & Binding::IsSignalHandlerObject
1150 || bindingFlags & Binding::IsPropertyObserver) {
1151 // These signal handlers cannot be custom-parsed. We have already established
1152 // that the signal exists.
1153 return false;
1154 }
1155
1156 // If the property isn't found, we may want to custom-parse the binding.
1157 obj->flags |= Object::HasCustomParserBindings;
1158 binding->setFlag(Binding::IsCustomParserBinding);
1159 return false;
1160 }();
1161
1162 bool seenSubObjectWithId = false;
1163 bool isExternal = false;
1164 if (binding->type() >= Binding::Type_Object) {
1165 const bool isOwnProperty = hasPropertyData || binding->isAttachedProperty();
1166 isExternal = !isOwnProperty && binding->isGroupedProperty();
1167 if (isOwnProperty || isExternal) {
1168 qSwap(_seenObjectWithId, seenSubObjectWithId);
1169
1170 // Implicit component wrappers are scope boundaries.
1171 const bool isImplicitComponent
1172 = compiler->implicitComponentForObject(binding->value.objectIndex) != -1;
1173
1174 const ScopeDeferred objectScope =
1175 !isImplicitComponent && (isExternal || scopeDeferred == ScopeDeferred::True)
1176 ? ScopeDeferred::True
1177 : ScopeDeferred::False;
1178 const bool subObjectValid = scanObject(binding->value.objectIndex, objectScope);
1179 qSwap(_seenObjectWithId, seenSubObjectWithId);
1180 if (!subObjectValid)
1181 return false;
1182 _seenObjectWithId |= seenSubObjectWithId;
1183 }
1184 }
1185
1186 bool isDeferred = false;
1187 if (!immediatePropertyNames.isEmpty() && !immediatePropertyNames.contains(name)) {
1188 if (seenSubObjectWithId) {
1189 COMPILE_EXCEPTION(binding, tr("You cannot assign an id to an object assigned "
1190 "to a deferred property."));
1191 }
1192 if (isExternal || !disableInternalDeferredProperties())
1193 isDeferred = true;
1194 } else if (!deferredPropertyNames.isEmpty() && deferredPropertyNames.contains(name)) {
1195 if (!seenSubObjectWithId && binding->type() != Binding::Type_GroupedProperty) {
1196 if (isExternal || !disableInternalDeferredProperties())
1197 isDeferred = true;
1198 }
1199 }
1200
1201 if (binding->type() >= Binding::Type_Object) {
1202 if (isExternal && !isDeferred && !customParser) {
1204 binding, tr("Cannot assign to non-existent property \"%1\"").arg(name));
1205 }
1206 }
1207
1208 if (isDeferred) {
1209 binding->setFlag(Binding::IsDeferredBinding);
1210 obj->flags |= Object::HasDeferredBindings;
1211 }
1212 }
1213
1214 return true;
1215}
1216
1217QT_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