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 | |
50 | QT_BEGIN_NAMESPACE |
51 | |
52 | QQmlPropertyValidator::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 | |
63 | QVector<QQmlJS::DiagnosticMessage> QQmlPropertyValidator::validate() |
64 | { |
65 | return validateObject(/*root object*/0, /*instantiatingBinding*/nullptr); |
66 | } |
67 | |
68 | typedef QVarLengthArray<const QV4::CompiledData::Binding *, 8> GroupPropertyVector; |
69 | |
70 | struct 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 | |
86 | QVector<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, ¬InRevision); |
170 | } else { |
171 | pd = propertyResolver.property(name, ¬InRevision, |
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" ), ¬InRevision); |
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 | |
334 | QQmlJS::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 | */ |
624 | bool 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 | |
636 | QVector<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 | |
643 | QVector<QQmlJS::DiagnosticMessage> QQmlPropertyValidator::recordError(const QQmlJS::DiagnosticMessage &error) const |
644 | { |
645 | QVector<QQmlJS::DiagnosticMessage> errors; |
646 | errors.append(error); |
647 | return errors; |
648 | } |
649 | |
650 | QQmlJS::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 | |
733 | QT_END_NAMESPACE |
734 | |