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