// Copyright (C) 2020 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
// Qt-Security score:significant

#include "qqmljscompiler_p.h"

#include <private/qqmlirbuilder_p.h>
#include <private/qqmljsaotirbuilder_p.h>
#include <private/qqmljsbasicblocks_p.h>
#include <private/qqmljscodegenerator_p.h>
#include <private/qqmljscompilerstats_p.h>
#include <private/qqmljsfunctioninitializer_p.h>
#include <private/qqmljsimportvisitor_p.h>
#include <private/qqmljslexer_p.h>
#include <private/qqmljsloadergenerator_p.h>
#include <private/qqmljsoptimizations_p.h>
#include <private/qqmljsparser_p.h>
#include <private/qqmljsshadowcheck_p.h>
#include <private/qqmljsstoragegeneralizer_p.h>
#include <private/qqmljsstorageinitializer_p.h>
#include <private/qqmljstypepropagator_p.h>

#include <QtCore/qfile.h>
#include <QtCore/qfileinfo.h>
#include <QtCore/qloggingcategory.h>
#include <QtCore/qelapsedtimer.h>

#include <QtQml/private/qqmlsignalnames_p.h>

#include <limits>

QT_BEGIN_NAMESPACE

using namespace Qt::StringLiterals;

Q_LOGGING_CATEGORY(lcAotCompiler, "qt.qml.compiler.aot", QtFatalMsg);

static const int FileScopeCodeIndex = -1;

void QQmlJSCompileError::print()
{
    fprintf(stderr, "%s\n", qPrintable(message));
}

QQmlJSCompileError QQmlJSCompileError::augment(const QString &contextErrorMessage) const
{
    QQmlJSCompileError augmented;
    augmented.message = contextErrorMessage + message;
    return augmented;
}

static QString diagnosticErrorMessage(const QString &fileName, const QQmlJS::DiagnosticMessage &m)
{
    QString message;
    message = fileName + QLatin1Char(':') + QString::number(m.loc.startLine) + QLatin1Char(':');
    if (m.loc.startColumn > 0)
        message += QString::number(m.loc.startColumn) + QLatin1Char(':');

    if (m.isError())
        message += QLatin1String(" error: ");
    else
        message += QLatin1String(" warning: ");
    message += m.message;
    return message;
}

void QQmlJSCompileError::appendDiagnostic(const QString &inputFileName,
                                          const QQmlJS::DiagnosticMessage &diagnostic)
{
    if (!message.isEmpty())
        message += QLatin1Char('\n');
    message += diagnosticErrorMessage(inputFileName, diagnostic);
}

void QQmlJSCompileError::appendDiagnostics(const QString &inputFileName,
                                           const QList<QQmlJS::DiagnosticMessage> &diagnostics)
{
    for (const QQmlJS::DiagnosticMessage &diagnostic: diagnostics)
        appendDiagnostic(inputFileName, diagnostic);
}

// Ensure that ListElement objects keep all property assignments in their string form
static void annotateListElements(QmlIR::Document *document)
{
    QStringList listElementNames;

    for (const QV4::CompiledData::Import *import : std::as_const(document->imports)) {
        const QString uri = document->stringAt(import->uriIndex);
        if (uri != QStringLiteral("QtQml.Models") && uri != QStringLiteral("QtQuick"))
            continue;

         QString listElementName = QStringLiteral("ListElement");
         const QString qualifier = document->stringAt(import->qualifierIndex);
         if (!qualifier.isEmpty()) {
             listElementName.prepend(QLatin1Char('.'));
             listElementName.prepend(qualifier);
         }
         listElementNames.append(listElementName);
    }

    if (listElementNames.isEmpty())
        return;

    for (QmlIR::Object *object : std::as_const(document->objects)) {
        if (!listElementNames.contains(document->stringAt(object->inheritedTypeNameIndex)))
            continue;
        for (QmlIR::Binding *binding = object->firstBinding(); binding; binding = binding->next) {
            if (binding->type() != QV4::CompiledData::Binding::Type_Script)
                continue;
            binding->stringIndex = document->registerString(object->bindingAsString(document, binding->value.compiledScriptIndex));
        }
    }
}

static bool checkArgumentsObjectUseInSignalHandlers(const QmlIR::Document &doc,
                                                    QQmlJSCompileError *error)
{
    for (QmlIR::Object *object: std::as_const(doc.objects)) {
        for (auto binding = object->bindingsBegin(); binding != object->bindingsEnd(); ++binding) {
            if (binding->type() != QV4::CompiledData::Binding::Type_Script)
                continue;
            const QString propName =  doc.stringAt(binding->propertyNameIndex);
            if (!QQmlSignalNames::isHandlerName(propName))
                continue;
            auto compiledFunction = doc.jsModule.functions.value(object->runtimeFunctionIndices.at(binding->value.compiledScriptIndex));
            if (!compiledFunction)
                continue;
            if (compiledFunction->usesArgumentsObject == QV4::Compiler::Context::UsesArgumentsObject::Used) {
                error->message = QLatin1Char(':') + QString::number(compiledFunction->line) + QLatin1Char(':');
                if (compiledFunction->column > 0)
                    error->message += QString::number(compiledFunction->column) + QLatin1Char(':');

                error->message += QLatin1String(" error: The use of eval() or the use of the arguments object in signal handlers is\n"
                                                "not supported when compiling qml files ahead of time. That is because it's ambiguous if \n"
                                                "any signal parameter is called \"arguments\". Similarly the string passed to eval might use\n"
                                                "\"arguments\". Unfortunately we cannot distinguish between it being a parameter or the\n"
                                                "JavaScript arguments object at this point.\n"
                                                "Consider renaming the parameter of the signal if applicable or moving the code into a\n"
                                                "helper function.");
                return false;
            }
        }
    }
    return true;
}

