diff options
-rw-r--r-- | src/qml/compiler/qv4codegen.cpp | 3 | ||||
-rw-r--r-- | src/qml/debugger/qv4debugservice.cpp | 91 | ||||
-rw-r--r-- | src/qml/jsruntime/qv4debugging.cpp | 80 | ||||
-rw-r--r-- | src/qml/jsruntime/qv4debugging_p.h | 2 | ||||
-rw-r--r-- | src/qml/jsruntime/qv4globalobject.cpp | 21 | ||||
-rw-r--r-- | src/qml/jsruntime/qv4script.cpp | 21 | ||||
-rw-r--r-- | src/qml/jsruntime/qv4script_p.h | 24 | ||||
-rw-r--r-- | tests/auto/qml/debugger/qqmldebugjs/tst_qqmldebugjs.cpp | 36 | ||||
-rw-r--r-- | tests/auto/qml/qv4debugger/tst_qv4debugger.cpp | 24 |
9 files changed, 219 insertions, 83 deletions
diff --git a/src/qml/compiler/qv4codegen.cpp b/src/qml/compiler/qv4codegen.cpp index 2d45543305..2f80b0e519 100644 --- a/src/qml/compiler/qv4codegen.cpp +++ b/src/qml/compiler/qv4codegen.cpp @@ -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); diff --git a/src/qml/debugger/qv4debugservice.cpp b/src/qml/debugger/qv4debugservice.cpp index 8e81d6e24e..1b30dd7f61 100644 --- a/src/qml/debugger/qv4debugservice.cpp +++ b/src/qml/debugger/qv4debugservice.cpp @@ -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) diff --git a/src/qml/jsruntime/qv4debugging.cpp b/src/qml/jsruntime/qv4debugging.cpp index 04422b9f5e..78de614664 100644 --- a/src/qml/jsruntime/qv4debugging.cpp +++ b/src/qml/jsruntime/qv4debugging.cpp @@ -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(); } diff --git a/src/qml/jsruntime/qv4debugging_p.h b/src/qml/jsruntime/qv4debugging_p.h index f834b8d15f..cedfee7f90 100644 --- a/src/qml/jsruntime/qv4debugging_p.h +++ b/src/qml/jsruntime/qv4debugging_p.h @@ -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(); diff --git a/src/qml/jsruntime/qv4globalobject.cpp b/src/qml/jsruntime/qv4globalobject.cpp index fc4a097915..a699442273 100644 --- a/src/qml/jsruntime/qv4globalobject.cpp +++ b/src/qml/jsruntime/qv4globalobject.cpp @@ -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(); diff --git a/src/qml/jsruntime/qv4script.cpp b/src/qml/jsruntime/qv4script.cpp index e08a63bd1f..27bb7c8d2f 100644 --- a/src/qml/jsruntime/qv4script.cpp +++ b/src/qml/jsruntime/qv4script.cpp @@ -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) diff --git a/src/qml/jsruntime/qv4script_p.h b/src/qml/jsruntime/qv4script_p.h index 6a749d1438..6f947cd430 100644 --- a/src/qml/jsruntime/qv4script_p.h +++ b/src/qml/jsruntime/qv4script_p.h @@ -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); diff --git a/tests/auto/qml/debugger/qqmldebugjs/tst_qqmldebugjs.cpp b/tests/auto/qml/debugger/qqmldebugjs/tst_qqmldebugjs.cpp index fef020704f..8763c4fa55 100644 --- a/tests/auto/qml/debugger/qqmldebugjs/tst_qqmldebugjs.cpp +++ b/tests/auto/qml/debugger/qqmldebugjs/tst_qqmldebugjs.cpp @@ -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(); - - int frameIndex = body.value("index").toInt(); - - //Verify the value of 'result' - client->evaluate(QLatin1String("a"),frameIndex); + { + QVariantMap body = value.value("body").toMap(); + int frameIndex = body.value("index").toInt(); - 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(); - - body = value.value("body").toMap(); - - QVERIFY(body.value("value").toInt() > out); + 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()); + + const int a = val.toInt(); + QVERIFY(a > out); } -#endif void tst_QQmlDebugJS::setBreakpointInScriptThatQuits() { diff --git a/tests/auto/qml/qv4debugger/tst_qv4debugger.cpp b/tests/auto/qml/qv4debugger/tst_qv4debugger.cpp index d7b1f2dbc3..45652d7191 100644 --- a/tests/auto/qml/qv4debugger/tst_qv4debugger.cpp +++ b/tests/auto/qml/qv4debugger/tst_qv4debugger.cpp @@ -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; |