From 0cbda65924ff5440bf283aa7a988f335bba4e660 Mon Sep 17 00:00:00 2001 From: Ulf Hermann Date: Fri, 26 Aug 2016 14:33:51 +0200 Subject: QmlTooling: Move native debug service into own plugin QV4DebugService and QQmlNativeDebugService cannot coexist at the same time. Thus, there is no point in putting them into one plugin. Change-Id: Ic042f7472fce23d504c62ccf96756b0f1bd68534 Reviewed-by: Simon Hausmann --- .../qmltooling/qmldbg_debugger/qmldbg_debugger.pro | 2 - .../qmldbg_debugger/qqmldebuggerservice.json | 2 +- .../qmldbg_debugger/qqmldebuggerservicefactory.cpp | 4 - .../qmldbg_debugger/qqmlnativedebugservice.cpp | 807 --------------------- .../qmldbg_debugger/qqmlnativedebugservice.h | 98 --- .../qmldbg_nativedebugger.pro | 21 + .../qqmlnativedebugservice.cpp | 807 +++++++++++++++++++++ .../qmldbg_nativedebugger/qqmlnativedebugservice.h | 96 +++ .../qqmlnativedebugservice.json | 3 + .../qqmlnativedebugservicefactory.cpp | 54 ++ .../qqmlnativedebugservicefactory.h | 57 ++ src/plugins/qmltooling/qmltooling.pro | 4 +- src/qml/debugger/qqmldebugserviceinterfaces_p.h | 4 +- 13 files changed, 1044 insertions(+), 915 deletions(-) delete mode 100644 src/plugins/qmltooling/qmldbg_debugger/qqmlnativedebugservice.cpp delete mode 100644 src/plugins/qmltooling/qmldbg_debugger/qqmlnativedebugservice.h create mode 100644 src/plugins/qmltooling/qmldbg_nativedebugger/qmldbg_nativedebugger.pro create mode 100644 src/plugins/qmltooling/qmldbg_nativedebugger/qqmlnativedebugservice.cpp create mode 100644 src/plugins/qmltooling/qmldbg_nativedebugger/qqmlnativedebugservice.h create mode 100644 src/plugins/qmltooling/qmldbg_nativedebugger/qqmlnativedebugservice.json create mode 100644 src/plugins/qmltooling/qmldbg_nativedebugger/qqmlnativedebugservicefactory.cpp create mode 100644 src/plugins/qmltooling/qmldbg_nativedebugger/qqmlnativedebugservicefactory.h diff --git a/src/plugins/qmltooling/qmldbg_debugger/qmldbg_debugger.pro b/src/plugins/qmltooling/qmldbg_debugger/qmldbg_debugger.pro index 820b8bbfec..f3f8a21ff8 100644 --- a/src/plugins/qmltooling/qmldbg_debugger/qmldbg_debugger.pro +++ b/src/plugins/qmltooling/qmldbg_debugger/qmldbg_debugger.pro @@ -4,7 +4,6 @@ QT = qml-private core-private packetprotocol-private SOURCES += \ $$PWD/qqmldebuggerservicefactory.cpp \ $$PWD/qqmlenginedebugservice.cpp \ - $$PWD/qqmlnativedebugservice.cpp \ $$PWD/qqmlwatcher.cpp \ $$PWD/qv4debugservice.cpp \ $$PWD/qv4debugger.cpp \ @@ -17,7 +16,6 @@ HEADERS += \ $$PWD/../shared/qqmldebugpacket.h \ $$PWD/qqmldebuggerservicefactory.h \ $$PWD/qqmlenginedebugservice.h \ - $$PWD/qqmlnativedebugservice.h \ $$PWD/qqmlwatcher.h \ $$PWD/qv4debugservice.h \ $$PWD/qv4debugger.h \ diff --git a/src/plugins/qmltooling/qmldbg_debugger/qqmldebuggerservice.json b/src/plugins/qmltooling/qmldbg_debugger/qqmldebuggerservice.json index b1152e2a31..442d7781a1 100644 --- a/src/plugins/qmltooling/qmldbg_debugger/qqmldebuggerservice.json +++ b/src/plugins/qmltooling/qmldbg_debugger/qqmldebuggerservice.json @@ -1,3 +1,3 @@ { - "Keys": [ "QmlDebugger", "V8Debugger", "NativeQmlDebugger" ] + "Keys": [ "QmlDebugger", "V8Debugger" ] } diff --git a/src/plugins/qmltooling/qmldbg_debugger/qqmldebuggerservicefactory.cpp b/src/plugins/qmltooling/qmldbg_debugger/qqmldebuggerservicefactory.cpp index 7b1ffb7ccc..9315adf4ce 100644 --- a/src/plugins/qmltooling/qmldbg_debugger/qqmldebuggerservicefactory.cpp +++ b/src/plugins/qmltooling/qmldbg_debugger/qqmldebuggerservicefactory.cpp @@ -40,7 +40,6 @@ #include "qqmldebuggerservicefactory.h" #include "qqmlenginedebugservice.h" #include "qv4debugservice.h" -#include "qqmlnativedebugservice.h" #include QT_BEGIN_NAMESPACE @@ -53,9 +52,6 @@ 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 deleted file mode 100644 index 9c198a8afc..0000000000 --- a/src/plugins/qmltooling/qmldbg_debugger/qqmlnativedebugservice.cpp +++ /dev/null @@ -1,807 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the QtQml module of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:LGPL$ -** 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 https://www.qt.io/terms-conditions. For further -** information use the contact form at https://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 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or (at your option) the GNU General -** Public license version 3 or any later version approved by the KDE Free -** Qt Foundation. The licenses are as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-2.0.html and -** https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -#include "qqmlnativedebugservice.h" -#include "qqmldebugpacket.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include - -//#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 m_breakPoints; -}; - -void BreakPointHandler::handleSetBreakpoint(QJsonObject *response, const QJsonObject &arguments) -{ - TRACE_PROTOCOL("SET BREAKPOINT" << arguments); - QString type = arguments.value(QLatin1String("type")).toString(); - - QString fileName = arguments.value(QLatin1String("file")).toString(); - if (fileName.isEmpty()) { - setError(response, QStringLiteral("breakpoint has no file name")); - return; - } - - int line = arguments.value(QLatin1String("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(QLatin1String("enabled")).toBool(true); - bp.condition = arguments.value(QLatin1String("condition")).toString(); - bp.ignoreCount = arguments.value(QLatin1String("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(QLatin1String("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); - - void 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); - evaluateExpression(scope, expression); - return scope.result.booleanValue(); -} - -void 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(); - if (!m_engine->hasException) - scope.result = script.run(); - - m_runningJob = false; -} - -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(); - - for (const QString &signal : qAsConst(breakOnSignals)) { - if (signal == signalName) { - // TODO: pause debugger - break; - } - } -} - -void NativeDebugger::handleCommand(QJsonObject *response, const QString &cmd, - const QJsonObject &arguments) -{ - if (cmd == QLatin1String("backtrace")) - handleBacktrace(response, arguments); - else if (cmd == QLatin1String("variables")) - handleVariables(response, arguments); - else if (cmd == QLatin1String("expressions")) - handleExpressions(response, arguments); - else if (cmd == QLatin1String("stepin")) - handleContinue(response, StepIn); - else if (cmd == QLatin1String("stepout")) - handleContinue(response, StepOut); - else if (cmd == QLatin1String("stepover")) - handleContinue(response, StepOver); - else if (cmd == QLatin1String("continue")) - handleContinue(response, NotStepping); -} - -static QString encodeContext(QV4::ExecutionContext *executionContext) -{ - QQmlDebugPacket ds; - ds << quintptr(executionContext); - return QString::fromLatin1(ds.data().toHex()); -} - -static void decodeContext(const QString &context, QV4::ExecutionContext **executionContext) -{ - quintptr rawContext; - QQmlDebugPacket ds(QByteArray::fromHex(context.toLatin1())); - ds >> rawContext; - *executionContext = reinterpret_cast(rawContext); -} - -void NativeDebugger::handleBacktrace(QJsonObject *response, const QJsonObject &arguments) -{ - int limit = arguments.value(QLatin1String("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.insert(QStringLiteral("language"), QStringLiteral("js")); - frame.insert(QStringLiteral("context"), encodeContext(executionContext)); - - if (QV4::Function *function = heapFunctionObject->function) { - if (QV4::Heap::String *functionName = function->name()) - frame.insert(QStringLiteral("function"), functionName->toQString()); - frame.insert(QStringLiteral("file"), function->sourceFile()); - } - int line = executionContext->d()->lineNumber; - frame.insert(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::method_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()) { - 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()) { - // 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()) { - 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(QLatin1String("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); - const QJsonArray expanded = arguments.value(QLatin1String("expanded")).toArray(); - for (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(QLatin1String("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); - const QJsonArray expanded = arguments.value(QLatin1String("expanded")).toArray(); - for (const QJsonValue &ex : expanded) - collector.m_expanded.append(ex.toString()); - TRACE_PROTOCOL("Expanded: " << collector.m_expanded); - - QJsonArray output; - QV4::Scope scope(engine); - - const QJsonArray expressions = arguments.value(QLatin1String("expressions")).toArray(); - for (const QJsonValue &expr : expressions) { - QString expression = expr.toObject().value(QLatin1String("expression")).toString(); - QString name = expr.toObject().value(QLatin1String("name")).toString(); - TRACE_PROTOCOL("Evaluate expression: " << expression); - m_runningJob = true; - - evaluateExpression(scope, expression); - QV4::ScopedValue result(scope, scope.result); - - m_runningJob = false; - if (result->isUndefined()) { - QJsonObject dict; - dict.insert(QStringLiteral("name"), name); - dict.insert(QStringLiteral("valueencoded"), QStringLiteral("undefined")); - output.append(dict); - } else if (result.ptr && result.ptr->rawValue()) { - collector.collect(&output, QString(), name, *result); - } else { - QJsonObject dict; - dict.insert(QStringLiteral("name"), name); - dict.insert(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 QStringRef base = fileName.midRef(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(QJSEngine *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(debugger)); - } - } - QQmlDebugService::engineAboutToBeAdded(engine); -} - -void QQmlNativeDebugServiceImpl::engineAboutToBeRemoved(QJSEngine *engine) -{ - TRACE_PROTOCOL("Removing engine" << engine); - if (engine) { - QV4::ExecutionEngine *executionEngine = QV8Engine::getV4(engine->handle()); - const auto debuggersCopy = m_debuggers; - for (NativeDebugger *debugger : debuggersCopy) { - if (debugger->engine() == executionEngine) - m_debuggers.removeAll(debugger); - } - } - QQmlDebugService::engineAboutToBeRemoved(engine); -} - -void QQmlNativeDebugServiceImpl::stateAboutToBeChanged(QQmlDebugService::State state) -{ - if (state == Enabled) { - for (NativeDebugger *debugger : qAsConst(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(QLatin1String("arguments")).toObject(); - QString cmd = request.value(QLatin1String("command")).toString(); - - if (cmd == QLatin1String("setbreakpoint")) { - m_breakHandler->handleSetBreakpoint(&response, arguments); - } else if (cmd == QLatin1String("removebreakpoint")) { - m_breakHandler->handleRemoveBreakpoint(&response, arguments); - } else if (cmd == QLatin1String("echo")) { - response.insert(QStringLiteral("result"), arguments); - } else { - for (NativeDebugger *debugger : qAsConst(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 deleted file mode 100644 index 8015513f9e..0000000000 --- a/src/plugins/qmltooling/qmldbg_debugger/qqmlnativedebugservice.h +++ /dev/null @@ -1,98 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the QtQml module of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:LGPL$ -** 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 https://www.qt.io/terms-conditions. For further -** information use the contact form at https://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 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or (at your option) the GNU General -** Public license version 3 or any later version approved by the KDE Free -** Qt Foundation. The licenses are as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-2.0.html and -** https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -#ifndef QQML_NATIVE_DEBUG_SERVICE_H -#define QQML_NATIVE_DEBUG_SERVICE_H - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#include - -#include -#include -#include -#include -#include -#include - -QT_BEGIN_NAMESPACE - -class NativeDebugger; -class BreakPointHandler; -class QQmlDebuggerServiceFactory; - -class QQmlNativeDebugServiceImpl : public QQmlNativeDebugService -{ -public: - QQmlNativeDebugServiceImpl(QObject *parent); - - ~QQmlNativeDebugServiceImpl() Q_DECL_OVERRIDE; - - void engineAboutToBeAdded(QJSEngine *engine) Q_DECL_OVERRIDE; - void engineAboutToBeRemoved(QJSEngine *engine) Q_DECL_OVERRIDE; - - void stateAboutToBeChanged(State state) Q_DECL_OVERRIDE; - - void messageReceived(const QByteArray &message) Q_DECL_OVERRIDE; - - void emitAsynchronousMessageToClient(const QJsonObject &message); - -private: - friend class QQmlDebuggerServiceFactory; - friend class NativeDebugger; - - QList > m_debuggers; - BreakPointHandler *m_breakHandler; -}; - -QT_END_NAMESPACE - -#endif // QQML_NATIVE_DEBUG_SERVICE_H diff --git a/src/plugins/qmltooling/qmldbg_nativedebugger/qmldbg_nativedebugger.pro b/src/plugins/qmltooling/qmldbg_nativedebugger/qmldbg_nativedebugger.pro new file mode 100644 index 0000000000..1873a6a77c --- /dev/null +++ b/src/plugins/qmltooling/qmldbg_nativedebugger/qmldbg_nativedebugger.pro @@ -0,0 +1,21 @@ +TARGET = qmldbg_nativedebugger +QT = qml-private core packetprotocol-private + +SOURCES += \ + $$PWD/qqmlnativedebugservicefactory.cpp \ + $$PWD/qqmlnativedebugservice.cpp + +HEADERS += \ + $$PWD/../shared/qqmldebugpacket.h \ + $$PWD/qqmlnativedebugservicefactory.h \ + $$PWD/qqmlnativedebugservice.h \ + +INCLUDEPATH += $$PWD \ + $$PWD/../shared + +OTHER_FILES += \ + $$PWD/qqmlnativedebugservice.json + +PLUGIN_TYPE = qmltooling +PLUGIN_CLASS_NAME = QQmlNativeDebugServiceFactory +load(qt_plugin) diff --git a/src/plugins/qmltooling/qmldbg_nativedebugger/qqmlnativedebugservice.cpp b/src/plugins/qmltooling/qmldbg_nativedebugger/qqmlnativedebugservice.cpp new file mode 100644 index 0000000000..9c198a8afc --- /dev/null +++ b/src/plugins/qmltooling/qmldbg_nativedebugger/qqmlnativedebugservice.cpp @@ -0,0 +1,807 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** 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 https://www.qt.io/terms-conditions. For further +** information use the contact form at https://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 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qqmlnativedebugservice.h" +#include "qqmldebugpacket.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +//#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 m_breakPoints; +}; + +void BreakPointHandler::handleSetBreakpoint(QJsonObject *response, const QJsonObject &arguments) +{ + TRACE_PROTOCOL("SET BREAKPOINT" << arguments); + QString type = arguments.value(QLatin1String("type")).toString(); + + QString fileName = arguments.value(QLatin1String("file")).toString(); + if (fileName.isEmpty()) { + setError(response, QStringLiteral("breakpoint has no file name")); + return; + } + + int line = arguments.value(QLatin1String("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(QLatin1String("enabled")).toBool(true); + bp.condition = arguments.value(QLatin1String("condition")).toString(); + bp.ignoreCount = arguments.value(QLatin1String("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(QLatin1String("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); + + void 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); + evaluateExpression(scope, expression); + return scope.result.booleanValue(); +} + +void 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(); + if (!m_engine->hasException) + scope.result = script.run(); + + m_runningJob = false; +} + +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(); + + for (const QString &signal : qAsConst(breakOnSignals)) { + if (signal == signalName) { + // TODO: pause debugger + break; + } + } +} + +void NativeDebugger::handleCommand(QJsonObject *response, const QString &cmd, + const QJsonObject &arguments) +{ + if (cmd == QLatin1String("backtrace")) + handleBacktrace(response, arguments); + else if (cmd == QLatin1String("variables")) + handleVariables(response, arguments); + else if (cmd == QLatin1String("expressions")) + handleExpressions(response, arguments); + else if (cmd == QLatin1String("stepin")) + handleContinue(response, StepIn); + else if (cmd == QLatin1String("stepout")) + handleContinue(response, StepOut); + else if (cmd == QLatin1String("stepover")) + handleContinue(response, StepOver); + else if (cmd == QLatin1String("continue")) + handleContinue(response, NotStepping); +} + +static QString encodeContext(QV4::ExecutionContext *executionContext) +{ + QQmlDebugPacket ds; + ds << quintptr(executionContext); + return QString::fromLatin1(ds.data().toHex()); +} + +static void decodeContext(const QString &context, QV4::ExecutionContext **executionContext) +{ + quintptr rawContext; + QQmlDebugPacket ds(QByteArray::fromHex(context.toLatin1())); + ds >> rawContext; + *executionContext = reinterpret_cast(rawContext); +} + +void NativeDebugger::handleBacktrace(QJsonObject *response, const QJsonObject &arguments) +{ + int limit = arguments.value(QLatin1String("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.insert(QStringLiteral("language"), QStringLiteral("js")); + frame.insert(QStringLiteral("context"), encodeContext(executionContext)); + + if (QV4::Function *function = heapFunctionObject->function) { + if (QV4::Heap::String *functionName = function->name()) + frame.insert(QStringLiteral("function"), functionName->toQString()); + frame.insert(QStringLiteral("file"), function->sourceFile()); + } + int line = executionContext->d()->lineNumber; + frame.insert(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::method_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()) { + 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()) { + // 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()) { + 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(QLatin1String("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); + const QJsonArray expanded = arguments.value(QLatin1String("expanded")).toArray(); + for (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(QLatin1String("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); + const QJsonArray expanded = arguments.value(QLatin1String("expanded")).toArray(); + for (const QJsonValue &ex : expanded) + collector.m_expanded.append(ex.toString()); + TRACE_PROTOCOL("Expanded: " << collector.m_expanded); + + QJsonArray output; + QV4::Scope scope(engine); + + const QJsonArray expressions = arguments.value(QLatin1String("expressions")).toArray(); + for (const QJsonValue &expr : expressions) { + QString expression = expr.toObject().value(QLatin1String("expression")).toString(); + QString name = expr.toObject().value(QLatin1String("name")).toString(); + TRACE_PROTOCOL("Evaluate expression: " << expression); + m_runningJob = true; + + evaluateExpression(scope, expression); + QV4::ScopedValue result(scope, scope.result); + + m_runningJob = false; + if (result->isUndefined()) { + QJsonObject dict; + dict.insert(QStringLiteral("name"), name); + dict.insert(QStringLiteral("valueencoded"), QStringLiteral("undefined")); + output.append(dict); + } else if (result.ptr && result.ptr->rawValue()) { + collector.collect(&output, QString(), name, *result); + } else { + QJsonObject dict; + dict.insert(QStringLiteral("name"), name); + dict.insert(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 QStringRef base = fileName.midRef(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(QJSEngine *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(debugger)); + } + } + QQmlDebugService::engineAboutToBeAdded(engine); +} + +void QQmlNativeDebugServiceImpl::engineAboutToBeRemoved(QJSEngine *engine) +{ + TRACE_PROTOCOL("Removing engine" << engine); + if (engine) { + QV4::ExecutionEngine *executionEngine = QV8Engine::getV4(engine->handle()); + const auto debuggersCopy = m_debuggers; + for (NativeDebugger *debugger : debuggersCopy) { + if (debugger->engine() == executionEngine) + m_debuggers.removeAll(debugger); + } + } + QQmlDebugService::engineAboutToBeRemoved(engine); +} + +void QQmlNativeDebugServiceImpl::stateAboutToBeChanged(QQmlDebugService::State state) +{ + if (state == Enabled) { + for (NativeDebugger *debugger : qAsConst(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(QLatin1String("arguments")).toObject(); + QString cmd = request.value(QLatin1String("command")).toString(); + + if (cmd == QLatin1String("setbreakpoint")) { + m_breakHandler->handleSetBreakpoint(&response, arguments); + } else if (cmd == QLatin1String("removebreakpoint")) { + m_breakHandler->handleRemoveBreakpoint(&response, arguments); + } else if (cmd == QLatin1String("echo")) { + response.insert(QStringLiteral("result"), arguments); + } else { + for (NativeDebugger *debugger : qAsConst(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_nativedebugger/qqmlnativedebugservice.h b/src/plugins/qmltooling/qmldbg_nativedebugger/qqmlnativedebugservice.h new file mode 100644 index 0000000000..58bf1bc94a --- /dev/null +++ b/src/plugins/qmltooling/qmldbg_nativedebugger/qqmlnativedebugservice.h @@ -0,0 +1,96 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** 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 https://www.qt.io/terms-conditions. For further +** information use the contact form at https://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 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQML_NATIVE_DEBUG_SERVICE_H +#define QQML_NATIVE_DEBUG_SERVICE_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class NativeDebugger; +class BreakPointHandler; + +class QQmlNativeDebugServiceImpl : public QQmlNativeDebugService +{ +public: + QQmlNativeDebugServiceImpl(QObject *parent); + + ~QQmlNativeDebugServiceImpl() Q_DECL_OVERRIDE; + + void engineAboutToBeAdded(QJSEngine *engine) Q_DECL_OVERRIDE; + void engineAboutToBeRemoved(QJSEngine *engine) Q_DECL_OVERRIDE; + + void stateAboutToBeChanged(State state) Q_DECL_OVERRIDE; + + void messageReceived(const QByteArray &message) Q_DECL_OVERRIDE; + + void emitAsynchronousMessageToClient(const QJsonObject &message); + +private: + friend class NativeDebugger; + + QList > m_debuggers; + BreakPointHandler *m_breakHandler; +}; + +QT_END_NAMESPACE + +#endif // QQML_NATIVE_DEBUG_SERVICE_H diff --git a/src/plugins/qmltooling/qmldbg_nativedebugger/qqmlnativedebugservice.json b/src/plugins/qmltooling/qmldbg_nativedebugger/qqmlnativedebugservice.json new file mode 100644 index 0000000000..2951166298 --- /dev/null +++ b/src/plugins/qmltooling/qmldbg_nativedebugger/qqmlnativedebugservice.json @@ -0,0 +1,3 @@ +{ + "Keys": [ "NativeQmlDebugger" ] +} diff --git a/src/plugins/qmltooling/qmldbg_nativedebugger/qqmlnativedebugservicefactory.cpp b/src/plugins/qmltooling/qmldbg_nativedebugger/qqmlnativedebugservicefactory.cpp new file mode 100644 index 0000000000..1841c82d5d --- /dev/null +++ b/src/plugins/qmltooling/qmldbg_nativedebugger/qqmlnativedebugservicefactory.cpp @@ -0,0 +1,54 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** 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 https://www.qt.io/terms-conditions. For further +** information use the contact form at https://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 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qqmlnativedebugservice.h" +#include "qqmlnativedebugservicefactory.h" +#include + +QT_BEGIN_NAMESPACE + +QQmlDebugService *QQmlNativeDebugServiceFactory::create(const QString &key) +{ + if (key == QQmlNativeDebugServiceImpl::s_key) + return new QQmlNativeDebugServiceImpl(this); + + return 0; +} + +QT_END_NAMESPACE diff --git a/src/plugins/qmltooling/qmldbg_nativedebugger/qqmlnativedebugservicefactory.h b/src/plugins/qmltooling/qmldbg_nativedebugger/qqmlnativedebugservicefactory.h new file mode 100644 index 0000000000..c4a8850bec --- /dev/null +++ b/src/plugins/qmltooling/qmldbg_nativedebugger/qqmlnativedebugservicefactory.h @@ -0,0 +1,57 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** 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 https://www.qt.io/terms-conditions. For further +** information use the contact form at https://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 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQMLNATIVEDEBUGSERVICEFACTORY_H +#define QQMLNATIVEDEBUGSERVICEFACTORY_H + +#include + +QT_BEGIN_NAMESPACE + +class QQmlNativeDebugServiceFactory : public QQmlDebugServiceFactory +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID QQmlDebugServiceFactory_iid FILE "qqmlnativedebugservice.json") +public: + QQmlDebugService *create(const QString &key); +}; + +QT_END_NAMESPACE + +#endif // QQMLNATIVEDEBUGSERVICEFACTORY_H diff --git a/src/plugins/qmltooling/qmltooling.pro b/src/plugins/qmltooling/qmltooling.pro index 56996a452f..8123e2999e 100644 --- a/src/plugins/qmltooling/qmltooling.pro +++ b/src/plugins/qmltooling/qmltooling.pro @@ -20,13 +20,15 @@ qtConfig(qml-network) { SUBDIRS += \ qmldbg_debugger \ qmldbg_profiler \ - qmldbg_messages + qmldbg_messages \ + qmldbg_nativedebugger qmldbg_server.depends = packetprotocol qmldbg_native.depends = packetprotocol qmldbg_debugger.depends = packetprotocol qmldbg_profiler.depends = packetprotocol qmldbg_messages.depends = packetprotocol +qmldbg_nativedebugger.depends = packetprotocol qtHaveModule(quick) { SUBDIRS += \ diff --git a/src/qml/debugger/qqmldebugserviceinterfaces_p.h b/src/qml/debugger/qqmldebugserviceinterfaces_p.h index ca6293c3ec..2fe3a588c3 100644 --- a/src/qml/debugger/qqmldebugserviceinterfaces_p.h +++ b/src/qml/debugger/qqmldebugserviceinterfaces_p.h @@ -205,14 +205,14 @@ protected: class Q_QML_PRIVATE_EXPORT QQmlNativeDebugService : public QQmlDebugService { Q_OBJECT +public: + static const QString s_key; protected: friend class QQmlDebugConnector; QQmlNativeDebugService(float version, QObject *parent = 0) : QQmlDebugService(s_key, version, parent) {} - - static const QString s_key; }; #endif -- cgit v1.2.3