class BindingOrFunction
{
public:
    BindingOrFunction(const QmlIR::Binding &b) : m_binding(&b) {}
    BindingOrFunction(const QmlIR::Function &f) : m_function(&f) {}

    friend bool operator<(const BindingOrFunction &lhs, const BindingOrFunction &rhs)
    {
        return lhs.index() < rhs.index();
    }

    const QmlIR::Binding *binding() const { return m_binding; }
    const QmlIR::Function *function() const { return m_function; }

    quint32 index() const
    {
        return m_binding
                ? m_binding->value.compiledScriptIndex
                : (m_function
                   ? m_function->index
                   : std::numeric_limits<quint32>::max());
    }

private:
    const QmlIR::Binding *m_binding = nullptr;
    const QmlIR::Function *m_function = nullptr;
};

bool qCompileQmlFile(const QString &inputFileName, const QQmlJSSaveFunction &saveFunction,
                     QQmlJSAotCompiler *aotCompiler, QQmlJSCompileError *error,
                     bool storeSourceLocation, QV4::Compiler::CodegenWarningInterface *wInterface,
                     const QString *fileContents)
{
    QmlIR::Document irDocument(QString(), QString(), /*debugMode*/false);
    return qCompileQmlFile(irDocument, inputFileName, saveFunction, aotCompiler, error,
                           storeSourceLocation, wInterface, fileContents);
}

