From 5b8a94eb8e5d4e3b79ab73a9a0325f838ecbe41a Mon Sep 17 00:00:00 2001 From: Ulf Hermann Date: Fri, 11 May 2018 12:27:25 +0200 Subject: V4 debugger: Allow retrieval of non-CallContext scopes We need to encode the scope type properly and we need to return something from the "scope" command. Previously the client didn't even get notified about QML contexts and couldn't actually retrieve anything but call context. The other scope types are not terribly interesting right now, but at least for the global context it should be possible to provide more data in the future. Task-number: QTBUG-68218 Change-Id: I88d3dbc15a93f19b00f6f12365e4fb64ec78862e Reviewed-by: hjk Reviewed-by: Simon Hausmann --- .../qmldbg_debugger/qv4datacollector.cpp | 46 +++++++-------- .../qmltooling/qmldbg_debugger/qv4datacollector.h | 2 +- .../qqmldebugjs/data/encodeQmlScope.qml | 19 ++++++ .../qqmldebugjs/qqmldebugjs/qqmldebugjs.pro | 3 +- .../qqmldebugjs/qqmldebugjs/tst_qqmldebugjs.cpp | 67 ++++++++++++++++++++++ 5 files changed, 111 insertions(+), 26 deletions(-) create mode 100644 tests/auto/qml/debugger/qqmldebugjs/qqmldebugjs/data/encodeQmlScope.qml diff --git a/src/plugins/qmltooling/qmldbg_debugger/qv4datacollector.cpp b/src/plugins/qmltooling/qmldbg_debugger/qv4datacollector.cpp index c86f3d1803..95e6d5704c 100644 --- a/src/plugins/qmltooling/qmldbg_debugger/qv4datacollector.cpp +++ b/src/plugins/qmltooling/qmldbg_debugger/qv4datacollector.cpp @@ -72,16 +72,12 @@ QV4::Heap::ExecutionContext *QV4DataCollector::findContext(int frame) return f ? f->context()->d() : nullptr; } -QV4::Heap::CallContext *QV4DataCollector::findScope(QV4::Heap::ExecutionContext *ctx, int scope) +QV4::Heap::ExecutionContext *QV4DataCollector::findScope(QV4::Heap::ExecutionContext *ctx, int scope) { - if (!ctx) - return nullptr; - for (; scope > 0 && ctx; --scope) ctx = ctx->outer; - return (ctx && ctx->type == QV4::Heap::ExecutionContext::Type_CallContext) ? - static_cast(ctx) : nullptr; + return ctx; } QVector QV4DataCollector::getScopeTypes(int frame) @@ -108,6 +104,7 @@ int QV4DataCollector::encodeScopeType(QV4::Heap::ExecutionContext::ContextType s case QV4::Heap::ExecutionContext::Type_CallContext: return 1; case QV4::Heap::ExecutionContext::Type_QmlContext: + return 3; default: return -1; } @@ -259,31 +256,32 @@ bool QV4DataCollector::isValidRef(QV4DataCollector::Ref ref) const bool QV4DataCollector::collectScope(QJsonObject *dict, int frameNr, int scopeNr) { - QStringList names; - QV4::Scope scope(engine()); - QV4::Scoped ctxt(scope, findScope(findContext(frameNr), scopeNr)); + QV4::Scoped ctxt(scope, findScope(findContext(frameNr), scopeNr)); if (!ctxt) return false; - Refs collectedRefs; - QV4::ScopedValue v(scope); - QV4::InternalClass *ic = ctxt->internalClass(); - for (uint i = 0; i < ic->size; ++i) { - QString name = ic->nameMap[i]->string; - names.append(name); - v = ctxt->d()->locals[i]; - collectedRefs.append(collect(v)); - } - QV4::ScopedObject scopeObject(scope, engine()->newObject()); + if (ctxt->d()->type == QV4::Heap::ExecutionContext::Type_CallContext) { + QStringList names; + Refs collectedRefs; + + QV4::ScopedValue v(scope); + QV4::InternalClass *ic = ctxt->internalClass(); + for (uint i = 0; i < ic->size; ++i) { + QString name = ic->nameMap[i]->string; + names.append(name); + v = static_cast(ctxt->d())->locals[i]; + collectedRefs.append(collect(v)); + } - Q_ASSERT(names.size() == collectedRefs.size()); - QV4::ScopedString propName(scope); - for (int i = 0, ei = collectedRefs.size(); i != ei; ++i) { - propName = engine()->newString(names.at(i)); - scopeObject->put(propName, QV4::Value::fromReturnedValue(getValue(collectedRefs.at(i)))); + Q_ASSERT(names.size() == collectedRefs.size()); + QV4::ScopedString propName(scope); + for (int i = 0, ei = collectedRefs.size(); i != ei; ++i) { + propName = engine()->newString(names.at(i)); + scopeObject->put(propName, (v = getValue(collectedRefs.at(i)))); + } } Ref scopeObjectRef = addRef(scopeObject); diff --git a/src/plugins/qmltooling/qmldbg_debugger/qv4datacollector.h b/src/plugins/qmltooling/qmldbg_debugger/qv4datacollector.h index 87be009de5..5494e10e9a 100644 --- a/src/plugins/qmltooling/qmldbg_debugger/qv4datacollector.h +++ b/src/plugins/qmltooling/qmldbg_debugger/qv4datacollector.h @@ -58,7 +58,7 @@ public: typedef uint Ref; typedef QVector Refs; - static QV4::Heap::CallContext *findScope(QV4::Heap::ExecutionContext *ctxt, int scope); + static QV4::Heap::ExecutionContext *findScope(QV4::Heap::ExecutionContext *ctxt, int scope); static int encodeScopeType(QV4::Heap::ExecutionContext::ContextType scopeType); QVector getScopeTypes(int frame); diff --git a/tests/auto/qml/debugger/qqmldebugjs/qqmldebugjs/data/encodeQmlScope.qml b/tests/auto/qml/debugger/qqmldebugjs/qqmldebugjs/data/encodeQmlScope.qml new file mode 100644 index 0000000000..7ea048044f --- /dev/null +++ b/tests/auto/qml/debugger/qqmldebugjs/qqmldebugjs/data/encodeQmlScope.qml @@ -0,0 +1,19 @@ +import QtQuick 2.0 + +Item { + property int a: 0 + property int b: 0 + onAChanged: console.log("inline") + onBChanged: { + console.log("extra braces"); + } + + Timer { + interval: 10 + running: true + onTriggered: { + parent.a += 10; + parent.b -= 10; + } + } +} diff --git a/tests/auto/qml/debugger/qqmldebugjs/qqmldebugjs/qqmldebugjs.pro b/tests/auto/qml/debugger/qqmldebugjs/qqmldebugjs/qqmldebugjs.pro index 90623c75a6..52d70bd1b1 100644 --- a/tests/auto/qml/debugger/qqmldebugjs/qqmldebugjs/qqmldebugjs.pro +++ b/tests/auto/qml/debugger/qqmldebugjs/qqmldebugjs/qqmldebugjs.pro @@ -20,4 +20,5 @@ OTHER_FILES += data/test.qml data/test.js \ data/changeBreakpoint.qml \ data/stepAction.qml \ data/breakpointRelocation.qml \ - data/createComponent.qml + data/createComponent.qml \ + data/encodeQmlScope.qml diff --git a/tests/auto/qml/debugger/qqmldebugjs/qqmldebugjs/tst_qqmldebugjs.cpp b/tests/auto/qml/debugger/qqmldebugjs/qqmldebugjs/tst_qqmldebugjs.cpp index 3cd359cf48..660afce216 100644 --- a/tests/auto/qml/debugger/qqmldebugjs/qqmldebugjs/tst_qqmldebugjs.cpp +++ b/tests/auto/qml/debugger/qqmldebugjs/qqmldebugjs/tst_qqmldebugjs.cpp @@ -124,6 +124,7 @@ const char *QUIT_QMLFILE = "quit.qml"; const char *CHANGEBREAKPOINT_QMLFILE = "changeBreakpoint.qml"; const char *STEPACTION_QMLFILE = "stepAction.qml"; const char *BREAKPOINTRELOCATION_QMLFILE = "breakpointRelocation.qml"; +const char *ENCODEQMLSCOPE_QMLFILE = "encodeQmlScope.qml"; #define VARIANTMAPINIT \ QString obj("{}"); \ @@ -217,6 +218,8 @@ private slots: void getScripts_data() { targetData(); } void getScripts(); + void encodeQmlScope(); + private: ConnectResult init(bool qmlscene, const QString &qmlFile = QString(TEST_QMLFILE), bool blockMode = true, bool restrictServices = false); @@ -1455,6 +1458,70 @@ void tst_QQmlDebugJS::getScripts() QVERIFY(scripts.first().toMap()[QStringLiteral("name")].toString().endsWith(QStringLiteral("data/test.qml"))); } +void tst_QQmlDebugJS::encodeQmlScope() +{ + QString file(ENCODEQMLSCOPE_QMLFILE); + QCOMPARE(init(true, file), ConnectSuccess); + + int numFrames = 0; + int numExpectedScopes = 0; + int numReceivedScopes = 0; + bool isStopped = false; + bool scopesFailed = false; + + QObject::connect(m_client, &QJSDebugClient::failure, this, [&]() { + qWarning() << "received failure" << m_client->response; + scopesFailed = true; + m_process->stop(); + numFrames = 2; + isStopped = false; + }); + + QObject::connect(m_client, &QJSDebugClient::stopped, this, [&]() { + m_client->frame(); + isStopped = true; + }); + + QObject::connect(m_client, &QJSDebugClient::result, this, [&]() { + const QVariantMap value = m_client->parser.call( + QJSValueList() << QJSValue(QString(m_client->response))).toVariant().toMap(); + + const QMap body = value.value("body").toMap(); + const QString command = value.value("command").toString(); + + if (command == QString("scope")) { + // If the scope commands fail we get a failure() signal above. + if (++numReceivedScopes == numExpectedScopes) { + m_client->continueDebugging(QJSDebugClient::Continue); + isStopped = false; + } + } else if (command == QString("frame")) { + + // We want at least a global scope and some kind of local scope here. + const QList scopes = body.value("scopes").toList(); + if (scopes.length() < 2) + scopesFailed = true; + + for (const QVariant &scope : scopes) { + ++numExpectedScopes; + m_client->scope(scope.toMap().value("index").toInt()); + } + + ++numFrames; + } + }); + + m_client->setBreakpoint(file, 6); + m_client->setBreakpoint(file, 8); + m_client->connect(); + + QTRY_COMPARE(numFrames, 2); + QVERIFY(numExpectedScopes > 3); + QVERIFY(!scopesFailed); + QTRY_VERIFY(!isStopped); + QCOMPARE(numReceivedScopes, numExpectedScopes); +} + QList tst_QQmlDebugJS::createClients() { m_client = new QJSDebugClient(m_connection); -- cgit v1.2.3