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