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