bool qCompileQmlFile(QmlIR::Document &irDocument, const QString &inputFileName,
                     const QQmlJSSaveFunction &saveFunction, QQmlJSAotCompiler *aotCompiler,
                     QQmlJSCompileError *error, bool storeSourceLocation,
                     QV4::Compiler::CodegenWarningInterface *wInterface, const QString *fileContents)
{
    QString sourceCode;

    if (fileContents != nullptr) {
        sourceCode = *fileContents;
    } else {
        QFile f(inputFileName);
        if (!f.open(QIODevice::ReadOnly)) {
            error->message = QLatin1String("Error opening ") + inputFileName + QLatin1Char(':') + f.errorString();
            return false;
        }
        sourceCode = QString::fromUtf8(f.readAll());
        if (f.error() != QFileDevice::NoError) {
            error->message = QLatin1String("Error reading from ") + inputFileName + QLatin1Char(':') + f.errorString();
            return false;
        }
    }

    {
        // For now, only use the AOT IRBuilder when linting
        std::unique_ptr<QmlIR::IRBuilder> irBuilder = aotCompiler && aotCompiler->isLintCompiler()
                ? std::make_unique<QQmlJSAOTIRBuilder>() : std::make_unique<QmlIR::IRBuilder>();
        if (!irBuilder->generateFromQml(sourceCode, inputFileName, &irDocument)) {
            error->appendDiagnostics(inputFileName, irBuilder->errors);
            return false;
        }
    }

    annotateListElements(&irDocument);
    QQmlJSAotFunctionMap aotFunctionsByIndex;

    {
        QmlIR::JSCodeGen v4CodeGen(&irDocument, wInterface, storeSourceLocation);

        if (aotCompiler)
            aotCompiler->setDocument(&v4CodeGen, &irDocument);

        QHash<QmlIR::Object *, QmlIR::Object *> effectiveScopes;
        for (QmlIR::Object *object: std::as_const(irDocument.objects)) {
            if (object->functionsAndExpressions->count == 0 && object->bindingCount() == 0)
                continue;

            if (!v4CodeGen.generateRuntimeFunctions(object)) {
                Q_ASSERT(v4CodeGen.hasError());
                error->appendDiagnostic(inputFileName, v4CodeGen.error());
                return false;
            }

            if (!aotCompiler)
                continue;

            QmlIR::Object *scope = object;
            for (auto it = effectiveScopes.constFind(scope), end = effectiveScopes.constEnd();
                 it != end; it = effectiveScopes.constFind(scope)) {
                scope = *it;
            }

            aotCompiler->setScope(object, scope);
            aotFunctionsByIndex[FileScopeCodeIndex] = aotCompiler->globalCode();

            std::vector<BindingOrFunction> bindingsAndFunctions;
            bindingsAndFunctions.reserve(object->bindingCount() + object->functionCount());

            std::copy(object->bindingsBegin(), object->bindingsEnd(),
                      std::back_inserter(bindingsAndFunctions));
            std::copy(object->functionsBegin(), object->functionsEnd(),
                      std::back_inserter(bindingsAndFunctions));

            QList<QmlIR::CompiledFunctionOrExpression> functionsToCompile;
            for (QmlIR::CompiledFunctionOrExpression *foe = object->functionsAndExpressions->first;
                 foe; foe = foe->next) {
                functionsToCompile << *foe;
            }

            // AOT-compile bindings and functions in the same order as above so that the runtime
            // class indices match
            auto contextMap = v4CodeGen.module()->contextMap;
            std::sort(bindingsAndFunctions.begin(), bindingsAndFunctions.end());
            std::for_each(bindingsAndFunctions.begin(), bindingsAndFunctions.end(),
                          [&](const BindingOrFunction &bindingOrFunction) {
                std::variant<QQmlJSAotFunction, QList<QQmlJS::DiagnosticMessage>> result;
                if (const auto *binding = bindingOrFunction.binding()) {
                    switch (binding->type()) {
                    case QmlIR::Binding::Type_AttachedProperty:
                    case QmlIR::Binding::Type_GroupProperty:
                        effectiveScopes.insert(
                                    irDocument.objects.at(binding->value.objectIndex), scope);
                        return;
                    case QmlIR::Binding::Type_Boolean:
                    case QmlIR::Binding::Type_Number:
                    case QmlIR::Binding::Type_String:
                    case QmlIR::Binding::Type_Null:
                    case QmlIR::Binding::Type_Object:
                    case QmlIR::Binding::Type_Translation:
                    case QmlIR::Binding::Type_TranslationById:
                        return;
                    default:
                        break;
                    }

                    Q_ASSERT(quint32(functionsToCompile.size()) > binding->value.compiledScriptIndex);
                    const auto &functionToCompile
                            = functionsToCompile[binding->value.compiledScriptIndex];
                    auto *parentNode = functionToCompile.parentNode;
                    Q_ASSERT(parentNode);
                    Q_ASSERT(contextMap.contains(parentNode));
                    QV4::Compiler::Context *context = contextMap.take(parentNode);
                    Q_ASSERT(context);

                    auto *node = functionToCompile.node;
                    Q_ASSERT(node);

                    if (context->returnsClosure) {
                        QQmlJS::AST::Node *inner
                                = QQmlJS::AST::cast<QQmlJS::AST::ExpressionStatement *>(
                                    node)->expression;
                        Q_ASSERT(inner);
                        QV4::Compiler::Context *innerContext = contextMap.take(inner);
                        Q_ASSERT(innerContext);
                        qCDebug(lcAotCompiler) << "Compiling signal handler for"
                                               << irDocument.stringAt(binding->propertyNameIndex);
                        std::variant<QQmlJSAotFunction, QList<QQmlJS::DiagnosticMessage>> innerResult
                                = aotCompiler->compileBinding(innerContext, *binding, inner);
                        if (auto *errors = std::get_if<QList<QQmlJS::DiagnosticMessage>>(&innerResult)) {
                            for (const auto &error : std::as_const(*errors)) {
                                qCDebug(lcAotCompiler) << "Compilation failed:"
                                                       << diagnosticErrorMessage(inputFileName, error);
                            }
                        } else if (auto *func = std::get_if<QQmlJSAotFunction>(&innerResult)) {
                            qCDebug(lcAotCompiler) << "Generated code:" << func->code;
                            aotFunctionsByIndex[innerContext->functionIndex] = *func;
                        }
                    }

                    qCDebug(lcAotCompiler) << "Compiling binding for property"
                                           << irDocument.stringAt(binding->propertyNameIndex);
                    result = aotCompiler->compileBinding(context, *binding, node);
                } else if (const auto *function = bindingOrFunction.function()) {
                    if (!aotCompiler->isLintCompiler() && !function->isQmlFunction)
                        return;

                    Q_ASSERT(quint32(functionsToCompile.size()) > function->index);
                    auto *node = functionsToCompile[function->index].node;
                    Q_ASSERT(node);
                    Q_ASSERT(contextMap.contains(node));
                    QV4::Compiler::Context *context = contextMap.take(node);
                    Q_ASSERT(context);

                    const QString functionName = irDocument.stringAt(function->nameIndex);
                    qCDebug(lcAotCompiler) << "Compiling function" << functionName;
                    result = aotCompiler->compileFunction(context, functionName, node);
                } else {
                    Q_UNREACHABLE();
                }

                if (auto *errors = std::get_if<QList<QQmlJS::DiagnosticMessage>>(&result)) {
                    for (const auto &error : std::as_const(*errors)) {
                        qCDebug(lcAotCompiler) << "Compilation failed:"
                                               << diagnosticErrorMessage(inputFileName, error);
                    }
                } else if (auto *func = std::get_if<QQmlJSAotFunction>(&result)) {
                    if (func->skipReason.has_value()) {
                        qCDebug(lcAotCompiler) << "Compilation skipped:" << func->skipReason.value();
                    } else {
                        qCDebug(lcAotCompiler) << "Generated code:" << func->code;
                        auto index = object->runtimeFunctionIndices[bindingOrFunction.index()];
                        aotFunctionsByIndex[index] = *func;
                    }
                }
            });
        }

        if (!checkArgumentsObjectUseInSignalHandlers(irDocument, error)) {
            *error = error->augment(inputFileName);
            return false;
        }

        QmlIR::QmlUnitGenerator generator;
        irDocument.javaScriptCompilationUnit = v4CodeGen.generateCompilationUnit(/*generate unit*/false);
        generator.generate(irDocument);

        const quint32 saveFlags
                = QV4::CompiledData::Unit::StaticData
                | QV4::CompiledData::Unit::PendingTypeCompilation;
        QV4::CompiledData::SaveableUnitPointer saveable(
                irDocument.javaScriptCompilationUnit->unitData(), saveFlags);
        LookupSignatures sigs = aotCompiler ? aotCompiler->lookupSignatures() : LookupSignatures();
        if (!saveFunction(saveable, aotFunctionsByIndex, sigs, &error->message))
            return false;
    }
    return true;
}

