Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qqmltccompilerpieces.cpp
Go to the documentation of this file.
1// Copyright (C) 2022 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
3// Qt-Security score:critical reason:code-generation
4
6
7#include <private/qqmljsutils_p.h>
8
9#include <tuple>
10
11QT_BEGIN_NAMESPACE
12
13using namespace Qt::StringLiterals;
14
15namespace QQmltc {
16
17static QString scopeName(const QQmlJSScope::ConstPtr &scope)
18{
19 Q_ASSERT(scope->isFullyResolved());
20 const auto scopeType = scope->scopeType();
21 if (scopeType == QQmlSA::ScopeType::GroupedPropertyScope
22 || scopeType == QQmlSA::ScopeType::AttachedPropertyScope) {
23 return scope->baseType()->internalName();
24 }
25 return scope->internalName();
26}
27
29 const QQmlJSScope::ConstPtr &type, const QQmlJSMetaProperty &p, const QString &accessor)
30{
31 Q_ASSERT(type->isFullyResolved());
32
33 QStringList prologue;
34 QString value = accessor;
35 QStringList epilogue;
36
37 auto [owner, ownerKind] = QQmlJSScope::ownerOfProperty(type, p.propertyName());
38 Q_ASSERT(owner);
39 Q_ASSERT(owner->isFullyResolved());
40
41 // properties are only visible when we use QML_{NAMESPACE_}EXTENDED
42 if (ownerKind == QQmlJSScope::ExtensionType) {
43 // extensions is a C++-only feature:
44 Q_ASSERT(!owner->isComposite());
45
46 // have to wrap the property into an extension, but we need to figure
47 // out whether the type is QObject-based or not
48 prologue << u"{"_s;
49 const QString extensionObjectName = u"extObject"_s;
50 if (type->accessSemantics() == QQmlJSScope::AccessSemantics::Reference) {
51 // we have a Q_OBJECT. in this case, we call qmlExtendedObject()
52 // function that should return us the extension object. for that we
53 // have to figure out which specific extension we want here
54
55 int extensionIndex = 0;
56 auto cppBase = QQmlJSScope::nonCompositeBaseType(type);
57 for (auto t = cppBase; t; t = t->baseType()) {
58 if (auto [ext, kind] = t->extensionType(); kind != QQmlJSScope::NotExtension) {
59 if (ext->isSameType(owner))
60 break;
61 ++extensionIndex;
62 }
63 }
64
65 prologue << u"static_assert(std::is_base_of<%1, %2>::value);"_s.arg(u"QObject"_s,
66 scopeName(type));
67 prologue << u"auto %1 = qobject_cast<%2 *>(QQmlPrivate::qmlExtendedObject(%3, %4));"_s
68 .arg(extensionObjectName, owner->internalName(), accessor,
69 QString::number(extensionIndex));
70 } else {
71 // we have a Q_GADGET. the assumption for extension types is that we
72 // can reinterpret_cast a Q_GADGET object into an extension type
73 // object and then interact with the extension object right away
74 prologue << u"static_assert(sizeof(%1) == sizeof(%2));"_s.arg(scopeName(type),
75 owner->internalName());
76 prologue << u"static_assert(alignof(%1) == alignof(%2));"_s.arg(scopeName(type),
77 owner->internalName());
78 prologue << u"auto %1 = reinterpret_cast<%2 *>(%3);"_s.arg(
79 extensionObjectName, owner->internalName(), accessor);
80 }
81 prologue << u"Q_ASSERT(%1);"_s.arg(extensionObjectName);
82 value = extensionObjectName;
83 epilogue << u"}"_s;
84 }
85
86 return { prologue, value, epilogue };
87}
88
89void CodeGenerator::generate_assignToListProperty(
90 QStringList *block, const QQmlJSScope::ConstPtr &type, const QQmlJSMetaProperty &p,
91 const QStringList &values, const QString &accessor, QString &qmlListVarName)
92{
93 Q_UNUSED(type); // might be needed
94 const bool populateLocalListProperty = qmlListVarName.isEmpty();
95
96 if (populateLocalListProperty) {
97 auto [extensionPrologue, extensionAccessor, extensionEpilogue] =
98 CodeGenerator::wrap_extensionType(
99 type, p, CodeGenerator::wrap_privateClass(accessor, p));
100
101 qmlListVarName = u"listprop_%1"_s.arg(p.propertyName());
102 const QQmlJSScope::ConstPtr elementType = p.type()->elementType();
103 *block << u"QQmlListProperty<%1> %2;"_s.arg(elementType->internalName(), qmlListVarName);
104 *block << extensionPrologue;
105 *block << u"%1 = %2->%3();"_s.arg(qmlListVarName, extensionAccessor, p.read());
106 *block << extensionEpilogue;
107 }
108 for (const QString &value : values) {
109 auto [prologue, wrappedValue, epilogue] =
110 CodeGenerator::wrap_mismatchingTypeConversion(p, value);
111 *block << prologue;
112 *block << u"%1.append(std::addressof(%1), %2);"_s.arg(qmlListVarName, wrappedValue);
113 *block << epilogue;
114 }
115}
116
117void CodeGenerator::generate_assignToProperty(QStringList *block, const QQmlJSScope::ConstPtr &type,
118 const QQmlJSMetaProperty &p, const QString &value,
119 const QString &accessor, bool constructFromQObject)
120{
121 Q_ASSERT(block);
122 Q_ASSERT(p.isValid());
123 Q_ASSERT(!p.isList()); // NB: this code does not handle list properties
124
125 const QString propertyName = p.propertyName();
126
127 if (type->hasOwnProperty(p.propertyName()) && !p.isAlias()) {
128 Q_ASSERT(!p.isPrivate());
129 // this object is compiled, so just assignment should work fine
130 auto [prologue, wrappedValue, epilogue] =
131 CodeGenerator::wrap_mismatchingTypeConversion(p, value);
132 *block += prologue;
133 *block << u"%1->m_%2 = %3;"_s.arg(accessor, propertyName, wrappedValue);
134 *block += epilogue;
135 } else if (QString propertySetter = p.write(); !propertySetter.isEmpty()
136 && !QQmlJSUtils::bindablePropertyHasDefaultAccessor(
137 p, QQmlJSUtils::PropertyAccessor_Write)) {
138 // there's a WRITE function
139 auto [prologue, wrappedValue, epilogue] =
140 CodeGenerator::wrap_mismatchingTypeConversion(p, value);
141 *block += prologue;
142
143 auto [extensionPrologue, extensionAccessor, extensionEpilogue] =
144 CodeGenerator::wrap_extensionType(
145 type, p, CodeGenerator::wrap_privateClass(accessor, p));
146 *block += extensionPrologue;
147 *block << extensionAccessor + u"->" + propertySetter + u"(" + wrappedValue + u");";
148 *block += extensionEpilogue;
149
150 *block += epilogue;
151 } else {
152 // this property is weird, fallback to `setProperty`
153 *block << u"{ // couldn't find property setter, so using QObject::setProperty()"_s;
154 QString val = value;
155 if (constructFromQObject) {
156 const QString variantName = u"var_" + propertyName;
157 *block << u"QVariant " + variantName + u";";
158 *block << variantName + u".setValue(" + val + u");";
159 val = u"std::move(" + variantName + u")";
160 }
161 // NB: setProperty() would handle private properties
162 *block << accessor + u"->setProperty(\"" + propertyName + u"\", " + val + u");";
163 *block << u"}"_s;
164 }
165}
166
167void CodeGenerator::generate_setIdValue(QStringList *block, const QString &context, qsizetype index,
168 const QString &accessor, const QString &idString)
169{
170 Q_ASSERT(index >= 0);
171 *block << u"Q_ASSERT(%1 < %2->numIdValues()); // make sure Id is in bounds"_s.arg(index).arg(
172 context);
173 *block << u"%1->setIdValue(%2 /* id: %3 */, %4);"_s.arg(context, QString::number(index),
174 idString, accessor);
175}
176
177void CodeGenerator::generate_callExecuteRuntimeFunction(
178 QStringList *block, const QString &url, QQmlJSMetaMethod::AbsoluteFunctionIndex index,
179 const QString &accessor, const QString &returnType, const QList<Variable> &parameters)
180{
181 *block << u"QQmlEnginePrivate *e = QQmlEnginePrivate::get(qmlEngine(" + accessor + u"));";
182
183 const QString returnValueName = u"_ret"_s;
184 QStringList args;
185 args.reserve(parameters.size() + 1);
186 QStringList types;
187 types.reserve(parameters.size() + 1);
188 if (returnType == u"void"_s) {
189 args << u"nullptr"_s;
190 types << u"QMetaType::fromType<void>()"_s;
191 } else {
192 *block << returnType + u" " + returnValueName + u"{};"; // TYPE _ret{};
193 args << u"const_cast<void *>(reinterpret_cast<const void *>(std::addressof("
194 + returnValueName + u")))";
195 types << u"QMetaType::fromType<std::decay_t<" + returnType + u">>()";
196 }
197
198 for (const Variable &p : parameters) {
199 args << u"const_cast<void *>(reinterpret_cast<const void *>(std::addressof(" + p.name
200 + u")))";
201 types << u"QMetaType::fromType<std::decay_t<" + p.cppType + u">>()";
202 }
203
204 *block << u"void *_a[] = { " + args.join(u", "_s) + u" };";
205 *block << u"QMetaType _t[] = { " + types.join(u", "_s) + u" };";
206 const qsizetype runtimeIndex = static_cast<qsizetype>(index);
207 Q_ASSERT(runtimeIndex >= 0);
208 *block << u"e->executeRuntimeFunction(" + url + u", " + QString::number(runtimeIndex) + u", "
209 + accessor + u", " + QString::number(parameters.size()) + u", _a, _t);";
210 if (returnType != u"void"_s)
211 *block << u"return " + returnValueName + u";";
212}
213
214void CodeGenerator::generate_createBindingOnProperty(
215 QStringList *block, const QString &unitVarName, const QString &scope,
216 qsizetype functionIndex, const QString &target, const QQmlJSScope::ConstPtr &targetType,
217 int propertyIndex, const QQmlJSMetaProperty &p, int valueTypeIndex,
218 const QString &subTarget)
219{
220 const QString propName = QQmlJSUtils::toLiteral(p.propertyName());
221 if (QString bindable = p.bindable(); !bindable.isEmpty()) {
222 // TODO: test that private properties are bindable
223 QString createBindingForBindable = u"QT_PREPEND_NAMESPACE(QQmlCppBinding)::"
224 u"createBindingForBindable("
225 + unitVarName + u", " + scope + u", " + QString::number(functionIndex) + u", "
226 + target + u", " + QString::number(propertyIndex) + u", "
227 + QString::number(valueTypeIndex) + u", " + propName + u")";
228 const QString accessor = (valueTypeIndex == -1) ? target : subTarget;
229
230 QStringList prologue;
231 QString value = CodeGenerator::wrap_privateClass(accessor, p);
232 QStringList epilogue;
233 if (targetType) {
234 auto [pro, v, epi] = CodeGenerator::wrap_extensionType(targetType, p, value);
235 std::tie(prologue, value, epilogue) = std::make_tuple(pro, v, epi);
236 }
237
238 *block += prologue;
239 *block << u"if (!initializedCache.contains(QStringLiteral(\"%1\")))"_s.arg(p.propertyName());
240 *block << u" "_s + value + u"->" + bindable + u"().setBinding(" + createBindingForBindable + u");";
241 *block += epilogue;
242 } else {
243 QString createBindingForNonBindable =
244 u" "_s
245 + u"QT_PREPEND_NAMESPACE(QQmlCppBinding)::createBindingForNonBindable(" + unitVarName
246 + u", " + scope + u", " + QString::number(functionIndex) + u", " + target + u", "
247 + QString::number(propertyIndex) + u", " + QString::number(valueTypeIndex) + u", "
248 + propName + u")";
249 // Note: in this version, the binding is set implicitly
250 *block << u"if (!initializedCache.contains(QStringLiteral(\"%1\")))"_s.arg(p.propertyName());
251 *block << createBindingForNonBindable + u";";
252 }
253}
254
255static QByteArray toLiteral(const QByteArray &utf8)
256{
257 return QQmlJSUtils::toLiteral<QByteArray, char, QByteArrayView>(utf8);
258}
259
260static QString serializeTranslation(const QQmlTranslation::QsTrIdData &data)
261{
262 return QStringLiteral(R"(QQmlTranslation(QQmlTranslation::QsTrIdData(
263 %1,
264 %4)))")
265 .arg(toLiteral(data.id()))
266 .arg(data.number());
268
269static QString serializeTranslation(const QQmlTranslation::QsTrData &data)
270{
271 return QStringLiteral(R"(QQmlTranslation(QQmlTranslation::QsTrData(
272 %1,
273 %2,
274 %3,
275 %4)))")
276 .arg(toLiteral(data.context()),
277 toLiteral(data.text()),
278 toLiteral(data.comment()))
279 .arg(data.number());
280}
281
282static QString serializeTranslation(const QQmlTranslation &translation)
283{
284 return translation.visit(
285 [](auto &&arg) -> QString {
286 using T = std::decay_t<decltype(arg)>;
287 if constexpr (!std::is_same_v<T, std::nullptr_t>)
288 return serializeTranslation(arg);
289 else {
290 Q_ASSERT_X(false, "QQmlTranslation", "Uninitialized Translation");
291 return {};
292 }
293 });
294}
295
296void CodeGenerator::generate_createTranslationBindingOnProperty(
297 QStringList *block, const TranslationBindingInfo &info)
298{
299 const QString propName = QQmlJSUtils::toLiteral(info.property.propertyName());
300 const QString qqmlTranslation = serializeTranslation(info.data);
301
302 if (QString bindable = info.property.bindable(); !bindable.isEmpty()) {
303 // TODO: test that private properties are bindable
304 QString createTranslationCode = uR"(QT_PREPEND_NAMESPACE(QQmlCppBinding)
305 ::createTranslationBindingForBindable(%1, %2, %3, %4, %5))"_s
306 .arg(info.unitVarName, info.target)
307 .arg(info.propertyIndex)
308 .arg(qqmlTranslation, propName);
309
310 *block << CodeGenerator::wrap_privateClass(info.target, info.property) + u"->"
311 + bindable + u"().setBinding(" + createTranslationCode + u");";
312 } else {
313 QString locationString =
314 u"QQmlSourceLocation(%1->fileName(), %2, %3)"_s.arg(info.unitVarName)
315 .arg(info.line)
316 .arg(info.column);
317 QString createTranslationCode = uR"(QT_PREPEND_NAMESPACE(QQmlCppBinding)
318 ::createTranslationBindingForNonBindable(
319 %1, //unit
320 %2, //location
321 %3, //translationData
322 %4, //thisObject
323 %5, //bindingTarget
324 %6, //metaPropertyIndex
325 %7, //propertyName
326 %8) //valueTypePropertyIndex
327 )"_s.arg(info.unitVarName,locationString,qqmlTranslation,info.scope,info.target)
328 .arg(info.propertyIndex)
329 .arg(propName)
330 .arg(info.valueTypeIndex);
331 // Note: in this version, the binding is set implicitly
332 *block << createTranslationCode + u";";
333 }
334}
335
337CodeGenerator::wrap_mismatchingTypeConversion(const QQmlJSMetaProperty &p, QString value)
338{
339 auto isDerivedFromBuiltin = [](const QQmlJSScope::ConstPtr &derived, const QString &builtin) {
340 for (QQmlJSScope::ConstPtr t = derived; t; t = t->baseType()) {
341 if (t->internalName() == builtin)
342 return true;
343 }
344 return false;
345 };
346 QStringList prologue;
347 QStringList epilogue;
348 const QQmlJSScope::ConstPtr propType = p.type();
349 if (isDerivedFromBuiltin(propType, u"QVariant"_s)) {
350 const QString variantName = u"var_" + p.propertyName();
351 prologue << u"{ // accepts QVariant"_s;
352 prologue << u"QVariant " + variantName + u";";
353 prologue << variantName + u".setValue(" + value + u");";
354 epilogue << u"}"_s;
355 value = u"std::move(" + variantName + u")";
356 } else if (isDerivedFromBuiltin(propType, u"QJSValue"_s)) {
357 const QString jsvalueName = u"jsvalue_" + p.propertyName();
358 prologue << u"{ // accepts QJSValue"_s;
359 // Note: do not assume we have the engine, acquire it from `this`
360 prologue << u"auto e = qmlEngine(this);"_s;
361 prologue << u"QJSValue " + jsvalueName + u" = e->toScriptValue(" + value + u");";
362 epilogue << u"}"_s;
363 value = u"std::move(" + jsvalueName + u")";
364 }
365 return { prologue, value, epilogue };
366}
367
368QString CodeGenerator::wrap_privateClass(const QString &accessor, const QQmlJSMetaProperty &p)
369{
370 if (!p.isPrivate())
371 return accessor;
372
373 const QString privateType = p.privateClass();
374 return u"static_cast<" + privateType + u" *>(QObjectPrivate::get(" + accessor + u"))";
375}
376
377QString CodeGenerator::wrap_addressof(const QString &addressed)
378{
379 return u"std::addressof(" + addressed + u")";
380}
381
382} // namespace QQmltc
383
384QT_END_NAMESPACE
static QString scopeName(const QQmlJSScope::ConstPtr &scope)
static QByteArray toLiteral(const QByteArray &utf8)
static QString serializeTranslation(const QQmlTranslation::QsTrIdData &data)