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