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