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