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

Provided by KDAB

Privacy Policy
Start learning QML with our Intro Training
Find out more

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