diff options
author | Ulf Hermann <ulf.hermann@theqtcompany.com> | 2015-10-22 13:07:11 +0200 |
---|---|---|
committer | Ulf Hermann <ulf.hermann@theqtcompany.com> | 2015-10-22 13:07:11 +0200 |
commit | b103f6a6b9cc0ddf3df2788816a6fd98369b1b6d (patch) | |
tree | fc90bc070f0f50a2d8768db8fa1e16611d158f18 /src/plugins | |
parent | 4867a49618e0d6a476e0549aeca5134b2e3c5892 (diff) | |
parent | 8ee3673e439b4499a8a4a4280637ee0270c4a54a (diff) |
Merge remote-tracking branch 'origin/5.6' into origin/dev
Conflicts:
src/plugins/qmltooling/qmldbg_debugger/qv4datacollector.cpp
src/plugins/qmltooling/qmldbg_debugger/qv4debuggeragent.cpp
src/plugins/qmltooling/qmldbg_debugger/qv4debuggeragent.h
src/plugins/qmltooling/qmldbg_debugger/qv4debugservice.cpp
src/plugins/qmltooling/qmldbg_debugger/qv4debugservice.h
src/qml/debugger/qqmldebugserviceinterfaces.cpp
src/qml/jsruntime/qv4debugging_p.h
Change-Id: I82a4ce1bcd4579181df886558f55ad2b328d1682
Diffstat (limited to 'src/plugins')
15 files changed, 1389 insertions, 57 deletions
diff --git a/src/plugins/qmltooling/qmldbg_debugger/qmldbg_debugger.pro b/src/plugins/qmltooling/qmldbg_debugger/qmldbg_debugger.pro index d860328dc8..8d1a54e9e4 100644 --- a/src/plugins/qmltooling/qmldbg_debugger/qmldbg_debugger.pro +++ b/src/plugins/qmltooling/qmldbg_debugger/qmldbg_debugger.pro @@ -9,6 +9,7 @@ SOURCES += \ $$PWD/qdebugmessageservice.cpp \ $$PWD/qqmldebuggerservicefactory.cpp \ $$PWD/qqmlenginedebugservice.cpp \ + $$PWD/qqmlnativedebugservice.cpp \ $$PWD/qqmlwatcher.cpp \ $$PWD/qv4debugservice.cpp \ $$PWD/qv4debuggeragent.cpp \ @@ -19,6 +20,7 @@ HEADERS += \ $$PWD/qdebugmessageservice.h \ $$PWD/qqmldebuggerservicefactory.h \ $$PWD/qqmlenginedebugservice.h \ + $$PWD/qqmlnativedebugservice.h \ $$PWD/qqmlwatcher.h \ $$PWD/qv4debugservice.h \ $$PWD/qv4debuggeragent.h \ diff --git a/src/plugins/qmltooling/qmldbg_debugger/qqmldebuggerservice.json b/src/plugins/qmltooling/qmldbg_debugger/qqmldebuggerservice.json index b1e90364d5..967a725903 100644 --- a/src/plugins/qmltooling/qmldbg_debugger/qqmldebuggerservice.json +++ b/src/plugins/qmltooling/qmldbg_debugger/qqmldebuggerservice.json @@ -1,3 +1,3 @@ { - "Keys": [ "DebugMessages", "QmlDebugger", "V8Debugger" ] + "Keys": [ "DebugMessages", "QmlDebugger", "V8Debugger", "NativeQmlDebugger" ] } diff --git a/src/plugins/qmltooling/qmldbg_debugger/qqmldebuggerservicefactory.cpp b/src/plugins/qmltooling/qmldbg_debugger/qqmldebuggerservicefactory.cpp index ac3b435350..4690da04d1 100644 --- a/src/plugins/qmltooling/qmldbg_debugger/qqmldebuggerservicefactory.cpp +++ b/src/plugins/qmltooling/qmldbg_debugger/qqmldebuggerservicefactory.cpp @@ -35,6 +35,7 @@ #include "qqmlenginedebugservice.h" #include "qdebugmessageservice.h" #include "qv4debugservice.h" +#include "qqmlnativedebugservice.h" #include <private/qqmldebugserviceinterfaces_p.h> QT_BEGIN_NAMESPACE @@ -50,6 +51,9 @@ QQmlDebugService *QQmlDebuggerServiceFactory::create(const QString &key) if (key == QV4DebugServiceImpl::s_key) return new QV4DebugServiceImpl(this); + if (key == QQmlNativeDebugServiceImpl::s_key) + return new QQmlNativeDebugServiceImpl(this); + return 0; } diff --git a/src/plugins/qmltooling/qmldbg_debugger/qqmlnativedebugservice.cpp b/src/plugins/qmltooling/qmldbg_debugger/qqmlnativedebugservice.cpp new file mode 100644 index 0000000000..f5cc78e77f --- /dev/null +++ b/src/plugins/qmltooling/qmldbg_debugger/qqmlnativedebugservice.cpp @@ -0,0 +1,804 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qqmlnativedebugservice.h" + +#include <private/qqmldebugconnector_p.h> +#include <private/qv4debugging_p.h> +#include <private/qv8engine_p.h> +#include <private/qv4engine_p.h> +#include <private/qv4debugging_p.h> +#include <private/qv4script_p.h> +#include <private/qv4string_p.h> +#include <private/qv4objectiterator_p.h> +#include <private/qv4identifier_p.h> +#include <private/qv4runtime_p.h> +#include <private/qv4isel_moth_p.h> +#include <private/qqmldebugserviceinterfaces_p.h> + +#include <qqmlengine.h> + +#include <QtCore/qjsonarray.h> +#include <QtCore/qjsondocument.h> +#include <QtCore/qjsonobject.h> +#include <QtCore/qjsonvalue.h> +#include <QtCore/qvector.h> +#include <QtCore/qpointer.h> + +//#define TRACE_PROTOCOL(s) qDebug() << s +#define TRACE_PROTOCOL(s) + +QT_BEGIN_NAMESPACE + +class BreakPoint +{ +public: + BreakPoint() : id(-1), lineNumber(-1), enabled(false), ignoreCount(0), hitCount(0) {} + bool isValid() const { return lineNumber >= 0 && !fileName.isEmpty(); } + + int id; + int lineNumber; + QString fileName; + bool enabled; + QString condition; + int ignoreCount; + + int hitCount; +}; + +inline uint qHash(const BreakPoint &b, uint seed = 0) Q_DECL_NOTHROW +{ + return qHash(b.fileName, seed) ^ b.lineNumber; +} + +inline bool operator==(const BreakPoint &a, const BreakPoint &b) +{ + return a.lineNumber == b.lineNumber && a.fileName == b.fileName + && a.enabled == b.enabled && a.condition == b.condition + && a.ignoreCount == b.ignoreCount; +} + +static void setError(QJsonObject *response, const QString &msg) +{ + response->insert(QStringLiteral("type"), QStringLiteral("error")); + response->insert(QStringLiteral("msg"), msg); +} + +class NativeDebugger; + +class Collector +{ +public: + Collector(QV4::ExecutionEngine *engine) + : m_engine(engine), m_anonCount(0) + {} + + void collect(QJsonArray *output, const QString &parentIName, const QString &name, + const QV4::Value &value); + + bool isExpanded(const QString &iname) const { return m_expanded.contains(iname); } + +public: + QV4::ExecutionEngine *m_engine; + int m_anonCount; + QStringList m_expanded; +}; + +// Encapsulate Breakpoint handling +// Could be made per-NativeDebugger (i.e. per execution engine, if needed) +class BreakPointHandler +{ +public: + BreakPointHandler() : m_haveBreakPoints(false), m_breakOnThrow(true), m_lastBreakpoint(1) {} + + void handleSetBreakpoint(QJsonObject *response, const QJsonObject &arguments); + void handleRemoveBreakpoint(QJsonObject *response, const QJsonObject &arguments); + + void removeBreakPoint(int id); + void enableBreakPoint(int id, bool onoff); + + void setBreakOnThrow(bool onoff); + bool m_haveBreakPoints; + bool m_breakOnThrow; + int m_lastBreakpoint; + QVector<BreakPoint> m_breakPoints; +}; + +void BreakPointHandler::handleSetBreakpoint(QJsonObject *response, const QJsonObject &arguments) +{ + TRACE_PROTOCOL("SET BREAKPOINT" << arguments); + QString type = arguments.value(QStringLiteral("type")).toString(); + + QString fileName = arguments.value(QStringLiteral("file")).toString(); + if (fileName.isEmpty()) { + setError(response, QStringLiteral("breakpoint has no file name")); + return; + } + + int line = arguments.value(QStringLiteral("line")).toInt(-1); + if (line < 0) { + setError(response, QStringLiteral("breakpoint has an invalid line number")); + return; + } + + BreakPoint bp; + bp.id = m_lastBreakpoint++; + bp.fileName = fileName.mid(fileName.lastIndexOf('/') + 1); + bp.lineNumber = line; + bp.enabled = arguments.value(QStringLiteral("enabled")).toBool(true); + bp.condition = arguments.value(QStringLiteral("condition")).toString(); + bp.ignoreCount = arguments.value(QStringLiteral("ignorecount")).toInt(); + m_breakPoints.append(bp); + + m_haveBreakPoints = true; + + response->insert(QStringLiteral("type"), type); + response->insert(QStringLiteral("breakpoint"), bp.id); +} + +void BreakPointHandler::handleRemoveBreakpoint(QJsonObject *response, const QJsonObject &arguments) +{ + int id = arguments.value(QStringLiteral("id")).toInt(); + removeBreakPoint(id); + response->insert(QStringLiteral("id"), id); +} + +class NativeDebugger : public QV4::Debugging::Debugger +{ +public: + NativeDebugger(QQmlNativeDebugServiceImpl *service, QV4::ExecutionEngine *engine); + + void signalEmitted(const QString &signal); + + QV4::ExecutionEngine *engine() const { return m_engine; } + + bool pauseAtNextOpportunity() const Q_DECL_OVERRIDE { + return m_pauseRequested + || m_service->m_breakHandler->m_haveBreakPoints + || m_stepping >= StepOver; + } + + void maybeBreakAtInstruction() Q_DECL_OVERRIDE; + void enteringFunction() Q_DECL_OVERRIDE; + void leavingFunction(const QV4::ReturnedValue &retVal) Q_DECL_OVERRIDE; + void aboutToThrow() Q_DECL_OVERRIDE; + + void handleCommand(QJsonObject *response, const QString &cmd, const QJsonObject &arguments); + +private: + void handleBacktrace(QJsonObject *response, const QJsonObject &arguments); + void handleVariables(QJsonObject *response, const QJsonObject &arguments); + void handleExpressions(QJsonObject *response, const QJsonObject &arguments); + + void handleDebuggerDeleted(QObject *debugger); + + QV4::ReturnedValue evaluateExpression(QV4::Scope &scope, const QString &expression); + bool checkCondition(const QString &expression); + + QStringList breakOnSignals; + + enum Speed { + NotStepping = 0, + StepOut, + StepOver, + StepIn, + }; + + void pauseAndWait(); + void pause(); + void handleContinue(QJsonObject *reponse, Speed speed); + + QV4::Function *getFunction() const; + + bool reallyHitTheBreakPoint(const QV4::Function *function, int lineNumber); + + QV4::ExecutionEngine *m_engine; + QQmlNativeDebugServiceImpl *m_service; + QV4::PersistentValue m_currentContext; + Speed m_stepping; + bool m_pauseRequested; + bool m_runningJob; + + QV4::PersistentValue m_returnedValue; +}; + +bool NativeDebugger::checkCondition(const QString &expression) +{ + QV4::Scope scope(m_engine); + QV4::ReturnedValue result = evaluateExpression(scope, expression); + QV4::ScopedValue val(scope, result); + return val->booleanValue(); +} + +QV4::ReturnedValue NativeDebugger::evaluateExpression(QV4::Scope &scope, const QString &expression) +{ + m_runningJob = true; + + QV4::ExecutionContextSaver saver(scope); + + QV4::ExecutionContext *ctx = m_engine->currentContext; + m_engine->pushContext(ctx); + + QV4::Script script(ctx, expression); + script.strictMode = ctx->d()->strictMode; + // In order for property lookups in QML to work, we need to disable fast v4 lookups. + // That is a side-effect of inheritContext. + script.inheritContext = true; + script.parse(); + QV4::ScopedValue result(scope); + if (!m_engine->hasException) + result = script.run(); + + m_runningJob = false; + return result->asReturnedValue(); +} + +NativeDebugger::NativeDebugger(QQmlNativeDebugServiceImpl *service, QV4::ExecutionEngine *engine) + : m_returnedValue(engine, QV4::Primitive::undefinedValue()) +{ + m_stepping = NotStepping; + m_pauseRequested = false; + m_runningJob = false; + m_service = service; + m_engine = engine; + TRACE_PROTOCOL("Creating native debugger"); +} + +void NativeDebugger::signalEmitted(const QString &signal) +{ + //This function is only called by QQmlBoundSignal + //only if there is a slot connected to the signal. Hence, there + //is no need for additional check. + + //Parse just the name and remove the class info + //Normalize to Lower case. + QString signalName = signal.left(signal.indexOf(QLatin1Char('('))).toLower(); + + foreach (const QString &signal, breakOnSignals) { + if (signal == signalName) { + // TODO: pause debugger + break; + } + } +} + +void NativeDebugger::handleCommand(QJsonObject *response, const QString &cmd, + const QJsonObject &arguments) +{ + if (cmd == QStringLiteral("backtrace")) + handleBacktrace(response, arguments); + else if (cmd == QStringLiteral("variables")) + handleVariables(response, arguments); + else if (cmd == QStringLiteral("expressions")) + handleExpressions(response, arguments); + else if (cmd == QStringLiteral("stepin")) + handleContinue(response, StepIn); + else if (cmd == QStringLiteral("stepout")) + handleContinue(response, StepOut); + else if (cmd == QStringLiteral("stepover")) + handleContinue(response, StepOver); + else if (cmd == QStringLiteral("continue")) + handleContinue(response, NotStepping); +} + +static QString encodeContext(QV4::ExecutionContext *executionContext) +{ + QByteArray ba; + QDataStream ds(&ba, QIODevice::WriteOnly); + ds << quintptr(executionContext); + return QString::fromLatin1(ba.toHex()); +} + +static void decodeContext(const QString &context, QV4::ExecutionContext **executionContext) +{ + quintptr rawContext; + QDataStream ds(QByteArray::fromHex(context.toLatin1())); + ds >> rawContext; + *executionContext = reinterpret_cast<QV4::ExecutionContext *>(rawContext); +} + +void NativeDebugger::handleBacktrace(QJsonObject *response, const QJsonObject &arguments) +{ + int limit = arguments.value(QStringLiteral("limit")).toInt(0); + + QJsonArray frameArray; + QV4::ExecutionContext *executionContext = m_engine->currentContext; + for (int i = 0; i < limit && executionContext; ++i) { + QV4::Heap::FunctionObject *heapFunctionObject = executionContext->getFunctionObject(); + if (heapFunctionObject) { + + QJsonObject frame; + frame[QStringLiteral("language")] = QStringLiteral("js"); + frame[QStringLiteral("context")] = encodeContext(executionContext); + + if (QV4::Function *function = heapFunctionObject->function) { + if (QV4::Heap::String *functionName = function->name()) + frame[QStringLiteral("function")] = functionName->toQString(); + frame[QStringLiteral("file")] = function->sourceFile(); + } + int line = executionContext->d()->lineNumber; + frame[QStringLiteral("line")] = (line < 0 ? -line : line); + + frameArray.push_back(frame); + } + + executionContext = m_engine->parentContext(executionContext); + } + + response->insert(QStringLiteral("frames"), frameArray); +} + +void Collector::collect(QJsonArray *out, const QString &parentIName, const QString &name, + const QV4::Value &value) +{ + QJsonObject dict; + QV4::Scope scope(m_engine); + + QString nonEmptyName = name.isEmpty() ? QString::fromLatin1("@%1").arg(m_anonCount++) : name; + QString iname = parentIName + QLatin1Char('.') + nonEmptyName; + dict.insert(QStringLiteral("iname"), iname); + dict.insert(QStringLiteral("name"), nonEmptyName); + + QV4::ScopedValue typeString(scope, QV4::Runtime::typeofValue(m_engine, value)); + dict.insert(QStringLiteral("type"), typeString->toQStringNoThrow()); + + switch (value.type()) { + case QV4::Value::Empty_Type: + dict.insert(QStringLiteral("valueencoded"), QStringLiteral("empty")); + dict.insert(QStringLiteral("haschild"), false); + break; + case QV4::Value::Undefined_Type: + dict.insert(QStringLiteral("valueencoded"), QStringLiteral("undefined")); + dict.insert(QStringLiteral("haschild"), false); + break; + case QV4::Value::Null_Type: + dict.insert(QStringLiteral("type"), QStringLiteral("object")); + dict.insert(QStringLiteral("valueencoded"), QStringLiteral("null")); + dict.insert(QStringLiteral("haschild"), false); + break; + case QV4::Value::Boolean_Type: + dict.insert(QStringLiteral("value"), value.booleanValue()); + dict.insert(QStringLiteral("haschild"), false); + break; + case QV4::Value::Managed_Type: + if (const QV4::String *string = value.as<QV4::String>()) { + dict.insert(QStringLiteral("value"), string->toQStringNoThrow()); + dict.insert(QStringLiteral("haschild"), false); + dict.insert(QStringLiteral("valueencoded"), QStringLiteral("utf16")); + dict.insert(QStringLiteral("quoted"), true); + } else if (const QV4::ArrayObject *array = value.as<QV4::ArrayObject>()) { + // The size of an array is number of its numerical properties. + // We don't consider free form object properties here. + const uint n = array->getLength(); + dict.insert(QStringLiteral("value"), qint64(n)); + dict.insert(QStringLiteral("valueencoded"), QStringLiteral("itemcount")); + dict.insert(QStringLiteral("haschild"), qint64(n)); + if (isExpanded(iname)) { + QJsonArray children; + for (uint i = 0; i < n; ++i) { + QV4::ReturnedValue v = array->getIndexed(i); + QV4::ScopedValue sval(scope, v); + collect(&children, iname, QString::number(i), *sval); + } + dict.insert(QStringLiteral("children"), children); + } + } else if (const QV4::Object *object = value.as<QV4::Object>()) { + QJsonArray children; + bool expanded = isExpanded(iname); + qint64 numProperties = 0; + QV4::ObjectIterator it(scope, object, QV4::ObjectIterator::EnumerableOnly); + QV4::ScopedProperty p(scope); + QV4::ScopedString name(scope); + while (true) { + QV4::PropertyAttributes attrs; + uint index; + it.next(name.getRef(), &index, p, &attrs); + if (attrs.isEmpty()) + break; + if (name.getPointer()) { + ++numProperties; + if (expanded) { + if (name.getPointer()) { + QV4::Value v = p.property->value; + collect(&children, iname, name->toQStringNoThrow(), v); + } + } + } + } + dict.insert(QStringLiteral("value"), numProperties); + dict.insert(QStringLiteral("valueencoded"), QStringLiteral("itemcount")); + dict.insert(QStringLiteral("haschild"), numProperties > 0); + if (expanded) + dict.insert(QStringLiteral("children"), children); + } + break; + case QV4::Value::Integer_Type: + dict.insert(QStringLiteral("value"), value.integerValue()); + dict.insert(QStringLiteral("haschild"), false); + break; + default: // double + dict.insert(QStringLiteral("value"), value.doubleValue()); + dict.insert(QStringLiteral("haschild"), false); + } + + out->append(dict); +} + +void NativeDebugger::handleVariables(QJsonObject *response, const QJsonObject &arguments) +{ + TRACE_PROTOCOL("Build variables"); + QV4::ExecutionContext *executionContext = 0; + decodeContext(arguments.value(QStringLiteral("context")).toString(), &executionContext); + if (!executionContext) { + setError(response, QStringLiteral("No execution context passed")); + return; + } + TRACE_PROTOCOL("Context: " << executionContext); + + QV4::ExecutionEngine *engine = executionContext->d()->engine; + if (!engine) { + setError(response, QStringLiteral("No execution engine passed")); + return; + } + TRACE_PROTOCOL("Engine: " << engine); + + Collector collector(engine); + QJsonArray expanded = arguments.value(QStringLiteral("expanded")).toArray(); + foreach (const QJsonValue &ex, expanded) + collector.m_expanded.append(ex.toString()); + TRACE_PROTOCOL("Expanded: " << collector.m_expanded); + + QJsonArray output; + QV4::Scope scope(engine); + + if (QV4::CallContext *callContext = executionContext->asCallContext()) { + QV4::Value thisObject = callContext->thisObject(); + collector.collect(&output, QString(), QStringLiteral("this"), thisObject); + QV4::Identifier *const *variables = callContext->variables(); + QV4::Identifier *const *formals = callContext->formals(); + for (unsigned i = 0, ei = callContext->variableCount(); i != ei; ++i) { + QString qName; + if (QV4::Identifier *name = variables[i]) + qName = name->string; + QV4::Value val = callContext->d()->locals[i]; + collector.collect(&output, QString(), qName, val); + } + for (unsigned i = 0, ei = callContext->formalCount(); i != ei; ++i) { + QString qName; + if (QV4::Identifier *name = formals[i]) + qName = name->string; + QV4::ReturnedValue rval = callContext->argument(i); + QV4::ScopedValue sval(scope, rval); + collector.collect(&output, QString(), qName, *sval); + } + } + + response->insert(QStringLiteral("variables"), output); +} + +void NativeDebugger::handleExpressions(QJsonObject *response, const QJsonObject &arguments) +{ + TRACE_PROTOCOL("Evaluate expressions"); + QV4::ExecutionContext *executionContext = 0; + decodeContext(arguments.value(QStringLiteral("context")).toString(), &executionContext); + if (!executionContext) { + setError(response, QStringLiteral("No execution context passed")); + return; + } + TRACE_PROTOCOL("Context: " << executionContext); + + QV4::ExecutionEngine *engine = executionContext->d()->engine; + if (!engine) { + setError(response, QStringLiteral("No execution engine passed")); + return; + } + TRACE_PROTOCOL("Engines: " << engine << m_engine); + + Collector collector(engine); + QJsonArray expanded = arguments.value(QStringLiteral("expanded")).toArray(); + foreach (const QJsonValue &ex, expanded) + collector.m_expanded.append(ex.toString()); + TRACE_PROTOCOL("Expanded: " << collector.m_expanded); + + QJsonArray output; + QV4::Scope scope(engine); + + QJsonArray expressions = arguments.value(QStringLiteral("expressions")).toArray(); + foreach (const QJsonValue &expr, expressions) { + QString expression = expr.toObject().value(QStringLiteral("expression")).toString(); + QString name = expr.toObject().value(QStringLiteral("name")).toString(); + TRACE_PROTOCOL("Evaluate expression: " << expression); + m_runningJob = true; + + QV4::ReturnedValue eval = evaluateExpression(scope, expression); + QV4::ScopedValue result(scope, eval); + + m_runningJob = false; + if (result->isUndefined()) { + QJsonObject dict; + dict[QStringLiteral("name")] = name; + dict[QStringLiteral("valueencoded")] = QStringLiteral("undefined"); + output.append(dict); + } else if (result.ptr && result.ptr->_val) { + collector.collect(&output, QString(), name, *result); + } else { + QJsonObject dict; + dict[QStringLiteral("name")] = name; + dict[QStringLiteral("valueencoded")] = QStringLiteral("notaccessible"); + output.append(dict); + } + TRACE_PROTOCOL("EXCEPTION: " << engine->hasException); + engine->hasException = false; + } + + response->insert(QStringLiteral("expressions"), output); +} + +void BreakPointHandler::removeBreakPoint(int id) +{ + for (int i = 0; i != m_breakPoints.size(); ++i) { + if (m_breakPoints.at(i).id == id) { + m_breakPoints.remove(i); + m_haveBreakPoints = !m_breakPoints.isEmpty(); + return; + } + } +} + +void BreakPointHandler::enableBreakPoint(int id, bool enabled) +{ + m_breakPoints[id].enabled = enabled; +} + +void NativeDebugger::pause() +{ + m_pauseRequested = true; +} + +void NativeDebugger::handleContinue(QJsonObject *response, Speed speed) +{ + Q_UNUSED(response); + + if (!m_returnedValue.isUndefined()) + m_returnedValue.set(m_engine, QV4::Encode::undefined()); + + m_currentContext.set(m_engine, *m_engine->currentContext); + m_stepping = speed; +} + +void NativeDebugger::maybeBreakAtInstruction() +{ + if (m_runningJob) // do not re-enter when we're doing a job for the debugger. + return; + + if (m_stepping == StepOver) { + if (m_currentContext.asManaged()->d() == m_engine->current) + pauseAndWait(); + return; + } + + if (m_stepping == StepIn) { + pauseAndWait(); + return; + } + + if (m_pauseRequested) { // Serve debugging requests from the agent + m_pauseRequested = false; + pauseAndWait(); + return; + } + + if (m_service->m_breakHandler->m_haveBreakPoints) { + if (QV4::Function *function = getFunction()) { + const int lineNumber = m_engine->current->lineNumber; + if (reallyHitTheBreakPoint(function, lineNumber)) + pauseAndWait(); + } + } +} + +void NativeDebugger::enteringFunction() +{ + if (m_runningJob) + return; + + if (m_stepping == StepIn) { + m_currentContext.set(m_engine, *m_engine->currentContext); + } +} + +void NativeDebugger::leavingFunction(const QV4::ReturnedValue &retVal) +{ + if (m_runningJob) + return; + + if (m_stepping != NotStepping && m_currentContext.asManaged()->d() == m_engine->current) { + m_currentContext.set(m_engine, *m_engine->parentContext(m_engine->currentContext)); + m_stepping = StepOver; + m_returnedValue.set(m_engine, retVal); + } +} + +void NativeDebugger::aboutToThrow() +{ + if (!m_service->m_breakHandler->m_breakOnThrow) + return; + + if (m_runningJob) // do not re-enter when we're doing a job for the debugger. + return; + + QJsonObject event; + // TODO: complete this! + event.insert(QStringLiteral("event"), QStringLiteral("exception")); + m_service->emitAsynchronousMessageToClient(event); +} + +QV4::Function *NativeDebugger::getFunction() const +{ + QV4::Scope scope(m_engine); + QV4::ExecutionContext *context = m_engine->currentContext; + QV4::ScopedFunctionObject function(scope, context->getFunctionObject()); + if (function) + return function->function(); + else + return context->d()->engine->globalCode; +} + +void NativeDebugger::pauseAndWait() +{ + QJsonObject event; + + event.insert(QStringLiteral("event"), QStringLiteral("break")); + event.insert(QStringLiteral("language"), QStringLiteral("js")); + if (QV4::ExecutionContext *executionContext = m_engine->currentContext) { + QV4::Heap::FunctionObject *heapFunctionObject = executionContext->getFunctionObject(); + if (heapFunctionObject) { + if (QV4::Function *function = heapFunctionObject->function) + event.insert(QStringLiteral("file"), function->sourceFile()); + int line = executionContext->d()->lineNumber; + event.insert(QStringLiteral("line"), (line < 0 ? -line : line)); + } + } + + m_service->emitAsynchronousMessageToClient(event); +} + +bool NativeDebugger::reallyHitTheBreakPoint(const QV4::Function *function, int lineNumber) +{ + for (int i = 0, n = m_service->m_breakHandler->m_breakPoints.size(); i != n; ++i) { + const BreakPoint &bp = m_service->m_breakHandler->m_breakPoints.at(i); + if (bp.lineNumber == lineNumber) { + const QString fileName = function->sourceFile(); + const QString base = fileName.mid(fileName.lastIndexOf('/') + 1); + if (bp.fileName.endsWith(base)) { + if (bp.condition.isEmpty() || checkCondition(bp.condition)) { + BreakPoint &mbp = m_service->m_breakHandler->m_breakPoints[i]; + ++mbp.hitCount; + if (mbp.hitCount > mbp.ignoreCount) + return true; + } + } + } + } + return false; +} + +QQmlNativeDebugServiceImpl::QQmlNativeDebugServiceImpl(QObject *parent) + : QQmlNativeDebugService(1.0, parent) +{ + m_breakHandler = new BreakPointHandler; +} + +QQmlNativeDebugServiceImpl::~QQmlNativeDebugServiceImpl() +{ + delete m_breakHandler; +} + +void QQmlNativeDebugServiceImpl::engineAboutToBeAdded(QQmlEngine *engine) +{ + TRACE_PROTOCOL("Adding engine" << engine); + if (engine) { + QV4::ExecutionEngine *ee = QV8Engine::getV4(engine->handle()); + TRACE_PROTOCOL("Adding execution engine" << ee); + if (ee) { + NativeDebugger *debugger = new NativeDebugger(this, ee); + ee->iselFactory.reset(new QV4::Moth::ISelFactory); + if (state() == Enabled) + ee->setDebugger(debugger); + m_debuggers.append(QPointer<NativeDebugger>(debugger)); + } + } + QQmlDebugService::engineAboutToBeAdded(engine); +} + +void QQmlNativeDebugServiceImpl::engineAboutToBeRemoved(QQmlEngine *engine) +{ + TRACE_PROTOCOL("Removing engine" << engine); + if (engine) { + QV4::ExecutionEngine *executionEngine = QV8Engine::getV4(engine->handle()); + foreach (NativeDebugger *debugger, m_debuggers) { + if (debugger->engine() == executionEngine) + m_debuggers.removeAll(debugger); + } + } + QQmlDebugService::engineAboutToBeRemoved(engine); +} + +void QQmlNativeDebugServiceImpl::stateAboutToBeChanged(QQmlDebugService::State state) +{ + if (state == Enabled) { + foreach (NativeDebugger *debugger, m_debuggers) { + QV4::ExecutionEngine *engine = debugger->engine(); + if (!engine->debugger) + engine->setDebugger(debugger); + } + } + QQmlDebugService::stateAboutToBeChanged(state); +} + +void QQmlNativeDebugServiceImpl::messageReceived(const QByteArray &message) +{ + TRACE_PROTOCOL("Native message received: " << message); + QJsonObject request = QJsonDocument::fromJson(message).object(); + QJsonObject response; + QJsonObject arguments = request.value(QStringLiteral("arguments")).toObject(); + QString cmd = request.value(QStringLiteral("command")).toString(); + + if (cmd == QStringLiteral("setbreakpoint")) { + m_breakHandler->handleSetBreakpoint(&response, arguments); + } else if (cmd == QStringLiteral("removebreakpoint")) { + m_breakHandler->handleRemoveBreakpoint(&response, arguments); + } else if (cmd == QStringLiteral("echo")) { + response.insert(QStringLiteral("result"), arguments); + } else { + foreach (NativeDebugger *debugger, m_debuggers) + if (debugger) + debugger->handleCommand(&response, cmd, arguments); + } + QJsonDocument doc; + doc.setObject(response); + QByteArray ba = doc.toJson(QJsonDocument::Compact); + TRACE_PROTOCOL("Sending synchronous response:" << ba.constData() << endl); + emit messageToClient(s_key, ba); +} + +void QQmlNativeDebugServiceImpl::emitAsynchronousMessageToClient(const QJsonObject &message) +{ + QJsonDocument doc; + doc.setObject(message); + QByteArray ba = doc.toJson(QJsonDocument::Compact); + TRACE_PROTOCOL("Sending asynchronous message:" << ba.constData() << endl); + emit messageToClient(s_key, ba); +} + +QT_END_NAMESPACE diff --git a/src/plugins/qmltooling/qmldbg_debugger/qqmlnativedebugservice.h b/src/plugins/qmltooling/qmldbg_debugger/qqmlnativedebugservice.h new file mode 100644 index 0000000000..9d0780a203 --- /dev/null +++ b/src/plugins/qmltooling/qmldbg_debugger/qqmlnativedebugservice.h @@ -0,0 +1,92 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQML_NATIVE_DEBUG_SERVICE_H +#define QQML_NATIVE_DEBUG_SERVICE_H + +#include <private/qqmldebugconnector_p.h> +#include <private/qv4debugging_p.h> +#include <private/qv8engine_p.h> +#include <private/qv4engine_p.h> +#include <private/qv4debugging_p.h> +#include <private/qv4script_p.h> +#include <private/qv4string_p.h> +#include <private/qv4objectiterator_p.h> +#include <private/qv4identifier_p.h> +#include <private/qv4runtime_p.h> +#include <private/qqmldebugserviceinterfaces_p.h> + +#include <QtCore/qjsonarray.h> + +#include <qqmlengine.h> + +#include <QJsonArray> +#include <QJsonDocument> +#include <QJsonObject> +#include <QJsonValue> +#include <QVector> +#include <QPointer> + +QT_BEGIN_NAMESPACE + +class NativeDebugger; +class BreakPointHandler; +class QQmlDebuggerServiceFactory; + +class QQmlNativeDebugServiceImpl : public QQmlNativeDebugService +{ +public: + QQmlNativeDebugServiceImpl(QObject *parent); + + ~QQmlNativeDebugServiceImpl(); + + void engineAboutToBeAdded(QQmlEngine *engine); + void engineAboutToBeRemoved(QQmlEngine *engine); + + void stateAboutToBeChanged(State state); + + void messageReceived(const QByteArray &message); + + void emitAsynchronousMessageToClient(const QJsonObject &message); + +private: + friend class QQmlDebuggerServiceFactory; + friend class NativeDebugger; + + QList<QPointer<NativeDebugger> > m_debuggers; + BreakPointHandler *m_breakHandler; +}; + +QT_END_NAMESPACE + +#endif // QQML_NATIVE_DEBUG_SERVICE_H diff --git a/src/plugins/qmltooling/qmldbg_debugger/qv4datacollector.cpp b/src/plugins/qmltooling/qmldbg_debugger/qv4datacollector.cpp index b13c469893..377f0845d0 100644 --- a/src/plugins/qmltooling/qmldbg_debugger/qv4datacollector.cpp +++ b/src/plugins/qmltooling/qmldbg_debugger/qv4datacollector.cpp @@ -208,13 +208,13 @@ QV4DataCollector::Ref QV4DataCollector::addScriptRef(const QString &scriptName) return ref; } -void QV4DataCollector::collectScope(QJsonObject *dict, QV4::Debugging::Debugger *debugger, +void QV4DataCollector::collectScope(QJsonObject *dict, QV4::Debugging::V4Debugger *debugger, int frameNr, int scopeNr) { QStringList names; Refs refs; - if (debugger->state() == QV4::Debugging::Debugger::Paused) { + if (debugger->state() == QV4::Debugging::V4Debugger::Paused) { RefHolder holder(this, &refs); ArgumentCollectJob argumentsJob(m_engine, this, &names, frameNr, scopeNr); debugger->runInEngine(&argumentsJob); diff --git a/src/plugins/qmltooling/qmldbg_debugger/qv4datacollector.h b/src/plugins/qmltooling/qmldbg_debugger/qv4datacollector.h index 39d621e95e..0ea40f896c 100644 --- a/src/plugins/qmltooling/qmldbg_debugger/qv4datacollector.h +++ b/src/plugins/qmltooling/qmldbg_debugger/qv4datacollector.h @@ -60,7 +60,7 @@ public: Ref addFunctionRef(const QString &functionName); Ref addScriptRef(const QString &scriptName); - void collectScope(QJsonObject *dict, QV4::Debugging::Debugger *debugger, int frameNr, + void collectScope(QJsonObject *dict, QV4::Debugging::V4Debugger *debugger, int frameNr, int scopeNr); QV4::ExecutionEngine *engine() const { return m_engine; } @@ -101,7 +101,7 @@ private: QV4DataCollector::Refs *m_previousRefs; }; -class ExpressionEvalJob: public QV4::Debugging::Debugger::JavaScriptJob +class ExpressionEvalJob: public QV4::Debugging::V4Debugger::JavaScriptJob { QV4DataCollector *collector; QString exception; @@ -113,7 +113,7 @@ public: const QString &exceptionMessage() const; }; -class GatherSourcesJob: public QV4::Debugging::Debugger::Job +class GatherSourcesJob: public QV4::Debugging::V4Debugger::Job { QV4::ExecutionEngine *engine; QStringList sources; @@ -124,7 +124,7 @@ public: const QStringList &result() const; }; -class ArgumentCollectJob: public QV4::Debugging::Debugger::Job +class ArgumentCollectJob: public QV4::Debugging::V4Debugger::Job { QV4::ExecutionEngine *engine; QV4DataCollector *collector; @@ -138,7 +138,7 @@ public: void run(); }; -class LocalCollectJob: public QV4::Debugging::Debugger::Job +class LocalCollectJob: public QV4::Debugging::V4Debugger::Job { QV4::ExecutionEngine *engine; QV4DataCollector *collector; @@ -152,7 +152,7 @@ public: void run(); }; -class ThisCollectJob: public QV4::Debugging::Debugger::Job +class ThisCollectJob: public QV4::Debugging::V4Debugger::Job { QV4::ExecutionEngine *engine; QV4DataCollector *collector; @@ -166,7 +166,7 @@ public: bool myRun(); }; -class ExceptionCollectJob: public QV4::Debugging::Debugger::Job +class ExceptionCollectJob: public QV4::Debugging::V4Debugger::Job { QV4::ExecutionEngine *engine; QV4DataCollector *collector; diff --git a/src/plugins/qmltooling/qmldbg_debugger/qv4debuggeragent.cpp b/src/plugins/qmltooling/qmldbg_debugger/qv4debuggeragent.cpp index 270a6958a9..dcb40dd548 100644 --- a/src/plugins/qmltooling/qmldbg_debugger/qv4debuggeragent.cpp +++ b/src/plugins/qmltooling/qmldbg_debugger/qv4debuggeragent.cpp @@ -44,7 +44,7 @@ QV4DebuggerAgent::QV4DebuggerAgent(QV4DebugServiceImpl *debugService) : m_breakOnThrow(false), m_debugService(debugService) {} -QV4::Debugging::Debugger *QV4DebuggerAgent::firstDebugger() const +QV4::Debugging::V4Debugger *QV4DebuggerAgent::firstDebugger() const { // Currently only 1 single engine is supported, so: if (m_debuggers.isEmpty()) @@ -56,13 +56,13 @@ QV4::Debugging::Debugger *QV4DebuggerAgent::firstDebugger() const bool QV4DebuggerAgent::isRunning() const { // Currently only 1 single engine is supported, so: - if (QV4::Debugging::Debugger *debugger = firstDebugger()) - return debugger->state() == QV4::Debugging::Debugger::Running; + if (QV4::Debugging::V4Debugger *debugger = firstDebugger()) + return debugger->state() == QV4::Debugging::V4Debugger::Running; else return false; } -void QV4DebuggerAgent::debuggerPaused(QV4::Debugging::Debugger *debugger, +void QV4DebuggerAgent::debuggerPaused(QV4::Debugging::V4Debugger *debugger, QV4::Debugging::PauseReason reason) { Q_UNUSED(reason); @@ -105,7 +105,7 @@ void QV4DebuggerAgent::debuggerPaused(QV4::Debugging::Debugger *debugger, m_debugService->send(event); } -void QV4DebuggerAgent::addDebugger(QV4::Debugging::Debugger *debugger) +void QV4DebuggerAgent::addDebugger(QV4::Debugging::V4Debugger *debugger) { Q_ASSERT(!m_debuggers.contains(debugger)); m_debuggers << debugger; @@ -118,49 +118,55 @@ void QV4DebuggerAgent::addDebugger(QV4::Debugging::Debugger *debugger) connect(debugger, SIGNAL(destroyed(QObject*)), this, SLOT(handleDebuggerDeleted(QObject*))); - connect(debugger, SIGNAL(debuggerPaused(QV4::Debugging::Debugger*,QV4::Debugging::PauseReason)), - this, SLOT(debuggerPaused(QV4::Debugging::Debugger*,QV4::Debugging::PauseReason)), + connect(debugger, + SIGNAL(debuggerPaused(QV4::Debugging::V4Debugger*,QV4::Debugging::PauseReason)), + this, SLOT(debuggerPaused(QV4::Debugging::V4Debugger*,QV4::Debugging::PauseReason)), Qt::QueuedConnection); } -void QV4DebuggerAgent::removeDebugger(QV4::Debugging::Debugger *debugger) +void QV4DebuggerAgent::removeDebugger(QV4::Debugging::V4Debugger *debugger) { m_debuggers.removeAll(debugger); disconnect(debugger, SIGNAL(destroyed(QObject*)), this, SLOT(handleDebuggerDeleted(QObject*))); disconnect(debugger, - SIGNAL(debuggerPaused(QV4::Debugging::Debugger*,QV4::Debugging::PauseReason)), + SIGNAL(debuggerPaused(QV4::Debugging::V4Debugger*,QV4::Debugging::PauseReason)), this, - SLOT(debuggerPaused(QV4::Debugging::Debugger*,QV4::Debugging::PauseReason))); + SLOT(debuggerPaused(QV4::Debugging::V4Debugger*,QV4::Debugging::PauseReason))); +} + +const QList<QV4::Debugging::V4Debugger *> &QV4DebuggerAgent::debuggers() +{ + return m_debuggers; } void QV4DebuggerAgent::handleDebuggerDeleted(QObject *debugger) { - m_debuggers.removeAll(static_cast<QV4::Debugging::Debugger *>(debugger)); + m_debuggers.removeAll(static_cast<QV4::Debugging::V4Debugger *>(debugger)); } -void QV4DebuggerAgent::pause(QV4::Debugging::Debugger *debugger) const +void QV4DebuggerAgent::pause(QV4::Debugging::V4Debugger *debugger) const { debugger->pause(); } void QV4DebuggerAgent::pauseAll() const { - foreach (QV4::Debugging::Debugger *debugger, m_debuggers) + foreach (QV4::Debugging::V4Debugger *debugger, m_debuggers) pause(debugger); } void QV4DebuggerAgent::resumeAll() const { - foreach (QV4::Debugging::Debugger *debugger, m_debuggers) - if (debugger->state() == QV4::Debugging::Debugger::Paused) - debugger->resume(QV4::Debugging::Debugger::FullThrottle); + foreach (QV4::Debugging::V4Debugger *debugger, m_debuggers) + if (debugger->state() == QV4::Debugging::V4Debugger::Paused) + debugger->resume(QV4::Debugging::V4Debugger::FullThrottle); } int QV4DebuggerAgent::addBreakPoint(const QString &fileName, int lineNumber, bool enabled, const QString &condition) { if (enabled) - foreach (QV4::Debugging::Debugger *debugger, m_debuggers) + foreach (QV4::Debugging::V4Debugger *debugger, m_debuggers) debugger->addBreakPoint(fileName, lineNumber, condition); int id = m_breakPoints.size(); @@ -177,7 +183,7 @@ void QV4DebuggerAgent::removeBreakPoint(int id) m_breakPoints.remove(id); if (breakPoint.enabled) - foreach (QV4::Debugging::Debugger *debugger, m_debuggers) + foreach (QV4::Debugging::V4Debugger *debugger, m_debuggers) debugger->removeBreakPoint(breakPoint.fileName, breakPoint.lineNr); } @@ -195,7 +201,7 @@ void QV4DebuggerAgent::enableBreakPoint(int id, bool onoff) return; breakPoint.enabled = onoff; - foreach (QV4::Debugging::Debugger *debugger, m_debuggers) { + foreach (QV4::Debugging::V4Debugger *debugger, m_debuggers) { if (onoff) debugger->addBreakPoint(breakPoint.fileName, breakPoint.lineNr, breakPoint.condition); else @@ -218,7 +224,7 @@ void QV4DebuggerAgent::setBreakOnThrow(bool onoff) { if (onoff != m_breakOnThrow) { m_breakOnThrow = onoff; - foreach (QV4::Debugging::Debugger *debugger, m_debuggers) + foreach (QV4::Debugging::V4Debugger *debugger, m_debuggers) debugger->setBreakOnThrow(onoff); } } diff --git a/src/plugins/qmltooling/qmldbg_debugger/qv4debuggeragent.h b/src/plugins/qmltooling/qmldbg_debugger/qv4debuggeragent.h index 64cf0e3d60..9f77a17b45 100644 --- a/src/plugins/qmltooling/qmldbg_debugger/qv4debuggeragent.h +++ b/src/plugins/qmltooling/qmldbg_debugger/qv4debuggeragent.h @@ -46,13 +46,14 @@ class QV4DebuggerAgent : public QObject public: QV4DebuggerAgent(QV4DebugServiceImpl *m_debugService); - QV4::Debugging::Debugger *firstDebugger() const; + QV4::Debugging::V4Debugger *firstDebugger() const; bool isRunning() const; - void addDebugger(QV4::Debugging::Debugger *debugger); - void removeDebugger(QV4::Debugging::Debugger *debugger); + void addDebugger(QV4::Debugging::V4Debugger *debugger); + void removeDebugger(QV4::Debugging::V4Debugger *debugger); + const QList<QV4::Debugging::V4Debugger *> &debuggers(); - void pause(QV4::Debugging::Debugger *debugger) const; + void pause(QV4::Debugging::V4Debugger *debugger) const; void pauseAll() const; void resumeAll() const; int addBreakPoint(const QString &fileName, int lineNumber, bool enabled = true, const QString &condition = QString()); @@ -65,11 +66,11 @@ public: void setBreakOnThrow(bool onoff); public slots: - void debuggerPaused(QV4::Debugging::Debugger *debugger, QV4::Debugging::PauseReason reason); + void debuggerPaused(QV4::Debugging::V4Debugger *debugger, QV4::Debugging::PauseReason reason); void handleDebuggerDeleted(QObject *debugger); private: - QList<QV4::Debugging::Debugger *> m_debuggers; + QList<QV4::Debugging::V4Debugger *> m_debuggers; struct BreakPoint { QString fileName; diff --git a/src/plugins/qmltooling/qmldbg_debugger/qv4debugservice.cpp b/src/plugins/qmltooling/qmldbg_debugger/qv4debugservice.cpp index 14333e01f6..f742502e2a 100644 --- a/src/plugins/qmltooling/qmldbg_debugger/qv4debugservice.cpp +++ b/src/plugins/qmltooling/qmldbg_debugger/qv4debugservice.cpp @@ -34,6 +34,7 @@ #include "qv4debugservice.h" #include "qqmlengine.h" #include <private/qv4engine_p.h> +#include <private/qv4isel_moth_p.h> #include <private/qv4function_p.h> #include <private/qqmldebugconnector_p.h> #include <private/qpacket_p.h> @@ -269,7 +270,7 @@ public: int toFrame = arguments.value(QStringLiteral("toFrame")).toInt(fromFrame + 10); // no idea what the bottom property is for, so we'll ignore it. - QV4::Debugging::Debugger *debugger = debugService->debuggerAgent.firstDebugger(); + QV4::Debugging::V4Debugger *debugger = debugService->debuggerAgent.firstDebugger(); QJsonArray frameArray; QVector<QV4::StackFrame> frames = debugger->stackTrace(toFrame); @@ -306,7 +307,7 @@ public: const int frameNr = arguments.value(QStringLiteral("number")).toInt( debugService->selectedFrame()); - QV4::Debugging::Debugger *debugger = debugService->debuggerAgent.firstDebugger(); + QV4::Debugging::V4Debugger *debugger = debugService->debuggerAgent.firstDebugger(); QVector<QV4::StackFrame> frames = debugger->stackTrace(frameNr + 1); if (frameNr < 0 || frameNr >= frames.size()) { createErrorResponse(QStringLiteral("frame command has invalid frame number")); @@ -339,7 +340,7 @@ public: debugService->selectedFrame()); const int scopeNr = arguments.value(QStringLiteral("number")).toInt(0); - QV4::Debugging::Debugger *debugger = debugService->debuggerAgent.firstDebugger(); + QV4::Debugging::V4Debugger *debugger = debugService->debuggerAgent.firstDebugger(); QVector<QV4::StackFrame> frames = debugger->stackTrace(frameNr + 1); if (frameNr < 0 || frameNr >= frames.size()) { createErrorResponse(QStringLiteral("scope command has invalid frame number")); @@ -397,10 +398,10 @@ public: // decypher the payload: QJsonObject arguments = req.value(QStringLiteral("arguments")).toObject(); - QV4::Debugging::Debugger *debugger = debugService->debuggerAgent.firstDebugger(); + QV4::Debugging::V4Debugger *debugger = debugService->debuggerAgent.firstDebugger(); if (arguments.empty()) { - debugger->resume(QV4::Debugging::Debugger::FullThrottle); + debugger->resume(QV4::Debugging::V4Debugger::FullThrottle); } else { QJsonObject arguments = req.value(QStringLiteral("arguments")).toObject(); QString stepAction = arguments.value(QStringLiteral("stepaction")).toString(); @@ -409,11 +410,11 @@ public: qWarning() << "Step count other than 1 is not supported."; if (stepAction == QStringLiteral("in")) { - debugger->resume(QV4::Debugging::Debugger::StepIn); + debugger->resume(QV4::Debugging::V4Debugger::StepIn); } else if (stepAction == QStringLiteral("out")) { - debugger->resume(QV4::Debugging::Debugger::StepOut); + debugger->resume(QV4::Debugging::V4Debugger::StepOut); } else if (stepAction == QStringLiteral("next")) { - debugger->resume(QV4::Debugging::Debugger::StepOver); + debugger->resume(QV4::Debugging::V4Debugger::StepOver); } else { createErrorResponse(QStringLiteral("continue command has invalid stepaction")); return; @@ -505,7 +506,7 @@ public: } // do it: - QV4::Debugging::Debugger *debugger = debugService->debuggerAgent.firstDebugger(); + QV4::Debugging::V4Debugger *debugger = debugService->debuggerAgent.firstDebugger(); GatherSourcesJob job(debugger->engine()); debugger->runInEngine(&job); @@ -560,8 +561,8 @@ public: virtual void handleRequest() { - QV4::Debugging::Debugger *debugger = debugService->debuggerAgent.firstDebugger(); - if (debugger->state() == QV4::Debugging::Debugger::Paused) { + QV4::Debugging::V4Debugger *debugger = debugService->debuggerAgent.firstDebugger(); + if (debugger->state() == QV4::Debugging::V4Debugger::Paused) { QJsonObject arguments = req.value(QStringLiteral("arguments")).toObject(); QString expression = arguments.value(QStringLiteral("expression")).toString(); const int frame = arguments.value(QStringLiteral("frame")).toInt(0); @@ -632,8 +633,11 @@ void QV4DebugServiceImpl::engineAboutToBeAdded(QQmlEngine *engine) QV4::ExecutionEngine *ee = QV8Engine::getV4(engine->handle()); if (QQmlDebugConnector *server = QQmlDebugConnector::instance()) { if (ee) { - ee->enableDebugger(); - debuggerAgent.addDebugger(ee->debugger); + ee->iselFactory.reset(new QV4::Moth::ISelFactory); + QV4::Debugging::V4Debugger *debugger = new QV4::Debugging::V4Debugger(ee); + if (state() == Enabled) + ee->setDebugger(debugger); + debuggerAgent.addDebugger(debugger); debuggerAgent.moveToThread(server->thread()); } } @@ -646,12 +650,29 @@ void QV4DebugServiceImpl::engineAboutToBeRemoved(QQmlEngine *engine) QMutexLocker lock(&m_configMutex); if (engine){ const QV4::ExecutionEngine *ee = QV8Engine::getV4(engine->handle()); - if (ee) - debuggerAgent.removeDebugger(ee->debugger); + if (ee) { + QV4::Debugging::V4Debugger *debugger + = qobject_cast<QV4::Debugging::V4Debugger *>(ee->debugger); + if (debugger) + debuggerAgent.removeDebugger(debugger); + } } QQmlConfigurableDebugService<QV4DebugService>::engineAboutToBeRemoved(engine); } +void QV4DebugServiceImpl::stateAboutToBeChanged(State state) +{ + QMutexLocker lock(&m_configMutex); + if (state == Enabled) { + foreach (QV4::Debugging::V4Debugger *debugger, debuggerAgent.debuggers()) { + QV4::ExecutionEngine *ee = debugger->engine(); + if (!ee->debugger) + ee->setDebugger(debugger); + } + } + QQmlConfigurableDebugService<QV4DebugService>::stateAboutToBeChanged(state); +} + void QV4DebugServiceImpl::signalEmitted(const QString &signal) { //This function is only called by QQmlBoundSignal @@ -766,7 +787,7 @@ void QV4DebugServiceImpl::clearHandles(QV4::ExecutionEngine *engine) } QJsonObject QV4DebugServiceImpl::buildFrame(const QV4::StackFrame &stackFrame, int frameNr, - QV4::Debugging::Debugger *debugger) + QV4::Debugging::V4Debugger *debugger) { QV4DataCollector::Ref ref; @@ -784,7 +805,7 @@ QJsonObject QV4DebugServiceImpl::buildFrame(const QV4::StackFrame &stackFrame, i frame[QLatin1String("column")] = stackFrame.column; QJsonArray scopes; - if (debugger->state() == QV4::Debugging::Debugger::Paused) { + if (debugger->state() == QV4::Debugging::V4Debugger::Paused) { RefHolder holder(theCollector.data(), &collectedRefs); bool foundThis = false; ThisCollectJob job(debugger->engine(), theCollector.data(), frameNr, &foundThis); @@ -834,7 +855,7 @@ int QV4DebugServiceImpl::encodeScopeType(QV4::Heap::ExecutionContext::ContextTyp } QJsonObject QV4DebugServiceImpl::buildScope(int frameNr, int scopeNr, - QV4::Debugging::Debugger *debugger) + QV4::Debugging::V4Debugger *debugger) { QJsonObject scope; @@ -842,7 +863,7 @@ QJsonObject QV4DebugServiceImpl::buildScope(int frameNr, int scopeNr, RefHolder holder(theCollector.data(), &collectedRefs); theCollector->collectScope(&object, debugger, frameNr, scopeNr); - if (debugger->state() == QV4::Debugging::Debugger::Paused) { + if (debugger->state() == QV4::Debugging::V4Debugger::Paused) { QVector<QV4::Heap::ExecutionContext::ContextType> scopeTypes = QV4DataCollector::getScopeTypes(debugger->engine(), frameNr); scope[QLatin1String("type")] = encodeScopeType(scopeTypes[scopeNr]); diff --git a/src/plugins/qmltooling/qmldbg_debugger/qv4debugservice.h b/src/plugins/qmltooling/qmldbg_debugger/qv4debugservice.h index dbf41ac363..ea4a695fed 100644 --- a/src/plugins/qmltooling/qmldbg_debugger/qv4debugservice.h +++ b/src/plugins/qmltooling/qmldbg_debugger/qv4debugservice.h @@ -73,16 +73,18 @@ public: void engineAboutToBeAdded(QQmlEngine *engine); void engineAboutToBeRemoved(QQmlEngine *engine); + void stateAboutToBeChanged(State state); + void signalEmitted(const QString &signal); void send(QJsonObject v8Payload); - QJsonObject buildScope(int frameNr, int scopeNr, QV4::Debugging::Debugger *debugger); + QJsonObject buildScope(int frameNr, int scopeNr, QV4::Debugging::V4Debugger *debugger); QJsonArray buildRefs(); QJsonValue lookup(QV4DataCollector::Ref refId); QJsonValue toRef(QV4DataCollector::Ref ref); QJsonObject buildFrame(const QV4::StackFrame &stackFrame, int frameNr, - QV4::Debugging::Debugger *debugger); + QV4::Debugging::V4Debugger *debugger); int selectedFrame() const; void selectFrame(int frameNr); diff --git a/src/plugins/qmltooling/qmldbg_native/qmldbg_native.pro b/src/plugins/qmltooling/qmldbg_native/qmldbg_native.pro new file mode 100644 index 0000000000..7dc16b8c44 --- /dev/null +++ b/src/plugins/qmltooling/qmldbg_native/qmldbg_native.pro @@ -0,0 +1,12 @@ +TARGET = qmldbg_native +QT += qml-private core-private + +PLUGIN_TYPE = qmltooling +PLUGIN_CLASS_NAME = QQmlNativeDebugConnectorFactory +load(qt_plugin) + +SOURCES += \ + $$PWD/qqmlnativedebugconnector.cpp + +OTHER_FILES += \ + $$PWD/qqmlnativedebugconnector.json diff --git a/src/plugins/qmltooling/qmldbg_native/qqmlnativedebugconnector.cpp b/src/plugins/qmltooling/qmldbg_native/qqmlnativedebugconnector.cpp new file mode 100644 index 0000000000..018b10d3e7 --- /dev/null +++ b/src/plugins/qmltooling/qmldbg_native/qqmlnativedebugconnector.cpp @@ -0,0 +1,384 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <private/qqmldebugconnector_p.h> +#include <private/qhooks_p.h> +#include <private/qpacket_p.h> + +#include <qqmlengine.h> + +#include <QtCore/qdebug.h> +#include <QtCore/qjsonarray.h> +#include <QtCore/qjsondocument.h> +#include <QtCore/qjsonobject.h> +#include <QtCore/qjsonvalue.h> +#include <QtCore/qpointer.h> +#include <QtCore/qvector.h> + +//#define TRACE_PROTOCOL(s) qDebug() << s +#define TRACE_PROTOCOL(s) + +QT_USE_NAMESPACE + +static bool expectSyncronousResponse = false; +Q_GLOBAL_STATIC(QByteArray, responseBuffer) + +extern "C" { + +Q_DECL_EXPORT const char *qt_qmlDebugMessageBuffer; +Q_DECL_EXPORT int qt_qmlDebugMessageLength; +Q_DECL_EXPORT bool qt_qmlDebugConnectionBlocker; + +// In blocking mode, this will busy wait until the debugger sets block to false. +Q_DECL_EXPORT void qt_qmlDebugConnectorOpen(); + +// First thing, set the debug stream version. Please use this function as we might move the version +// member to some other place. +Q_DECL_EXPORT void qt_qmlDebugSetStreamVersion(int version) +{ + QPacket::setDataStreamVersion(version); +} + + +// Break in this one to process output from an asynchronous message/ +Q_DECL_EXPORT void qt_qmlDebugMessageAvailable() +{ +} + + +// Break in this one to get notified about construction and destruction of +// interesting objects, such as QmlEngines. +Q_DECL_EXPORT void qt_qmlDebugObjectAvailable() +{ +} + +Q_DECL_EXPORT void qt_qmlDebugClearBuffer() +{ + responseBuffer->clear(); +} + +// Send a message to a service. +Q_DECL_EXPORT bool qt_qmlDebugSendDataToService(const char *serviceName, const char *hexData) +{ + QByteArray msg = QByteArray::fromHex(hexData); + + QQmlDebugConnector *instance = QQmlDebugConnector::instance(); + if (!instance) + return false; + + QQmlDebugService *recipient = instance->service(serviceName); + if (!recipient) + return false; + + TRACE_PROTOCOL("Recipient: " << recipient << " got message: " << msg); + expectSyncronousResponse = true; + recipient->messageReceived(msg); + expectSyncronousResponse = false; + + return true; +} + +// Enable a service. +Q_DECL_EXPORT bool qt_qmlDebugEnableService(const char *data) +{ + QQmlDebugConnector *instance = QQmlDebugConnector::instance(); + if (!instance) + return false; + + QString name = QString::fromLatin1(data); + QQmlDebugService *service = instance->service(name); + if (!service || service->state() == QQmlDebugService::Enabled) + return false; + + service->stateAboutToBeChanged(QQmlDebugService::Enabled); + service->setState(QQmlDebugService::Enabled); + service->stateChanged(QQmlDebugService::Enabled); + return true; +} + +Q_DECL_EXPORT bool qt_qmlDebugDisableService(const char *data) +{ + QQmlDebugConnector *instance = QQmlDebugConnector::instance(); + if (!instance) + return false; + + QString name = QString::fromLatin1(data); + QQmlDebugService *service = instance->service(name); + if (!service || service->state() == QQmlDebugService::Unavailable) + return false; + + service->stateAboutToBeChanged(QQmlDebugService::Unavailable); + service->setState(QQmlDebugService::Unavailable); + service->stateChanged(QQmlDebugService::Unavailable); + return true; +} + +quintptr qt_qmlDebugTestHooks[] = { + quintptr(1), // Internal Version + quintptr(6), // Number of entries following + quintptr(&qt_qmlDebugMessageBuffer), + quintptr(&qt_qmlDebugMessageLength), + quintptr(&qt_qmlDebugSendDataToService), + quintptr(&qt_qmlDebugEnableService), + quintptr(&qt_qmlDebugDisableService), + quintptr(&qt_qmlDebugObjectAvailable) +}; + +// In blocking mode, this will busy wait until the debugger sets block to false. +Q_DECL_EXPORT void qt_qmlDebugConnectorOpen() +{ + TRACE_PROTOCOL("Opening native debug connector"); + + // FIXME: Use a dedicated hook. Startup is a safe workaround, though, + // as we are already beyond its only use. + qtHookData[QHooks::Startup] = quintptr(&qt_qmlDebugTestHooks); + + while (qt_qmlDebugConnectionBlocker) + ; + + TRACE_PROTOCOL("Opened native debug connector"); +} + +} // extern "C" + +QT_BEGIN_NAMESPACE + +class QQmlNativeDebugConnector : public QQmlDebugConnector +{ + Q_OBJECT + +public: + QQmlNativeDebugConnector(); + ~QQmlNativeDebugConnector(); + + bool blockingMode() const; + QQmlDebugService *service(const QString &name) const; + void addEngine(QQmlEngine *engine); + void removeEngine(QQmlEngine *engine); + bool addService(const QString &name, QQmlDebugService *service); + bool removeService(const QString &name); + bool open(const QVariantHash &configuration); + +private slots: + void sendMessage(const QString &name, const QByteArray &message); + void sendMessages(const QString &name, const QList<QByteArray> &messages); + +private: + void announceObjectAvailability(const QString &objectType, QObject *object, bool available); + + QVector<QQmlDebugService *> m_services; + bool m_blockingMode; +}; + +QQmlNativeDebugConnector::QQmlNativeDebugConnector() + : m_blockingMode(false) +{ + const QString args = commandLineArguments(); + const QStringList lstjsDebugArguments = args.split(QLatin1Char(',')); + QStringList services; + QStringList::const_iterator argsItEnd = lstjsDebugArguments.cend(); + QStringList::const_iterator argsIt = lstjsDebugArguments.cbegin(); + for (; argsIt != argsItEnd; ++argsIt) { + const QString strArgument = *argsIt; + if (strArgument == QLatin1String("block")) { + m_blockingMode = true; + } else if (strArgument == QLatin1String("native")) { + // Ignore. This is used to signal that this connector + // should be loaded and that has already happened. + } else if (strArgument.startsWith(QLatin1String("services:"))) { + services.append(strArgument.mid(9)); + } else if (!services.isEmpty()) { + services.append(strArgument); + } else { + qWarning("QML Debugger: Invalid argument \"%s\" detected. Ignoring the same.", + qUtf8Printable(strArgument)); + } + } + setServices(services); +} + +QQmlNativeDebugConnector::~QQmlNativeDebugConnector() +{ + foreach (QQmlDebugService *service, m_services) { + service->stateAboutToBeChanged(QQmlDebugService::NotConnected); + service->setState(QQmlDebugService::NotConnected); + service->stateChanged(QQmlDebugService::NotConnected); + } +} + +bool QQmlNativeDebugConnector::blockingMode() const +{ + return m_blockingMode; +} + +QQmlDebugService *QQmlNativeDebugConnector::service(const QString &name) const +{ + for (QVector<QQmlDebugService *>::ConstIterator i = m_services.begin(); i != m_services.end(); + ++i) { + if ((*i)->name() == name) + return *i; + } + return 0; +} + +void QQmlNativeDebugConnector::addEngine(QQmlEngine *engine) +{ + TRACE_PROTOCOL("Add engine to connector:" << engine); + foreach (QQmlDebugService *service, m_services) + service->engineAboutToBeAdded(engine); + + announceObjectAvailability(QLatin1String("qmlengine"), engine, true); + + foreach (QQmlDebugService *service, m_services) + service->engineAdded(engine); +} + +void QQmlNativeDebugConnector::removeEngine(QQmlEngine *engine) +{ + TRACE_PROTOCOL("Remove engine from connector:" << engine); + foreach (QQmlDebugService *service, m_services) + service->engineAboutToBeRemoved(engine); + + announceObjectAvailability(QLatin1String("qmlengine"), engine, false); + + foreach (QQmlDebugService *service, m_services) + service->engineRemoved(engine); +} + +void QQmlNativeDebugConnector::announceObjectAvailability(const QString &objectType, + QObject *object, bool available) +{ + QJsonObject ob; + ob.insert(QLatin1String("objecttype"), objectType); + ob.insert(QLatin1String("object"), QString::number(quintptr(object))); + ob.insert(QLatin1String("available"), available); + QJsonDocument doc; + doc.setObject(ob); + + QByteArray ba = doc.toJson(QJsonDocument::Compact); + qt_qmlDebugMessageBuffer = ba.constData(); + qt_qmlDebugMessageLength = ba.size(); + TRACE_PROTOCOL("Reporting engine availabilty"); + qt_qmlDebugObjectAvailable(); // Trigger native breakpoint. +} + +bool QQmlNativeDebugConnector::addService(const QString &name, QQmlDebugService *service) +{ + TRACE_PROTOCOL("Add service to connector: " << qPrintable(name) << service); + for (QVector<QQmlDebugService *>::ConstIterator i = m_services.begin(); i != m_services.end(); + ++i) { + if ((*i)->name() == name) + return false; + } + + connect(service, &QQmlDebugService::messageToClient, + this, &QQmlNativeDebugConnector::sendMessage); + connect(service, &QQmlDebugService::messagesToClient, + this, &QQmlNativeDebugConnector::sendMessages); + + service->setState(QQmlDebugService::Unavailable); + + m_services << service; + return true; +} + +bool QQmlNativeDebugConnector::removeService(const QString &name) +{ + for (QVector<QQmlDebugService *>::Iterator i = m_services.begin(); i != m_services.end(); ++i) { + if ((*i)->name() == name) { + QQmlDebugService *service = *i; + m_services.erase(i); + service->setState(QQmlDebugService::NotConnected); + + disconnect(service, &QQmlDebugService::messagesToClient, + this, &QQmlNativeDebugConnector::sendMessages); + disconnect(service, &QQmlDebugService::messageToClient, + this, &QQmlNativeDebugConnector::sendMessage); + + return true; + } + } + return false; +} + +bool QQmlNativeDebugConnector::open(const QVariantHash &configuration) +{ + m_blockingMode = configuration.value(QStringLiteral("block"), m_blockingMode).toBool(); + qt_qmlDebugConnectionBlocker = m_blockingMode; + qt_qmlDebugConnectorOpen(); + return true; +} + +void QQmlNativeDebugConnector::sendMessage(const QString &name, const QByteArray &message) +{ + (*responseBuffer) += name.toUtf8() + ' ' + QByteArray::number(message.size()) + ' ' + message; + qt_qmlDebugMessageBuffer = responseBuffer->constData(); + qt_qmlDebugMessageLength = responseBuffer->size(); + // Responses are allowed to accumulate, the buffer will be cleared by + // separate calls to qt_qmlDebugClearBuffer() once the synchronous + // function return ('if' branch below) or in the native breakpoint handler + // ('else' branch below). + if (expectSyncronousResponse) { + TRACE_PROTOCOL("Expected synchronous response in " << message); + // Do not trigger the native breakpoint on qt_qmlDebugMessageFromService. + } else { + TRACE_PROTOCOL("Found asynchronous message in " << message); + // Trigger native breakpoint. + qt_qmlDebugMessageAvailable(); + } +} + +void QQmlNativeDebugConnector::sendMessages(const QString &name, const QList<QByteArray> &messages) +{ + for (int i = 0; i != messages.size(); ++i) + sendMessage(name, messages.at(i)); +} + +class QQmlNativeDebugConnectorFactory : public QQmlDebugConnectorFactory +{ + Q_OBJECT + + Q_PLUGIN_METADATA(IID QQmlDebugConnectorFactory_iid FILE "qqmlnativedebugconnector.json") + +public: + QQmlNativeDebugConnectorFactory() {} + + QQmlDebugConnector *create(const QString &key) + { + return key == QLatin1String("QQmlNativeDebugConnector") ? new QQmlNativeDebugConnector : 0; + } +}; + +QT_END_NAMESPACE + +#include "qqmlnativedebugconnector.moc" diff --git a/src/plugins/qmltooling/qmldbg_native/qqmlnativedebugconnector.json b/src/plugins/qmltooling/qmldbg_native/qqmlnativedebugconnector.json new file mode 100644 index 0000000000..925e6a665c --- /dev/null +++ b/src/plugins/qmltooling/qmldbg_native/qqmlnativedebugconnector.json @@ -0,0 +1,3 @@ +{ + "Keys": [ "QQmlNativeDebugConnector" ] +} diff --git a/src/plugins/qmltooling/qmltooling.pro b/src/plugins/qmltooling/qmltooling.pro index 75da89f3e8..5b39747674 100644 --- a/src/plugins/qmltooling/qmltooling.pro +++ b/src/plugins/qmltooling/qmltooling.pro @@ -2,6 +2,7 @@ TEMPLATE = subdirs # Connectors SUBDIRS += \ + qmldbg_native \ qmldbg_server \ qmldbg_local \ qmldbg_tcp |