V4 Debugger: Add an option to pass additional context for evaluate

Interpret the "context" option as an ID for a QObject whose QML context
is then injected when evaluating the expression. The QObject needs to
be tracked by some other debug service for this to work, e.g. the QML
debugger or the inspector.

Task-number: QTCREATORBUG-17177
Change-Id: I6a9e8b9ae23e8bb67ed1905a2ef73f7c4faeb990
Reviewed-by: Simon Hausmann <simon.hausmann@qt.io>
This commit is contained in:
Ulf Hermann 2016-12-07 14:24:04 +01:00
parent 3dd96630a2
commit bff7302fc2
6 changed files with 119 additions and 28 deletions

View File

@ -42,6 +42,7 @@
#include <private/qv4script_p.h>
#include <private/qqmlcontext_p.h>
#include <private/qv4qobjectwrapper_p.h>
#include <private/qqmldebugservice_p.h>
#include <QtQml/qqmlengine.h>
@ -51,9 +52,10 @@ QV4DebugJob::~QV4DebugJob()
{
}
JavaScriptJob::JavaScriptJob(QV4::ExecutionEngine *engine, int frameNr,
const QString &script) :
engine(engine), frameNr(frameNr), script(script), resultIsException(false)
JavaScriptJob::JavaScriptJob(QV4::ExecutionEngine *engine, int frameNr, int context,
const QString &script) :
engine(engine), frameNr(frameNr), context(context), script(script),
resultIsException(false)
{}
void JavaScriptJob::run()
@ -64,7 +66,23 @@ void JavaScriptJob::run()
QV4::ExecutionContext *ctx = engine->currentContext;
QObject scopeObject;
if (frameNr < 0) { // Use QML context if available
if (frameNr > 0) {
for (int i = 0; i < frameNr; ++i) {
ctx = engine->parentContext(ctx);
}
engine->pushContext(ctx);
ctx = engine->currentContext;
}
if (context >= 0) {
QQmlContext *extraContext = qmlContext(QQmlDebugService::objectForId(context));
if (extraContext) {
engine->pushContext(ctx->newQmlContext(QQmlContextData::get(extraContext),
&scopeObject));
ctx = engine->currentContext;
}
} else if (frameNr < 0) { // Use QML context if available
QQmlEngine *qmlEngine = engine->qmlEngine();
if (qmlEngine) {
QQmlContext *qmlRootContext = qmlEngine->rootContext();
@ -88,13 +106,6 @@ void JavaScriptJob::run()
engine->pushContext(ctx->newWithContext(withContext->toObject(engine)));
ctx = engine->currentContext;
}
} else {
if (frameNr > 0) {
for (int i = 0; i < frameNr; ++i) {
ctx = engine->parentContext(ctx);
}
engine->pushContext(ctx);
}
}
QV4::Script script(ctx, this->script);
@ -224,8 +235,9 @@ const QString &ValueLookupJob::exceptionMessage() const
}
ExpressionEvalJob::ExpressionEvalJob(QV4::ExecutionEngine *engine, int frameNr,
const QString &expression, QV4DataCollector *collector) :
JavaScriptJob(engine, frameNr, expression), collector(collector)
int context, const QString &expression,
QV4DataCollector *collector) :
JavaScriptJob(engine, frameNr, context, expression), collector(collector)
{
}
@ -271,7 +283,7 @@ const QStringList &GatherSourcesJob::result() const
}
EvalJob::EvalJob(QV4::ExecutionEngine *engine, const QString &script) :
JavaScriptJob(engine, /*frameNr*/-1, script), result(false)
JavaScriptJob(engine, /*frameNr*/-1, /*context*/ -1, script), result(false)
{}
void EvalJob::handleResult(QV4::ScopedValue &result)

View File

