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