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