bool qCompileJSFile(
        const QString &inputFileName, const QString &inputFileUrl,
        const QQmlJSSaveFunction &saveFunction, QQmlJSCompileError *error)
{
    Q_UNUSED(inputFileUrl);

    QQmlRefPointer<QV4::CompiledData::CompilationUnit> unit;

    QString sourceCode;
    {
        QFile f(inputFileName);
        if (!f.open(QIODevice::ReadOnly)) {
            error->message = QLatin1String("Error opening ") + inputFileName + QLatin1Char(':') + f.errorString();
            return false;
        }
        sourceCode = QString::fromUtf8(f.readAll());
        if (f.error() != QFileDevice::NoError) {
            error->message = QLatin1String("Error reading from ") + inputFileName + QLatin1Char(':') + f.errorString();
            return false;
        }
    }

    const bool isModule = inputFileName.endsWith(QLatin1String(".mjs"));
    if (isModule) {
        QList<QQmlJS::DiagnosticMessage> diagnostics;
        // Precompiled files are relocatable and the final location will be set when loading.
        QString url;
        unit = QV4::Compiler::Codegen::compileModule(/*debugMode*/false, url, sourceCode,
                                                     QDateTime(), &diagnostics);
        error->appendDiagnostics(inputFileName, diagnostics);
        if (!unit || !unit->unitData())
            return false;
    } else {
        QmlIR::Document irDocument(QString(), QString(), /*debugMode*/false);

        QQmlJS::Engine *engine = &irDocument.jsParserEngine;
        QmlIR::ScriptDirectivesCollector directivesCollector(&irDocument);
        QQmlJS::Directives *oldDirs = engine->directives();
        engine->setDirectives(&directivesCollector);
        auto directivesGuard = qScopeGuard([engine, oldDirs]{
            engine->setDirectives(oldDirs);
        });

        QQmlJS::AST::Program *program = nullptr;

        {
            QQmlJS::Lexer lexer(engine);
            lexer.setCode(sourceCode, /*line*/1, /*parseAsBinding*/false);
            QQmlJS::Parser parser(engine);

            bool parsed = parser.parseProgram();

            error->appendDiagnostics(inputFileName, parser.diagnosticMessages());

            if (!parsed)
                return false;

            program = QQmlJS::AST::cast<QQmlJS::AST::Program*>(parser.rootNode());
            if (!program) {
                lexer.setCode(QStringLiteral("undefined;"), 1, false);
                parsed = parser.parseProgram();
                Q_ASSERT(parsed);
                program = QQmlJS::AST::cast<QQmlJS::AST::Program*>(parser.rootNode());
                Q_ASSERT(program);
            }
        }

        {
            QmlIR::JSCodeGen v4CodeGen(&irDocument);
            v4CodeGen.generateFromProgram(
                    sourceCode, program, &irDocument.jsModule,
                    QV4::Compiler::ContextType::ScriptImportedByQML);
            if (v4CodeGen.hasError()) {
                error->appendDiagnostic(inputFileName, v4CodeGen.error());
                return false;
            }

            // Precompiled files are relocatable and the final location will be set when loading.
            Q_ASSERT(irDocument.jsModule.fileName.isEmpty());
            Q_ASSERT(irDocument.jsModule.finalUrl.isEmpty());

            irDocument.javaScriptCompilationUnit = v4CodeGen.generateCompilationUnit(/*generate unit*/false);
            QmlIR::QmlUnitGenerator generator;
            generator.generate(irDocument);
            unit = std::move(irDocument.javaScriptCompilationUnit);
        }
    }

    QQmlJSAotFunctionMap funcs;
    LookupSignatures sigs;
    return saveFunction(
            QV4::CompiledData::SaveableUnitPointer(unit->unitData()), funcs, sigs, &error->message);
}

