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
qqmljscompiler.cpp
Go to the documentation of this file.
1// Copyright (C) 2020 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:significant
4
6
7#include <private/qqmlirbuilder_p.h>
8#include <private/qqmljsaotirbuilder_p.h>
9#include <private/qqmljsbasicblocks_p.h>
10#include <private/qqmljscodegenerator_p.h>
11#include <private/qqmljscompilerstats_p.h>
12#include <private/qqmljsfunctioninitializer_p.h>
13#include <private/qqmljsimportvisitor_p.h>
14#include <private/qqmljslexer_p.h>
15#include <private/qqmljsloadergenerator_p.h>
16#include <private/qqmljsoptimizations_p.h>
17#include <private/qqmljsparser_p.h>
18#include <private/qqmljsshadowcheck_p.h>
19#include <private/qqmljsstoragegeneralizer_p.h>
20#include <private/qqmljsstorageinitializer_p.h>
21#include <private/qqmljstypepropagator_p.h>
22
23#include <QtCore/qfile.h>
24#include <QtCore/qfileinfo.h>
25#include <QtCore/qloggingcategory.h>
26#include <QtCore/qelapsedtimer.h>
27
28#include <QtQml/private/qqmlsignalnames_p.h>
29
30#include <limits>
31
32QT_BEGIN_NAMESPACE
33
34using namespace Qt::StringLiterals;
35
36Q_LOGGING_CATEGORY(lcAotCompiler, "qt.qml.compiler.aot", QtFatalMsg);
37
38static const int FileScopeCodeIndex = -1;
39
40void QQmlJSCompileError::print()
41{
42 fprintf(stderr, "%s\n", qPrintable(message));
43}
44
45QQmlJSCompileError QQmlJSCompileError::augment(const QString &contextErrorMessage) const
46{
47 QQmlJSCompileError augmented;
48 augmented.message = contextErrorMessage + message;
49 return augmented;
50}
51
52static QString diagnosticErrorMessage(const QString &fileName, const QQmlJS::DiagnosticMessage &m)
53{
54 QString message;
55 message = fileName + QLatin1Char(':') + QString::number(m.loc.startLine) + QLatin1Char(':');
56 if (m.loc.startColumn > 0)
57 message += QString::number(m.loc.startColumn) + QLatin1Char(':');
58
59 if (m.isError())
60 message += QLatin1String(" error: ");
61 else
62 message += QLatin1String(" warning: ");
63 message += m.message;
64 return message;
65}
66
67void QQmlJSCompileError::appendDiagnostic(const QString &inputFileName,
68 const QQmlJS::DiagnosticMessage &diagnostic)
69{
70 if (!message.isEmpty())
71 message += QLatin1Char('\n');
72 message += diagnosticErrorMessage(inputFileName, diagnostic);
73}
74
75void QQmlJSCompileError::appendDiagnostics(const QString &inputFileName,
76 const QList<QQmlJS::DiagnosticMessage> &diagnostics)
77{
78 for (const QQmlJS::DiagnosticMessage &diagnostic: diagnostics)
79 appendDiagnostic(inputFileName, diagnostic);
80}
81
82// Ensure that ListElement objects keep all property assignments in their string form
83static void annotateListElements(QmlIR::Document *document)
84{
85 QStringList listElementNames;
86
87 for (const QV4::CompiledData::Import *import : std::as_const(document->imports)) {
88 const QString uri = document->stringAt(import->uriIndex);
89 if (uri != QStringLiteral("QtQml.Models") && uri != QStringLiteral("QtQuick"))
90 continue;
91
92 QString listElementName = QStringLiteral("ListElement");
93 const QString qualifier = document->stringAt(import->qualifierIndex);
94 if (!qualifier.isEmpty()) {
95 listElementName.prepend(QLatin1Char('.'));
96 listElementName.prepend(qualifier);
97 }
98 listElementNames.append(listElementName);
99 }
100
101 if (listElementNames.isEmpty())
102 return;
103
104 for (QmlIR::Object *object : std::as_const(document->objects)) {
105 if (!listElementNames.contains(document->stringAt(object->inheritedTypeNameIndex)))
106 continue;
107 for (QmlIR::Binding *binding = object->firstBinding(); binding; binding = binding->next) {
108 if (binding->type() != QV4::CompiledData::Binding::Type_Script)
109 continue;
110 binding->stringIndex = document->registerString(object->bindingAsString(document, binding->value.compiledScriptIndex));
111 }
112 }
113}
114
115static bool checkArgumentsObjectUseInSignalHandlers(const QmlIR::Document &doc,
116 QQmlJSCompileError *error)
117{
118 for (QmlIR::Object *object: std::as_const(doc.objects)) {
119 for (auto binding = object->bindingsBegin(); binding != object->bindingsEnd(); ++binding) {
120 if (binding->type() != QV4::CompiledData::Binding::Type_Script)
121 continue;
122 const QString propName = doc.stringAt(binding->propertyNameIndex);
123 if (!QQmlSignalNames::isHandlerName(propName))
124 continue;
125 auto compiledFunction = doc.jsModule.functions.value(object->runtimeFunctionIndices.at(binding->value.compiledScriptIndex));
126 if (!compiledFunction)
127 continue;
128 if (compiledFunction->usesArgumentsObject == QV4::Compiler::Context::UsesArgumentsObject::Used) {
129 error->message = QLatin1Char(':') + QString::number(compiledFunction->line) + QLatin1Char(':');
130 if (compiledFunction->column > 0)
131 error->message += QString::number(compiledFunction->column) + QLatin1Char(':');
132
133 error->message += QLatin1String(" error: The use of eval() or the use of the arguments object in signal handlers is\n"
134 "not supported when compiling qml files ahead of time. That is because it's ambiguous if \n"
135 "any signal parameter is called \"arguments\". Similarly the string passed to eval might use\n"
136 "\"arguments\". Unfortunately we cannot distinguish between it being a parameter or the\n"
137 "JavaScript arguments object at this point.\n"
138 "Consider renaming the parameter of the signal if applicable or moving the code into a\n"
139 "helper function.");
140 return false;
141 }
142 }
143 }
144 return true;
145}
146
148{
149public:
150 BindingOrFunction(const QmlIR::Binding &b) : m_binding(&b) {}
151 BindingOrFunction(const QmlIR::Function &f) : m_function(&f) {}
152
153 friend bool operator<(const BindingOrFunction &lhs, const BindingOrFunction &rhs)
154 {
155 return lhs.index() < rhs.index();
156 }
157
158 const QmlIR::Binding *binding() const { return m_binding; }
159 const QmlIR::Function *function() const { return m_function; }
160
162 {
163 return m_binding
164 ? m_binding->value.compiledScriptIndex
165 : (m_function
166 ? m_function->index
167 : std::numeric_limits<quint32>::max());
168 }
169
170private:
171 const QmlIR::Binding *m_binding = nullptr;
172 const QmlIR::Function *m_function = nullptr;
173};
174
175bool qCompileQmlFile(const QString &inputFileName, const QQmlJSSaveFunction &saveFunction,
176 QQmlJSAotCompiler *aotCompiler, QQmlJSCompileError *error,
177 bool storeSourceLocation, QV4::Compiler::CodegenWarningInterface *wInterface,
178 const QString *fileContents)
179{
180 QmlIR::Document irDocument(QString(), QString(), /*debugMode*/false);
181 return qCompileQmlFile(irDocument, inputFileName, saveFunction, aotCompiler, error,
182 storeSourceLocation, wInterface, fileContents);
183}
184
185bool qCompileQmlFile(QmlIR::Document &irDocument, const QString &inputFileName,
186 const QQmlJSSaveFunction &saveFunction, QQmlJSAotCompiler *aotCompiler,
187 QQmlJSCompileError *error, bool storeSourceLocation,
188 QV4::Compiler::CodegenWarningInterface *wInterface, const QString *fileContents)
189{
190 QString sourceCode;
191
192 if (fileContents != nullptr) {
193 sourceCode = *fileContents;
194 } else {
195 QFile f(inputFileName);
196 if (!f.open(QIODevice::ReadOnly)) {
197 error->message = QLatin1String("Error opening ") + inputFileName + QLatin1Char(':') + f.errorString();
198 return false;
199 }
200 sourceCode = QString::fromUtf8(f.readAll());
201 if (f.error() != QFileDevice::NoError) {
202 error->message = QLatin1String("Error reading from ") + inputFileName + QLatin1Char(':') + f.errorString();
203 return false;
204 }
205 }
206
207 {
208 // For now, only use the AOT IRBuilder when linting
209 std::unique_ptr<QmlIR::IRBuilder> irBuilder = aotCompiler && aotCompiler->isLintCompiler()
210 ? std::make_unique<QQmlJSAOTIRBuilder>() : std::make_unique<QmlIR::IRBuilder>();
211 if (!irBuilder->generateFromQml(sourceCode, inputFileName, &irDocument)) {
212 error->appendDiagnostics(inputFileName, irBuilder->errors);
213 return false;
214 }
215 }
216
217 annotateListElements(&irDocument);
218 QQmlJSAotFunctionMap aotFunctionsByIndex;
219
220 {
221 QmlIR::JSCodeGen v4CodeGen(&irDocument, wInterface, storeSourceLocation);
222
223 if (aotCompiler)
224 aotCompiler->setDocument(&v4CodeGen, &irDocument);
225
226 QHash<QmlIR::Object *, QmlIR::Object *> effectiveScopes;
227 for (QmlIR::Object *object: std::as_const(irDocument.objects)) {
228 if (object->functionsAndExpressions->count == 0 && object->bindingCount() == 0)
229 continue;
230
231 if (!v4CodeGen.generateRuntimeFunctions(object)) {
232 Q_ASSERT(v4CodeGen.hasError());
233 error->appendDiagnostic(inputFileName, v4CodeGen.error());
234 return false;
235 }
236
237 if (!aotCompiler)
238 continue;
239
240 QmlIR::Object *scope = object;
241 for (auto it = effectiveScopes.constFind(scope), end = effectiveScopes.constEnd();
242 it != end; it = effectiveScopes.constFind(scope)) {
243 scope = *it;
244 }
245
246 aotCompiler->setScope(object, scope);
247 aotFunctionsByIndex[FileScopeCodeIndex] = aotCompiler->globalCode();
248
249 std::vector<BindingOrFunction> bindingsAndFunctions;
250 bindingsAndFunctions.reserve(object->bindingCount() + object->functionCount());
251
252 std::copy(object->bindingsBegin(), object->bindingsEnd(),
253 std::back_inserter(bindingsAndFunctions));
254 std::copy(object->functionsBegin(), object->functionsEnd(),
255 std::back_inserter(bindingsAndFunctions));
256
257 QList<QmlIR::CompiledFunctionOrExpression> functionsToCompile;
258 for (QmlIR::CompiledFunctionOrExpression *foe = object->functionsAndExpressions->first;
259 foe; foe = foe->next) {
260 functionsToCompile << *foe;
261 }
262
263 // AOT-compile bindings and functions in the same order as above so that the runtime
264 // class indices match
265 auto contextMap = v4CodeGen.module()->contextMap;
266 std::sort(bindingsAndFunctions.begin(), bindingsAndFunctions.end());
267 std::for_each(bindingsAndFunctions.begin(), bindingsAndFunctions.end(),
268 [&](const BindingOrFunction &bindingOrFunction) {
269 std::variant<QQmlJSAotFunction, QList<QQmlJS::DiagnosticMessage>> result;
270 if (const auto *binding = bindingOrFunction.binding()) {
271 switch (binding->type()) {
272 case QmlIR::Binding::Type_AttachedProperty:
273 case QmlIR::Binding::Type_GroupProperty:
274 effectiveScopes.insert(
275 irDocument.objects.at(binding->value.objectIndex), scope);
276 return;
277 case QmlIR::Binding::Type_Boolean:
278 case QmlIR::Binding::Type_Number:
279 case QmlIR::Binding::Type_String:
280 case QmlIR::Binding::Type_Null:
281 case QmlIR::Binding::Type_Object:
282 case QmlIR::Binding::Type_Translation:
283 case QmlIR::Binding::Type_TranslationById:
284 return;
285 default:
286 break;
287 }
288
289 Q_ASSERT(quint32(functionsToCompile.size()) > binding->value.compiledScriptIndex);
290 const auto &functionToCompile
291 = functionsToCompile[binding->value.compiledScriptIndex];
292 auto *parentNode = functionToCompile.parentNode;
293 Q_ASSERT(parentNode);
294 Q_ASSERT(contextMap.contains(parentNode));
295 QV4::Compiler::Context *context = contextMap.take(parentNode);
296 Q_ASSERT(context);
297
298 auto *node = functionToCompile.node;
299 Q_ASSERT(node);
300
301 if (context->returnsClosure) {
302 QQmlJS::AST::Node *inner
303 = QQmlJS::AST::cast<QQmlJS::AST::ExpressionStatement *>(
304 node)->expression;
305 Q_ASSERT(inner);
306 QV4::Compiler::Context *innerContext = contextMap.take(inner);
307 Q_ASSERT(innerContext);
308 qCDebug(lcAotCompiler) << "Compiling signal handler for"
309 << irDocument.stringAt(binding->propertyNameIndex);
310 std::variant<QQmlJSAotFunction, QList<QQmlJS::DiagnosticMessage>> innerResult
311 = aotCompiler->compileBinding(innerContext, *binding, inner);
312 if (auto *errors = std::get_if<QList<QQmlJS::DiagnosticMessage>>(&innerResult)) {
313 for (const auto &error : std::as_const(*errors)) {
314 qCDebug(lcAotCompiler) << "Compilation failed:"
315 << diagnosticErrorMessage(inputFileName, error);
316 }
317 } else if (auto *func = std::get_if<QQmlJSAotFunction>(&innerResult)) {
318 qCDebug(lcAotCompiler) << "Generated code:" << func->code;
319 aotFunctionsByIndex[innerContext->functionIndex] = *func;
320 }
321 }
322
323 qCDebug(lcAotCompiler) << "Compiling binding for property"
324 << irDocument.stringAt(binding->propertyNameIndex);
325 result = aotCompiler->compileBinding(context, *binding, node);
326 } else if (const auto *function = bindingOrFunction.function()) {
327 if (!aotCompiler->isLintCompiler() && !function->isQmlFunction)
328 return;
329
330 Q_ASSERT(quint32(functionsToCompile.size()) > function->index);
331 auto *node = functionsToCompile[function->index].node;
332 Q_ASSERT(node);
333 Q_ASSERT(contextMap.contains(node));
334 QV4::Compiler::Context *context = contextMap.take(node);
335 Q_ASSERT(context);
336
337 const QString functionName = irDocument.stringAt(function->nameIndex);
338 qCDebug(lcAotCompiler) << "Compiling function" << functionName;
339 result = aotCompiler->compileFunction(context, functionName, node);
340 } else {
341 Q_UNREACHABLE();
342 }
343
344 if (auto *errors = std::get_if<QList<QQmlJS::DiagnosticMessage>>(&result)) {
345 for (const auto &error : std::as_const(*errors)) {
346 qCDebug(lcAotCompiler) << "Compilation failed:"
347 << diagnosticErrorMessage(inputFileName, error);
348 }
349 } else if (auto *func = std::get_if<QQmlJSAotFunction>(&result)) {
350 if (func->skipReason.has_value()) {
351 qCDebug(lcAotCompiler) << "Compilation skipped:" << func->skipReason.value();
352 } else {
353 qCDebug(lcAotCompiler) << "Generated code:" << func->code;
354 auto index = object->runtimeFunctionIndices[bindingOrFunction.index()];
355 aotFunctionsByIndex[index] = *func;
356 }
357 }
358 });
359 }
360
361 if (!checkArgumentsObjectUseInSignalHandlers(irDocument, error)) {
362 *error = error->augment(inputFileName);
363 return false;
364 }
365
366 QmlIR::QmlUnitGenerator generator;
367 irDocument.javaScriptCompilationUnit = v4CodeGen.generateCompilationUnit(/*generate unit*/false);
368 generator.generate(irDocument);
369
370 const quint32 saveFlags
371 = QV4::CompiledData::Unit::StaticData
372 | QV4::CompiledData::Unit::PendingTypeCompilation;
373 QV4::CompiledData::SaveableUnitPointer saveable(
374 irDocument.javaScriptCompilationUnit->unitData(), saveFlags);
375 LookupSignatures sigs = aotCompiler ? aotCompiler->lookupSignatures() : LookupSignatures();
376 if (!saveFunction(saveable, aotFunctionsByIndex, sigs, &error->message))
377 return false;
378 }
379 return true;
380}
381
383 const QString &inputFileName, const QString &inputFileUrl,
384 const QQmlJSSaveFunction &saveFunction, QQmlJSCompileError *error)
385{
386 Q_UNUSED(inputFileUrl);
387
388 QQmlRefPointer<QV4::CompiledData::CompilationUnit> unit;
389
390 QString sourceCode;
391 {
392 QFile f(inputFileName);
393 if (!f.open(QIODevice::ReadOnly)) {
394 error->message = QLatin1String("Error opening ") + inputFileName + QLatin1Char(':') + f.errorString();
395 return false;
396 }
397 sourceCode = QString::fromUtf8(f.readAll());
398 if (f.error() != QFileDevice::NoError) {
399 error->message = QLatin1String("Error reading from ") + inputFileName + QLatin1Char(':') + f.errorString();
400 return false;
401 }
402 }
403
404 const bool isModule = inputFileName.endsWith(QLatin1String(".mjs"));
405 if (isModule) {
406 QList<QQmlJS::DiagnosticMessage> diagnostics;
407 // Precompiled files are relocatable and the final location will be set when loading.
408 QString url;
409 unit = QV4::Compiler::Codegen::compileModule(/*debugMode*/false, url, sourceCode,
410 QDateTime(), &diagnostics);
411 error->appendDiagnostics(inputFileName, diagnostics);
412 if (!unit || !unit->unitData())
413 return false;
414 } else {
415 QmlIR::Document irDocument(QString(), QString(), /*debugMode*/false);
416
417 QQmlJS::Engine *engine = &irDocument.jsParserEngine;
418 QmlIR::ScriptDirectivesCollector directivesCollector(&irDocument);
419 QQmlJS::Directives *oldDirs = engine->directives();
420 engine->setDirectives(&directivesCollector);
421 auto directivesGuard = qScopeGuard([engine, oldDirs]{
422 engine->setDirectives(oldDirs);
423 });
424
425 QQmlJS::AST::Program *program = nullptr;
426
427 {
428 QQmlJS::Lexer lexer(engine);
429 lexer.setCode(sourceCode, /*line*/1, /*parseAsBinding*/false);
430 QQmlJS::Parser parser(engine);
431
432 bool parsed = parser.parseProgram();
433
434 error->appendDiagnostics(inputFileName, parser.diagnosticMessages());
435
436 if (!parsed)
437 return false;
438
439 program = QQmlJS::AST::cast<QQmlJS::AST::Program*>(parser.rootNode());
440 if (!program) {
441 lexer.setCode(QStringLiteral("undefined;"), 1, false);
442 parsed = parser.parseProgram();
443 Q_ASSERT(parsed);
444 program = QQmlJS::AST::cast<QQmlJS::AST::Program*>(parser.rootNode());
445 Q_ASSERT(program);
446 }
447 }
448
449 {
450 QmlIR::JSCodeGen v4CodeGen(&irDocument);
451 v4CodeGen.generateFromProgram(
452 sourceCode, program, &irDocument.jsModule,
453 QV4::Compiler::ContextType::ScriptImportedByQML);
454 if (v4CodeGen.hasError()) {
455 error->appendDiagnostic(inputFileName, v4CodeGen.error());
456 return false;
457 }
458
459 // Precompiled files are relocatable and the final location will be set when loading.
460 Q_ASSERT(irDocument.jsModule.fileName.isEmpty());
461 Q_ASSERT(irDocument.jsModule.finalUrl.isEmpty());
462
463 irDocument.javaScriptCompilationUnit = v4CodeGen.generateCompilationUnit(/*generate unit*/false);
464 QmlIR::QmlUnitGenerator generator;
465 generator.generate(irDocument);
466 unit = std::move(irDocument.javaScriptCompilationUnit);
467 }
468 }
469
470 QQmlJSAotFunctionMap funcs;
471 LookupSignatures sigs;
472 return saveFunction(
473 QV4::CompiledData::SaveableUnitPointer(unit->unitData()), funcs, sigs, &error->message);
474}
475
476static const char *funcHeaderCode = R"(
477 [](const QQmlPrivate::AOTCompiledContext *aotContext, void **argv) {
478Q_UNUSED(aotContext)
479Q_UNUSED(argv)
480)";
481
482static QString wrapString(const QString &s)
483{
484 return "u\"%1\"_s"_L1.arg(s);
485}
486
487static QString typeToString(const QQmlPrivate::AOTLookupValidation::Type &type)
488{
489 bool isComposite = type.isComposite == QQmlPrivate::AOTLookupValidation::IsComposite::Yes;
490 bool isIC = type.isInlineComponent == QQmlPrivate::AOTLookupValidation::IsIC::Yes;
491 return u"Type{ %1, %2, %3, %4, %5 }"_s
492 .arg(wrapString(type.module), wrapString(type.name),
493 wrapString(type.icNameOrExtensionTypeName),
494 isComposite ? "IsComposite::Yes"_L1 : "IsComposite::No"_L1,
495 isIC ? "IsIC::Yes"_L1 : "IsIC::No"_L1);
496};
497
498static QString lookupToString(const QQmlPrivate::AOTLookupValidation::Lookup &lookup)
499{
500 return "Lookup{ %1, %2, %3 }"_L1.arg(typeToString(lookup.base), wrapString(lookup.member),
501 wrapString(lookup.enumName));
502};
503
504static QString signatureToString(const QQmlPrivate::AOTLookupValidation::Signature &signature)
505{
506 if (const auto *p = std::get_if<QQmlPrivate::AOTLookupValidation::PropertySignature>(&signature)) {
507 return u"PropertySignature{ %1, %2 }"_s
508 .arg(typeToString(p->type), QString::number(p->relativeIndex));
509 } else if (const auto *e = std::get_if<QQmlPrivate::AOTLookupValidation::EnumKeySignature>(&signature)) {
510 bool isFlag = e->isFlag == QQmlPrivate::AOTLookupValidation::IsFlag::Yes;
511 return u"EnumKeySignature{ %1, %2 }"_s
512 .arg(QString::number(e->value), isFlag ? "IsFlag::Yes"_L1 : "IsFlag::No"_L1);
513 } else if (const auto *m = std::get_if<QQmlPrivate::AOTLookupValidation::MethodSignature>(&signature)) {
514 QString paramNames = "{ "_L1;
515 for (const auto &paramName : m->paramNames)
516 paramNames += wrapString(paramName) + ", "_L1;
517 paramNames += u'}';
518
519 QString types = "{ "_L1;
520 for (const auto &paramType : m->types)
521 types += typeToString(paramType) + ", "_L1;
522 types += u'}';
523
524 bool isSignal = m->isSignal == QQmlPrivate::AOTLookupValidation::IsSignal::Yes;
525 return u"MethodSignature{ %1, %2, %3, %4 }"_s
526 .arg(paramNames, types, QString::number(m->relativeIndex),
527 isSignal ? "IsSignal::Yes"_L1 : "IsSignal::No"_L1);
528 }
529 Q_UNREACHABLE_RETURN(QString());
530};
531
532static const char *skippedValidationCode = R"(
533bool validateLookupSignatures(QQmlEngine *engine, QV4::CompiledData::CompilationUnit *cu)
534{
535 // AOT validation code not generated (NO_GENERATE_AOT_VALIDATION)
536 Q_UNUSED(engine);
537 Q_UNUSED(cu);
538 return true;
539}
540
541)";
542
543template <typename WriteStr>
545 const WriteStr &writeStr, const LookupSignatures &lookupSignatures, bool noAotValidation)
546{
547 if (noAotValidation) {
548 if (!writeStr(skippedValidationCode))
549 return false;
550 return true;
551 }
552
553 using namespace QQmlPrivate::AOTLookupValidation;
554 if (!writeStr("QQmlPrivate::AOTLookupValidation::LookupSignatures expectedLookupSignatures()\n"
555 "{\n"
556 " using namespace Qt::StringLiterals;\n"
557 " using namespace QQmlPrivate::AOTLookupValidation;\n"
558 " return {\n")) {
559 return false;
560 }
561
562 const auto &signatures = lookupSignatures.asKeyValueRange();
563 QList<std::pair<Lookup, Signature>> sorted{ signatures.begin(), signatures.end() };
564 std::stable_sort(sorted.begin(), sorted.end(), [&](const auto &lhs, const auto &rhs) {
565 const auto &lTypeString = typeToString(lhs.first.base);
566 const auto &rTypeString = typeToString(rhs.first.base);
567 if (lTypeString == rTypeString)
568 return lhs.first.member < rhs.first.member;
569 return lTypeString < rTypeString;
570 });
571
572 for (const auto &[memberlookup, signature] : sorted) {
573 if (!writeStr(" { %1, %2 },\n"_L1
574 .arg(lookupToString(memberlookup), signatureToString(signature))
575 .toLatin1())) {
576 return false;
577 }
578 }
579
580 if (!writeStr(" };\n}\n"))
581 return false;
582
583 const QString validateLookupSignatures = uR"(
584bool validateLookupSignatures(QQmlEngine *engine, QV4::CompiledData::CompilationUnit *cu)
585{
586 enum ValidationState { Pending, Failed, Succeeded };
587 static ValidationState state = Pending;
588 if (state == Failed)
589 return false;
590 if (state == Succeeded)
591 return true;
592 const auto &expectedSignatures = expectedLookupSignatures();
593 for (const auto &[lookup, expectedSignature] : expectedSignatures.asKeyValueRange()) {
594 if (!QQmlPrivate::AOTLookupValidation::validateLookupSignature(engine, cu, lookup, expectedSignature)) {
595 state = Failed;
596 return false;
597 }
598 }
599 state = Succeeded;
600 return true;
601}
602
603)"_s;
604
605 if (!writeStr(validateLookupSignatures.toUtf8()))
606 return false;
607
608 return true;
609}
610
611bool qSaveQmlJSUnitAsCpp(const QString &inputFileName, const QString &outputFileName,
612 const QV4::CompiledData::SaveableUnitPointer &unit,
613 const QQmlJSAotFunctionMap &aotFunctions,
614 const LookupSignatures &lookupSignatures, bool noAotValidation,
615 QString *errorString)
616{
617#if QT_CONFIG(temporaryfile)
618 QSaveFile f(outputFileName);
619#else
620 QFile f(outputFileName);
621#endif
622 if (!f.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
623 *errorString = f.errorString();
624 return false;
625 }
626
627 auto writeStr = [&f, errorString](const QByteArray &data) {
628 if (f.write(data) != data.size()) {
629 *errorString = f.errorString();
630 return false;
631 }
632 return true;
633 };
634
635 if (!writeStr("// "))
636 return false;
637
638 if (!writeStr(inputFileName.toUtf8()))
639 return false;
640
641 if (!writeStr("\n"))
642 return false;
643
644 if (!writeStr("#include <QtQml/qqmlprivate.h>\n"))
645 return false;
646
647 if (!noAotValidation) {
648 if (!writeStr("#include <QtCore/qhash.h>\n"))
649 return false;
650 }
651
652 if (!aotFunctions.isEmpty()) {
653 QStringList includes;
654
655 for (const auto &function : aotFunctions)
656 includes.append(function.includes);
657
658 std::sort(includes.begin(), includes.end());
659 const auto end = std::unique(includes.begin(), includes.end());
660 for (auto it = includes.begin(); it != end; ++it) {
661 if (!writeStr(QStringLiteral("#include <%1>\n").arg(*it).toUtf8()))
662 return false;
663 }
664 }
665
666 if (!writeStr(QByteArrayLiteral("\nnamespace QmlCacheGeneratedCode {\nnamespace ")))
667 return false;
668
669 if (!writeStr(qQmlJSSymbolNamespaceForPath(inputFileName).toUtf8()))
670 return false;
671
672 if (!writeStr(" {\n"))
673 return false;
674
675 if (!generateAotValidationCode(writeStr, lookupSignatures, noAotValidation))
676 return false;
677
678 if (!writeStr(QByteArrayLiteral("extern const unsigned char qmlData alignas(16) [];\n"
679 "extern const unsigned char qmlData alignas(16) [] = {\n")))
680 return false;
681
682 unit.saveToDisk<uchar>([&writeStr](const uchar *begin, quint32 size) {
683 QByteArray hexifiedData;
684 {
685 QTextStream stream(&hexifiedData);
686 const uchar *end = begin + size;
687 stream << Qt::hex;
688 int col = 0;
689 for (const uchar *data = begin; data < end; ++data, ++col) {
690 if (data > begin)
691 stream << ',';
692 if (col % 8 == 0) {
693 stream << '\n';
694 col = 0;
695 }
696 stream << "0x" << *data;
697 }
698 stream << '\n';
699 }
700 return writeStr(hexifiedData);
701 });
702
703
704
705 if (!writeStr("};\n"))
706 return false;
707
708 writeStr(aotFunctions[FileScopeCodeIndex].code.toUtf8().constData());
709 if (aotFunctions.size() <= 1) {
710 // FileScopeCodeIndex is always there, but it may be the only one.
711 writeStr("extern const QQmlPrivate::AOTCompiledFunction aotBuiltFunctions[];\n"
712 "extern const QQmlPrivate::AOTCompiledFunction aotBuiltFunctions[] = { { 0, 0, nullptr, nullptr } };\n");
713 } else {
714 writeStr("extern const QQmlPrivate::AOTCompiledFunction aotBuiltFunctions[];\n"
715 "extern const QQmlPrivate::AOTCompiledFunction aotBuiltFunctions[] = {\n");
716
717 QString footer = QStringLiteral("}\n");
718
719 for (QQmlJSAotFunctionMap::ConstIterator func = aotFunctions.constBegin(),
720 end = aotFunctions.constEnd();
721 func != end; ++func) {
722
723 if (func.key() == FileScopeCodeIndex)
724 continue;
725
726 const QString function = QString::fromUtf8(funcHeaderCode) + func.value().code + footer;
727
728 writeStr(QStringLiteral("{ %1, %2, [](QV4::ExecutableCompilationUnit *contextUnit, "
729 "QMetaType *argTypes) {\n%3}, %4 },")
730 .arg(func.key())
731 .arg(func->numArguments)
732 .arg(func->signature.isEmpty() ? u" Q_UNUSED(contextUnit);\n Q_UNUSED(argTypes);\n"_s : func->signature, function)
733 .toUtf8().constData());
735
736 // Conclude the list with a nullptr
737 writeStr("{ 0, 0, nullptr, nullptr }");
738 writeStr("};\n");
739 }
740
741 if (!writeStr("}\n}\n"))
742 return false;
743
744#if QT_CONFIG(temporaryfile)
745 if (!f.commit()) {
746 *errorString = f.errorString();
747 return false;
748 }
749#endif
750
751 return true;
752}
753
754QQmlJSAotCompiler::QQmlJSAotCompiler(
755 QQmlJSImporter *importer, const QString &resourcePath, const QStringList &qmldirFiles,
756 QQmlJSLogger *logger)
757 : m_typeResolver(importer)
758 , m_resourcePath(resourcePath)
759 , m_qmldirFiles(qmldirFiles)
760 , m_importer(importer)
761 , m_logger(logger)
762{
763}
764
765void QQmlJSAotCompiler::setDocument(
766 const QmlIR::JSCodeGen *codegen, const QmlIR::Document *irDocument)
768 Q_UNUSED(codegen);
769 m_document = irDocument;
770 const QFileInfo resourcePathInfo(m_resourcePath);
771 if (m_logger->filePath().isEmpty())
772 m_logger->setFilePath(resourcePathInfo.fileName());
773 m_logger->setCode(irDocument->code);
774 m_unitGenerator = &irDocument->jsGenerator;
775 QQmlJSImportVisitor visitor(m_importer, m_logger,
776 resourcePathInfo.canonicalPath() + u'/',
777 m_qmldirFiles);
778 m_typeResolver.init(&visitor, irDocument->program);
779}
780
781void QQmlJSAotCompiler::setScope(const QmlIR::Object *object, const QmlIR::Object *scope)
782{
783 m_currentObject = object;
784 m_currentScope = scope;
785}
786
787static bool isStrict(const QmlIR::Document *doc)
788{
789 for (const QmlIR::Pragma *pragma : doc->pragmas) {
790 if (pragma->type == QmlIR::Pragma::Strict)
791 return true;
792 }
793 return false;
795
796QQmlJS::DiagnosticMessage QQmlJSAotCompiler::diagnose(
797 const QString &message, QtMsgType type, const QQmlJS::SourceLocation &location) const
798{
799 if (isStrict(m_document)
800 && (type == QtWarningMsg || type == QtCriticalMsg || type == QtFatalMsg)
801 && m_logger->categorySeverity(qmlCompiler) == QQmlSA::WarningSeverity::Error) {
802 qFatal("%s:%d: (strict mode) %s",
803 qPrintable(QFileInfo(m_resourcePath).fileName()),
804 location.startLine, qPrintable(message));
805 }
806
807 return QQmlJS::DiagnosticMessage {
808 message,
809 type,
810 location
811 };
812}
813
814std::variant<QQmlJSAotFunction, QList<QQmlJS::DiagnosticMessage>> QQmlJSAotCompiler::compileBinding(
815 const QV4::Compiler::Context *context, const QmlIR::Binding &irBinding,
816 QQmlJS::AST::Node *astNode)
817{
818 QQmlJSFunctionInitializer initializer(
819 &m_typeResolver, m_currentObject->location, m_currentScope->location, m_logger);
820
821 const QString name = m_document->stringAt(irBinding.propertyNameIndex);
822 QQmlJSCompilePass::Function function = initializer.run( context, name, astNode, irBinding);
823
824 const QQmlJSAotFunction aotFunction = doCompileAndRecordAotStats(
825 context, &function, name, astNode->firstSourceLocation());
826
827 if (const auto errors = finalizeBindingOrFunction())
828 return *errors;
829
830 qCDebug(lcAotCompiler()) << "includes:" << aotFunction.includes;
831 qCDebug(lcAotCompiler()) << "binding code:" << aotFunction.code;
832 return aotFunction;
834
835std::variant<QQmlJSAotFunction, QList<QQmlJS::DiagnosticMessage>> QQmlJSAotCompiler::compileFunction(
836 const QV4::Compiler::Context *context, const QString &name, QQmlJS::AST::Node *astNode)
837{
838 QQmlJSFunctionInitializer initializer(
839 &m_typeResolver, m_currentObject->location, m_currentScope->location, m_logger);
840 QQmlJSCompilePass::Function function = initializer.run(context, name, astNode);
841
842 const QQmlJSAotFunction aotFunction = doCompileAndRecordAotStats(
843 context, &function, name, astNode->firstSourceLocation());
844
845 if (const auto errors = finalizeBindingOrFunction())
846 return *errors;
847
848 qCDebug(lcAotCompiler()) << "includes:" << aotFunction.includes;
849 qCDebug(lcAotCompiler()) << "binding code:" << aotFunction.code;
850 return aotFunction;
851}
852
853QQmlJSAotFunction QQmlJSAotCompiler::globalCode() const
854{
855 QQmlJSAotFunction global;
856 global.includes = {
857 u"QtQml/qjsengine.h"_s,
858 u"QtQml/qjsprimitivevalue.h"_s,
859 u"QtQml/qjsvalue.h"_s,
860 u"QtQml/qqmlcomponent.h"_s,
861 u"QtQml/qqmlcontext.h"_s,
862 u"QtQml/qqmlengine.h"_s,
863 u"QtQml/qqmllist.h"_s,
864
865 u"QtCore/qdatetime.h"_s,
866 u"QtCore/qtimezone.h"_s,
867 u"QtCore/qobject.h"_s,
868 u"QtCore/qstring.h"_s,
869 u"QtCore/qstringlist.h"_s,
870 u"QtCore/qurl.h"_s,
871 u"QtCore/qvariant.h"_s,
872
873 u"type_traits"_s
874 };
875 return global;
876}
877
878std::optional<QList<QQmlJS::DiagnosticMessage>> QQmlJSAotCompiler::finalizeBindingOrFunction()
879{
880 const auto archiveMessages = qScopeGuard([this]() { m_logger->finalizeFunction(); });
881
882 if (!m_logger->currentFunctionHasCompileError())
883 return {};
884
885 QList<QQmlJS::DiagnosticMessage> errors;
886 m_logger->iterateCurrentFunctionMessages([&](const Message &msg) {
887 if (msg.compilationStatus == Message::CompilationStatus::Error)
888 errors.append(diagnose(msg.message, msg.type, msg.loc));
889 });
890 return errors;
891}
892
893QQmlJSAotFunction QQmlJSAotCompiler::doCompile(
894 const QV4::Compiler::Context *context, const QQmlJSCompilePass::Function *function)
895{
896 if (m_logger->currentFunctionHasErrorOrSkip())
897 return QQmlJSAotFunction();
898
899 bool basicBlocksValidationFailed = false;
900 QQmlJSBasicBlocks basicBlocks(context, m_unitGenerator, &m_typeResolver, m_logger);
901 auto passResult = basicBlocks.run(function, m_flags, basicBlocksValidationFailed);
902 auto &[blocks, annotations] = passResult;
903
904 QQmlJSTypePropagator propagator(
905 m_unitGenerator, &m_typeResolver, m_logger, blocks, annotations);
906 passResult = propagator.run(function);
907 if (m_logger->currentFunctionHasErrorOrSkip())
908 return QQmlJSAotFunction();
909
910 QQmlJSShadowCheck shadowCheck(
911 m_unitGenerator, &m_typeResolver, m_logger, blocks, annotations);
912 passResult = shadowCheck.run(function);
913 if (m_logger->currentFunctionHasErrorOrSkip())
914 return QQmlJSAotFunction();
915
916 QQmlJSOptimizations optimizer(
917 m_unitGenerator, &m_typeResolver, m_logger, blocks, annotations,
918 basicBlocks.objectAndArrayDefinitions());
919 passResult = optimizer.run(function);
920 if (m_logger->currentFunctionHasErrorOrSkip())
921 return QQmlJSAotFunction();
922
923 QQmlJSStorageInitializer initializer(
924 m_unitGenerator, &m_typeResolver, m_logger, blocks, annotations);
925 passResult = initializer.run(function);
926
927 // Generalize all arguments, registers, and the return type.
928 QQmlJSStorageGeneralizer generalizer(
929 m_unitGenerator, &m_typeResolver, m_logger, blocks, annotations);
930 passResult = generalizer.run(function);
931 if (m_logger->currentFunctionHasErrorOrSkip())
932 return QQmlJSAotFunction();
933
934 QQmlJSCodeGenerator codegen(context, m_unitGenerator, &m_typeResolver, m_logger, blocks,
935 annotations, noAotValidation());
936 QQmlJSAotFunction result = codegen.run(function, basicBlocksValidationFailed);
937 if (m_logger->currentFunctionHasErrorOrSkip())
938 return QQmlJSAotFunction();
939
940 m_lookupSignatures.insert(codegen.lookupSignatures());
941 return result;
942}
943
944QQmlJSAotFunction QQmlJSAotCompiler::doCompileAndRecordAotStats(
945 const QV4::Compiler::Context *context, const QQmlJSCompilePass::Function *function,
946 const QString &name, QQmlJS::SourceLocation location)
947{
948 QElapsedTimer timer {};
949 timer.start();
950 QQmlJSAotFunction result;
951 if (!m_logger->currentFunctionHasCompileError())
952 result = doCompile(context, function);
953 auto elapsed = std::chrono::milliseconds { timer.elapsed() };
954
955 if (QQmlJS::QQmlJSAotCompilerStats::recordAotStats()) {
956 QQmlJS::AotStatsEntry entry;
957 entry.codegenDuration = elapsed;
958 entry.functionName = name;
959 entry.message = m_logger->currentFunctionWasSkipped()
960 ? m_logger->currentFunctionCompileSkipMessage()
961 : m_logger->currentFunctionCompileErrorMessage();
962 entry.line = location.startLine;
963 entry.column = location.startColumn;
964 if (m_logger->currentFunctionWasSkipped())
965 entry.codegenResult = QQmlJS::CodegenResult::Skip;
966 else if (m_logger->currentFunctionHasCompileError())
967 entry.codegenResult = QQmlJS::CodegenResult::Failure;
968 else
969 entry.codegenResult = QQmlJS::CodegenResult::Success;
970 QQmlJS::QQmlJSAotCompilerStats::addEntry(
971 function->qmlScope.containedType()->filePath(), entry);
972 }
973
974 if (m_logger->currentFunctionWasSkipped())
975 result.skipReason = m_logger->currentFunctionCompileSkipMessage();
976
977 return result;
978}
979
980QT_END_NAMESPACE
const QmlIR::Function * function() const
const QmlIR::Binding * binding() const
friend bool operator<(const BindingOrFunction &lhs, const BindingOrFunction &rhs)
BindingOrFunction(const QmlIR::Binding &b)
quint32 index() const
Q_LOGGING_CATEGORY(lcEventDispatcher, "qt.eventdispatcher")
static const char * funcHeaderCode
static QString lookupToString(const QQmlPrivate::AOTLookupValidation::Lookup &lookup)
static QString wrapString(const QString &s)
static QString signatureToString(const QQmlPrivate::AOTLookupValidation::Signature &signature)
static bool generateAotValidationCode(const WriteStr &writeStr, const LookupSignatures &lookupSignatures, bool noAotValidation)
static bool checkArgumentsObjectUseInSignalHandlers(const QmlIR::Document &doc, QQmlJSCompileError *error)
bool qCompileQmlFile(QmlIR::Document &irDocument, const QString &inputFileName, const QQmlJSSaveFunction &saveFunction, QQmlJSAotCompiler *aotCompiler, QQmlJSCompileError *error, bool storeSourceLocation, QV4::Compiler::CodegenWarningInterface *wInterface, const QString *fileContents)
static void annotateListElements(QmlIR::Document *document)
bool qCompileQmlFile(const QString &inputFileName, const QQmlJSSaveFunction &saveFunction, QQmlJSAotCompiler *aotCompiler, QQmlJSCompileError *error, bool storeSourceLocation, QV4::Compiler::CodegenWarningInterface *wInterface, const QString *fileContents)
static QString diagnosticErrorMessage(const QString &fileName, const QQmlJS::DiagnosticMessage &m)
static QString typeToString(const QQmlPrivate::AOTLookupValidation::Type &type)
bool qCompileJSFile(const QString &inputFileName, const QString &inputFileUrl, const QQmlJSSaveFunction &saveFunction, QQmlJSCompileError *error)
static const int FileScopeCodeIndex
static const char * skippedValidationCode
std::function< bool(const QV4::CompiledData::SaveableUnitPointer &, const QQmlJSAotFunctionMap &, const LookupSignatures &, QString *)> QQmlJSSaveFunction