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

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