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

source code of qtdeclarative/src/qml/qml/qqmlpropertyvalidator.cpp