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 if (!saveFunction(saveable, aotFunctionsByIndex, &error->message))
376 return false;
377 }
378 return true;
379}
380
382 const QString &inputFileName, const QString &inputFileUrl,
383 const QQmlJSSaveFunction &saveFunction, QQmlJSCompileError *error)
384{
385 Q_UNUSED(inputFileUrl);
386
387 QQmlRefPointer<QV4::CompiledData::CompilationUnit> unit;
388
389 QString sourceCode;
390 {
391 QFile f(inputFileName);
392 if (!f.open(QIODevice::ReadOnly)) {
393 error->message = QLatin1String("Error opening ") + inputFileName + QLatin1Char(':') + f.errorString();
394 return false;
395 }
396 sourceCode = QString::fromUtf8(f.readAll());
397 if (f.error() != QFileDevice::NoError) {
398 error->message = QLatin1String("Error reading from ") + inputFileName + QLatin1Char(':') + f.errorString();
399 return false;
400 }
401 }
402
403 const bool isModule = inputFileName.endsWith(QLatin1String(".mjs"));
404 if (isModule) {
405 QList<QQmlJS::DiagnosticMessage> diagnostics;
406 // Precompiled files are relocatable and the final location will be set when loading.
407 QString url;
408 unit = QV4::Compiler::Codegen::compileModule(/*debugMode*/false, url, sourceCode,
409 QDateTime(), &diagnostics);
410 error->appendDiagnostics(inputFileName, diagnostics);
411 if (!unit || !unit->unitData())
412 return false;
413 } else {
414 QmlIR::Document irDocument(QString(), QString(), /*debugMode*/false);
415
416 QQmlJS::Engine *engine = &irDocument.jsParserEngine;
417 QmlIR::ScriptDirectivesCollector directivesCollector(&irDocument);
418 QQmlJS::Directives *oldDirs = engine->directives();
419 engine->setDirectives(&directivesCollector);
420 auto directivesGuard = qScopeGuard([engine, oldDirs]{
421 engine->setDirectives(oldDirs);
422 });
423
424 QQmlJS::AST::Program *program = nullptr;
425
426 {
427 QQmlJS::Lexer lexer(engine);
428 lexer.setCode(sourceCode, /*line*/1, /*parseAsBinding*/false);
429 QQmlJS::Parser parser(engine);
430
431 bool parsed = parser.parseProgram();
432
433 error->appendDiagnostics(inputFileName, parser.diagnosticMessages());
434
435 if (!parsed)
436 return false;
437
438 program = QQmlJS::AST::cast<QQmlJS::AST::Program*>(parser.rootNode());
439 if (!program) {
440 lexer.setCode(QStringLiteral("undefined;"), 1, false);
441 parsed = parser.parseProgram();
442 Q_ASSERT(parsed);
443 program = QQmlJS::AST::cast<QQmlJS::AST::Program*>(parser.rootNode());
444 Q_ASSERT(program);
445 }
446 }
447
448 {
449 QmlIR::JSCodeGen v4CodeGen(&irDocument);
450 v4CodeGen.generateFromProgram(
451 sourceCode, program, &irDocument.jsModule,
452 QV4::Compiler::ContextType::ScriptImportedByQML);
453 if (v4CodeGen.hasError()) {
454 error->appendDiagnostic(inputFileName, v4CodeGen.error());
455 return false;
456 }
457
458 // Precompiled files are relocatable and the final location will be set when loading.
459 Q_ASSERT(irDocument.jsModule.fileName.isEmpty());
460 Q_ASSERT(irDocument.jsModule.finalUrl.isEmpty());
461
462 irDocument.javaScriptCompilationUnit = v4CodeGen.generateCompilationUnit(/*generate unit*/false);
463 QmlIR::QmlUnitGenerator generator;
464 generator.generate(irDocument);
465 unit = std::move(irDocument.javaScriptCompilationUnit);
466 }
467 }
468
469 QQmlJSAotFunctionMap empty;
470 return saveFunction(
471 QV4::CompiledData::SaveableUnitPointer(unit->unitData()), empty, &error->message);
472}
473
474static const char *funcHeaderCode = R"(
475 [](const QQmlPrivate::AOTCompiledContext *aotContext, void **argv) {
476Q_UNUSED(aotContext)
477Q_UNUSED(argv)
478)";
479
480bool qSaveQmlJSUnitAsCpp(const QString &inputFileName, const QString &outputFileName, const QV4::CompiledData::SaveableUnitPointer &unit, const QQmlJSAotFunctionMap &aotFunctions, QString *errorString)
481{
482#if QT_CONFIG(temporaryfile)
483 QSaveFile f(outputFileName);
484#else
485 QFile f(outputFileName);
486#endif
487 if (!f.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
488 *errorString = f.errorString();
489 return false;
490 }
491
492 auto writeStr = [&f, errorString](const QByteArray &data) {
493 if (f.write(data) != data.size()) {
494 *errorString = f.errorString();
495 return false;
496 }
497 return true;
498 };
499
500 if (!writeStr("// "))
501 return false;
502
503 if (!writeStr(inputFileName.toUtf8()))
504 return false;
505
506 if (!writeStr("\n"))
507 return false;
508
509 if (!writeStr("#include <QtQml/qqmlprivate.h>\n"))
510 return false;
511
512 if (!aotFunctions.isEmpty()) {
513 QStringList includes;
514
515 for (const auto &function : aotFunctions)
516 includes.append(function.includes);
517
518 std::sort(includes.begin(), includes.end());
519 const auto end = std::unique(includes.begin(), includes.end());
520 for (auto it = includes.begin(); it != end; ++it) {
521 if (!writeStr(QStringLiteral("#include <%1>\n").arg(*it).toUtf8()))
522 return false;
523 }
524 }
525
526 if (!writeStr(QByteArrayLiteral("namespace QmlCacheGeneratedCode {\nnamespace ")))
527 return false;
528
529 if (!writeStr(qQmlJSSymbolNamespaceForPath(inputFileName).toUtf8()))
530 return false;
531
532 if (!writeStr(QByteArrayLiteral(" {\nextern const unsigned char qmlData alignas(16) [];\n"
533 "extern const unsigned char qmlData alignas(16) [] = {\n")))
534 return false;
535
536 unit.saveToDisk<uchar>([&writeStr](const uchar *begin, quint32 size) {
537 QByteArray hexifiedData;
538 {
539 QTextStream stream(&hexifiedData);
540 const uchar *end = begin + size;
541 stream << Qt::hex;
542 int col = 0;
543 for (const uchar *data = begin; data < end; ++data, ++col) {
544 if (data > begin)
545 stream << ',';
546 if (col % 8 == 0) {
547 stream << '\n';
548 col = 0;
549 }
550 stream << "0x" << *data;
551 }
552 stream << '\n';
553 }
554 return writeStr(hexifiedData);
555 });
556
557
558
559 if (!writeStr("};\n"))
560 return false;
561
562 writeStr(aotFunctions[FileScopeCodeIndex].code.toUtf8().constData());
563 if (aotFunctions.size() <= 1) {
564 // FileScopeCodeIndex is always there, but it may be the only one.
565 writeStr("extern const QQmlPrivate::AOTCompiledFunction aotBuiltFunctions[];\n"
566 "extern const QQmlPrivate::AOTCompiledFunction aotBuiltFunctions[] = { { 0, 0, nullptr, nullptr } };\n");
567 } else {
568 writeStr("extern const QQmlPrivate::AOTCompiledFunction aotBuiltFunctions[];\n"
569 "extern const QQmlPrivate::AOTCompiledFunction aotBuiltFunctions[] = {\n");
570
571 QString footer = QStringLiteral("}\n");
572
573 for (QQmlJSAotFunctionMap::ConstIterator func = aotFunctions.constBegin(),
574 end = aotFunctions.constEnd();
575 func != end; ++func) {
576
577 if (func.key() == FileScopeCodeIndex)
578 continue;
579
580 const QString function = QString::fromUtf8(funcHeaderCode) + func.value().code + footer;
581
582 writeStr(QStringLiteral("{ %1, %2, [](QV4::ExecutableCompilationUnit *contextUnit, "
583 "QMetaType *argTypes) {\n%3}, %4 },")
584 .arg(func.key())
585 .arg(func->numArguments)
586 .arg(func->signature, function)
587 .toUtf8().constData());
588 }
589
590 // Conclude the list with a nullptr
591 writeStr("{ 0, 0, nullptr, nullptr }");
592 writeStr("};\n");
593 }
594
595 if (!writeStr("}\n}\n"))
596 return false;
597
598#if QT_CONFIG(temporaryfile)
599 if (!f.commit()) {
600 *errorString = f.errorString();
601 return false;
602 }
603#endif
604
605 return true;
606}
607
608QQmlJSAotCompiler::QQmlJSAotCompiler(
609 QQmlJSImporter *importer, const QString &resourcePath, const QStringList &qmldirFiles,
610 QQmlJSLogger *logger)
611 : m_typeResolver(importer)
612 , m_resourcePath(resourcePath)
613 , m_qmldirFiles(qmldirFiles)
614 , m_importer(importer)
615 , m_logger(logger)
616{
617}
618
619void QQmlJSAotCompiler::setDocument(
620 const QmlIR::JSCodeGen *codegen, const QmlIR::Document *irDocument)
621{
622 Q_UNUSED(codegen);
623 m_document = irDocument;
624 const QFileInfo resourcePathInfo(m_resourcePath);
625 if (m_logger->filePath().isEmpty())
626 m_logger->setFilePath(resourcePathInfo.fileName());
627 m_logger->setCode(irDocument->code);
628 m_unitGenerator = &irDocument->jsGenerator;
629 QQmlJSImportVisitor visitor(m_importer, m_logger,
630 resourcePathInfo.canonicalPath() + u'/',
631 m_qmldirFiles);
632 m_typeResolver.init(&visitor, irDocument->program);
633}
634
635void QQmlJSAotCompiler::setScope(const QmlIR::Object *object, const QmlIR::Object *scope)
636{
637 m_currentObject = object;
638 m_currentScope = scope;
639}
640
641static bool isStrict(const QmlIR::Document *doc)
642{
643 for (const QmlIR::Pragma *pragma : doc->pragmas) {
644 if (pragma->type == QmlIR::Pragma::Strict)
645 return true;
646 }
647 return false;
648}
649
650QQmlJS::DiagnosticMessage QQmlJSAotCompiler::diagnose(
651 const QString &message, QtMsgType type, const QQmlJS::SourceLocation &location) const
652{
653 if (isStrict(m_document)
654 && (type == QtWarningMsg || type == QtCriticalMsg || type == QtFatalMsg)
655 && m_logger->categorySeverity(qmlCompiler) == QQmlSA::WarningSeverity::Error) {
656 qFatal("%s:%d: (strict mode) %s",
657 qPrintable(QFileInfo(m_resourcePath).fileName()),
658 location.startLine, qPrintable(message));
659 }
660
661 return QQmlJS::DiagnosticMessage {
662 message,
663 type,
664 location
665 };
666}
667
668std::variant<QQmlJSAotFunction, QList<QQmlJS::DiagnosticMessage>> QQmlJSAotCompiler::compileBinding(
669 const QV4::Compiler::Context *context, const QmlIR::Binding &irBinding,
670 QQmlJS::AST::Node *astNode)
671{
672 QQmlJSFunctionInitializer initializer(
673 &m_typeResolver, m_currentObject->location, m_currentScope->location, m_logger);
674
675 const QString name = m_document->stringAt(irBinding.propertyNameIndex);
676 QQmlJSCompilePass::Function function = initializer.run( context, name, astNode, irBinding);
677
678 const QQmlJSAotFunction aotFunction = doCompileAndRecordAotStats(
679 context, &function, name, astNode->firstSourceLocation());
680
681 if (const auto errors = finalizeBindingOrFunction())
682 return *errors;
683
684 qCDebug(lcAotCompiler()) << "includes:" << aotFunction.includes;
685 qCDebug(lcAotCompiler()) << "binding code:" << aotFunction.code;
686 return aotFunction;
687}
688
689std::variant<QQmlJSAotFunction, QList<QQmlJS::DiagnosticMessage>> QQmlJSAotCompiler::compileFunction(
690 const QV4::Compiler::Context *context, const QString &name, QQmlJS::AST::Node *astNode)
691{
692 QQmlJSFunctionInitializer initializer(
693 &m_typeResolver, m_currentObject->location, m_currentScope->location, m_logger);
694 QQmlJSCompilePass::Function function = initializer.run(context, name, astNode);
695
696 const QQmlJSAotFunction aotFunction = doCompileAndRecordAotStats(
697 context, &function, name, astNode->firstSourceLocation());
698
699 if (const auto errors = finalizeBindingOrFunction())
700 return *errors;
701
702 qCDebug(lcAotCompiler()) << "includes:" << aotFunction.includes;
703 qCDebug(lcAotCompiler()) << "binding code:" << aotFunction.code;
704 return aotFunction;
705}
706
707QQmlJSAotFunction QQmlJSAotCompiler::globalCode() const
708{
709 QQmlJSAotFunction global;
710 global.includes = {
711 u"QtQml/qjsengine.h"_s,
712 u"QtQml/qjsprimitivevalue.h"_s,
713 u"QtQml/qjsvalue.h"_s,
714 u"QtQml/qqmlcomponent.h"_s,
715 u"QtQml/qqmlcontext.h"_s,
716 u"QtQml/qqmlengine.h"_s,
717 u"QtQml/qqmllist.h"_s,
718
719 u"QtCore/qdatetime.h"_s,
720 u"QtCore/qtimezone.h"_s,
721 u"QtCore/qobject.h"_s,
722 u"QtCore/qstring.h"_s,
723 u"QtCore/qstringlist.h"_s,
724 u"QtCore/qurl.h"_s,
725 u"QtCore/qvariant.h"_s,
726
727 u"type_traits"_s
728 };
729 return global;
730}
731
732std::optional<QList<QQmlJS::DiagnosticMessage>> QQmlJSAotCompiler::finalizeBindingOrFunction()
733{
734 const auto archiveMessages = qScopeGuard([this]() { m_logger->finalizeFunction(); });
735
736 if (!m_logger->currentFunctionHasCompileError())
737 return {};
738
739 QList<QQmlJS::DiagnosticMessage> errors;
740 m_logger->iterateCurrentFunctionMessages([&](const Message &msg) {
741 if (msg.compilationStatus == Message::CompilationStatus::Error)
742 errors.append(diagnose(msg.message, msg.type, msg.loc));
743 });
744 return errors;
745}
746
747QQmlJSAotFunction QQmlJSAotCompiler::doCompile(
748 const QV4::Compiler::Context *context, const QQmlJSCompilePass::Function *function)
749{
750 if (m_logger->currentFunctionHasErrorOrSkip())
751 return QQmlJSAotFunction();
752
753 bool basicBlocksValidationFailed = false;
754 QQmlJSBasicBlocks basicBlocks(context, m_unitGenerator, &m_typeResolver, m_logger);
755 auto passResult = basicBlocks.run(function, m_flags, basicBlocksValidationFailed);
756 auto &[blocks, annotations] = passResult;
757
758 QQmlJSTypePropagator propagator(
759 m_unitGenerator, &m_typeResolver, m_logger, blocks, annotations);
760 passResult = propagator.run(function);
761 if (m_logger->currentFunctionHasErrorOrSkip())
762 return QQmlJSAotFunction();
763
764 QQmlJSShadowCheck shadowCheck(
765 m_unitGenerator, &m_typeResolver, m_logger, blocks, annotations);
766 passResult = shadowCheck.run(function);
767 if (m_logger->currentFunctionHasErrorOrSkip())
768 return QQmlJSAotFunction();
769
770 QQmlJSOptimizations optimizer(
771 m_unitGenerator, &m_typeResolver, m_logger, blocks, annotations,
772 basicBlocks.objectAndArrayDefinitions());
773 passResult = optimizer.run(function);
774 if (m_logger->currentFunctionHasErrorOrSkip())
775 return QQmlJSAotFunction();
776
777 QQmlJSStorageInitializer initializer(
778 m_unitGenerator, &m_typeResolver, m_logger, blocks, annotations);
779 passResult = initializer.run(function);
780
781 // Generalize all arguments, registers, and the return type.
782 QQmlJSStorageGeneralizer generalizer(
783 m_unitGenerator, &m_typeResolver, m_logger, blocks, annotations);
784 passResult = generalizer.run(function);
785 if (m_logger->currentFunctionHasErrorOrSkip())
786 return QQmlJSAotFunction();
787
788 QQmlJSCodeGenerator codegen(
789 context, m_unitGenerator, &m_typeResolver, m_logger, blocks, annotations);
790 QQmlJSAotFunction result = codegen.run(function, basicBlocksValidationFailed);
791 if (m_logger->currentFunctionHasErrorOrSkip())
792 return QQmlJSAotFunction();
793
794 return result;
795}
796
797QQmlJSAotFunction QQmlJSAotCompiler::doCompileAndRecordAotStats(
798 const QV4::Compiler::Context *context, const QQmlJSCompilePass::Function *function,
799 const QString &name, QQmlJS::SourceLocation location)
800{
801 QElapsedTimer timer {};
802 timer.start();
803 QQmlJSAotFunction result;
804 if (!m_logger->currentFunctionHasCompileError())
805 result = doCompile(context, function);
806 auto elapsed = std::chrono::milliseconds { timer.elapsed() };
807
808 if (QQmlJS::QQmlJSAotCompilerStats::recordAotStats()) {
809 QQmlJS::AotStatsEntry entry;
810 entry.codegenDuration = elapsed;
811 entry.functionName = name;
812 entry.message = m_logger->currentFunctionWasSkipped()
813 ? m_logger->currentFunctionCompileSkipMessage()
814 : m_logger->currentFunctionCompileErrorMessage();
815 entry.line = location.startLine;
816 entry.column = location.startColumn;
817 if (m_logger->currentFunctionWasSkipped())
818 entry.codegenResult = QQmlJS::CodegenResult::Skip;
819 else if (m_logger->currentFunctionHasCompileError())
820 entry.codegenResult = QQmlJS::CodegenResult::Failure;
821 else
822 entry.codegenResult = QQmlJS::CodegenResult::Success;
823 QQmlJS::QQmlJSAotCompilerStats::addEntry(
824 function->qmlScope.containedType()->filePath(), entry);
825 }
826
827 if (m_logger->currentFunctionWasSkipped())
828 result.skipReason = m_logger->currentFunctionCompileSkipMessage();
829
830 return result;
831}
832
833QT_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 bool isStrict(const QmlIR::Document *doc)
static const char * funcHeaderCode
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)
bool qSaveQmlJSUnitAsCpp(const QString &inputFileName, const QString &outputFileName, const QV4::CompiledData::SaveableUnitPointer &unit, const QQmlJSAotFunctionMap &aotFunctions, QString *errorString)
bool qCompileJSFile(const QString &inputFileName, const QString &inputFileUrl, const QQmlJSSaveFunction &saveFunction, QQmlJSCompileError *error)
static const int FileScopeCodeIndex
std::function< bool(const QV4::CompiledData::SaveableUnitPointer &, const QQmlJSAotFunctionMap &, QString *)> QQmlJSSaveFunction