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

source code of qtdeclarative/src/qmlcompiler/qqmljscompiler.cpp