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

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