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 | static bool isPrimitiveType(int typeId) |
53 | { |
54 | switch (typeId) { |
55 | #define HANDLE_PRIMITIVE(Type, id, T) \ |
56 | case QMetaType::Type: |
57 | QT_FOR_EACH_STATIC_PRIMITIVE_TYPE(HANDLE_PRIMITIVE); |
58 | #undef HANDLE_PRIMITIVE |
59 | return true; |
60 | default: |
61 | return false; |
62 | } |
63 | } |
64 | |
65 | QQmlPropertyValidator::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 | |
76 | QVector<QQmlError> QQmlPropertyValidator::validate() |
77 | { |
78 | return validateObject(/*root object*/objectIndex: 0, /*instantiatingBinding*/nullptr); |
79 | } |
80 | |
81 | typedef QVarLengthArray<const QV4::CompiledData::Binding *, 8> GroupPropertyVector; |
82 | |
83 | struct 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 | |
99 | QVector<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: ¬InRevision); |
186 | } else { |
187 | pd = propertyResolver.property(name, notInRevision: ¬InRevision, |
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: ¬InRevision); |
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 | |
360 | QQmlError 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 | */ |
650 | bool 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 | |
675 | QVector<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 | |
682 | QVector<QQmlError> QQmlPropertyValidator::recordError(const QQmlError &error) const |
683 | { |
684 | QVector<QQmlError> errors; |
685 | errors.append(t: error); |
686 | return errors; |
687 | } |
688 | |
689 | QQmlError 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 | |
798 | QT_END_NAMESPACE |
799 | |