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