Add support for conditional breakpoints and evaluate.
Also centralized the context state saver and added line number saving, so that the JS jobs for evaluation of breakpoint conditions don't change the state of the current engine context. Task-number: QTBUG-37119 Task-number: QTCREATORBUG-11516 Change-Id: Ia21b3d64e239e5b67f3c07e1c006d8e6748f29b6 Reviewed-by: Erik Verbruggen <erik.verbruggen@digia.com> Reviewed-by: Simon Hausmann <simon.hausmann@digia.com>
This commit is contained in:
parent
94669499fd
commit
6333e0913f
|
@ -1950,7 +1950,8 @@ int Codegen::defineFunction(const QString &name, AST::Node *ast,
|
|||
|
||||
IR::BasicBlock *entryBlock = function->newBasicBlock(0);
|
||||
IR::BasicBlock *exitBlock = function->newBasicBlock(0, IR::Function::DontInsertBlock);
|
||||
function->hasDirectEval = _env->hasDirectEval || _env->compilationMode == EvalCode;
|
||||
function->hasDirectEval = _env->hasDirectEval || _env->compilationMode == EvalCode
|
||||
|| _module->debugMode; // Conditional breakpoints are like eval in the function
|
||||
function->usesArgumentsObject = _env->parent && (_env->usesArgumentsObject == Environment::ArgumentsObjectUsed);
|
||||
function->usesThis = _env->usesThis;
|
||||
function->maxNumberOfArguments = qMax(_env->maxNumberOfArguments, (int)QV4::Global::ReservedArgumentCount);
|
||||
|
|
|
@ -405,7 +405,7 @@ public:
|
|||
|
||||
void clearHandles(QV4::ExecutionEngine *engine)
|
||||
{
|
||||
collector.reset(new VariableCollector(engine));
|
||||
theCollector.reset(new VariableCollector(engine));
|
||||
}
|
||||
|
||||
QJsonObject buildFrame(const QV4::StackFrame &stackFrame, int frameNr,
|
||||
|
@ -414,18 +414,18 @@ public:
|
|||
QJsonObject frame;
|
||||
frame[QLatin1String("index")] = frameNr;
|
||||
frame[QLatin1String("debuggerFrame")] = false;
|
||||
frame[QLatin1String("func")] = collector->addFunctionRef(stackFrame.function);
|
||||
frame[QLatin1String("script")] = collector->addScriptRef(stackFrame.source);
|
||||
frame[QLatin1String("func")] = theCollector->addFunctionRef(stackFrame.function);
|
||||
frame[QLatin1String("script")] = theCollector->addScriptRef(stackFrame.source);
|
||||
frame[QLatin1String("line")] = stackFrame.line - 1;
|
||||
if (stackFrame.column >= 0)
|
||||
frame[QLatin1String("column")] = stackFrame.column;
|
||||
|
||||
QJsonArray properties;
|
||||
collector->setDestination(&properties);
|
||||
if (debugger->collectThisInContext(collector.data(), frameNr)) {
|
||||
theCollector->setDestination(&properties);
|
||||
if (debugger->collectThisInContext(theCollector.data(), frameNr)) {
|
||||
QJsonObject obj;
|
||||
obj[QLatin1String("properties")] = properties;
|
||||
frame[QLatin1String("receiver")] = collector->addObjectRef(obj, false);
|
||||
frame[QLatin1String("receiver")] = theCollector->addObjectRef(obj, false);
|
||||
}
|
||||
|
||||
QJsonArray scopes;
|
||||
|
@ -473,7 +473,7 @@ public:
|
|||
QJsonObject scope;
|
||||
|
||||
QJsonArray properties;
|
||||
collector->collectScope(&properties, debugger, frameNr, scopeNr);
|
||||
theCollector->collectScope(&properties, debugger, frameNr, scopeNr);
|
||||
|
||||
QJsonObject anonymous;
|
||||
anonymous[QLatin1String("properties")] = properties;
|
||||
|
@ -482,16 +482,21 @@ public:
|
|||
scope[QLatin1String("type")] = encodeScopeType(scopeTypes[scopeNr]);
|
||||
scope[QLatin1String("index")] = scopeNr;
|
||||
scope[QLatin1String("frameIndex")] = frameNr;
|
||||
scope[QLatin1String("object")] = collector->addObjectRef(anonymous, true);
|
||||
scope[QLatin1String("object")] = theCollector->addObjectRef(anonymous, true);
|
||||
|
||||
return scope;
|
||||
}
|
||||
|
||||
QJsonValue lookup(int refId) const { return collector->lookup(refId); }
|
||||
QJsonValue lookup(int refId) const { return theCollector->lookup(refId); }
|
||||
|
||||
QJsonArray buildRefs()
|
||||
{
|
||||
return collector->retrieveRefsToInclude();
|
||||
return theCollector->retrieveRefsToInclude();
|
||||
}
|
||||
|
||||
VariableCollector *collector() const
|
||||
{
|
||||
return theCollector.data();
|
||||
}
|
||||
|
||||
void selectFrame(int frameNr)
|
||||
|
@ -501,7 +506,7 @@ public:
|
|||
{ return theSelectedFrame; }
|
||||
|
||||
private:
|
||||
QScopedPointer<VariableCollector> collector;
|
||||
QScopedPointer<VariableCollector> theCollector;
|
||||
int theSelectedFrame;
|
||||
|
||||
void addHandler(V8CommandHandler* handler);
|
||||
|
@ -959,6 +964,67 @@ public:
|
|||
// response will be send by
|
||||
}
|
||||
};
|
||||
|
||||
// Request:
|
||||
// {
|
||||
// "seq": 4,
|
||||
// "type": "request",
|
||||
// "command": "evaluate",
|
||||
// "arguments": {
|
||||
// "expression": "a",
|
||||
// "frame": 0
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// Response:
|
||||
// {
|
||||
// "body": {
|
||||
// "handle": 3,
|
||||
// "type": "number",
|
||||
// "value": 1
|
||||
// },
|
||||
// "command": "evaluate",
|
||||
// "refs": [],
|
||||
// "request_seq": 4,
|
||||
// "running": false,
|
||||
// "seq": 5,
|
||||
// "success": true,
|
||||
// "type": "response"
|
||||
// }
|
||||
//
|
||||
// The "value" key in "body" is the result of evaluating the expression in the request.
|
||||
class V8EvaluateRequest: public V8CommandHandler
|
||||
{
|
||||
public:
|
||||
V8EvaluateRequest(): V8CommandHandler(QStringLiteral("evaluate")) {}
|
||||
|
||||
virtual void handleRequest()
|
||||
{
|
||||
//decypher the payload:
|
||||
QJsonObject arguments = req.value(QStringLiteral("arguments")).toObject();
|
||||
QString expression = arguments.value(QStringLiteral("expression")).toString();
|
||||
const int frame = arguments.value(QStringLiteral("frame")).toInt(0);
|
||||
|
||||
QV4::Debugging::Debugger *debugger = debugServicePrivate->debuggerAgent.firstDebugger();
|
||||
Q_ASSERT(debugger->state() == QV4::Debugging::Debugger::Paused);
|
||||
|
||||
VariableCollector *collector = debugServicePrivate->collector();
|
||||
QJsonArray dest;
|
||||
collector->setDestination(&dest);
|
||||
debugger->evaluateExpression(frame, expression, collector);
|
||||
|
||||
const int ref = dest.at(0).toObject().value(QStringLiteral("value")).toObject()
|
||||
.value(QStringLiteral("ref")).toInt();
|
||||
|
||||
// response:
|
||||
addCommand();
|
||||
addRequestSequence();
|
||||
addSuccess(true);
|
||||
addRunning();
|
||||
addBody(collector->lookup(ref).toObject());
|
||||
addRefs();
|
||||
}
|
||||
};
|
||||
} // anonymous namespace
|
||||
|
||||
QV4DebugServicePrivate::QV4DebugServicePrivate()
|
||||
|
@ -978,8 +1044,7 @@ QV4DebugServicePrivate::QV4DebugServicePrivate()
|
|||
addHandler(new V8DisconnectRequest);
|
||||
addHandler(new V8SetExceptionBreakRequest);
|
||||
addHandler(new V8ScriptsRequest);
|
||||
|
||||
// TODO: evaluate
|
||||
addHandler(new V8EvaluateRequest);
|
||||
}
|
||||
|
||||
void QV4DebugServicePrivate::addHandler(V8CommandHandler* handler)
|
||||
|
|
|
@ -45,6 +45,7 @@
|
|||
#include "qv4function_p.h"
|
||||
#include "qv4instr_moth_p.h"
|
||||
#include "qv4runtime_p.h"
|
||||
#include "qv4script_p.h"
|
||||
#include <iostream>
|
||||
|
||||
#include <algorithm>
|
||||
|
@ -53,29 +54,74 @@ using namespace QV4;
|
|||
using namespace QV4::Debugging;
|
||||
|
||||
namespace {
|
||||
class EvalJob: public Debugger::Job
|
||||
class JavaScriptJob: public Debugger::Job
|
||||
{
|
||||
QV4::ExecutionEngine *engine;
|
||||
const QString &script;
|
||||
|
||||
public:
|
||||
EvalJob(QV4::ExecutionEngine *engine, const QString &script)
|
||||
JavaScriptJob(QV4::ExecutionEngine *engine, const QString &script)
|
||||
: engine(engine)
|
||||
, script(script)
|
||||
{}
|
||||
|
||||
~EvalJob() {}
|
||||
|
||||
void run()
|
||||
{
|
||||
// TODO
|
||||
qDebug() << "Evaluating script:" << script;
|
||||
Q_UNUSED(engine);
|
||||
QV4::Scope scope(engine);
|
||||
QV4::ExecutionContext *ctx = engine->currentContext();
|
||||
ContextStateSaver ctxSaver(ctx);
|
||||
QV4::ScopedValue result(scope);
|
||||
|
||||
QV4::Script script(ctx, this->script);
|
||||
script.strictMode = ctx->d()->strictMode;
|
||||
script.inheritContext = false;
|
||||
script.parse();
|
||||
if (!scope.engine->hasException)
|
||||
result = script.run();
|
||||
if (scope.engine->hasException)
|
||||
result = ctx->catchException();
|
||||
handleResult(result);
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual void handleResult(QV4::ScopedValue &result) = 0;
|
||||
};
|
||||
|
||||
class EvalJob: public JavaScriptJob
|
||||
{
|
||||
bool result;
|
||||
|
||||
public:
|
||||
EvalJob(QV4::ExecutionEngine *engine, const QString &script)
|
||||
: JavaScriptJob(engine, script)
|
||||
, result(false)
|
||||
{}
|
||||
|
||||
virtual void handleResult(QV4::ScopedValue &result)
|
||||
{
|
||||
this->result = result->toBoolean();
|
||||
}
|
||||
|
||||
bool resultAsBoolean() const
|
||||
{
|
||||
return true;
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
class ExpressionEvalJob: public JavaScriptJob
|
||||
{
|
||||
Debugger::Collector *collector;
|
||||
|
||||
public:
|
||||
ExpressionEvalJob(ExecutionEngine *engine, const QString &expression, Debugger::Collector *collector)
|
||||
: JavaScriptJob(engine, expression)
|
||||
, collector(collector)
|
||||
{
|
||||
}
|
||||
|
||||
virtual void handleResult(QV4::ScopedValue &result)
|
||||
{
|
||||
collector->collect(QStringLiteral("body"), result);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -444,6 +490,19 @@ QVector<ExecutionContext::ContextType> Debugger::getScopeTypes(int frame) const
|
|||
return types;
|
||||
}
|
||||
|
||||
|
||||
void Debugger::evaluateExpression(int frameNr, const QString &expression, Debugger::Collector *resultsCollector)
|
||||
{
|
||||
Q_ASSERT(state() == Paused);
|
||||
Q_UNUSED(frameNr);
|
||||
|
||||
Q_ASSERT(m_runningJob == 0);
|
||||
ExpressionEvalJob job(m_engine, expression, resultsCollector);
|
||||
m_runningJob = &job;
|
||||
m_runningJob->run();
|
||||
m_runningJob = 0;
|
||||
}
|
||||
|
||||
void Debugger::maybeBreakAtInstruction()
|
||||
{
|
||||
if (m_runningJob) // do not re-enter when we're doing a job for the debugger.
|
||||
|
@ -481,6 +540,8 @@ void Debugger::maybeBreakAtInstruction()
|
|||
|
||||
void Debugger::enteringFunction()
|
||||
{
|
||||
if (m_runningJob)
|
||||
return;
|
||||
QMutexLocker locker(&m_lock);
|
||||
|
||||
if (m_stepping == StepIn) {
|
||||
|
@ -490,6 +551,8 @@ void Debugger::enteringFunction()
|
|||
|
||||
void Debugger::leavingFunction(const ReturnedValue &retVal)
|
||||
{
|
||||
if (m_runningJob)
|
||||
return;
|
||||
Q_UNUSED(retVal); // TODO
|
||||
|
||||
QMutexLocker locker(&m_lock);
|
||||
|
@ -560,6 +623,7 @@ bool Debugger::reallyHitTheBreakPoint(const QString &filename, int linenr)
|
|||
EvalJob evilJob(m_engine, condition);
|
||||
m_runningJob = &evilJob;
|
||||
m_runningJob->run();
|
||||
m_runningJob = 0;
|
||||
|
||||
return evilJob.resultAsBoolean();
|
||||
}
|
||||
|
|
|
@ -181,6 +181,8 @@ public:
|
|||
void collectReturnedValue(Collector *collector) const;
|
||||
QVector<ExecutionContext::ContextType> getScopeTypes(int frame = 0) const;
|
||||
|
||||
void evaluateExpression(int frameNr, const QString &expression, Collector *resultsCollector);
|
||||
|
||||
public: // compile-time interface
|
||||
void maybeBreakAtInstruction();
|
||||
|
||||
|
|
|
@ -357,27 +357,6 @@ EvalFunction::Data::Data(ExecutionContext *scope)
|
|||
|
||||
ReturnedValue EvalFunction::evalCall(CallData *callData, bool directCall)
|
||||
{
|
||||
struct ContextStateSaver {
|
||||
ExecutionContext *savedContext;
|
||||
bool strictMode;
|
||||
ExecutionContext::EvalCode *evalCode;
|
||||
CompiledData::CompilationUnit *compilationUnit;
|
||||
|
||||
ContextStateSaver(ExecutionContext *context)
|
||||
: savedContext(context)
|
||||
, strictMode(context->d()->strictMode)
|
||||
, evalCode(context->d()->currentEvalCode)
|
||||
, compilationUnit(context->d()->compilationUnit)
|
||||
{}
|
||||
|
||||
~ContextStateSaver()
|
||||
{
|
||||
savedContext->d()->strictMode = strictMode;
|
||||
savedContext->d()->currentEvalCode = evalCode;
|
||||
savedContext->d()->compilationUnit = compilationUnit;
|
||||
}
|
||||
};
|
||||
|
||||
if (callData->argc < 1)
|
||||
return Encode::undefined();
|
||||
|
||||
|
|
|
@ -287,27 +287,6 @@ void Script::parse()
|
|||
|
||||
ReturnedValue Script::run()
|
||||
{
|
||||
struct ContextStateSaver {
|
||||
ExecutionContext *savedContext;
|
||||
bool strictMode;
|
||||
Lookup *lookups;
|
||||
CompiledData::CompilationUnit *compilationUnit;
|
||||
|
||||
ContextStateSaver(ExecutionContext *context)
|
||||
: savedContext(context)
|
||||
, strictMode(context->d()->strictMode)
|
||||
, lookups(context->d()->lookups)
|
||||
, compilationUnit(context->d()->compilationUnit)
|
||||
{}
|
||||
|
||||
~ContextStateSaver()
|
||||
{
|
||||
savedContext->d()->strictMode = strictMode;
|
||||
savedContext->d()->lookups = lookups;
|
||||
savedContext->d()->compilationUnit = compilationUnit;
|
||||
}
|
||||
};
|
||||
|
||||
if (!parsed)
|
||||
parse();
|
||||
if (!vmFunction)
|
||||
|
|
|
@ -55,6 +55,30 @@ namespace QV4 {
|
|||
|
||||
struct ExecutionContext;
|
||||
|
||||
struct ContextStateSaver {
|
||||
ExecutionContext *savedContext;
|
||||
bool strictMode;
|
||||
Lookup *lookups;
|
||||
CompiledData::CompilationUnit *compilationUnit;
|
||||
int lineNumber;
|
||||
|
||||
ContextStateSaver(ExecutionContext *context)
|
||||
: savedContext(context)
|
||||
, strictMode(context->d()->strictMode)
|
||||
, lookups(context->d()->lookups)
|
||||
, compilationUnit(context->d()->compilationUnit)
|
||||
, lineNumber(context->d()->lineNumber)
|
||||
{}
|
||||
|
||||
~ContextStateSaver()
|
||||
{
|
||||
savedContext->d()->strictMode = strictMode;
|
||||
savedContext->d()->lookups = lookups;
|
||||
savedContext->d()->compilationUnit = compilationUnit;
|
||||
savedContext->d()->lineNumber = lineNumber;
|
||||
}
|
||||
};
|
||||
|
||||
struct Q_QML_EXPORT QmlBindingWrapper : FunctionObject {
|
||||
struct Data : FunctionObject::Data {
|
||||
Data(ExecutionContext *scope, Function *f, Object *qml);
|
||||
|
|
|
@ -187,7 +187,7 @@ private slots:
|
|||
void setBreakpointInScriptOnComment();
|
||||
void setBreakpointInScriptOnEmptyLine();
|
||||
void setBreakpointInScriptOnOptimizedBinding();
|
||||
// void setBreakpointInScriptWithCondition(); // Not supported yet.
|
||||
void setBreakpointInScriptWithCondition();
|
||||
void setBreakpointInScriptThatQuits();
|
||||
//void setBreakpointInFunction(); //NOT SUPPORTED
|
||||
// void setBreakpointOnEvent();
|
||||
|
@ -1082,13 +1082,8 @@ void tst_QQmlDebugJS::setBreakpointInScriptOnOptimizedBinding()
|
|||
QCOMPARE(QFileInfo(body.value("script").toMap().value("name").toString()).fileName(), QLatin1String(BREAKPOINTRELOCATION_QMLFILE));
|
||||
}
|
||||
|
||||
#if 0
|
||||
void tst_QQmlDebugJS::setBreakpointInScriptWithCondition()
|
||||
{
|
||||
QFAIL("conditional breakpoints are not yet supported");
|
||||
|
||||
//void setBreakpoint(QString type, QString target, int line = -1, int column = -1, bool enabled = false, QString condition = QString(), int ignoreCount = -1)
|
||||
|
||||
int out = 10;
|
||||
int sourceLine = 50;
|
||||
QVERIFY(init(CONDITION_QMLFILE));
|
||||
|
@ -1102,23 +1097,26 @@ void tst_QQmlDebugJS::setBreakpointInScriptWithCondition()
|
|||
QString jsonString = client->response;
|
||||
QVariantMap value = client->parser.call(QJSValueList() << QJSValue(jsonString)).toVariant().toMap();
|
||||
|
||||
QVariantMap body = value.value("body").toMap();
|
||||
{
|
||||
QVariantMap body = value.value("body").toMap();
|
||||
int frameIndex = body.value("index").toInt();
|
||||
|
||||
int frameIndex = body.value("index").toInt();
|
||||
|
||||
//Verify the value of 'result'
|
||||
client->evaluate(QLatin1String("a"),frameIndex);
|
||||
|
||||
QVERIFY(QQmlDebugTest::waitForSignal(client, SIGNAL(result())));
|
||||
//Verify the value of 'result'
|
||||
client->evaluate(QLatin1String("a"),frameIndex);
|
||||
QVERIFY(QQmlDebugTest::waitForSignal(client, SIGNAL(result())));
|
||||
}
|
||||
|
||||
jsonString = client->response;
|
||||
value = client->parser.call(QJSValueList() << QJSValue(jsonString)).toVariant().toMap();
|
||||
QJSValue val = client->parser.call(QJSValueList() << QJSValue(jsonString));
|
||||
QVERIFY(val.isObject());
|
||||
QJSValue body = val.property(QStringLiteral("body"));
|
||||
QVERIFY(body.isObject());
|
||||
val = body.property("value");
|
||||
QVERIFY(val.isNumber());
|
||||
|
||||
body = value.value("body").toMap();
|
||||
|
||||
QVERIFY(body.value("value").toInt() > out);
|
||||
const int a = val.toInt();
|
||||
QVERIFY(a > out);
|
||||
}
|
||||
#endif
|
||||
|
||||
void tst_QQmlDebugJS::setBreakpointInScriptThatQuits()
|
||||
{
|
||||
|
|
|
@ -266,6 +266,7 @@ private slots:
|
|||
void removePendingBreakPoint();
|
||||
void addBreakPointWhilePaused();
|
||||
void removeBreakPointForNextInstruction();
|
||||
void conditionalBreakPoint();
|
||||
|
||||
// context access:
|
||||
void readArguments();
|
||||
|
@ -412,6 +413,29 @@ void tst_qv4debugger::removeBreakPointForNextInstruction()
|
|||
QVERIFY(!m_debuggerAgent->m_wasPaused);
|
||||
}
|
||||
|
||||
void tst_qv4debugger::conditionalBreakPoint()
|
||||
{
|
||||
m_debuggerAgent->m_captureContextInfo = true;
|
||||
QString script =
|
||||
"function test() {\n"
|
||||
" for (var i = 0; i < 15; ++i) {\n"
|
||||
" var x = i;\n"
|
||||
" }\n"
|
||||
"}\n"
|
||||
"test()\n";
|
||||
|
||||
m_debuggerAgent->addBreakPoint("conditionalBreakPoint", 3, /*enabled*/true, QStringLiteral("i > 10"));
|
||||
evaluateJavaScript(script, "conditionalBreakPoint");
|
||||
QVERIFY(m_debuggerAgent->m_wasPaused);
|
||||
QCOMPARE(m_debuggerAgent->m_statesWhenPaused.count(), 4);
|
||||
QV4::Debugging::Debugger::ExecutionState state = m_debuggerAgent->m_statesWhenPaused.first();
|
||||
QCOMPARE(state.fileName, QString("conditionalBreakPoint"));
|
||||
QCOMPARE(state.lineNumber, 3);
|
||||
QCOMPARE(m_debuggerAgent->m_capturedLocals[0].size(), 2);
|
||||
QVERIFY(m_debuggerAgent->m_capturedLocals[0].contains(QStringLiteral("i")));
|
||||
QCOMPARE(m_debuggerAgent->m_capturedLocals[0]["i"].toInt(), 11);
|
||||
}
|
||||
|
||||
void tst_qv4debugger::readArguments()
|
||||
{
|
||||
m_debuggerAgent->m_captureContextInfo = true;
|
||||
|
|
Loading…
Reference in New Issue