static const char *funcHeaderCode = R"(
    [](const QQmlPrivate::AOTCompiledContext *aotContext, void **argv) {
Q_UNUSED(aotContext)
Q_UNUSED(argv)
)";

static QString wrapString(const QString &s)
{
    return "u\"%1\"_s"_L1.arg(s);
}

static QString typeToString(const QQmlPrivate::AOTLookupValidation::Type &type)
{
    bool isComposite = type.isComposite == QQmlPrivate::AOTLookupValidation::IsComposite::Yes;
    bool isIC = type.isInlineComponent == QQmlPrivate::AOTLookupValidation::IsIC::Yes;
    return u"Type{ %1, %2, %3, %4, %5 }"_s
            .arg(wrapString(type.module), wrapString(type.name),
                 wrapString(type.icNameOrExtensionTypeName),
                 isComposite ? "IsComposite::Yes"_L1 : "IsComposite::No"_L1,
                 isIC ? "IsIC::Yes"_L1 : "IsIC::No"_L1);
};

static QString lookupToString(const QQmlPrivate::AOTLookupValidation::Lookup &lookup)
{
    return "Lookup{ %1, %2, %3 }"_L1.arg(typeToString(lookup.base), wrapString(lookup.member),
                                         wrapString(lookup.enumName));
};

static QString signatureToString(const QQmlPrivate::AOTLookupValidation::Signature &signature)
{
    if (const auto *p = std::get_if<QQmlPrivate::AOTLookupValidation::PropertySignature>(&signature)) {
        return u"PropertySignature{ %1, %2 }"_s
                .arg(typeToString(p->type), QString::number(p->relativeIndex));
    } else if (const auto *e = std::get_if<QQmlPrivate::AOTLookupValidation::EnumKeySignature>(&signature)) {
        bool isFlag = e->isFlag == QQmlPrivate::AOTLookupValidation::IsFlag::Yes;
        return u"EnumKeySignature{ %1, %2 }"_s
                .arg(QString::number(e->value), isFlag ? "IsFlag::Yes"_L1 : "IsFlag::No"_L1);
    } else if (const auto *m = std::get_if<QQmlPrivate::AOTLookupValidation::MethodSignature>(&signature)) {
        QString paramNames = "{ "_L1;
        for (const auto &paramName : m->paramNames)
            paramNames += wrapString(paramName) + ", "_L1;
        paramNames += u'}';

        QString types = "{ "_L1;
        for (const auto &paramType : m->types)
            types += typeToString(paramType) + ", "_L1;
        types += u'}';

        bool isSignal = m->isSignal == QQmlPrivate::AOTLookupValidation::IsSignal::Yes;
        return u"MethodSignature{ %1, %2, %3, %4 }"_s
                .arg(paramNames, types, QString::number(m->relativeIndex),
                     isSignal ? "IsSignal::Yes"_L1 : "IsSignal::No"_L1);
    }
    Q_UNREACHABLE_RETURN(QString());
};

static const char *skippedValidationCode = R"(
bool validateLookupSignatures(QQmlEngine *engine, QV4::CompiledData::CompilationUnit *cu)
{
    // AOT validation code not generated (NO_GENERATE_AOT_VALIDATION)
    Q_UNUSED(engine);
    Q_UNUSED(cu);
    return true;
}

)";

template <typename WriteStr>
static bool generateAotValidationCode(
        const WriteStr &writeStr, const LookupSignatures &lookupSignatures, bool noAotValidation)
{
    if (noAotValidation) {
        if (!writeStr(skippedValidationCode))
            return false;
        return true;
    }

    using namespace QQmlPrivate::AOTLookupValidation;
    if (!writeStr("QQmlPrivate::AOTLookupValidation::LookupSignatures expectedLookupSignatures()\n"
                  "{\n"
                  "    using namespace Qt::StringLiterals;\n"
                  "    using namespace QQmlPrivate::AOTLookupValidation;\n"
                  "    return {\n")) {
        return false;
    }

    const auto &signatures = lookupSignatures.asKeyValueRange();
    QList<std::pair<Lookup, Signature>> sorted{ signatures.begin(), signatures.end() };
    std::stable_sort(sorted.begin(), sorted.end(), [&](const auto &lhs, const auto &rhs) {
        const auto &lTypeString = typeToString(lhs.first.base);
        const auto &rTypeString = typeToString(rhs.first.base);
        if (lTypeString == rTypeString)
            return lhs.first.member < rhs.first.member;
        return lTypeString < rTypeString;
    });

    for (const auto &[memberlookup, signature] : sorted) {
        if (!writeStr("        { %1, %2 },\n"_L1
                              .arg(lookupToString(memberlookup), signatureToString(signature))
                              .toLatin1())) {
            return false;
        }
    }

    if (!writeStr("    };\n}\n"))
        return false;

    const QString validateLookupSignatures = uR"(
bool validateLookupSignatures(QQmlEngine *engine, QV4::CompiledData::CompilationUnit *cu)
{
    enum ValidationState { Pending, Failed, Succeeded };
    static ValidationState state = Pending;
    if (state == Failed)
        return false;
    if (state == Succeeded)
        return true;
    const auto &expectedSignatures = expectedLookupSignatures();
    for (const auto &[lookup, expectedSignature] : expectedSignatures.asKeyValueRange()) {
        if (!QQmlPrivate::AOTLookupValidation::validateLookupSignature(engine, cu, lookup, expectedSignature)) {
            state = Failed;
            return false;
        }
    }
    state = Succeeded;
    return true;
}

)"_s;

    if (!writeStr(validateLookupSignatures.toUtf8()))
        return false;

    return true;
}

