Revision control

++ // Copyright (C) 2021 The Qt Company Ltd.
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
++ // Qt-Security score:critical reason:code-generation
++
++ #include "qqmltccodewriter_p.h"
++
++ #include <QtCore/qfileinfo.h>
++ #include <QtCore/qstringbuilder.h>
++ #include <QtCore/qstring.h>
++ #include <QtCore/qmap.h>
++ #include <QtCore/qlist.h>
++
++ #include <utility>
++
++ QT_BEGIN_NAMESPACE
++
++ using namespace Qt::StringLiterals;
++
++ namespace QQmltc {
++
++ static QString urlToMacro(const QString &url)
++ {
++ QFileInfo fi(url);
++ return u"Q_QMLTC_" + fi.baseName().toUpper();
++ }
++
++ static QString getFunctionCategory(const MethodBase &method)
++ {
++ QString category;
++ switch (method.access) {
++ case QQmlJSMetaMethod::Private:
++ category = u"private"_s;
++ break;
++ case QQmlJSMetaMethod::Protected:
++ category = u"protected"_s;
++ break;
++ case QQmlJSMetaMethod::Public:
++ category = u"public"_s;
++ break;
++ }
++ return category;
++ }
++
++ static QString getFunctionCategory(const Method &method)
++ {
++ QString category = getFunctionCategory(static_cast<const MethodBase &>(method));
++ switch (method.type) {
++ case QQmlJSMetaMethodType::Signal:
++ category = u"Q_SIGNALS"_s;
++ break;
++ case QQmlJSMetaMethodType::Slot:
++ category += u" Q_SLOTS"_s;
++ break;
++ case QQmlJSMetaMethodType::Method:
++ case QQmlJSMetaMethodType::StaticMethod:
++ break;
++ }
++ return category;
++ }
++
++ static QString appendSpace(const QString &s)
++ {
++ if (s.isEmpty())
++ return s;
++ return s + u" ";
++ }
++
++ static QString prependSpace(const QString &s)
++ {
++ if (s.isEmpty())
++ return s;
++ return u" " + s;
++ }
++
++ static std::pair<QString, QString> functionSignatures(const MethodBase &method)
++ {
++ const QString name = method.name;
++ const QList<Variable> &parameterList = method.parameterList;
++
++ QStringList headerParamList;
++ QStringList cppParamList;
++ for (const Variable &variable : parameterList) {
++ const QString commonPart = variable.cppType + u" " + variable.name;
++ cppParamList << commonPart;
++ headerParamList << commonPart;
++ if (!variable.defaultValue.isEmpty())
++ headerParamList.back() += u" = " + variable.defaultValue;
++ }
++
++ const QString headerSignature = name + u"(" + headerParamList.join(u", "_s) + u")"
++ + prependSpace(method.modifiers.join(u" "));
++ const QString cppSignature = name + u"(" + cppParamList.join(u", "_s) + u")"
++ + prependSpace(method.modifiers.join(u" "));
++ return { headerSignature, cppSignature };
++ }
++
++ static QString functionReturnType(const Method &m)
++ {
++ return appendSpace(m.declarationPrefixes.join(u" "_s)) + m.returnType;
++ }
++
++ void CodeWriter::writeGlobalHeader(OutputWrapper &code, const QString &sourcePath,
++ const QString &hPath, const QString &cppPath,
++ const QString &outNamespace,
++ const QSet<QString> &requiredCppIncludes)
++ {
++ Q_UNUSED(cppPath);
++ const QString preamble = u"// This code is auto-generated by the qmltc tool from the file '"
++ + sourcePath + u"'\n// WARNING! All changes made in this file will be lost!\n";
++ code.rawAppendToHeader(preamble);
++ code.rawAppendToCpp(preamble);
++ code.rawAppendToHeader(
++ u"// NOTE: This generated API is to be considered implementation detail.");
++ code.rawAppendToHeader(
++ u"// It may change from version to version and should not be relied upon.");
++
++ const QString headerMacro = urlToMacro(sourcePath);
++ code.rawAppendToHeader(u"#ifndef %1_H"_s.arg(headerMacro));
++ code.rawAppendToHeader(u"#define %1_H"_s.arg(headerMacro));
++
++ code.rawAppendToHeader(u"#include <QtCore/qproperty.h>");
++ code.rawAppendToHeader(u"#include <QtCore/qobject.h>");
++ code.rawAppendToHeader(u"#include <QtCore/qcoreapplication.h>");
++ code.rawAppendToHeader(u"#include <QtCore/qxpfunctional.h>");
++ code.rawAppendToHeader(u"#include <QtQml/qqmlengine.h>");
++ code.rawAppendToHeader(u"#include <QtCore/qurl.h>"); // used in engine execution
++ code.rawAppendToHeader(u"#include <QtQml/qqml.h>"); // used for attached properties
++
++ code.rawAppendToHeader(u"#include <private/qqmlengine_p.h>"); // executeRuntimeFunction(), etc.
++ code.rawAppendToHeader(u"#include <private/qqmltcobjectcreationhelper_p.h>"); // QmltcSupportLib
++
++ code.rawAppendToHeader(u"#include <QtQml/qqmllist.h>"); // QQmlListProperty
++
++ // include custom C++ includes required by used types
++ code.rawAppendToHeader(u"// BEGIN(custom_cpp_includes)");
++ for (const auto &requiredInclude : requiredCppIncludes)
++ code.rawAppendToHeader(u"#include \"" + requiredInclude + u"\"");
++ code.rawAppendToHeader(u"// END(custom_cpp_includes)");
++
++ code.rawAppendToCpp(u"#include \"" + hPath + u"\""); // include own .h file
++ code.rawAppendToCpp(u"// qmltc support library:");
++ code.rawAppendToCpp(u"#include <private/qqmlcppbinding_p.h>"); // QmltcSupportLib
++ code.rawAppendToCpp(u"#include <private/qqmlcpponassignment_p.h>"); // QmltcSupportLib
++ code.rawAppendToHeader(u"#include <private/qqmlcpptypehelpers_p.h> "); // QmltcSupportLib
++
++ code.rawAppendToCpp(u"#include <private/qqmlobjectcreator_p.h>"); // createComponent()
++ code.rawAppendToCpp(u"#include <private/qqmlcomponent_p.h>"); // QQmlComponentPrivate::get()
++
++ code.rawAppendToCpp(u"");
++ code.rawAppendToCpp(u"#include <private/qobject_p.h>"); // NB: for private properties
++ code.rawAppendToCpp(u"#include <private/qqmlobjectcreator_p.h>"); // for finalize callbacks
++ code.rawAppendToCpp(u"#include <QtQml/qqmlprivate.h>"); // QQmlPrivate::qmlExtendedObject()
++
++ code.rawAppendToCpp(u""); // blank line
++ code.rawAppendToCpp(u"QT_USE_NAMESPACE // avoid issues with QT_NAMESPACE");
++
++ code.rawAppendToHeader(u""); // blank line
++
++ const QStringList namespaces = outNamespace.split(u"::"_s);
++
++ for (const QString &currentNamespace : namespaces) {
++ code.rawAppendToHeader(u"namespace %1 {"_s.arg(currentNamespace));
++ code.rawAppendToCpp(u"namespace %1 {"_s.arg(currentNamespace));
++ }
++ }
++
++ void CodeWriter::write(OutputWrapper &code, const PropertyInitializer &propertyInitializer,
++ const Type &wrappedType)
++ {
++ code.rawAppendToHeader(u"class " + propertyInitializer.name + u" {");
++
++ {
++ {
++ [[maybe_unused]] OutputWrapper::HeaderIndentationScope headerIndent(&code);
++
++ code.rawAppendToHeader(u"friend class " + wrappedType.cppType + u";");
++ }
++
++ code.rawAppendToHeader(u"public:"_s);
++
++ [[maybe_unused]] OutputWrapper::MemberNameScope typeScope(&code, propertyInitializer.name);
++ {
++ [[maybe_unused]] OutputWrapper::HeaderIndentationScope headerIndent(&code);
++
++ write(code, propertyInitializer.constructor);
++ code.rawAppendToHeader(u""); // blank line
++
++ for (const auto &propertySetter : propertyInitializer.propertySetters) {
++ write(code, propertySetter);
++ }
++ }
++
++ code.rawAppendToHeader(u""); // blank line
++ code.rawAppendToHeader(u"private:"_s);
++
++ {
++ [[maybe_unused]] OutputWrapper::HeaderIndentationScope headerIndent(&code);
++
++ write(code, propertyInitializer.component);
++ write(code, propertyInitializer.initializedCache);
++ }
++ }
++
++ code.rawAppendToHeader(u"};"_s);
++ code.rawAppendToHeader(u""); // blank line
++ }
++
++ void CodeWriter::write(OutputWrapper &code, const RequiredPropertiesBundle &requiredPropertiesBundle)
++ {
++ code.rawAppendToHeader(u"struct " + requiredPropertiesBundle.name + u" {");
++
++ {
++ [[maybe_unused]] OutputWrapper::HeaderIndentationScope headerIndent(&code);
++
++ for (const auto &member : requiredPropertiesBundle.members) {
++ write(code, member);
++ }
++ }
++
++ code.rawAppendToHeader(u"};"_s);
++ code.rawAppendToHeader(u""); // blank line
++ }
++
++ void CodeWriter::writeGlobalFooter(OutputWrapper &code, const QString &sourcePath,
++ const QString &outNamespace)
++ {
++ const QStringList namespaces = outNamespace.split(u"::"_s);
++
++ for (auto it = namespaces.crbegin(), end = namespaces.crend(); it != end; it++) {
++ code.rawAppendToCpp(u"} // namespace %1"_s.arg(*it));
++ code.rawAppendToHeader(u"} // namespace %1"_s.arg(*it));
++ }
++
++ code.rawAppendToHeader(u""); // blank line
++ code.rawAppendToHeader(u"#endif // %1_H"_s.arg(urlToMacro(sourcePath)));
++ code.rawAppendToHeader(u""); // blank line
++ }
++
++ static void writeToFile(const QString &path, const QByteArray &data)
++ {
++ // When not using dependency files, changing a single qml invalidates all
++ // qml files and would force the recompilation of everything. To avoid that,
++ // we check if the data is equal to the existing file, if yes, don't touch
++ // it so the build system will not recompile unnecessary things.
++ //
++ // If the build system use dependency file, we should anyway touch the file
++ // so qmltc is not re-run
++ QFileInfo fi(path);
++ if (fi.exists() && fi.size() == data.size()) {
++ QFile oldFile(path);
++ if (oldFile.open(QIODevice::ReadOnly)) {
++ if (oldFile.readAll() == data)
++ return;
++ }
++ }
++ QFile file(path);
++ if (!file.open(QIODevice::WriteOnly))
++ qFatal("Could not open file %s", qPrintable(path));
++ file.write(data);
++ }
++
++ void CodeWriter::write(OutputWrapper &code, const Program &program)
++ {
++ writeGlobalHeader(code, program.url, program.hPath, program.cppPath, program.outNamespace,
++ program.includes);
++
++ // url method comes first
++ writeUrl(code, program.urlMethod);
++
++ // forward declare all the types first
++ for (const Type &type : std::as_const(program.compiledTypes))
++ code.rawAppendToHeader(u"class " + type.cppType + u";");
++ // write all the types and their content
++ for (const Type &type : std::as_const(program.compiledTypes))
++ write(code, type, program.exportMacro);
++
++ // add typeCount definitions. after all types have been written down (so
++ // they are now complete types as per C++). practically, this only concerns
++ // document root type
++ for (const Type &type : std::as_const(program.compiledTypes)) {
++ if (!type.typeCount)
++ continue;
++ code.rawAppendToHeader(u""); // blank line
++ code.rawAppendToHeader(u"constexpr %1 %2::%3()"_s.arg(type.typeCount->returnType,
++ type.cppType, type.typeCount->name));
++ code.rawAppendToHeader(u"{");
++ for (const QString &line : std::as_const(type.typeCount->body))
++ code.rawAppendToHeader(line, 1);
++ code.rawAppendToHeader(u"}");
++ }
++
++ writeGlobalFooter(code, program.url, program.outNamespace);
++
++ writeToFile(program.hPath, code.code().header.toUtf8());
++ writeToFile(program.cppPath, code.code().cpp.toUtf8());
++ }
++
++ template<typename Predicate>
++ static void dumpFunctions(OutputWrapper &code, const QList<Method> &functions, Predicate pred)
++ {
++ // functions are _ordered_ by access and kind. ordering is important to
++ // provide consistent output
++ QMap<QString, QList<const Method *>> orderedFunctions;
++ for (const auto &function : functions) {
++ if (pred(function))
++ orderedFunctions[getFunctionCategory(function)].append(std::addressof(function));
++ }
++
++ for (auto it = orderedFunctions.cbegin(); it != orderedFunctions.cend(); ++it) {
++ code.rawAppendToHeader(it.key() + u":", -1);
++ for (const Method *function : std::as_const(it.value()))
++ CodeWriter::write(code, *function);
++ }
++ }
++
++ void CodeWriter::write(OutputWrapper &code, const Type &type, const QString &exportMacro)
++ {
++ const auto constructClassString = [&]() {
++ QString str = u"class "_s;
++ if (!exportMacro.isEmpty())
++ str.append(exportMacro).append(u" "_s);
++ str.append(type.cppType);
++ QStringList nonEmptyBaseClasses;
++ nonEmptyBaseClasses.reserve(type.baseClasses.size());
++ std::copy_if(type.baseClasses.cbegin(), type.baseClasses.cend(),
++ std::back_inserter(nonEmptyBaseClasses),
++ [](const QString &entry) { return !entry.isEmpty(); });
++ if (!nonEmptyBaseClasses.isEmpty())
++ str += u" : public " + nonEmptyBaseClasses.join(u", public "_s);
++ return str;
++ };
++
++ code.rawAppendToHeader(u""); // blank line
++ code.rawAppendToCpp(u""); // blank line
++
++ code.rawAppendToHeader(constructClassString());
++ code.rawAppendToHeader(u"{");
++ for (const QString &mocLine : std::as_const(type.mocCode))
++ code.rawAppendToHeader(mocLine, 1);
++
++ OutputWrapper::MemberNameScope typeScope(&code, type.cppType);
++ Q_UNUSED(typeScope);
++ {
++ OutputWrapper::HeaderIndentationScope headerIndent(&code);
++ Q_UNUSED(headerIndent);
++
++ // first, write user-visible code, then everything else. someone might
++ // want to look at the generated code, so let's make an effort when
++ // writing it down
++
++ code.rawAppendToHeader(u"/* ----------------- */");
++ code.rawAppendToHeader(u"/* External C++ API */");
++ code.rawAppendToHeader(u"public:", -1);
++
++ if (!type.propertyInitializer.name.isEmpty())
++ write(code, type.propertyInitializer, type);
++
++ if (type.requiredPropertiesBundle)
++ write(code, *type.requiredPropertiesBundle);
++
++ // NB: when non-document root, the externalCtor won't be public - but we
++ // really don't care about the output format of such types
++ if (!type.ignoreInit && type.externalCtor.access == QQmlJSMetaMethod::Public) {
++ // TODO: ignoreInit must be eliminated
++
++ CodeWriter::write(code, type.externalCtor);
++ if (type.staticCreate)
++ CodeWriter::write(code, *type.staticCreate);
++ }
++
++ // dtor
++ if (type.dtor)
++ CodeWriter::write(code, *type.dtor);
++
++ // enums
++ for (const auto &enumeration : std::as_const(type.enums))
++ CodeWriter::write(code, enumeration);
++
++ // visible functions
++ const auto isUserVisibleFunction = [](const Method &function) {
++ return function.userVisible;
++ };
++ dumpFunctions(code, type.functions, isUserVisibleFunction);
++
++ code.rawAppendToHeader(u"/* ----------------- */");
++ code.rawAppendToHeader(u""); // blank line
++ code.rawAppendToHeader(u"/* Internal functionality (do NOT use it!) */");
++
++ // below are the hidden parts of the type
++
++ // (rest of the) ctors
++ if (type.ignoreInit) { // TODO: this branch should be eliminated
++ Q_ASSERT(type.baselineCtor.access == QQmlJSMetaMethod::Public);
++ code.rawAppendToHeader(u"public:", -1);
++ CodeWriter::write(code, type.baselineCtor);
++ } else {
++ code.rawAppendToHeader(u"protected:", -1);
++ if (type.externalCtor.access != QQmlJSMetaMethod::Public) {
++ Q_ASSERT(type.externalCtor.access == QQmlJSMetaMethod::Protected);
++ CodeWriter::write(code, type.externalCtor);
++ }
++ CodeWriter::write(code, type.baselineCtor);
++ CodeWriter::write(code, type.init);
++ CodeWriter::write(code, type.endInit);
++ CodeWriter::write(code, type.setComplexBindings);
++ CodeWriter::write(code, type.beginClass);
++ CodeWriter::write(code, type.completeComponent);
++ CodeWriter::write(code, type.finalizeComponent);
++ CodeWriter::write(code, type.handleOnCompleted);
++ }
++
++ // children
++ for (const auto &child : std::as_const(type.children))
++ CodeWriter::write(code, child, exportMacro);
++
++ // (non-visible) functions
++ dumpFunctions(code, type.functions,
++ [&](const auto &m) { return !isUserVisibleFunction(m); });
++
++ // variables and properties
++ if (!type.variables.isEmpty() || !type.properties.isEmpty()) {
++ code.rawAppendToHeader(u""); // blank line
++ code.rawAppendToHeader(u"protected:", -1);
++ }
++ for (const auto &property : std::as_const(type.properties))
++ write(code, property);
++ for (const auto &variable : std::as_const(type.variables))
++ write(code, variable);
++ }
++
++ code.rawAppendToHeader(u"private:", -1);
++ for (const QString &otherLine : std::as_const(type.otherCode))
++ code.rawAppendToHeader(otherLine, 1);
++
++ if (type.typeCount) {
++ // add typeCount declaration, definition is added later
++ code.rawAppendToHeader(u""); // blank line
++ code.rawAppendToHeader(u"protected:");
++ code.rawAppendToHeader(u"constexpr static %1 %2();"_s.arg(type.typeCount->returnType,
++ type.typeCount->name),
++ 1);
++ }
++
++ code.rawAppendToHeader(u"};");
++ }
++
++ void CodeWriter::write(OutputWrapper &code, const Enum &enumeration)
++ {
++ code.rawAppendToHeader(u"enum " + enumeration.cppType + u" {");
++ for (qsizetype i = 0; i < enumeration.keys.size(); ++i) {
++ QString str;
++ if (enumeration.values.isEmpty()) {
++ str += enumeration.keys.at(i) + u",";
++ } else {
++ str += enumeration.keys.at(i) + u" = " + enumeration.values.at(i) + u",";
++ }
++ code.rawAppendToHeader(str, 1);
++ }
++ code.rawAppendToHeader(u"};");
++ code.rawAppendToHeader(enumeration.ownMocLine);
++ }
++
++ void CodeWriter::write(OutputWrapper &code, const Method &method)
++ {
++ const auto [hSignature, cppSignature] = functionSignatures(method);
++ // Note: augment return type with preambles in declaration
++ code.rawAppendToHeader((method.type == QQmlJSMetaMethodType::StaticMethod
++ ? u"static " + functionReturnType(method)
++ : functionReturnType(method))
++ + u" " + hSignature + u";");
++
++ // do not generate method implementation if it is a signal
++ const auto methodType = method.type;
++ if (methodType != QQmlJSMetaMethodType::Signal) {
++ code.rawAppendToCpp(u""_s); // blank line
++ if (method.comments.size() > 0) {
++ code.rawAppendToCpp(u"/*! \\internal"_s);
++ for (const auto &comment : method.comments)
++ code.rawAppendToCpp(comment, 1);
++ code.rawAppendToCpp(u"*/"_s);
++ }
++ code.rawAppendToCpp(method.returnType);
++ code.rawAppendSignatureToCpp(cppSignature);
++ code.rawAppendToCpp(u"{");
++ {
++ OutputWrapper::CppIndentationScope cppIndent(&code);
++ Q_UNUSED(cppIndent);
++ for (const QString &line : std::as_const(method.body))
++ code.rawAppendToCpp(line);
++ }
++ code.rawAppendToCpp(u"}");
++ }
++ }
++
++ template<typename WriteInitialization>
++ static void writeSpecialMethod(OutputWrapper &code, const MethodBase &specialMethod,
++ WriteInitialization writeInit)
++ {
++ const auto [hSignature, cppSignature] = functionSignatures(specialMethod);
++ code.rawAppendToHeader(hSignature + u";");
++
++ code.rawAppendToCpp(u""); // blank line
++ code.rawAppendSignatureToCpp(cppSignature);
++
++ writeInit(specialMethod);
++
++ code.rawAppendToCpp(u"{");
++ {
++ OutputWrapper::CppIndentationScope cppIndent(&code);
++ Q_UNUSED(cppIndent);
++ for (const QString &line : std::as_const(specialMethod.body))
++ code.rawAppendToCpp(line);
++ }
++ code.rawAppendToCpp(u"}");
++ }
++
++ void CodeWriter::write(OutputWrapper &code, const Ctor &ctor)
++ {
++ const auto writeInitializerList = [&](const MethodBase &ctorBase) {
++ auto ctor = static_cast<const Ctor &>(ctorBase);
++ if (!ctor.initializerList.isEmpty()) {
++ code.rawAppendToCpp(u":", 1);
++ // double \n to make separate initializer list lines stand out more
++ code.rawAppendToCpp(
++ ctor.initializerList.join(u",\n\n" + u" "_s.repeated(code.cppIndent + 1)),
++ 1);
++ }
++ };
++
++ writeSpecialMethod(code, ctor, writeInitializerList);
++ }
++
++ void CodeWriter::write(OutputWrapper &code, const Dtor &dtor)
++ {
++ const auto noop = [](const MethodBase &) {};
++ writeSpecialMethod(code, dtor, noop);
++ }
++
++ void CodeWriter::write(OutputWrapper &code, const Variable &var)
++ {
++ const QString optionalPart = var.defaultValue.isEmpty() ? u""_s : u" = " + var.defaultValue;
++ code.rawAppendToHeader(var.cppType + u" " + var.name + optionalPart + u";");
++ }
++
++ void CodeWriter::write(OutputWrapper &code, const Property &prop)
++ {
++ Q_ASSERT(prop.defaultValue.isEmpty()); // we don't support it yet (or at all?)
++ code.rawAppendToHeader(u"Q_OBJECT_BINDABLE_PROPERTY(%1, %2, %3, &%1::%4)"_s.arg(
++ prop.containingClass, prop.cppType, prop.name, prop.signalName));
++ }
++
++ void CodeWriter::writeUrl(OutputWrapper &code, const Method &urlMethod)
++ {
++ // unlike ordinary methods, url function only exists in .cpp
++ Q_ASSERT(!urlMethod.returnType.isEmpty());
++ const auto [hSignature, _] = functionSignatures(urlMethod);
++ Q_UNUSED(_);
++ // Note: augment return type with preambles in declaration
++ code.rawAppendToCpp(functionReturnType(urlMethod) + u" " + hSignature);
++ code.rawAppendToCpp(u"{");
++ {
++ OutputWrapper::CppIndentationScope cppIndent(&code);
++ Q_UNUSED(cppIndent);
++ for (const QString &line : std::as_const(urlMethod.body))
++ code.rawAppendToCpp(line);
++ }
++ code.rawAppendToCpp(u"}");
++ }
++
++ } // namespace QQmltc
++
++ QT_END_NAMESPACE