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