bool qSaveQmlJSUnitAsCpp(const QString &inputFileName, const QString &outputFileName,
                         const QV4::CompiledData::SaveableUnitPointer &unit,
                         const QQmlJSAotFunctionMap &aotFunctions,
                         const LookupSignatures &lookupSignatures, bool noAotValidation,
                         QString *errorString)
{
#if QT_CONFIG(temporaryfile)
    QSaveFile f(outputFileName);
#else
    QFile f(outputFileName);
#endif
    if (!f.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
        *errorString = f.errorString();
        return false;
    }

    auto writeStr = [&f, errorString](const QByteArray &data) {
        if (f.write(data) != data.size()) {
            *errorString = f.errorString();
            return false;
        }
        return true;
    };

    if (!writeStr("// "))
        return false;

    if (!writeStr(inputFileName.toUtf8()))
        return false;

    if (!writeStr("\n"))
        return false;

    if (!writeStr("#include <QtQml/qqmlprivate.h>\n"))
        return false;

    if (!noAotValidation) {
        if (!writeStr("#include <QtCore/qhash.h>\n"))
            return false;
    }

    if (!aotFunctions.isEmpty()) {
        QStringList includes;

        for (const auto &function : aotFunctions)
            includes.append(function.includes);

        std::sort(includes.begin(), includes.end());
        const auto end = std::unique(includes.begin(), includes.end());
        for (auto it = includes.begin(); it != end; ++it) {
            if (!writeStr(QStringLiteral("#include <%1>\n").arg(*it).toUtf8()))
                return false;
        }
    }

    if (!writeStr(QByteArrayLiteral("\nnamespace QmlCacheGeneratedCode {\nnamespace ")))
        return false;

    if (!writeStr(qQmlJSSymbolNamespaceForPath(inputFileName).toUtf8()))
        return false;

    if (!writeStr(" {\n"))
        return false;

    if (!generateAotValidationCode(writeStr, lookupSignatures, noAotValidation))
        return false;

    if (!writeStr(QByteArrayLiteral("extern const unsigned char qmlData alignas(16) [];\n"
                                    "extern const unsigned char qmlData alignas(16) [] = {\n")))
        return false;

    unit.saveToDisk<uchar>([&writeStr](const uchar *begin, quint32 size) {
        QByteArray hexifiedData;
        {
            QTextStream stream(&hexifiedData);
            const uchar *end = begin + size;
            stream << Qt::hex;
            int col = 0;
            for (const uchar *data = begin; data < end; ++data, ++col) {
                if (data > begin)
                    stream << ',';
                if (col % 8 == 0) {
                    stream << '\n';
                    col = 0;
                }
                stream << "0x" << *data;
            }
            stream << '\n';
        }
        return writeStr(hexifiedData);
    });



    if (!writeStr("};\n"))
        return false;

    writeStr(aotFunctions[FileScopeCodeIndex].code.toUtf8().constData());
    if (aotFunctions.size() <= 1) {
        // FileScopeCodeIndex is always there, but it may be the only one.
        writeStr("extern const QQmlPrivate::AOTCompiledFunction aotBuiltFunctions[];\n"
                 "extern const QQmlPrivate::AOTCompiledFunction aotBuiltFunctions[] = { { 0, 0, nullptr, nullptr } };\n");
    } else {
        writeStr("extern const QQmlPrivate::AOTCompiledFunction aotBuiltFunctions[];\n"
                 "extern const QQmlPrivate::AOTCompiledFunction aotBuiltFunctions[] = {\n");

        QString footer = QStringLiteral("}\n");

        for (QQmlJSAotFunctionMap::ConstIterator func = aotFunctions.constBegin(),
             end = aotFunctions.constEnd();
             func != end; ++func) {

            if (func.key() == FileScopeCodeIndex)
                continue;

            const QString function = QString::fromUtf8(funcHeaderCode) + func.value().code + footer;

            writeStr(QStringLiteral("{ %1, %2, [](QV4::ExecutableCompilationUnit *contextUnit, "
                                    "QMetaType *argTypes) {\n%3}, %4 },")
                     .arg(func.key())
                     .arg(func->numArguments)
                     .arg(func->signature.isEmpty() ? u"    Q_UNUSED(contextUnit);\n    Q_UNUSED(argTypes);\n"_s : func->signature, function)
                     .toUtf8().constData());
        }

        // Conclude the list with a nullptr
        writeStr("{ 0, 0, nullptr, nullptr }");
        writeStr("};\n");
    }

    if (!writeStr("}\n}\n"))
        return false;

#if QT_CONFIG(temporaryfile)
    if (!f.commit()) {
        *errorString = f.errorString();
        return false;
    }
#endif

    return true;
}

