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