@ -60,11 +60,12 @@ class JavaScriptJob : public QV4DebugJob
{
QV4::ExecutionEngine *engine;
int frameNr;
int context;
const QString &script;
bool resultIsException;
public:
JavaScriptJob(QV4::ExecutionEngine *engine, int frameNr, const QString &script);
JavaScriptJob(QV4::ExecutionEngine *engine, int frameNr, int context, const QString &script);
void run() override;
bool hasExeption() const;
@ -135,8 +136,8 @@ class ExpressionEvalJob: public JavaScriptJob
QJsonArray collectedRefs;
public:
ExpressionEvalJob(QV4::ExecutionEngine *engine, int frameNr, const QString &expression,
QV4DataCollector *collector);
ExpressionEvalJob(QV4::ExecutionEngine *engine, int frameNr, int context,
const QString &expression, QV4DataCollector *collector);
void handleResult(QV4::ScopedValue &value) override;
const QString &exceptionMessage() const;
const QJsonObject &returnValue() const;

View File

@ -177,6 +177,7 @@ public:
body.insert(QStringLiteral("V8Version"),
QLatin1String("this is not V8, this is V4 in Qt " QT_VERSION_STR));
body.insert(QStringLiteral("UnpausedEvaluate"), true);
body.insert(QStringLiteral("ContextEvaluate"), true);
addBody(body);
}
};
@ -610,6 +611,7 @@ public:
{
QJsonObject arguments = req.value(QLatin1String("arguments")).toObject();
QString expression = arguments.value(QLatin1String("expression")).toString();
int context = arguments.value(QLatin1String("context")).toInt(-1);
int frame = -1;
QV4Debugger *debugger = debugService->debuggerAgent.pausedDebugger();
@ -627,7 +629,8 @@ public:
frame = arguments.value(QLatin1String("frame")).toInt(0);
}
ExpressionEvalJob job(debugger->engine(), frame, expression, debugger->collector());
ExpressionEvalJob job(debugger->engine(), frame, context, expression,
debugger->collector());
debugger->runInEngine(&job);
if (job.hasExeption()) {
createErrorResponse(job.exceptionMessage());

View File

@ -9,6 +9,7 @@ SOURCES += tst_qqmldebugjs.cpp
INCLUDEPATH += ../../shared
include(../../../../shared/util.pri)
include(../../shared/debugutil.pri)
include(../../shared/qqmlenginedebugclient.pri)
TESTDATA = data/*

View File

@ -27,6 +27,7 @@
****************************************************************************/
#include "debugutil_p.h"
#include "../../shared/qqmlenginedebugclient.h"
#include "../../../../shared/util.h"
#include <private/qqmldebugclient_p.h>
@ -52,6 +53,7 @@ const char *STEPACTION = "stepaction";
const char *STEPCOUNT = "stepcount";
const char *EXPRESSION = "expression";
const char *FRAME = "frame";
const char *CONTEXT = "context";
const char *GLOBAL = "global";
const char *DISABLEBREAK = "disable_break";
const char *HANDLES = "handles";
@ -215,6 +217,8 @@ private slots:
void evaluateInLocalScope_data() { targetData(); }
void evaluateInLocalScope();
void evaluateInContext();
void getScripts_data() { targetData(); }
void getScripts();
@ -257,7 +261,7 @@ public:
void interrupt();
void continueDebugging(StepAction stepAction);
void evaluate(QString expr, int frame = -1);
void evaluate(QString expr, int frame = -1, int context = -1);
void lookup(QList<int> handles, bool includeSource = false);
void backtrace(int fromFrame = -1, int toFrame = -1, bool bottom = false);
void frame(int number = -1);
@ -280,6 +284,7 @@ signals:
void connected();
void interruptRequested();
void result();
void failure();
void stopped();
private:
@ -340,13 +345,14 @@ void QJSDebugClient::continueDebugging(StepAction action)
sendMessage(packMessage(V8REQUEST, json.toString().toUtf8()));
}
void QJSDebugClient::evaluate(QString expr, int frame)
void QJSDebugClient::evaluate(QString expr, int frame, int context)
{
// { "seq" : <number>,
// "type" : "request",
// "command" : "evaluate",
// "arguments" : { "expression" : <expression to evaluate>,
// "frame" : <number>
// "frame" : <number>,
// "context" : <object ID>
// }
// }
VARIANTMAPINIT;
@ -358,6 +364,9 @@ void QJSDebugClient::evaluate(QString expr, int frame)
if (frame != -1)
args.setProperty(QLatin1String(FRAME),QJSValue(frame));
if (context != -1)
args.setProperty(QLatin1String(CONTEXT), QJSValue(context));
if (!args.isUndefined()) {
jsonVal.setProperty(QLatin1String(ARGUMENTS),args);
}
@ -684,6 +693,7 @@ void QJSDebugClient::messageReceived(const QByteArray &data)
if (type == "response") {
if (!value.value("success").toBool()) {
emit failure();
qDebug() << "Received success == false response from application";
return;
}
@ -1394,6 +1404,58 @@ void tst_QQmlDebugJS::evaluateInLocalScope()
QCOMPARE(body.value("value").toInt(),10);
}
void tst_QQmlDebugJS::evaluateInContext()
{
connection = new QQmlDebugConnection();
process = new QQmlDebugProcess(QLibraryInfo::location(QLibraryInfo::BinariesPath)
+ "/qmlscene", this);
client = new QJSDebugClient(connection);
QScopedPointer<QQmlEngineDebugClient> engineClient(new QQmlEngineDebugClient(connection));
process->start(QStringList() << QLatin1String(BLOCKMODE) << testFile(ONCOMPLETED_QMLFILE));
QVERIFY(process->waitForSessionStart());
connection->connectToHost("127.0.0.1", process->debugPort());
QVERIFY(connection->waitForConnected());
QTRY_COMPARE(client->state(), QQmlEngineDebugClient::Enabled);
QTRY_COMPARE(engineClient->state(), QQmlEngineDebugClient::Enabled);
client->connect();
// "a" not accessible without extra context
client->evaluate(QLatin1String("a + 10"), -1, -1);
QVERIFY(QQmlDebugTest::waitForSignal(client, SIGNAL(failure())));
bool success = false;
engineClient->queryAvailableEngines(&success);
QVERIFY(success);
QVERIFY(QQmlDebugTest::waitForSignal(engineClient.data(), SIGNAL(result())));
QVERIFY(engineClient->engines().count());
engineClient->queryRootContexts(engineClient->engines()[0].debugId, &success);
QVERIFY(success);
QVERIFY(QQmlDebugTest::waitForSignal(engineClient.data(), SIGNAL(result())));
auto contexts = engineClient->rootContext().contexts;
QCOMPARE(contexts.count(), 1);
auto objects = contexts[0].objects;
QCOMPARE(objects.count(), 1);
engineClient->queryObjectRecursive(objects[0], &success);
QVERIFY(success);
QVERIFY(QQmlDebugTest::waitForSignal(engineClient.data(), SIGNAL(result())));
auto object = engineClient->object();
// "a" accessible in context of surrounding object
client->evaluate(QLatin1String("a + 10"), -1, object.debugId);
QVERIFY(QQmlDebugTest::waitForSignal(client, SIGNAL(result())));
QString jsonString = client->response;
QVariantMap value = client->parser.call(QJSValueList() << QJSValue(jsonString)).toVariant().toMap();
QVariantMap body = value.value("body").toMap();
QTRY_COMPARE(body.value("value").toInt(), 20);
}
void tst_QQmlDebugJS::getScripts()
{
//void scripts(int types = -1, QList<int> ids = QList<int>(), bool includeSource = false, QVariant filter = QVariant());

View File

@ -41,6 +41,7 @@
#include <private/qv4isel_moth_p.h>
#include <private/qv4string_p.h>
#include <private/qqmlbuiltinfunctions_p.h>
#include <private/qqmldebugservice_p.h>
using namespace QV4;
using namespace QV4::Debugging;
@ -203,8 +204,8 @@ public slots:
while (!m_expressionRequests.isEmpty()) {
Q_ASSERT(debugger->state() == QV4Debugger::Paused);
ExpressionRequest request = m_expressionRequests.takeFirst();
ExpressionEvalJob job(debugger->engine(), request.frameNr, request.expression,
&collector);
ExpressionEvalJob job(debugger->engine(), request.frameNr, request.context,
request.expression, &collector);
debugger->runInEngine(&job);
m_expressionResults << job.returnValue();
m_expressionRefs << job.refs();
@ -276,6 +277,7 @@ public:
struct ExpressionRequest {
QString expression;
int frameNr;
int context;
};
QVector<ExpressionRequest> m_expressionRequests;
QList<QJsonObject> m_expressionResults;
@ -726,24 +728,34 @@ void tst_qv4debugger::evaluateExpression()
TestAgent::ExpressionRequest request;
request.expression = "x";
request.frameNr = 0;
request.context = -1; // no extra context
m_debuggerAgent->m_expressionRequests << request;
request.expression = "x";
request.frameNr = 1;
m_debuggerAgent->m_expressionRequests << request;
request.context = 5355; // invalid context object
m_debuggerAgent->m_expressionRequests << request;
QObject object; // some object without QML context
request.context = QQmlDebugService::idForObject(&object);
m_debuggerAgent->m_expressionRequests << request;
debugger()->addBreakPoint("evaluateExpression", 3);
evaluateJavaScript(script, "evaluateExpression");
QCOMPARE(m_debuggerAgent->m_expressionRefs.count(), 2);
QCOMPARE(m_debuggerAgent->m_expressionRefs.count(), 4);
QCOMPARE(m_debuggerAgent->m_expressionRefs[0].size(), 1);
QJsonObject result0 = m_debuggerAgent->m_expressionRefs[0].first().toObject();
QCOMPARE(result0.value("type").toString(), QStringLiteral("number"));
QCOMPARE(result0.value("value").toInt(), 10);
QCOMPARE(m_debuggerAgent->m_expressionRefs[1].size(), 1);
QJsonObject result1 = m_debuggerAgent->m_expressionRefs[1].first().toObject();
QCOMPARE(result1.value("type").toString(), QStringLiteral("number"));
QCOMPARE(result1.value("value").toInt(), 20);
for (int i = 1; i < 4; ++i) {
QCOMPARE(m_debuggerAgent->m_expressionRefs[i].size(), 1);
QJsonObject result1 = m_debuggerAgent->m_expressionRefs[1].first().toObject();
QCOMPARE(result1.value("type").toString(), QStringLiteral("number"));
QCOMPARE(result1.value("value").toInt(), 20);
}
}
QTEST_MAIN(tst_qv4debugger)