QQmlJSAotCompiler::QQmlJSAotCompiler(
        QQmlJSImporter *importer, const QString &resourcePath, const QStringList &qmldirFiles,
        QQmlJSLogger *logger)
    : m_typeResolver(importer)
    , m_resourcePath(resourcePath)
    , m_qmldirFiles(qmldirFiles)
    , m_importer(importer)
    , m_logger(logger)
{
}

void QQmlJSAotCompiler::setDocument(
        const QmlIR::JSCodeGen *codegen, const QmlIR::Document *irDocument)
{
    Q_UNUSED(codegen);
    m_document = irDocument;
    const QFileInfo resourcePathInfo(m_resourcePath);
    if (m_logger->filePath().isEmpty())
        m_logger->setFilePath(resourcePathInfo.fileName());
    m_logger->setCode(irDocument->code);
    m_unitGenerator = &irDocument->jsGenerator;
    QQmlJSImportVisitor visitor(m_importer, m_logger,
                                resourcePathInfo.canonicalPath() + u'/',
                                m_qmldirFiles);
    m_typeResolver.init(&visitor, irDocument->program);
}

void QQmlJSAotCompiler::setScope(const QmlIR::Object *object, const QmlIR::Object *scope)
{
    m_currentObject = object;
    m_currentScope = scope;
}

static bool isStrict(const QmlIR::Document *doc)
{
    for (const QmlIR::Pragma *pragma : doc->pragmas) {
        if (pragma->type == QmlIR::Pragma::Strict)
            return true;
    }
    return false;
}

QQmlJS::DiagnosticMessage QQmlJSAotCompiler::diagnose(
        const QString &message, QtMsgType type, const QQmlJS::SourceLocation &location) const
{
    if (isStrict(m_document)
            && (type == QtWarningMsg || type == QtCriticalMsg || type == QtFatalMsg)
            && m_logger->categorySeverity(qmlCompiler) == QQmlSA::WarningSeverity::Error) {
        qFatal("%s:%d: (strict mode) %s",
               qPrintable(QFileInfo(m_resourcePath).fileName()),
               location.startLine, qPrintable(message));
    }

    return QQmlJS::DiagnosticMessage {
        message,
        type,
        location
    };
}

std::variant<QQmlJSAotFunction, QList<QQmlJS::DiagnosticMessage>> QQmlJSAotCompiler::compileBinding(
        const QV4::Compiler::Context *context, const QmlIR::Binding &irBinding,
        QQmlJS::AST::Node *astNode)
{
    QQmlJSFunctionInitializer initializer(
                &m_typeResolver, m_currentObject->location, m_currentScope->location, m_logger);

    const QString name = m_document->stringAt(irBinding.propertyNameIndex);
    QQmlJSCompilePass::Function function = initializer.run( context, name, astNode, irBinding);

    const QQmlJSAotFunction aotFunction = doCompileAndRecordAotStats(
            context, &function, name, astNode->firstSourceLocation());

    if (const auto errors = finalizeBindingOrFunction())
        return *errors;

    qCDebug(lcAotCompiler()) << "includes:" << aotFunction.includes;
    qCDebug(lcAotCompiler()) << "binding code:" << aotFunction.code;
    return aotFunction;
}

std::variant<QQmlJSAotFunction, QList<QQmlJS::DiagnosticMessage>> QQmlJSAotCompiler::compileFunction(
        const QV4::Compiler::Context *context, const QString &name, QQmlJS::AST::Node *astNode)
{
    QQmlJSFunctionInitializer initializer(
                &m_typeResolver, m_currentObject->location, m_currentScope->location, m_logger);
    QQmlJSCompilePass::Function function = initializer.run(context, name, astNode);

    const QQmlJSAotFunction aotFunction = doCompileAndRecordAotStats(
            context, &function, name, astNode->firstSourceLocation());

    if (const auto errors = finalizeBindingOrFunction())
        return *errors;

    qCDebug(lcAotCompiler()) << "includes:" << aotFunction.includes;
    qCDebug(lcAotCompiler()) << "binding code:" << aotFunction.code;
    return aotFunction;
}

QQmlJSAotFunction QQmlJSAotCompiler::globalCode() const
{
    QQmlJSAotFunction global;
    global.includes = {
        u"QtQml/qjsengine.h"_s,
        u"QtQml/qjsprimitivevalue.h"_s,
        u"QtQml/qjsvalue.h"_s,
        u"QtQml/qqmlcomponent.h"_s,
        u"QtQml/qqmlcontext.h"_s,
        u"QtQml/qqmlengine.h"_s,
        u"QtQml/qqmllist.h"_s,

        u"QtCore/qdatetime.h"_s,
        u"QtCore/qtimezone.h"_s,
        u"QtCore/qobject.h"_s,
        u"QtCore/qstring.h"_s,
        u"QtCore/qstringlist.h"_s,
        u"QtCore/qurl.h"_s,
        u"QtCore/qvariant.h"_s,

        u"type_traits"_s
    };
    return global;
}

