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