aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorUlf Hermann <ulf.hermann@qt.io>2022-11-15 10:18:37 +0100
committerUlf Hermann <ulf.hermann@qt.io>2022-11-23 10:27:55 +0100
commited47bff4118f677e135f3b7c113b035ac991c5ca (patch)
tree0709485d06f22c397a45e4e1aaf79378089d49d0
parentbce216d5c086a5aa8f88d13933eeccebca316359 (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.cpp53
-rw-r--r--src/qml/qml/qqmlbuiltinfunctions.cpp9
-rw-r--r--src/qml/qml/qqmlprivate.h8
-rw-r--r--src/qmlcompiler/qqmljscodegenerator.cpp87
-rw-r--r--src/qmlcompiler/qqmljscodegenerator_p.h7
-rw-r--r--src/qmlcompiler/qqmljstypepropagator.cpp40
-rw-r--r--tests/auto/qml/qmlcppcodegen/data/CMakeLists.txt1
-rw-r--r--tests/auto/qml/qmlcppcodegen/data/consoleObject.qml30
-rw-r--r--tests/auto/qml/qmlcppcodegen/data/outOfBounds.qml1
-rw-r--r--tests/auto/qml/qmlcppcodegen/tst_qmlcppcodegen.cpp39
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"