std::optional<QList<QQmlJS::DiagnosticMessage>> QQmlJSAotCompiler::finalizeBindingOrFunction()
{
    const auto archiveMessages = qScopeGuard([this]() { m_logger->finalizeFunction(); });

    if (!m_logger->currentFunctionHasCompileError())
        return {};

    QList<QQmlJS::DiagnosticMessage> errors;
    m_logger->iterateCurrentFunctionMessages([&](const Message &msg) {
        if (msg.compilationStatus == Message::CompilationStatus::Error)
            errors.append(diagnose(msg.message, msg.type, msg.loc));
    });
    return errors;
}

QQmlJSAotFunction QQmlJSAotCompiler::doCompile(
        const QV4::Compiler::Context *context, const QQmlJSCompilePass::Function *function)
{
    if (m_logger->currentFunctionHasErrorOrSkip())
        return QQmlJSAotFunction();

    bool basicBlocksValidationFailed = false;
    QQmlJSBasicBlocks basicBlocks(context, m_unitGenerator, &m_typeResolver, m_logger);
    auto passResult = basicBlocks.run(function, m_flags, basicBlocksValidationFailed);
    auto &[blocks, annotations] = passResult;

    QQmlJSTypePropagator propagator(
            m_unitGenerator, &m_typeResolver, m_logger, blocks, annotations);
    passResult = propagator.run(function);
    if (m_logger->currentFunctionHasErrorOrSkip())
        return QQmlJSAotFunction();

    QQmlJSShadowCheck shadowCheck(
            m_unitGenerator, &m_typeResolver, m_logger, blocks, annotations);
    passResult = shadowCheck.run(function);
    if (m_logger->currentFunctionHasErrorOrSkip())
        return QQmlJSAotFunction();

    QQmlJSOptimizations optimizer(
            m_unitGenerator, &m_typeResolver, m_logger, blocks, annotations,
            basicBlocks.objectAndArrayDefinitions());
    passResult = optimizer.run(function);
    if (m_logger->currentFunctionHasErrorOrSkip())
        return QQmlJSAotFunction();

    QQmlJSStorageInitializer initializer(
            m_unitGenerator, &m_typeResolver, m_logger, blocks, annotations);
    passResult = initializer.run(function);

    // Generalize all arguments, registers, and the return type.
    QQmlJSStorageGeneralizer generalizer(
            m_unitGenerator, &m_typeResolver, m_logger, blocks, annotations);
    passResult = generalizer.run(function);
    if (m_logger->currentFunctionHasErrorOrSkip())
        return QQmlJSAotFunction();

    QQmlJSCodeGenerator codegen(context, m_unitGenerator, &m_typeResolver, m_logger, blocks,
                                annotations, noAotValidation());
    QQmlJSAotFunction result = codegen.run(function, basicBlocksValidationFailed);
    if (m_logger->currentFunctionHasErrorOrSkip())
        return QQmlJSAotFunction();

    m_lookupSignatures.insert(codegen.lookupSignatures());
    return result;
}

QQmlJSAotFunction QQmlJSAotCompiler::doCompileAndRecordAotStats(
        const QV4::Compiler::Context *context, const QQmlJSCompilePass::Function *function,
        const QString &name, QQmlJS::SourceLocation location)
{
    QElapsedTimer timer {};
    timer.start();
    QQmlJSAotFunction result;
    if (!m_logger->currentFunctionHasCompileError())
        result = doCompile(context, function);
    auto elapsed = std::chrono::milliseconds { timer.elapsed() };

    if (QQmlJS::QQmlJSAotCompilerStats::recordAotStats()) {
        QQmlJS::AotStatsEntry entry;
        entry.codegenDuration = elapsed;
        entry.functionName = name;
        entry.message = m_logger->currentFunctionWasSkipped()
                ? m_logger->currentFunctionCompileSkipMessage()
                : m_logger->currentFunctionCompileErrorMessage();
        entry.line = location.startLine;
        entry.column = location.startColumn;
        if (m_logger->currentFunctionWasSkipped())
            entry.codegenResult = QQmlJS::CodegenResult::Skip;
        else if (m_logger->currentFunctionHasCompileError())
            entry.codegenResult = QQmlJS::CodegenResult::Failure;
        else
            entry.codegenResult = QQmlJS::CodegenResult::Success;
        QQmlJS::QQmlJSAotCompilerStats::addEntry(
                function->qmlScope.containedType()->filePath(), entry);
    }

    if (m_logger->currentFunctionWasSkipped())
        result.skipReason = m_logger->currentFunctionCompileSkipMessage();

    return result;
}

QT_END_NAMESPACE
