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