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