diff options
author | Ulf Hermann <ulf.hermann@qt.io> | 2022-11-15 10:18:37 +0100 |
---|---|---|
committer | Ulf Hermann <ulf.hermann@qt.io> | 2022-11-23 10:27:55 +0100 |
commit | ed47bff4118f677e135f3b7c113b035ac991c5ca (patch) | |
tree | 0709485d06f22c397a45e4e1aaf79378089d49d0 | |
parent | bce216d5c086a5aa8f88d13933eeccebca316359 (diff) |
QmlCompiler: Implement console logging methods
We provide semi-private functions in the AOT context for this. Since we
cannot know the complete run time type of the potential logging category
at compile time, we have to check any first argument that might be one
separately.
Fixes: QTBUG-107175
Change-Id: I46a8922b1c5c16d2b450b8728d650d31dfd867e3
Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io>
-rw-r--r-- | src/qml/qml/qqml.cpp | 53 | ||||
-rw-r--r-- | src/qml/qml/qqmlbuiltinfunctions.cpp | 9 | ||||
-rw-r--r-- | src/qml/qml/qqmlprivate.h | 8 | ||||
-rw-r--r-- | src/qmlcompiler/qqmljscodegenerator.cpp | 87 | ||||
-rw-r--r-- | src/qmlcompiler/qqmljscodegenerator_p.h | 7 | ||||
-rw-r--r-- | src/qmlcompiler/qqmljstypepropagator.cpp | 40 | ||||
-rw-r--r-- | tests/auto/qml/qmlcppcodegen/data/CMakeLists.txt | 1 | ||||
-rw-r--r-- | tests/auto/qml/qmlcppcodegen/data/consoleObject.qml | 30 | ||||
-rw-r--r-- | tests/auto/qml/qmlcppcodegen/data/outOfBounds.qml | 1 | ||||
-rw-r--r-- | tests/auto/qml/qmlcppcodegen/tst_qmlcppcodegen.cpp | 39 |
10 files changed, 270 insertions, 5 deletions
diff --git a/src/qml/qml/qqml.cpp b/src/qml/qml/qqml.cpp index 5e99f92bc7..db36cf6655 100644 --- a/src/qml/qml/qqml.cpp +++ b/src/qml/qml/qqml.cpp @@ -21,11 +21,14 @@ #include <private/qv4errorobject_p.h> #include <private/qqmlbuiltinfunctions_p.h> #include <private/qqmlfinalizer_p.h> +#include <private/qqmlloggingcategory_p.h> #include <QtCore/qmutex.h> QT_BEGIN_NAMESPACE +Q_DECLARE_LOGGING_CATEGORY(lcQml); +Q_DECLARE_LOGGING_CATEGORY(lcJs); /*! \internal @@ -1239,6 +1242,56 @@ QJSValue AOTCompiledContext::javaScriptGlobalProperty(uint nameIndex) const return QJSValuePrivate::fromReturnedValue(global->get(name->toPropertyKey())); } +const QLoggingCategory *AOTCompiledContext::resolveLoggingCategory(QObject *wrapper, bool *ok) const +{ + if (wrapper) { + // We have to check this here because you may pass a plain QObject that only + // turns out to be a QQmlLoggingCategory at run time. + if (QQmlLoggingCategory *qQmlLoggingCategory + = qobject_cast<QQmlLoggingCategory *>(wrapper)) { + QLoggingCategory *loggingCategory = qQmlLoggingCategory->category(); + *ok = true; + if (!loggingCategory) { + engine->handle()->throwError( + QStringLiteral("A QmlLoggingCatgory was provided without a valid name")); + } + return loggingCategory; + } + } + + *ok = false; + return qmlEngine() ? &lcQml() : &lcJs(); +} + +void AOTCompiledContext::writeToConsole( + QtMsgType type, const QString &message, const QLoggingCategory *loggingCategory) const +{ + Q_ASSERT(loggingCategory->isEnabled(type)); + + const QV4::CppStackFrame *frame = engine->handle()->currentStackFrame; + Q_ASSERT(frame); + + QMessageLogger logger(qUtf8Printable(frame->source()), frame->lineNumber(), + qUtf8Printable(frame->function()), loggingCategory->categoryName()); + + switch (type) { + case QtDebugMsg: + logger.debug("%s", qUtf8Printable(message)); + break; + case QtInfoMsg: + logger.info("%s", qUtf8Printable(message)); + break; + case QtWarningMsg: + logger.warning("%s", qUtf8Printable(message)); + break; + case QtCriticalMsg: + logger.critical("%s", qUtf8Printable(message)); + break; + default: + break; + } +} + bool AOTCompiledContext::callQmlContextPropertyLookup( uint index, void **args, const QMetaType *types, int argc) const { diff --git a/src/qml/qml/qqmlbuiltinfunctions.cpp b/src/qml/qml/qqmlbuiltinfunctions.cpp index f18885b0cb..79475435bf 100644 --- a/src/qml/qml/qqmlbuiltinfunctions.cpp +++ b/src/qml/qml/qqmlbuiltinfunctions.cpp @@ -49,6 +49,8 @@ QT_BEGIN_NAMESPACE Q_LOGGING_CATEGORY(lcRootProperties, "qt.qml.rootObjectProperties"); +Q_LOGGING_CATEGORY(lcQml, "qml"); +Q_LOGGING_CATEGORY(lcJs, "js"); using namespace QV4; @@ -1566,7 +1568,7 @@ static QString serializeArray(Object *array, ExecutionEngine *v4, QSet<QV4::Heap static ReturnedValue writeToConsole(const FunctionObject *b, const Value *argv, int argc, ConsoleLogTypes logType, bool printStack = false) { - QLoggingCategory *loggingCategory = nullptr; + const QLoggingCategory *loggingCategory = nullptr; QString result; QV4::Scope scope(b); QV4::ExecutionEngine *v4 = scope.engine; @@ -1599,11 +1601,8 @@ static ReturnedValue writeToConsole(const FunctionObject *b, const Value *argv, if (printStack) result += QLatin1Char('\n') + jsStack(v4); - static QLoggingCategory qmlLoggingCategory("qml"); - static QLoggingCategory jsLoggingCategory("js"); - if (!loggingCategory) - loggingCategory = v4->qmlEngine() ? &qmlLoggingCategory : &jsLoggingCategory; + loggingCategory = v4->qmlEngine() ? &lcQml() : &lcJs(); QV4::CppStackFrame *frame = v4->currentStackFrame; const QByteArray baSource = frame ? frame->source().toUtf8() : QByteArray(); const QByteArray baFunction = frame ? frame->function().toUtf8() : QByteArray(); diff --git a/src/qml/qml/qqmlprivate.h b/src/qml/qml/qqmlprivate.h index 800c534b54..9b6a3488e0 100644 --- a/src/qml/qml/qqmlprivate.h +++ b/src/qml/qml/qqmlprivate.h @@ -634,6 +634,14 @@ namespace QQmlPrivate void storeNameSloppy(uint nameIndex, void *value, QMetaType type) const; QJSValue javaScriptGlobalProperty(uint nameIndex) const; + const QLoggingCategory *resolveLoggingCategory(QObject *wrapper, bool *ok) const; + + void writeToConsole( + QtMsgType type, const QString &message, + const QLoggingCategory *loggingCategory) const; + + QString objectToString(QObject *object) const; + // All of these lookup functions should be used as follows: // // while (!fooBarLookup(...)) { diff --git a/src/qmlcompiler/qqmljscodegenerator.cpp b/src/qmlcompiler/qqmljscodegenerator.cpp index c79a5f5d1e..ca0457c92c 100644 --- a/src/qmlcompiler/qqmljscodegenerator.cpp +++ b/src/qmlcompiler/qqmljscodegenerator.cpp @@ -1472,6 +1472,88 @@ bool QQmlJSCodeGenerator::inlineMathMethod(const QString &name, int argc, int ar return true; } +static QString messageTypeForMethod(const QString &method) +{ + if (method == u"log" || method == u"debug") + return u"QtDebugMsg"_s; + if (method == u"info") + return u"QtInfoMsg"_s; + if (method == u"warn") + return u"QtWarningMsg"_s; + if (method == u"error") + return u"QtCriticalMsg"_s; + return QString(); +} + +bool QQmlJSCodeGenerator::inlineConsoleMethod(const QString &name, int argc, int argv) +{ + const QString type = messageTypeForMethod(name); + if (type.isEmpty()) + return false; + + addInclude(u"qloggingcategory.h"_s); + + m_body += u"{\n"; + m_body += u" bool firstArgIsCategory = false;\n"; + const QQmlJSRegisterContent firstArg = argc > 0 ? registerType(argv) : QQmlJSRegisterContent(); + + // We could check for internalName == "QQmlLoggingCategory" here, but we don't want to + // because QQmlLoggingCategory is not a builtin. Tying the specific internal name and + // intheritance hierarchy in here would be fragile. + // TODO: We could drop the check for firstArg in some cases if we made some base class + // of QQmlLoggingCategory a builtin. + const bool firstArgIsReference = argc > 0 + && m_typeResolver->containedType(firstArg)->isReferenceType(); + + if (firstArgIsReference) { + m_body += u" QObject *firstArg = "; + m_body += conversion( + firstArg.storedType(), + m_typeResolver->genericType(firstArg.storedType()), + registerVariable(argv)); + m_body += u";\n"; + } + + m_body += u" const QLoggingCategory *category = aotContext->resolveLoggingCategory("; + m_body += firstArgIsReference ? u"firstArg" : u"nullptr"; + m_body += u", &firstArgIsCategory);\n"; + m_body += u" if (category && category->isEnabled(" + type + u")) {\n"; + + m_body += u" const QString message = "; + if (argc > 0) { + const QString firstArgStringConversion = conversion( + registerType(argv).storedType(), + m_typeResolver->stringType(), registerVariable(argv)); + if (firstArgIsReference) { + m_body += u"(firstArgIsCategory ? QString() : (" + firstArgStringConversion; + if (argc > 1) + m_body += u".append(QLatin1Char(' ')))).append("; + else + m_body += u"))"; + } else { + m_body += firstArgStringConversion; + if (argc > 1) + m_body += u".append(QLatin1Char(' ')).append("; + } + + for (int i = 1; i < argc; ++i) { + if (i > 1) + m_body += u".append(QLatin1Char(' ')).append("_s; + m_body += conversion( + registerType(argv + i).storedType(), + m_typeResolver->stringType(), registerVariable(argv + i)) + u')'; + } + } else { + m_body += u"QString()"; + } + m_body += u";\n"; + + m_body += u" aotContext->writeToConsole(" + type + u", message, category);\n"; + m_body += u" }\n"; + m_body += u"}\n"; + return true; +} + void QQmlJSCodeGenerator::generate_CallPropertyLookup(int index, int base, int argc, int argv) { INJECT_TRACE_INFO(generate_CallPropertyLookup); @@ -1490,6 +1572,11 @@ void QQmlJSCodeGenerator::generate_CallPropertyLookup(int index, int base, int a return; } + if (m_typeResolver->equals(m_typeResolver->originalContainedType(baseType), consoleObject())) { + if (inlineConsoleMethod(name, argc, argv)) + return; + } + if (m_typeResolver->equals(m_typeResolver->originalContainedType(baseType), m_typeResolver->stringType())) { if (inlineStringMethod(name, base, argc, argv)) diff --git a/src/qmlcompiler/qqmljscodegenerator_p.h b/src/qmlcompiler/qqmljscodegenerator_p.h index 2a60a70e24..5a119b296a 100644 --- a/src/qmlcompiler/qqmljscodegenerator_p.h +++ b/src/qmlcompiler/qqmljscodegenerator_p.h @@ -269,6 +269,7 @@ private: bool inlineStringMethod(const QString &name, int base, int argc, int argv); bool inlineTranslateMethod(const QString &name, int argc, int argv); bool inlineMathMethod(const QString &name, int argc, int argv); + bool inlineConsoleMethod(const QString &name, int argc, int argv); QQmlJSScope::ConstPtr mathObject() const { @@ -276,6 +277,12 @@ private: return m_typeResolver->jsGlobalObject()->property(u"Math"_s).type(); } + QQmlJSScope::ConstPtr consoleObject() const + { + using namespace Qt::StringLiterals; + return m_typeResolver->jsGlobalObject()->property(u"console"_s).type(); + } + int nextJSLine(uint line) const; QStringList m_sourceCodeLines; diff --git a/src/qmlcompiler/qqmljstypepropagator.cpp b/src/qmlcompiler/qqmljstypepropagator.cpp index 423530d274..b8b4222b27 100644 --- a/src/qmlcompiler/qqmljstypepropagator.cpp +++ b/src/qmlcompiler/qqmljstypepropagator.cpp @@ -989,6 +989,12 @@ void QQmlJSTypePropagator::generate_CallWithReceiver(int name, int thisObject, i INSTR_PROLOGUE_NOT_IMPLEMENTED(); } +static bool isLoggingMethod(const QString &consoleMethod) +{ + return consoleMethod == u"log" || consoleMethod == u"debug" || consoleMethod == u"info" + || consoleMethod == u"warn" || consoleMethod == u"error"; +} + void QQmlJSTypePropagator::generate_CallProperty(int nameIndex, int base, int argc, int argv) { Q_ASSERT(m_state.registers.contains(base)); @@ -1012,6 +1018,40 @@ void QQmlJSTypePropagator::generate_CallProperty(int nameIndex, int base, int ar return; } + if (m_typeResolver->registerContains( + callBase, m_typeResolver->jsGlobalObject()->property(u"console"_s).type()) + && isLoggingMethod(propertyName)) { + + const QQmlJSRegisterContent voidType + = m_typeResolver->globalType(m_typeResolver->voidType()); + + // If we call a method on the console object we don't need the console object. + addReadRegister(base, voidType); + + const QQmlJSRegisterContent stringType + = m_typeResolver->globalType(m_typeResolver->stringType()); + + if (argc > 0) { + const QQmlJSScope::ConstPtr firstArg + = m_typeResolver->containedType(m_state.registers[argv]); + if (firstArg->isReferenceType()) { + // We cannot know whether this will be a logging category at run time. + // Therefore we always pass any object types as special last argument. + addReadRegister(argv, m_typeResolver->globalType( + m_typeResolver->genericType(firstArg))); + } else { + addReadRegister(argv, stringType); + } + } + + for (int i = 1; i < argc; ++i) + addReadRegister(argv + i, stringType); + + m_state.setHasSideEffects(true); + setAccumulator(voidType); + return; + } + if (m_typeResolver->registerContains(callBase, m_typeResolver->jsValueType()) || m_typeResolver->registerContains(callBase, m_typeResolver->varType())) { const auto jsValueType = m_typeResolver->globalType(m_typeResolver->jsValueType()); diff --git a/tests/auto/qml/qmlcppcodegen/data/CMakeLists.txt b/tests/auto/qml/qmlcppcodegen/data/CMakeLists.txt index 97e669ec7f..b7f8f28aeb 100644 --- a/tests/auto/qml/qmlcppcodegen/data/CMakeLists.txt +++ b/tests/auto/qml/qmlcppcodegen/data/CMakeLists.txt @@ -58,6 +58,7 @@ set(qml_files childobject.qml colorAsVariant.qml colorString.qml + consoleObject.qml componentReturnType.qml compositeTypeMethod.qml compositesingleton.qml diff --git a/tests/auto/qml/qmlcppcodegen/data/consoleObject.qml b/tests/auto/qml/qmlcppcodegen/data/consoleObject.qml new file mode 100644 index 0000000000..9fa32bafee --- /dev/null +++ b/tests/auto/qml/qmlcppcodegen/data/consoleObject.qml @@ -0,0 +1,30 @@ +pragma Strict +import QtQml + +LoggingCategory { + id: self + name: "foobar" + Component.onCompleted: { + console.debug("b", 4.55); + console.log("b", 4.55); + console.info("b", 4.55); + console.warn("b", 4.55); + console.error("b", 4.55); + console.debug(self, "b", 4.55); + console.log(self, "b", 4.55); + console.info(self, "b", 4.55); + console.warn(self, "b", 4.55); + console.error(self, "b", 4.55); + console.debug(Component, "b", 4.55); + console.log(Component, "b", 4.55); + console.info(Component, "b", 4.55); + console.warn(Component, "b", 4.55); + console.error(Component, "b", 4.55); + + console.log("a", undefined, "b", false, null, 7); + console.log(); + console.log(4) + console.log(self); + console.log(Component); + } +} diff --git a/tests/auto/qml/qmlcppcodegen/data/outOfBounds.qml b/tests/auto/qml/qmlcppcodegen/data/outOfBounds.qml index a9f6725a5a..085e2ebd53 100644 --- a/tests/auto/qml/qmlcppcodegen/data/outOfBounds.qml +++ b/tests/auto/qml/qmlcppcodegen/data/outOfBounds.qml @@ -1,3 +1,4 @@ +pragma Strict import QtQuick Item { diff --git a/tests/auto/qml/qmlcppcodegen/tst_qmlcppcodegen.cpp b/tests/auto/qml/qmlcppcodegen/tst_qmlcppcodegen.cpp index 7278ea70bb..4b4cc6042a 100644 --- a/tests/auto/qml/qmlcppcodegen/tst_qmlcppcodegen.cpp +++ b/tests/auto/qml/qmlcppcodegen/tst_qmlcppcodegen.cpp @@ -146,6 +146,7 @@ private slots: void signalIndexMismatch(); void callWithSpread(); void nullComparison(); + void consoleObject(); }; void tst_QmlCppCodegen::initTestCase() @@ -2820,6 +2821,44 @@ void tst_QmlCppCodegen::nullComparison() QCOMPARE(o->property("y").toInt(), 5); }; +void tst_QmlCppCodegen::consoleObject() +{ + QQmlEngine engine; + QQmlComponent c(&engine, QUrl(u"qrc:/qt/qml/TestTypes/consoleObject.qml"_s)); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + + QTest::ignoreMessage(QtDebugMsg, "b 4.55"); + QTest::ignoreMessage(QtDebugMsg, "b 4.55"); + QTest::ignoreMessage(QtInfoMsg, "b 4.55"); + QTest::ignoreMessage(QtWarningMsg, "b 4.55"); + QTest::ignoreMessage(QtCriticalMsg, "b 4.55"); + + // Unfortunately we cannot check the logging category with QTest::ignoreMessage + QTest::ignoreMessage(QtDebugMsg, "b 4.55"); + QTest::ignoreMessage(QtDebugMsg, "b 4.55"); + QTest::ignoreMessage(QtInfoMsg, "b 4.55"); + QTest::ignoreMessage(QtWarningMsg, "b 4.55"); + QTest::ignoreMessage(QtCriticalMsg, "b 4.55"); + + const QRegularExpression re(u"QQmlComponentAttached\\(0x[0-9a-f]+\\) b 4\\.55"_s); + QTest::ignoreMessage(QtDebugMsg, re); + QTest::ignoreMessage(QtDebugMsg, re); + QTest::ignoreMessage(QtInfoMsg, re); + QTest::ignoreMessage(QtWarningMsg, re); + QTest::ignoreMessage(QtCriticalMsg, re); + + QTest::ignoreMessage(QtDebugMsg, "a undefined b false null 7"); + QTest::ignoreMessage(QtDebugMsg, ""); + QTest::ignoreMessage(QtDebugMsg, "4"); + QTest::ignoreMessage(QtDebugMsg, ""); + + const QRegularExpression re2(u"QQmlComponentAttached\\(0x[0-9a-f]+\\)"_s); + QTest::ignoreMessage(QtDebugMsg, re2); + + QScopedPointer<QObject> o(c.create()); + QVERIFY(!o.isNull()); +} + QTEST_MAIN(tst_QmlCppCodegen) #include "tst_qmlcppcodegen.moc" |