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
qqmlpropertyvalidator.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
7
8#include <private/qqmlcustomparser_p.h>
9#include <private/qqmlglobal_p.h>
10#include <private/qqmlirbuilder_p.h>
11#include <private/qqmlpropertycachecreator_p.h>
12#include <private/qqmlpropertyresolver_p.h>
13#include <private/qqmlstringconverters_p.h>
14#include <private/qqmlsignalnames_p.h>
15
16#include <QtCore/qdatetime.h>
17
19
20static bool isPrimitiveType(QMetaType metaType)
21{
22 switch (metaType.id()) {
23#define HANDLE_PRIMITIVE(Type, id, T)
24 case QMetaType::Type:
25QT_FOR_EACH_STATIC_PRIMITIVE_TYPE(HANDLE_PRIMITIVE);
26#undef HANDLE_PRIMITIVE
27 return true;
28 default:
29 return false;
30 }
31}
32
33QQmlPropertyValidator::QQmlPropertyValidator(
34 QQmlTypeLoader *typeLoader, const QQmlImports *imports,
35 const QQmlRefPointer<QV4::CompiledData::CompilationUnit> &compilationUnit)
36 : m_typeLoader(typeLoader)
37 , compilationUnit(compilationUnit)
38 , imports(imports)
39 , qmlUnit(compilationUnit->unitData())
40 , propertyCaches(compilationUnit->propertyCaches)
41 , bindingPropertyDataPerObject(&compilationUnit->bindingPropertyDataPerObject)
42{
43 bindingPropertyDataPerObject->resize(compilationUnit->objectCount());
44}
45
46QList<QQmlError> QQmlPropertyValidator::validate()
47{
48 return validateObject(/*root object*/0, /*instantiatingBinding*/nullptr);
49}
50
52
54{
55 bool operator()(quint32 name, const QV4::CompiledData::Binding *binding) const
56 {
57 return name < binding->propertyNameIndex;
58 }
59 bool operator()(const QV4::CompiledData::Binding *binding, quint32 name) const
60 {
61 return binding->propertyNameIndex < name;
62 }
63 bool operator()(const QV4::CompiledData::Binding *lhs, const QV4::CompiledData::Binding *rhs) const
64 {
65 return lhs->propertyNameIndex < rhs->propertyNameIndex;
66 }
67};
68
69QList<QQmlError> QQmlPropertyValidator::validateObject(
70 int objectIndex, const QV4::CompiledData::Binding *instantiatingBinding,
71 bool populatingValueTypeGroupedProperty,
72 QQmlPropertyResolver::RevisionCheck checkRevision) const
73{
74 // Implicit component wrappers (>= objectCount) have no CU object.
75 // Validate the child object instead.
76 if (objectIndex >= compilationUnit->objectCount()) {
77 return validateObject(
78 compilationUnit->resolvedIndex(objectIndex), instantiatingBinding,
79 populatingValueTypeGroupedProperty, checkRevision);
80 }
81
82 const QV4::CompiledData::Object *obj = compilationUnit->objectAt(objectIndex);
83 for (auto it = obj->inlineComponentsBegin(); it != obj->inlineComponentsEnd(); ++it) {
84 const auto errors = validateObject(it->objectIndex, /* instantiatingBinding*/ nullptr);
85 if (!errors.isEmpty())
86 return errors;
87 }
88
89 if (obj->hasFlag(QV4::CompiledData::Object::IsComponent)
90 && !obj->hasFlag(QV4::CompiledData::Object::IsInlineComponentRoot)) {
91 Q_ASSERT(obj->nBindings == 1);
92 const QV4::CompiledData::Binding *componentBinding = obj->bindingTable();
93 Q_ASSERT(componentBinding->type() == QV4::CompiledData::Binding::Type_Object);
94 return validateObject(componentBinding->value.objectIndex, componentBinding);
95 }
96
97 QQmlPropertyCache::ConstPtr propertyCache = propertyCaches.at(objectIndex);
98 if (!propertyCache)
99 return QList<QQmlError>();
100
101 QQmlCustomParser *customParser = nullptr;
102 if (auto typeRef = resolvedType(obj->inheritedTypeNameIndex)) {
103
104 // This binding instantiates a separate object. The separate object can have an ID and its
105 // own grouped properties even if it's then assigned to a value type, for example a 'var', or
106 // anything with an invokable ctor taking a QObject*.
107 populatingValueTypeGroupedProperty = false;
108
109 const auto type = typeRef->type();
110 if (type.isValid())
111 customParser = type.customParser();
112 }
113
114 QList<const QV4::CompiledData::Binding*> customBindings;
115
116 // Collect grouped properties first for sanity checking
117 // vector values are sorted by property name string index.
118 GroupedPropertyVector groupedProperties;
119 const QV4::CompiledData::Binding *binding = obj->bindingTable();
120 for (quint32 i = 0; i < obj->nBindings; ++i, ++binding) {
121 if (!binding->isGroupedProperty())
122 continue;
123
124 if (binding->hasFlag(QV4::CompiledData::Binding::IsOnAssignment))
125 continue;
126
127 if (populatingValueTypeGroupedProperty) {
128 return recordError(binding->location, tr("Property assignment expected"));
129 }
130
131 GroupedPropertyVector::const_iterator pos = std::lower_bound(groupedProperties.constBegin(), groupedProperties.constEnd(), binding->propertyNameIndex, BindingFinder());
132 groupedProperties.insert(pos, binding);
133 }
134
135 QQmlPropertyResolver propertyResolver(propertyCache);
136
137 QString defaultPropertyName;
138 const QQmlPropertyData *defaultProperty = nullptr;
139 if (obj->indexOfDefaultPropertyOrAlias != -1) {
140 const QQmlPropertyCache *cache = propertyCache->parent().data();
141 defaultPropertyName = cache->defaultPropertyName();
142 defaultProperty = cache->defaultProperty();
143 } else {
144 defaultPropertyName = propertyCache->defaultPropertyName();
145 defaultProperty = propertyCache->defaultProperty();
146 }
147
148 QV4::CompiledData::BindingPropertyData collectedBindingPropertyData(obj->nBindings);
149
150 binding = obj->bindingTable();
151 for (quint32 i = 0; i < obj->nBindings; ++i, ++binding) {
152 QString name = stringAt(binding->propertyNameIndex);
153 const QV4::CompiledData::Binding::Type bindingType = binding->type();
154 const QV4::CompiledData::Binding::Flags bindingFlags = binding->flags();
155
156 if (customParser) {
157 if (bindingType == QV4::CompiledData::Binding::Type_AttachedProperty) {
158 if (customParser->flags() & QQmlCustomParser::AcceptsAttachedProperties) {
159 customBindings << binding;
160 continue;
161 }
162 } else if (QQmlSignalNames::isHandlerName(name)
163 && !(customParser->flags() & QQmlCustomParser::AcceptsSignalHandlers)) {
164 customBindings << binding;
165 continue;
166 }
167 }
168
169 bool bindingToDefaultProperty = false;
170 bool isGroupedProperty = instantiatingBinding
171 && instantiatingBinding->type() == QV4::CompiledData::Binding::Type_GroupedProperty;
172
173 bool notInRevision = false;
174 const QQmlPropertyData *pd = nullptr;
175 if (!name.isEmpty()) {
176 if (bindingFlags & QV4::CompiledData::Binding::IsSignalHandlerExpression
177 || bindingFlags & QV4::CompiledData::Binding::IsSignalHandlerObject) {
178 pd = propertyResolver.signal(
179 *QQmlSignalNames::anyHandlerNameToSignalName(name), &notInRevision,
180 checkRevision);
181 } else if (bindingFlags & QV4::CompiledData::Binding::IsPropertyObserver) {
182 pd = propertyResolver.property(
183 *QQmlSignalNames::changedHandlerNameToPropertyName(name), &notInRevision,
184 checkRevision);
185 } else {
186 pd = propertyResolver.property(name, &notInRevision, checkRevision);
187 }
188
189 if (notInRevision) {
190 QString typeName = stringAt(obj->inheritedTypeNameIndex);
191 if (auto *objectType = resolvedType(obj->inheritedTypeNameIndex)) {
192 const auto type = objectType->type();
193 if (type.isValid()) {
194 const auto version = objectType->version();
195 return recordError(binding->location,
196 tr("\"%1.%2\" is not available in %3 %4.%5.")
197 .arg(typeName, name, type.module(),
198 QString::number(version.majorVersion()),
199 QString::number(version.minorVersion())));
200 }
201 } else {
202 return recordError(binding->location,
203 tr("\"%1.%2\" is not available due to component versioning.")
204 .arg(typeName, name));
205 }
206 }
207 } else {
208 if (isGroupedProperty)
209 return recordError(binding->location, tr("Cannot assign a value directly to a grouped property"));
210
211 pd = defaultProperty;
212 name = defaultPropertyName;
213 bindingToDefaultProperty = true;
214 }
215
216 if (pd)
217 collectedBindingPropertyData[i] = pd;
218
219 if (name.constData()->isUpper() && !binding->isAttachedProperty()) {
220 QQmlType type;
221 QQmlImportNamespace *typeNamespace = nullptr;
222 imports->resolveType(
223 m_typeLoader, stringAt(binding->propertyNameIndex),
224 &type, nullptr, &typeNamespace);
225 if (typeNamespace)
226 return recordError(binding->location, tr("Invalid use of namespace"));
227 return recordError(binding->location, tr("Invalid attached object assignment"));
228 }
229
230 if (bindingType >= QV4::CompiledData::Binding::Type_Object
231 && (pd || binding->isAttachedProperty() || binding->isGroupedProperty())) {
232 const bool populatingValueTypeGroupedProperty
233 = pd
234 && QQmlMetaType::metaObjectForValueType(pd->propType())
235 && !binding->hasFlag(QV4::CompiledData::Binding::IsOnAssignment);
236
237 // As this is a sub-object, its properties are qualified. We can ignore revisions.
238 const QList<QQmlError> subObjectValidatorErrors = validateObject(
239 binding->value.objectIndex, binding, populatingValueTypeGroupedProperty,
240 QQmlPropertyResolver::IgnoreRevision);
241
242 if (!subObjectValidatorErrors.isEmpty())
243 return subObjectValidatorErrors;
244 }
245
246 // Signal handlers were resolved and checked earlier in the signal handler conversion pass.
247 if (binding->flags() & (QV4::CompiledData::Binding::IsSignalHandlerExpression
248 | QV4::CompiledData::Binding::IsSignalHandlerObject
249 | QV4::CompiledData::Binding::IsPropertyObserver)) {
250 continue;
251 }
252
253 if ((pd && bindingType == QV4::CompiledData::Binding::Type_AttachedProperty)
254 || (!pd && bindingType == QV4::CompiledData::Binding::Type_GroupedProperty)) {
255 if (instantiatingBinding && (instantiatingBinding->isAttachedProperty()
256 || instantiatingBinding->isGroupedProperty())) {
257 return recordError(
258 binding->location, tr("%1 properties cannot be used here")
259 .arg(bindingType == QV4::CompiledData::Binding::Type_AttachedProperty
260 ? QStringLiteral("Attached")
261 : QStringLiteral("Group")));
262 }
263 continue;
264 } else if (bindingType == QV4::CompiledData::Binding::Type_AttachedProperty) {
265 continue;
266 }
267
268 if (pd) {
269 GroupedPropertyVector::const_iterator assignedGroupedProperty = std::lower_bound(groupedProperties.constBegin(), groupedProperties.constEnd(), binding->propertyNameIndex, BindingFinder());
270 const bool assigningToGroupedProperty = assignedGroupedProperty != groupedProperties.constEnd() && !(binding->propertyNameIndex < (*assignedGroupedProperty)->propertyNameIndex);
271
272 if (!pd->isWritable()
273 && !pd->isQList()
274 && !binding->isGroupedProperty()
275 && !(bindingFlags & QV4::CompiledData::Binding::InitializerForReadOnlyDeclaration)
276 ) {
277
278 if (assigningToGroupedProperty && bindingType < QV4::CompiledData::Binding::Type_Object)
279 return recordError(binding->valueLocation, tr("Cannot assign a value directly to a grouped property"));
280 return recordError(binding->valueLocation, tr("Invalid property assignment: \"%1\" is a read-only property").arg(name));
281 }
282
283 if (!pd->isQList() && (bindingFlags & QV4::CompiledData::Binding::IsListItem)) {
284 QString error;
285 if (pd->propType() == QMetaType::fromType<QQmlScriptString>())
286 error = tr( "Cannot assign multiple values to a script property");
287 else
288 error = tr( "Cannot assign multiple values to a singular property");
289 return recordError(binding->valueLocation, error);
290 }
291
292 if (!bindingToDefaultProperty
293 && !binding->isGroupedProperty()
294 && !(bindingFlags & QV4::CompiledData::Binding::IsOnAssignment)
295 && assigningToGroupedProperty) {
296 QV4::CompiledData::Location loc = binding->valueLocation;
297 if (loc < (*assignedGroupedProperty)->valueLocation)
298 loc = (*assignedGroupedProperty)->valueLocation;
299
300 if (pd && QQmlMetaType::isValueType(pd->propType()))
301 return recordError(loc, tr("Property has already been assigned a value"));
302 return recordError(loc, tr("Cannot assign a value directly to a grouped property"));
303 }
304
305 if (bindingType < QV4::CompiledData::Binding::Type_Script) {
306 QQmlError bindingError = validateLiteralBinding(propertyCache, pd, binding);
307 if (bindingError.isValid())
308 return recordError(bindingError);
309 } else if (bindingType == QV4::CompiledData::Binding::Type_Object) {
310 QQmlError bindingError = validateObjectBinding(pd, name, binding);
311 if (bindingError.isValid())
312 return recordError(bindingError);
313 } else if (binding->isGroupedProperty()) {
314 if (QQmlMetaType::isValueType(pd->propType())) {
315 if (QQmlMetaType::metaObjectForValueType(pd->propType())) {
316 if (!pd->isWritable()) {
317 return recordError(binding->location, tr("Invalid property assignment: \"%1\" is a read-only property").arg(name));
318 }
319 } else {
320 return recordError(binding->location, tr("Invalid grouped property access"));
321 }
322 } else {
323 const QMetaType type = pd->propType();
324 if (isPrimitiveType(type)) {
325 return recordError(
326 binding->location,
327 tr("Invalid grouped property access: Property \"%1\" with primitive type \"%2\".")
328 .arg(name, QString::fromUtf8(type.name()))
329 );
330 }
331
332 if (!QQmlMetaType::propertyCacheForType(type)) {
333 auto mo = type.metaObject();
334 if (!mo) {
335 return recordError(binding->location,
336 tr("Invalid grouped property access: Property \"%1\" with type \"%2\", which is neither a value nor an object type")
337 .arg(name, QString::fromUtf8(type.name()))
338 );
339 }
340 if (QMetaObjectPrivate::get(mo)->flags & DynamicMetaObject) {
341 return recordError(binding->location,
342 tr("Unsupported grouped property access: Property \"%1\" with type \"%2\" has a dynamic meta-object.")
343 .arg(name, QString::fromUtf8(type.name()))
344 );
345 }
346 // fall through, this is okay
347 }
348 }
349 }
350 } else {
351 if (customParser) {
352 customBindings << binding;
353 continue;
354 }
355 if (bindingToDefaultProperty) {
356 return recordError(binding->location, tr("Cannot assign to non-existent default property"));
357 } else {
358 return recordError(binding->location, tr("Cannot assign to non-existent property \"%1\"").arg(name));
359 }
360 }
361 }
362
363 if (obj->idNameIndex) {
364 if (populatingValueTypeGroupedProperty)
365 return recordError(obj->locationOfIdProperty, tr("Invalid use of id property with a value type"));
366
367 bool notInRevision = false;
368 collectedBindingPropertyData
369 << propertyResolver.property(QStringLiteral("id"), &notInRevision, checkRevision);
370 }
371
372 if (customParser && !customBindings.isEmpty()) {
373 customParser->clearErrors();
374 customParser->validator = this;
375 customParser->imports = imports;
376 customParser->verifyBindings(compilationUnit, customBindings);
377 customParser->validator = nullptr;
378 customParser->imports = (QQmlImports*)nullptr;
379 QList<QQmlError> parserErrors = customParser->errors();
380 if (!parserErrors.isEmpty())
381 return parserErrors;
382 }
383
384 (*bindingPropertyDataPerObject)[objectIndex] = collectedBindingPropertyData;
385
386 QList<QQmlError> noError;
387 return noError;
388}
389
390QQmlError QQmlPropertyValidator::validateLiteralBinding(
391 const QQmlPropertyCache::ConstPtr &propertyCache, const QQmlPropertyData *property,
392 const QV4::CompiledData::Binding *binding) const
393{
394 if (property->isQList()) {
395 return qQmlCompileError(binding->valueLocation, tr("Cannot assign primitives to lists"));
396 }
397
398 QQmlError noError;
399
400 if (property->isEnum()) {
401 if (binding->hasFlag(QV4::CompiledData::Binding::IsResolvedEnum))
402 return noError;
403
404 // TODO: For historical reasons you can assign any number to an enum property alias
405 // This can be fixed with an opt-out mechanism, for example a pragma.
406 if (property->isAlias() && binding->isNumberBinding())
407 return noError;
408
409 QString value = compilationUnit->bindingValueAsString(binding);
410 QMetaProperty p = propertyCache->firstCppMetaObject()->property(property->coreIndex());
411 bool ok;
412 if (p.isFlagType()) {
413 p.enumerator().keysToValue(value.toUtf8().constData(), &ok);
414 } else
415 p.enumerator().keyToValue(value.toUtf8().constData(), &ok);
416
417 if (!ok) {
418 return qQmlCompileError(binding->valueLocation, tr("Invalid property assignment: unknown enumeration"));
419 }
420 return noError;
421 }
422
423 auto warnOrError = [&](const QString &error) {
424 return qQmlCompileError(binding->valueLocation, error);
425 };
426
427 const QV4::CompiledData::Binding::Type bindingType = binding->type();
428 const auto isStringBinding = [&]() -> bool {
429 // validateLiteralBinding is not supposed to be used on scripts
430 Q_ASSERT(bindingType != QV4::CompiledData::Binding::Type_Script);
431 return bindingType == QV4::CompiledData::Binding::Type_String;
432 };
433
434 switch (property->propType().id()) {
435 case QMetaType::QVariant:
436 break;
437 case QMetaType::QString: {
438 if (!binding->evaluatesToString()) {
439 return warnOrError(tr("Invalid property assignment: string expected"));
440 }
441 }
442 break;
443 case QMetaType::QStringList: {
444 if (!binding->evaluatesToString()) {
445 return warnOrError(tr("Invalid property assignment: string or string list expected"));
446 }
447 }
448 break;
449 case QMetaType::QByteArray: {
450 if (bindingType != QV4::CompiledData::Binding::Type_String)
451 return warnOrError(tr("Invalid property assignment: byte array expected"));
452 }
453 break;
454 case QMetaType::QUrl: {
455 if (bindingType != QV4::CompiledData::Binding::Type_String)
456 return warnOrError(tr("Invalid property assignment: url expected"));
457 }
458 break;
459 case QMetaType::UInt: {
460 if (bindingType == QV4::CompiledData::Binding::Type_Number) {
461 double d = compilationUnit->bindingValueAsNumber(binding);
462 if (double(uint(d)) == d)
463 return noError;
464 }
465 return warnOrError(tr("Invalid property assignment: unsigned int expected"));
466 }
467 break;
468 case QMetaType::Int: {
469 if (bindingType == QV4::CompiledData::Binding::Type_Number) {
470 double d = compilationUnit->bindingValueAsNumber(binding);
471 if (double(int(d)) == d)
472 return noError;
473 }
474 return warnOrError(tr("Invalid property assignment: int expected"));
475 }
476 break;
477 case QMetaType::LongLong:
478 case QMetaType::ULongLong:
479 case QMetaType::Long:
480 case QMetaType::ULong:
481 case QMetaType::Float:
482 case QMetaType::Double: {
483 if (bindingType != QV4::CompiledData::Binding::Type_Number) {
484 return warnOrError(tr("Invalid property assignment: number expected"));
485 }
486 }
487 break;
488 case QMetaType::QColor: {
489 bool ok = false;
490 if (isStringBinding())
491 QQmlStringConverters::rgbaFromString(compilationUnit->bindingValueAsString(binding), &ok);
492 if (!ok) {
493 return warnOrError(tr("Invalid property assignment: color expected"));
494 }
495 }
496 break;
497#if QT_CONFIG(datestring)
498 case QMetaType::QDate: {
499 bool ok = false;
500 if (isStringBinding())
501 QQmlStringConverters::dateFromString(compilationUnit->bindingValueAsString(binding), &ok);
502 if (!ok) {
503 return warnOrError(tr("Invalid property assignment: date expected"));
504 }
505 }
506 break;
507 case QMetaType::QTime: {
508 bool ok = false;
509 if (isStringBinding())
510 QQmlStringConverters::timeFromString(compilationUnit->bindingValueAsString(binding), &ok);
511 if (!ok) {
512 return warnOrError(tr("Invalid property assignment: time expected"));
513 }
514 }
515 break;
516 case QMetaType::QDateTime: {
517 bool ok = false;
518 if (isStringBinding())
519 QQmlStringConverters::dateTimeFromString(compilationUnit->bindingValueAsString(binding), &ok);
520 if (!ok) {
521 return warnOrError(tr("Invalid property assignment: datetime expected"));
522 }
523 }
524 break;
525#endif // datestring
526 case QMetaType::QPoint: {
527 bool ok = false;
528 if (isStringBinding())
529 QQmlStringConverters::pointFFromString(compilationUnit->bindingValueAsString(binding), &ok);
530 if (!ok) {
531 return warnOrError(tr("Invalid property assignment: point expected"));
532 }
533 }
534 break;
535 case QMetaType::QPointF: {
536 bool ok = false;
537 if (isStringBinding())
538 QQmlStringConverters::pointFFromString(compilationUnit->bindingValueAsString(binding), &ok);
539 if (!ok) {
540 return warnOrError(tr("Invalid property assignment: point expected"));
541 }
542 }
543 break;
544 case QMetaType::QSize: {
545 bool ok = false;
546 if (isStringBinding())
547 QQmlStringConverters::sizeFFromString(compilationUnit->bindingValueAsString(binding), &ok);
548 if (!ok) {
549 return warnOrError(tr("Invalid property assignment: size expected"));
550 }
551 }
552 break;
553 case QMetaType::QSizeF: {
554 bool ok = false;
555 if (isStringBinding())
556 QQmlStringConverters::sizeFFromString(compilationUnit->bindingValueAsString(binding), &ok);
557 if (!ok) {
558 return warnOrError(tr("Invalid property assignment: size expected"));
559 }
560 }
561 break;
562 case QMetaType::QRect: {
563 bool ok = false;
564 if (isStringBinding())
565 QQmlStringConverters::rectFFromString(compilationUnit->bindingValueAsString(binding), &ok);
566 if (!ok) {
567 return warnOrError(tr("Invalid property assignment: rect expected"));
568 }
569 }
570 break;
571 case QMetaType::QRectF: {
572 bool ok = false;
573 if (isStringBinding())
574 QQmlStringConverters::rectFFromString(compilationUnit->bindingValueAsString(binding), &ok);
575 if (!ok) {
576 return warnOrError(tr("Invalid property assignment: point expected"));
577 }
578 }
579 break;
580 case QMetaType::Bool: {
581 if (bindingType != QV4::CompiledData::Binding::Type_Boolean) {
582 return warnOrError(tr("Invalid property assignment: boolean expected"));
583 }
584 }
585 break;
586 case QMetaType::QVector2D:
587 case QMetaType::QVector3D:
588 case QMetaType::QVector4D:
589 case QMetaType::QQuaternion: {
590 auto typeName = [&]() {
591 switch (property->propType().id()) {
592 case QMetaType::QVector2D: return QStringLiteral("2D vector");
593 case QMetaType::QVector3D: return QStringLiteral("3D vector");
594 case QMetaType::QVector4D: return QStringLiteral("4D vector");
595 case QMetaType::QQuaternion: return QStringLiteral("quaternion");
596 default: return QString();
597 }
598 };
599 const QVariant result = QQmlValueTypeProvider::createValueType(
600 compilationUnit->bindingValueAsString(binding),
601 property->propType());
602 if (!result.isValid()) {
603 return warnOrError(tr("Invalid property assignment: %1 expected")
604 .arg(typeName()));
605 }
606 }
607 break;
608 case QMetaType::QRegularExpression:
609 return warnOrError(tr("Invalid property assignment: regular expression expected; use /pattern/ syntax"));
610 default: {
611 // generate single literal value assignment to a list property if required
612 if (property->propType() == QMetaType::fromType<QList<qreal> >()) {
613 if (bindingType != QV4::CompiledData::Binding::Type_Number) {
614 return warnOrError(tr("Invalid property assignment: number or array of numbers expected"));
615 }
616 break;
617 } else if (property->propType() == QMetaType::fromType<QList<int> >()) {
618 bool ok = (bindingType == QV4::CompiledData::Binding::Type_Number);
619 if (ok) {
620 double n = compilationUnit->bindingValueAsNumber(binding);
621 if (double(int(n)) != n)
622 ok = false;
623 }
624 if (!ok)
625 return warnOrError(tr("Invalid property assignment: int or array of ints expected"));
626 break;
627 } else if (property->propType() == QMetaType::fromType<QList<bool> >()) {
628 if (bindingType != QV4::CompiledData::Binding::Type_Boolean) {
629 return warnOrError(tr("Invalid property assignment: bool or array of bools expected"));
630 }
631 break;
632 } else if (property->propType() == QMetaType::fromType<QList<QUrl> >()) {
633 if (bindingType != QV4::CompiledData::Binding::Type_String) {
634 return warnOrError(tr("Invalid property assignment: url or array of urls expected"));
635 }
636 break;
637 } else if (property->propType() == QMetaType::fromType<QList<QString> >()) {
638 if (!binding->evaluatesToString()) {
639 return warnOrError(tr("Invalid property assignment: string or array of strings expected"));
640 }
641 break;
642 } else if (property->propType() == QMetaType::fromType<QJSValue>()) {
643 break;
644 } else if (property->propType() == QMetaType::fromType<QQmlScriptString>()) {
645 break;
646 } else if (property->isQObject()
647 && bindingType == QV4::CompiledData::Binding::Type_Null) {
648 break;
649 } else if (QQmlMetaType::qmlType(property->propType()).canConstructValueType()) {
650 break;
651 }
652
653 return warnOrError(tr("Invalid property assignment: unsupported type \"%1\"").arg(QLatin1StringView(property->propType().name())));
654 }
655 break;
656 }
657 return noError;
658}
659
660/*!
661 Returns true if from can be assigned to a (QObject) property of type
662 to.
663*/
664bool QQmlPropertyValidator::canCoerce(QMetaType to, QQmlPropertyCache::ConstPtr fromMo) const
665{
666 if (QQmlMetaType::canConvert(fromMo, to))
667 return true;
668
669 // if we have an inline component from the current file,
670 // it is not properly registered at this point, as registration
671 // only occurs after the whole file has been validated
672 // Therefore we need to check the ICs here
673 for (const auto &icDatum : std::as_const(compilationUnit->inlineComponentData)) {
674 if (icDatum.qmlType.typeId() != to)
675 continue;
676
677 const auto toMo = compilationUnit->propertyCaches.at(icDatum.objectIndex);
678 for (QQmlPropertyCache::ConstPtr parent = fromMo; parent; parent = parent->parent()) {
679 if (parent == toMo)
680 return true;
681 }
682 return false;
683 }
684
685 return false;
686}
687
688QList<QQmlError> QQmlPropertyValidator::recordError(const QV4::CompiledData::Location &location, const QString &description) const
689{
690 QList<QQmlError> errors;
691 errors.append(qQmlCompileError(location, description));
692 return errors;
693}
694
695QList<QQmlError> QQmlPropertyValidator::recordError(const QQmlError &error) const
696{
697 QList<QQmlError> errors;
698 errors.append(error);
699 return errors;
700}
701
702QQmlError QQmlPropertyValidator::validateObjectBinding(const QQmlPropertyData *property, const QString &propertyName, const QV4::CompiledData::Binding *binding) const
703{
704 QQmlError noError;
705
706 if (binding->hasFlag(QV4::CompiledData::Binding::IsOnAssignment)) {
707 Q_ASSERT(binding->type() == QV4::CompiledData::Binding::Type_Object);
708
709 bool isValueSource = false;
710 bool isPropertyInterceptor = false;
711
712 const QV4::CompiledData::Object *targetObject = compilationUnit->objectAt(binding->value.objectIndex);
713 if (auto *typeRef = resolvedType(targetObject->inheritedTypeNameIndex)) {
714 QQmlPropertyCache::ConstPtr cache = typeRef->createPropertyCache();
715 const QMetaObject *mo = cache ? cache->firstCppMetaObject() : nullptr;
716 QQmlType qmlType;
717 while (mo && !qmlType.isValid()) {
718 qmlType = QQmlMetaType::qmlType(mo);
719 mo = mo->superClass();
720 }
721
722 isValueSource = qmlType.propertyValueSourceCast() != -1;
723 isPropertyInterceptor = qmlType.propertyValueInterceptorCast() != -1;
724 }
725
726 if (!isValueSource && !isPropertyInterceptor) {
727 return qQmlCompileError(
728 binding->valueLocation,
729 tr("\"%1\" cannot operate on \"%2\"")
730 .arg(stringAt(targetObject->inheritedTypeNameIndex), propertyName));
731 }
732
733 return noError;
734 }
735
736 const QMetaType propType = property->propType();
737 const auto rhsType = [&]() {
738 return stringAt(compilationUnit->objectAt(binding->value.objectIndex)
739 ->inheritedTypeNameIndex);
740 };
741
742 if (QQmlMetaType::isInterface(propType)) {
743 // Can only check at instantiation time if the created sub-object successfully casts to the
744 // target interface.
745 return noError;
746 } else if (propType == QMetaType::fromType<QVariant>()
747 || propType == QMetaType::fromType<QJSValue>()) {
748 // We can convert everything to QVariant :)
749 return noError;
750 } else if (property->isQList()) {
751 const QMetaType listType = QQmlMetaType::listValueType(property->propType());
752 if (!QQmlMetaType::isInterface(listType)) {
753 QQmlPropertyCache::ConstPtr source = propertyCaches.at(binding->value.objectIndex);
754
755 if (const int wrapper = compilationUnit->implicitComponentForObject(
756 binding->value.objectIndex); wrapper != -1) {
757 // If the child is wrapped in an implicit component, use the wrapper for coercion.
758 source = propertyCaches.at(wrapper);
759 }
760
761 if (!canCoerce(listType, source)) {
762 const QString expectedTypeName = QString::fromUtf8(listType.name()).remove(QLatin1Char('*'));
763 return qQmlCompileError(binding->valueLocation,
764 tr("Cannot assign object of type \"%1\" to list property \"%2\"; expected \"%3\"")
765 .arg(source->className(), propertyName, expectedTypeName));
766 }
767 }
768 return noError;
769 } else if (binding->hasFlag(QV4::CompiledData::Binding::IsSignalHandlerObject)
770 && property->isFunction()) {
771 return noError;
772 } else if (isPrimitiveType(propType)) {
773 auto typeName = QString::fromUtf8(QMetaType(propType).name());
774 return qQmlCompileError(
775 binding->location,
776 tr("Cannot assign value of type \"%1\" to property \"%2\", expecting \"%3\"")
777 .arg(rhsType(), propertyName, typeName));
778 } else if (propType == QMetaType::fromType<QQmlScriptString>()) {
779 return qQmlCompileError(binding->valueLocation, tr("Invalid property assignment: script expected"));
780 } else if (!QQmlMetaType::isValueType(property->propType())) {
781 QQmlPropertyCache::ConstPtr propertyMetaObject;
782
783 // if we have an inline component from the current file,
784 // it is not properly registered at this point, as registration
785 // only occurs after the whole file has been validated
786 // Therefore we need to check the ICs here
787 for (const auto &icDatum: std::as_const(compilationUnit->inlineComponentData)) {
788 if (icDatum.qmlType.typeId() == property->propType()) {
789 propertyMetaObject = compilationUnit->propertyCaches.at(icDatum.objectIndex);
790 break;
791 }
792 }
793
794 if (!propertyMetaObject) {
795 // We want to use the raw metaObject here as the raw metaobject is the
796 // actual property type before we applied any extensions that might
797 // effect the properties on the type, but don't effect assignability
798 // Not passing a version ensures that we get the raw metaObject.
799 propertyMetaObject = QQmlMetaType::rawPropertyCacheForType(propType);
800 }
801
802 if (propertyMetaObject) {
803 // Will be true if the assigned type inherits propertyMetaObject
804 // Determine isAssignable value
805 bool isAssignable = false;
806 QQmlPropertyCache::ConstPtr source = propertyCaches.at(binding->value.objectIndex);
807
808 if (const int wrapper = compilationUnit->implicitComponentForObject(
809 binding->value.objectIndex); wrapper != -1) {
810 // If the child is wrapped in an implicit component, use the wrapper.
811 source = propertyCaches.at(wrapper);
812 }
813
814 const auto inheritsFrom = [&](const QQmlPropertyCache::ConstPtr &target) {
815 for (QQmlPropertyCache::ConstPtr c = source; c; c = c->parent()) {
816 if (c == target)
817 return true;
818 }
819 return false;
820 };
821
822 isAssignable = inheritsFrom(propertyMetaObject);
823
824 if (!isAssignable && propertyMetaObject->isComposite()) {
825 // a single metatype can map to multiple property caches when there are
826 // multiple engines; rawPropertyCacheForType only returns one of them.
827 const auto candidates = QQmlMetaType::rawCompositePropertyCachesForType(propType);
828 for (const auto &candidate : candidates) {
829 if (inheritsFrom(candidate)) {
830 isAssignable = true;
831 break;
832 }
833 }
834 }
835
836 if (!isAssignable) {
837 return qQmlCompileError(binding->valueLocation, tr("Cannot assign object of type \"%1\" to property of type \"%2\" as the former is neither the same as the latter nor a sub-class of it.")
838 .arg(rhsType()).arg(QLatin1String(property->propType().name())));
839 }
840 } else {
841 return qQmlCompileError(binding->valueLocation, tr("Cannot assign to property of unknown type \"%1\".")
842 .arg(QLatin1String(property->propType().name())));
843 }
844
845 }
846 return noError;
847}
848
849QT_END_NAMESPACE
Combined button and popup list for selecting options.
QVarLengthArray< const QV4::CompiledData::Binding *, 8 > GroupedPropertyVector
static QT_BEGIN_NAMESPACE bool isPrimitiveType(QMetaType metaType)
bool operator()(quint32 name, const QV4::CompiledData::Binding *binding) const
bool operator()(const QV4::CompiledData::Binding *binding, quint32 name) const
bool operator()(const QV4::CompiledData::Binding *lhs, const QV4::CompiledData::Binding *rhs) const