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:
parent
3dd96630a2
commit
bff7302fc2
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -9,6 +9,7 @@ SOURCES += tst_qqmldebugjs.cpp
|
|||
INCLUDEPATH += ../../shared
|
||||
include(../../../../shared/util.pri)
|
||||
include(../../shared/debugutil.pri)
|
||||
include(../../shared/qqmlenginedebugclient.pri)
|
||||
|
||||
TESTDATA = data/*
|
||||
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in New Issue