From 5652c4163f94aaf4bd9cef994be2ae8e7f4096c5 Mon Sep 17 00:00:00 2001 From: Ulf Hermann Date: Tue, 21 Jul 2015 16:11:16 +0200 Subject: Move debugger-specific services into a common plugin Change-Id: Icd4e6a6c57bc3ac65cb43d2329d236012b988678 Reviewed-by: Simon Hausmann --- .../qmldbg_debugger/qdebugmessageservice.cpp | 96 ++ .../qmldbg_debugger/qdebugmessageservice.h | 82 ++ .../qmltooling/qmldbg_debugger/qmldbg_debugger.pro | 27 + .../qmldbg_debugger/qqmldebuggerservice.json | 3 + .../qmldbg_debugger/qqmldebuggerservicefactory.cpp | 56 + .../qmldbg_debugger/qqmldebuggerservicefactory.h | 51 + .../qmldbg_debugger/qqmlenginedebugservice.cpp | 820 +++++++++++++ .../qmldbg_debugger/qqmlenginedebugservice.h | 135 +++ .../qmltooling/qmldbg_debugger/qqmlwatcher.cpp | 181 +++ .../qmltooling/qmldbg_debugger/qqmlwatcher.h | 86 ++ .../qmltooling/qmldbg_debugger/qv4debugservice.cpp | 1199 ++++++++++++++++++++ .../qmltooling/qmldbg_debugger/qv4debugservice.h | 139 +++ src/plugins/qmltooling/qmltooling.pro | 1 + 13 files changed, 2876 insertions(+) create mode 100644 src/plugins/qmltooling/qmldbg_debugger/qdebugmessageservice.cpp create mode 100644 src/plugins/qmltooling/qmldbg_debugger/qdebugmessageservice.h create mode 100644 src/plugins/qmltooling/qmldbg_debugger/qmldbg_debugger.pro create mode 100644 src/plugins/qmltooling/qmldbg_debugger/qqmldebuggerservice.json create mode 100644 src/plugins/qmltooling/qmldbg_debugger/qqmldebuggerservicefactory.cpp create mode 100644 src/plugins/qmltooling/qmldbg_debugger/qqmldebuggerservicefactory.h create mode 100644 src/plugins/qmltooling/qmldbg_debugger/qqmlenginedebugservice.cpp create mode 100644 src/plugins/qmltooling/qmldbg_debugger/qqmlenginedebugservice.h create mode 100644 src/plugins/qmltooling/qmldbg_debugger/qqmlwatcher.cpp create mode 100644 src/plugins/qmltooling/qmldbg_debugger/qqmlwatcher.h create mode 100644 src/plugins/qmltooling/qmldbg_debugger/qv4debugservice.cpp create mode 100644 src/plugins/qmltooling/qmldbg_debugger/qv4debugservice.h (limited to 'src/plugins') diff --git a/src/plugins/qmltooling/qmldbg_debugger/qdebugmessageservice.cpp b/src/plugins/qmltooling/qmldbg_debugger/qdebugmessageservice.cpp new file mode 100644 index 0000000000..6bccec08b1 --- /dev/null +++ b/src/plugins/qmltooling/qmldbg_debugger/qdebugmessageservice.cpp @@ -0,0 +1,96 @@ +/**************************************************************************** +** +** 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 "qdebugmessageservice.h" +#include + +#include + +QT_BEGIN_NAMESPACE + +const QString QDebugMessageService::s_key = QStringLiteral("DebugMessages"); + +void DebugMessageHandler(QtMsgType type, const QMessageLogContext &ctxt, + const QString &buf) +{ + QQmlDebugConnector::service()->sendDebugMessage(type, ctxt, buf); +} + +QDebugMessageService::QDebugMessageService(QObject *parent) : + QQmlDebugService(s_key, 2, parent), oldMsgHandler(0), + prevState(QQmlDebugService::NotConnected) +{ + // don't execute stateChanged() in parallel + QMutexLocker lock(&initMutex); + if (state() == Enabled) { + oldMsgHandler = qInstallMessageHandler(DebugMessageHandler); + prevState = Enabled; + } +} + +void QDebugMessageService::sendDebugMessage(QtMsgType type, + const QMessageLogContext &ctxt, + const QString &buf) +{ + //We do not want to alter the message handling mechanism + //We just eavesdrop and forward the messages to a port + //only if a client is connected to it. + QByteArray message; + QQmlDebugStream ws(&message, QIODevice::WriteOnly); + ws << QByteArray("MESSAGE") << type << buf.toUtf8(); + ws << QString::fromLatin1(ctxt.file).toUtf8(); + ws << ctxt.line << QString::fromLatin1(ctxt.function).toUtf8(); + + emit messageToClient(name(), message); + if (oldMsgHandler) + (*oldMsgHandler)(type, ctxt, buf); +} + +void QDebugMessageService::stateChanged(State state) +{ + QMutexLocker lock(&initMutex); + + if (state != Enabled && prevState == Enabled) { + QtMessageHandler handler = qInstallMessageHandler(oldMsgHandler); + // has our handler been overwritten in between? + if (handler != DebugMessageHandler) + qInstallMessageHandler(handler); + + } else if (state == Enabled && prevState != Enabled) { + oldMsgHandler = qInstallMessageHandler(DebugMessageHandler); + } + + prevState = state; +} + +QT_END_NAMESPACE diff --git a/src/plugins/qmltooling/qmldbg_debugger/qdebugmessageservice.h b/src/plugins/qmltooling/qmldbg_debugger/qdebugmessageservice.h new file mode 100644 index 0000000000..c0dc41bcd0 --- /dev/null +++ b/src/plugins/qmltooling/qmldbg_debugger/qdebugmessageservice.h @@ -0,0 +1,82 @@ +/**************************************************************************** +** +** 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 QDEBUGMESSAGESERVICE_H +#define QDEBUGMESSAGESERVICE_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include + +#include +#include + +QT_BEGIN_NAMESPACE + +class QDebugMessageServicePrivate; + +class QDebugMessageService : public QQmlDebugService +{ + Q_OBJECT +public: + QDebugMessageService(QObject *parent = 0); + + void sendDebugMessage(QtMsgType type, const QMessageLogContext &ctxt, + const QString &buf); + +protected: + static const QString s_key; + + void stateChanged(State); + +private: + friend class QQmlDebugConnector; + friend class QQmlDebuggerServiceFactory; + + QtMessageHandler oldMsgHandler; + QQmlDebugService::State prevState; + QMutex initMutex; +}; + +QT_END_NAMESPACE + +#endif // QDEBUGMESSAGESERVICE_H diff --git a/src/plugins/qmltooling/qmldbg_debugger/qmldbg_debugger.pro b/src/plugins/qmltooling/qmldbg_debugger/qmldbg_debugger.pro new file mode 100644 index 0000000000..951b212d17 --- /dev/null +++ b/src/plugins/qmltooling/qmldbg_debugger/qmldbg_debugger.pro @@ -0,0 +1,27 @@ +TARGET = qmldbg_debugger +QT = qml-private core-private + +PLUGIN_TYPE = qmltooling +PLUGIN_CLASS_NAME = QQmlDebuggerServiceFactory +load(qt_plugin) + +SOURCES += \ + $$PWD/qdebugmessageservice.cpp \ + $$PWD/qqmldebuggerservicefactory.cpp \ + $$PWD/qqmlenginedebugservice.cpp \ + $$PWD/qqmlwatcher.cpp \ + $$PWD/qv4debugservice.cpp + +HEADERS += \ + $$PWD/qdebugmessageservice.h \ + $$PWD/qqmldebuggerservicefactory.h \ + $$PWD/qqmlenginedebugservice.h \ + $$PWD/qqmlwatcher.h \ + $$PWD/qv4debugservice.h + +INCLUDEPATH += $$PWD \ + $$PWD/../shared + +OTHER_FILES += \ + $$PWD/qqmldebuggerservice.json + diff --git a/src/plugins/qmltooling/qmldbg_debugger/qqmldebuggerservice.json b/src/plugins/qmltooling/qmldbg_debugger/qqmldebuggerservice.json new file mode 100644 index 0000000000..b1e90364d5 --- /dev/null +++ b/src/plugins/qmltooling/qmldbg_debugger/qqmldebuggerservice.json @@ -0,0 +1,3 @@ +{ + "Keys": [ "DebugMessages", "QmlDebugger", "V8Debugger" ] +} diff --git a/src/plugins/qmltooling/qmldbg_debugger/qqmldebuggerservicefactory.cpp b/src/plugins/qmltooling/qmldbg_debugger/qqmldebuggerservicefactory.cpp new file mode 100644 index 0000000000..7ba2aeecb0 --- /dev/null +++ b/src/plugins/qmltooling/qmldbg_debugger/qqmldebuggerservicefactory.cpp @@ -0,0 +1,56 @@ +/**************************************************************************** +** +** 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 "qqmldebuggerservicefactory.h" +#include "qqmlenginedebugservice.h" +#include "qdebugmessageservice.h" +#include "qv4debugservice.h" +#include + +QT_BEGIN_NAMESPACE + +QQmlDebugService *QQmlDebuggerServiceFactory::create(const QString &key) +{ + if (key == QDebugMessageService::s_key) + return new QDebugMessageService(this); + + if (key == QQmlEngineDebugServiceImpl::s_key) + return new QQmlEngineDebugServiceImpl(this); + + if (key == QV4DebugServiceImpl::s_key) + return new QV4DebugServiceImpl(this); + + return 0; +} + +QT_END_NAMESPACE diff --git a/src/plugins/qmltooling/qmldbg_debugger/qqmldebuggerservicefactory.h b/src/plugins/qmltooling/qmldbg_debugger/qqmldebuggerservicefactory.h new file mode 100644 index 0000000000..2c7509ba12 --- /dev/null +++ b/src/plugins/qmltooling/qmldbg_debugger/qqmldebuggerservicefactory.h @@ -0,0 +1,51 @@ +/**************************************************************************** +** +** 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 QQMLDEBUGGERSERVICEFACTORY_H +#define QQMLDEBUGGERSERVICEFACTORY_H + +#include + +QT_BEGIN_NAMESPACE + +class QQmlDebuggerServiceFactory : public QQmlDebugServiceFactory +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID QQmlDebugServiceFactory_iid FILE "qqmldebuggerservice.json") +public: + QQmlDebugService *create(const QString &key); +}; + +QT_END_NAMESPACE + +#endif // QQMLDEBUGGERSERVICEFACTORY_H diff --git a/src/plugins/qmltooling/qmldbg_debugger/qqmlenginedebugservice.cpp b/src/plugins/qmltooling/qmldbg_debugger/qqmlenginedebugservice.cpp new file mode 100644 index 0000000000..8f53dc6d50 --- /dev/null +++ b/src/plugins/qmltooling/qmldbg_debugger/qqmlenginedebugservice.cpp @@ -0,0 +1,820 @@ +/**************************************************************************** +** +** 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 "qqmlenginedebugservice.h" +#include "qqmlwatcher.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +QQmlEngineDebugServiceImpl::QQmlEngineDebugServiceImpl(QObject *parent) : + QQmlEngineDebugService(2, parent), m_watch(new QQmlWatcher(this)), m_statesDelegate(0) +{ + QObject::connect(m_watch, SIGNAL(propertyChanged(int,int,QMetaProperty,QVariant)), + this, SLOT(propertyChanged(int,int,QMetaProperty,QVariant))); +} + +QQmlEngineDebugServiceImpl::~QQmlEngineDebugServiceImpl() +{ + delete m_statesDelegate; +} + +QDataStream &operator<<(QDataStream &ds, + const QQmlEngineDebugServiceImpl::QQmlObjectData &data) +{ + ds << data.url << data.lineNumber << data.columnNumber << data.idString + << data.objectName << data.objectType << data.objectId << data.contextId + << data.parentId; + return ds; +} + +QDataStream &operator>>(QDataStream &ds, + QQmlEngineDebugServiceImpl::QQmlObjectData &data) +{ + ds >> data.url >> data.lineNumber >> data.columnNumber >> data.idString + >> data.objectName >> data.objectType >> data.objectId >> data.contextId + >> data.parentId; + return ds; +} + +QDataStream &operator<<(QDataStream &ds, + const QQmlEngineDebugServiceImpl::QQmlObjectProperty &data) +{ + ds << (int)data.type << data.name; + // check first whether the data can be saved + // (otherwise we assert in QVariant::operator<<) + QByteArray buffer; + QDataStream fakeStream(&buffer, QIODevice::WriteOnly); + if (QMetaType::save(fakeStream, data.value.type(), data.value.constData())) + ds << data.value; + else + ds << QVariant(); + ds << data.valueTypeName << data.binding << data.hasNotifySignal; + return ds; +} + +QDataStream &operator>>(QDataStream &ds, + QQmlEngineDebugServiceImpl::QQmlObjectProperty &data) +{ + int type; + ds >> type >> data.name >> data.value >> data.valueTypeName + >> data.binding >> data.hasNotifySignal; + data.type = (QQmlEngineDebugServiceImpl::QQmlObjectProperty::Type)type; + return ds; +} + +static inline bool isSignalPropertyName(const QString &signalName) +{ + // see QmlCompiler::isSignalPropertyName + return signalName.length() >= 3 && signalName.startsWith(QLatin1String("on")) && + signalName.at(2).isLetter() && signalName.at(2).isUpper(); +} + +static bool hasValidSignal(QObject *object, const QString &propertyName) +{ + if (!isSignalPropertyName(propertyName)) + return false; + + QString signalName = propertyName.mid(2); + signalName[0] = signalName.at(0).toLower(); + + int sigIdx = QQmlPropertyPrivate::findSignalByName(object->metaObject(), signalName.toLatin1()).methodIndex(); + + if (sigIdx == -1) + return false; + + return true; +} + +QQmlEngineDebugServiceImpl::QQmlObjectProperty +QQmlEngineDebugServiceImpl::propertyData(QObject *obj, int propIdx) +{ + QQmlObjectProperty rv; + + QMetaProperty prop = obj->metaObject()->property(propIdx); + + rv.type = QQmlObjectProperty::Unknown; + rv.valueTypeName = QString::fromUtf8(prop.typeName()); + rv.name = QString::fromUtf8(prop.name()); + rv.hasNotifySignal = prop.hasNotifySignal(); + QQmlAbstractBinding *binding = + QQmlPropertyPrivate::binding(QQmlProperty(obj, rv.name)); + if (binding) + rv.binding = binding->expression(); + + if (QQmlValueTypeFactory::isValueType(prop.userType())) { + rv.type = QQmlObjectProperty::Basic; + } else if (QQmlMetaType::isQObject(prop.userType())) { + rv.type = QQmlObjectProperty::Object; + } else if (QQmlMetaType::isList(prop.userType())) { + rv.type = QQmlObjectProperty::List; + } else if (prop.userType() == QMetaType::QVariant) { + rv.type = QQmlObjectProperty::Variant; + } + + QVariant value; + if (rv.type != QQmlObjectProperty::Unknown && prop.userType() != 0) { + value = prop.read(obj); + } + rv.value = valueContents(value); + + return rv; +} + +QVariant QQmlEngineDebugServiceImpl::valueContents(QVariant value) const +{ + // We can't send JS objects across the wire, so transform them to variant + // maps for serialization. + if (value.userType() == qMetaTypeId()) + value = value.value().toVariant(); + const int userType = value.userType(); + + //QObject * is not streamable. + //Convert all such instances to a String value + + if (value.type() == QVariant::List) { + QVariantList contents; + QVariantList list = value.toList(); + int count = list.size(); + contents.reserve(count); + for (int i = 0; i < count; i++) + contents << valueContents(list.at(i)); + return contents; + } + + if (value.type() == QVariant::Map) { + QVariantMap contents; + QMapIterator i(value.toMap()); + while (i.hasNext()) { + i.next(); + contents.insert(i.key(), valueContents(i.value())); + } + return contents; + } + + if (QQmlValueTypeFactory::isValueType(userType)) { + const QMetaObject *mo = QQmlValueTypeFactory::metaObjectForMetaType(userType); + if (mo) { + int toStringIndex = mo->indexOfMethod("toString"); + if (toStringIndex != -1) { + QMetaMethod mm = mo->method(toStringIndex); + QMetaType info(userType); + QString s; + if (info.flags() & QMetaType::IsGadget + && mm.invokeOnGadget(value.data(), Q_RETURN_ARG(QString, s))) + return s; + } + } + + return value; + } + + if (QQmlMetaType::isQObject(userType)) { + QObject *o = QQmlMetaType::toQObject(value); + if (o) { + QString name = o->objectName(); + if (name.isEmpty()) + name = QStringLiteral(""); + return name; + } + } + + return QString(QStringLiteral("")); +} + +void QQmlEngineDebugServiceImpl::buildObjectDump(QDataStream &message, + QObject *object, bool recur, bool dumpProperties) +{ + message << objectData(object); + + QObjectList children = object->children(); + + int childrenCount = children.count(); + for (int ii = 0; ii < children.count(); ++ii) { + if (qobject_cast(children[ii])) + --childrenCount; + } + + message << childrenCount << recur; + + QList fakeProperties; + + for (int ii = 0; ii < children.count(); ++ii) { + QObject *child = children.at(ii); + if (qobject_cast(child)) + continue; + if (recur) + buildObjectDump(message, child, recur, dumpProperties); + else + message << objectData(child); + } + + if (!dumpProperties) { + message << 0; + return; + } + + QList propertyIndexes; + for (int ii = 0; ii < object->metaObject()->propertyCount(); ++ii) { + if (object->metaObject()->property(ii).isScriptable()) + propertyIndexes << ii; + } + + QQmlData *ddata = QQmlData::get(object); + if (ddata && ddata->signalHandlers) { + QQmlBoundSignal *signalHandler = ddata->signalHandlers; + + while (signalHandler) { + QQmlObjectProperty prop; + prop.type = QQmlObjectProperty::SignalProperty; + prop.hasNotifySignal = false; + QQmlBoundSignalExpression *expr = signalHandler->expression(); + if (expr) { + prop.value = expr->expression(); + QObject *scope = expr->scopeObject(); + if (scope) { + QString methodName = QString::fromLatin1(QMetaObjectPrivate::signal(scope->metaObject(), signalHandler->signalIndex()).name()); + if (!methodName.isEmpty()) { + prop.name = QLatin1String("on") + methodName[0].toUpper() + + methodName.mid(1); + } + } + } + fakeProperties << prop; + + signalHandler = nextSignal(signalHandler); + } + } + + message << propertyIndexes.size() + fakeProperties.count(); + + for (int ii = 0; ii < propertyIndexes.size(); ++ii) + message << propertyData(object, propertyIndexes.at(ii)); + + for (int ii = 0; ii < fakeProperties.count(); ++ii) + message << fakeProperties[ii]; +} + +void QQmlEngineDebugServiceImpl::prepareDeferredObjects(QObject *obj) +{ + qmlExecuteDeferred(obj); + + QObjectList children = obj->children(); + for (int ii = 0; ii < children.count(); ++ii) { + QObject *child = children.at(ii); + prepareDeferredObjects(child); + } + +} + +void QQmlEngineDebugServiceImpl::storeObjectIds(QObject *co) +{ + QQmlDebugService::idForObject(co); + QObjectList children = co->children(); + for (int ii = 0; ii < children.count(); ++ii) + storeObjectIds(children.at(ii)); +} + +void QQmlEngineDebugServiceImpl::buildObjectList(QDataStream &message, + QQmlContext *ctxt, + const QList > &instances) +{ + QQmlContextData *p = QQmlContextData::get(ctxt); + + QString ctxtName = ctxt->objectName(); + int ctxtId = QQmlDebugService::idForObject(ctxt); + if (ctxt->contextObject()) + storeObjectIds(ctxt->contextObject()); + + message << ctxtName << ctxtId; + + int count = 0; + + QQmlContextData *child = p->childContexts; + while (child) { + ++count; + child = child->nextChild; + } + + message << count; + + child = p->childContexts; + while (child) { + buildObjectList(message, child->asQQmlContext(), instances); + child = child->nextChild; + } + + count = 0; + for (int ii = 0; ii < instances.count(); ++ii) { + QQmlData *data = QQmlData::get(instances.at(ii)); + if (data->context == p) + count ++; + } + message << count; + + for (int ii = 0; ii < instances.count(); ++ii) { + QQmlData *data = QQmlData::get(instances.at(ii)); + if (data->context == p) + message << objectData(instances.at(ii)); + } +} + +void QQmlEngineDebugServiceImpl::buildStatesList(bool cleanList, + const QList > &instances) +{ + if (m_statesDelegate) + m_statesDelegate->buildStatesList(cleanList, instances); +} + +QQmlEngineDebugServiceImpl::QQmlObjectData +QQmlEngineDebugServiceImpl::objectData(QObject *object) +{ + QQmlData *ddata = QQmlData::get(object); + QQmlObjectData rv; + if (ddata && ddata->outerContext) { + rv.url = ddata->outerContext->url(); + rv.lineNumber = ddata->lineNumber; + rv.columnNumber = ddata->columnNumber; + } else { + rv.lineNumber = -1; + rv.columnNumber = -1; + } + + QQmlContext *context = qmlContext(object); + if (context) { + QQmlContextData *cdata = QQmlContextData::get(context); + if (cdata) + rv.idString = cdata->findObjectId(object); + } + + rv.objectName = object->objectName(); + rv.objectId = QQmlDebugService::idForObject(object); + rv.contextId = QQmlDebugService::idForObject(qmlContext(object)); + rv.parentId = QQmlDebugService::idForObject(object->parent()); + QQmlType *type = QQmlMetaType::qmlType(object->metaObject()); + if (type) { + QString typeName = type->qmlTypeName(); + int lastSlash = typeName.lastIndexOf(QLatin1Char('/')); + rv.objectType = lastSlash < 0 ? typeName : typeName.mid(lastSlash+1); + } else { + rv.objectType = QString::fromUtf8(object->metaObject()->className()); + int marker = rv.objectType.indexOf(QLatin1String("_QMLTYPE_")); + if (marker != -1) + rv.objectType = rv.objectType.left(marker); + } + + return rv; +} + +void QQmlEngineDebugServiceImpl::messageReceived(const QByteArray &message) +{ + QMetaObject::invokeMethod(this, "processMessage", Qt::QueuedConnection, Q_ARG(QByteArray, message)); +} + +/*! + Returns a list of objects matching the given filename, line and column. +*/ +QList QQmlEngineDebugServiceImpl::objectForLocationInfo(const QString &filename, + int lineNumber, int columnNumber) +{ + QList objects; + const QHash &hash = objectsForIds(); + for (QHash::ConstIterator i = hash.constBegin(); i != hash.constEnd(); ++i) { + QQmlData *ddata = QQmlData::get(i.value()); + if (ddata && ddata->outerContext) { + if (QFileInfo(ddata->outerContext->urlString()).fileName() == filename && + ddata->lineNumber == lineNumber && + ddata->columnNumber >= columnNumber) { + objects << i.value(); + } + } + } + return objects; +} + +void QQmlEngineDebugServiceImpl::processMessage(const QByteArray &message) +{ + QQmlDebugStream ds(message); + + QByteArray type; + int queryId; + ds >> type >> queryId; + + QByteArray reply; + QQmlDebugStream rs(&reply, QIODevice::WriteOnly); + + if (type == "LIST_ENGINES") { + rs << QByteArray("LIST_ENGINES_R"); + rs << queryId << m_engines.count(); + + for (int ii = 0; ii < m_engines.count(); ++ii) { + QQmlEngine *engine = m_engines.at(ii); + + QString engineName = engine->objectName(); + int engineId = QQmlDebugService::idForObject(engine); + + rs << engineName << engineId; + } + + } else if (type == "LIST_OBJECTS") { + int engineId = -1; + ds >> engineId; + + QQmlEngine *engine = + qobject_cast(QQmlDebugService::objectForId(engineId)); + + rs << QByteArray("LIST_OBJECTS_R") << queryId; + + if (engine) { + QQmlContext *rootContext = engine->rootContext(); + // Clean deleted objects + QQmlContextPrivate *ctxtPriv = QQmlContextPrivate::get(rootContext); + for (int ii = 0; ii < ctxtPriv->instances.count(); ++ii) { + if (!ctxtPriv->instances.at(ii)) { + ctxtPriv->instances.removeAt(ii); + --ii; + } + } + buildObjectList(rs, rootContext, ctxtPriv->instances); + buildStatesList(true, ctxtPriv->instances); + } + + } else if (type == "FETCH_OBJECT") { + int objectId; + bool recurse; + bool dumpProperties = true; + + ds >> objectId >> recurse >> dumpProperties; + + QObject *object = QQmlDebugService::objectForId(objectId); + + rs << QByteArray("FETCH_OBJECT_R") << queryId; + + if (object) { + if (recurse) + prepareDeferredObjects(object); + buildObjectDump(rs, object, recurse, dumpProperties); + } + + } else if (type == "FETCH_OBJECTS_FOR_LOCATION") { + QString file; + int lineNumber; + int columnNumber; + bool recurse; + bool dumpProperties = true; + + ds >> file >> lineNumber >> columnNumber >> recurse >> dumpProperties; + + QList objects = objectForLocationInfo(file, lineNumber, columnNumber); + + rs << QByteArray("FETCH_OBJECTS_FOR_LOCATION_R") << queryId + << objects.count(); + + foreach (QObject *object, objects) { + if (recurse) + prepareDeferredObjects(object); + buildObjectDump(rs, object, recurse, dumpProperties); + } + + } else if (type == "WATCH_OBJECT") { + int objectId; + + ds >> objectId; + bool ok = m_watch->addWatch(queryId, objectId); + + rs << QByteArray("WATCH_OBJECT_R") << queryId << ok; + + } else if (type == "WATCH_PROPERTY") { + int objectId; + QByteArray property; + + ds >> objectId >> property; + bool ok = m_watch->addWatch(queryId, objectId, property); + + rs << QByteArray("WATCH_PROPERTY_R") << queryId << ok; + + } else if (type == "WATCH_EXPR_OBJECT") { + int debugId; + QString expr; + + ds >> debugId >> expr; + bool ok = m_watch->addWatch(queryId, debugId, expr); + + rs << QByteArray("WATCH_EXPR_OBJECT_R") << queryId << ok; + + } else if (type == "NO_WATCH") { + bool ok = m_watch->removeWatch(queryId); + + rs << QByteArray("NO_WATCH_R") << queryId << ok; + + } else if (type == "EVAL_EXPRESSION") { + int objectId; + QString expr; + + ds >> objectId >> expr; + int engineId = -1; + if (!ds.atEnd()) + ds >> engineId; + + QObject *object = QQmlDebugService::objectForId(objectId); + QQmlContext *context = qmlContext(object); + if (!context) { + QQmlEngine *engine = qobject_cast( + QQmlDebugService::objectForId(engineId)); + if (engine && m_engines.contains(engine)) + context = engine->rootContext(); + } + QVariant result; + if (context) { + QQmlExpression exprObj(context, object, expr); + bool undefined = false; + QVariant value = exprObj.evaluate(&undefined); + if (undefined) + result = QString(QStringLiteral("")); + else + result = valueContents(value); + } else { + result = QString(QStringLiteral("")); + } + + rs << QByteArray("EVAL_EXPRESSION_R") << queryId << result; + + } else if (type == "SET_BINDING") { + int objectId; + QString propertyName; + QVariant expr; + bool isLiteralValue; + QString filename; + int line; + ds >> objectId >> propertyName >> expr >> isLiteralValue >> + filename >> line; + bool ok = setBinding(objectId, propertyName, expr, isLiteralValue, + filename, line); + + rs << QByteArray("SET_BINDING_R") << queryId << ok; + + } else if (type == "RESET_BINDING") { + int objectId; + QString propertyName; + ds >> objectId >> propertyName; + bool ok = resetBinding(objectId, propertyName); + + rs << QByteArray("RESET_BINDING_R") << queryId << ok; + + } else if (type == "SET_METHOD_BODY") { + int objectId; + QString methodName; + QString methodBody; + ds >> objectId >> methodName >> methodBody; + bool ok = setMethodBody(objectId, methodName, methodBody); + + rs << QByteArray("SET_METHOD_BODY_R") << queryId << ok; + + } + emit messageToClient(name(), reply); +} + +bool QQmlEngineDebugServiceImpl::setBinding(int objectId, + const QString &propertyName, + const QVariant &expression, + bool isLiteralValue, + QString filename, + int line, + int column) +{ + bool ok = true; + QObject *object = objectForId(objectId); + QQmlContext *context = qmlContext(object); + + if (object && context) { + QQmlProperty property(object, propertyName, context); + if (property.isValid()) { + + bool inBaseState = true; + if (m_statesDelegate) { + m_statesDelegate->updateBinding(context, property, expression, isLiteralValue, + filename, line, column, &inBaseState); + } + + if (inBaseState) { + if (isLiteralValue) { + property.write(expression); + } else if (hasValidSignal(object, propertyName)) { + QQmlBoundSignalExpression *qmlExpression = new QQmlBoundSignalExpression(object, QQmlPropertyPrivate::get(property)->signalIndex(), + QQmlContextData::get(context), object, expression.toString(), + filename, line, column); + QQmlPropertyPrivate::takeSignalExpression(property, qmlExpression); + } else if (property.isProperty()) { + QQmlBinding *binding = new QQmlBinding(expression.toString(), object, QQmlContextData::get(context), filename, line, column); + binding->setTarget(property); + QQmlPropertyPrivate::setBinding(binding); + binding->update(); + } else { + ok = false; + qWarning() << "QQmlEngineDebugService::setBinding: unable to set property" << propertyName << "on object" << object; + } + } + + } else { + // not a valid property + if (m_statesDelegate) + ok = m_statesDelegate->setBindingForInvalidProperty(object, propertyName, expression, isLiteralValue); + if (!ok) + qWarning() << "QQmlEngineDebugService::setBinding: unable to set property" << propertyName << "on object" << object; + } + } + return ok; +} + +bool QQmlEngineDebugServiceImpl::resetBinding(int objectId, const QString &propertyName) +{ + QObject *object = objectForId(objectId); + QQmlContext *context = qmlContext(object); + + if (object && context) { + QString parentProperty = propertyName; + if (propertyName.indexOf(QLatin1Char('.')) != -1) + parentProperty = propertyName.left(propertyName.indexOf(QLatin1Char('.'))); + + if (object->property(parentProperty.toLatin1()).isValid()) { + QQmlProperty property(object, propertyName); + QQmlPropertyPrivate::removeBinding(property); + if (property.isResettable()) { + // Note: this will reset the property in any case, without regard to states + // Right now almost no QQuickItem has reset methods for its properties (with the + // notable exception of QQuickAnchors), so this is not a big issue + // later on, setBinding does take states into account + property.reset(); + } else { + // overwrite with default value + if (QQmlType *objType = QQmlMetaType::qmlType(object->metaObject())) { + if (QObject *emptyObject = objType->create()) { + if (emptyObject->property(parentProperty.toLatin1()).isValid()) { + QVariant defaultValue = QQmlProperty(emptyObject, propertyName).read(); + if (defaultValue.isValid()) { + setBinding(objectId, propertyName, defaultValue, true); + } + } + delete emptyObject; + } + } + } + return true; + } + + if (hasValidSignal(object, propertyName)) { + QQmlProperty property(object, propertyName, context); + QQmlPropertyPrivate::setSignalExpression(property, 0); + return true; + } + + if (m_statesDelegate) { + m_statesDelegate->resetBindingForInvalidProperty(object, propertyName); + return true; + } + + return false; + } + // object or context null. + return false; +} + +bool QQmlEngineDebugServiceImpl::setMethodBody(int objectId, const QString &method, const QString &body) +{ + QObject *object = objectForId(objectId); + QQmlContext *context = qmlContext(object); + if (!object || !context || !context->engine()) + return false; + QQmlContextData *contextData = QQmlContextData::get(context); + if (!contextData) + return false; + + QQmlPropertyData dummy; + QQmlPropertyData *prop = + QQmlPropertyCache::property(context->engine(), object, method, contextData, dummy); + + if (!prop || !prop->isVMEFunction()) + return false; + + QMetaMethod metaMethod = object->metaObject()->method(prop->coreIndex); + QList paramNames = metaMethod.parameterNames(); + + QString paramStr; + for (int ii = 0; ii < paramNames.count(); ++ii) { + if (ii != 0) paramStr.append(QLatin1Char(',')); + paramStr.append(QString::fromUtf8(paramNames.at(ii))); + } + + QString jsfunction = QLatin1String("(function ") + method + QLatin1Char('(') + paramStr + + QLatin1String(") {"); + jsfunction += body; + jsfunction += QLatin1String("\n})"); + + QQmlVMEMetaObject *vmeMetaObject = QQmlVMEMetaObject::get(object); + Q_ASSERT(vmeMetaObject); // the fact we found the property above should guarentee this + + int lineNumber = vmeMetaObject->vmeMethodLineNumber(prop->coreIndex); + QV4::ExecutionEngine *v4 = QV8Engine::getV4(qmlEngine(object)->handle()); + QV4::Scope scope(v4); + QV4::ScopedValue v(scope, QQmlJavaScriptExpression::evalFunction(contextData, object, jsfunction, contextData->urlString(), lineNumber)); + vmeMetaObject->setVmeMethod(prop->coreIndex, v); + return true; +} + +void QQmlEngineDebugServiceImpl::propertyChanged(int id, int objectId, const QMetaProperty &property, const QVariant &value) +{ + QByteArray reply; + QQmlDebugStream rs(&reply, QIODevice::WriteOnly); + + rs << QByteArray("UPDATE_WATCH") << id << objectId << QByteArray(property.name()) << valueContents(value); + + emit messageToClient(name(), reply); +} + +void QQmlEngineDebugServiceImpl::engineAboutToBeAdded(QQmlEngine *engine) +{ + Q_ASSERT(engine); + Q_ASSERT(!m_engines.contains(engine)); + + m_engines.append(engine); + emit attachedToEngine(engine); +} + +void QQmlEngineDebugServiceImpl::engineAboutToBeRemoved(QQmlEngine *engine) +{ + Q_ASSERT(engine); + Q_ASSERT(m_engines.contains(engine)); + + m_engines.removeAll(engine); + emit detachedFromEngine(engine); +} + +void QQmlEngineDebugServiceImpl::objectCreated(QQmlEngine *engine, QObject *object) +{ + Q_ASSERT(engine); + Q_ASSERT(m_engines.contains(engine)); + + int engineId = QQmlDebugService::idForObject(engine); + int objectId = QQmlDebugService::idForObject(object); + int parentId = QQmlDebugService::idForObject(object->parent()); + + QByteArray reply; + QQmlDebugStream rs(&reply, QIODevice::WriteOnly); + + //unique queryId -1 + rs << QByteArray("OBJECT_CREATED") << -1 << engineId << objectId << parentId; + emit messageToClient(name(), reply); +} + +void QQmlEngineDebugServiceImpl::setStatesDelegate(QQmlDebugStatesDelegate *delegate) +{ + m_statesDelegate = delegate; +} + +QT_END_NAMESPACE diff --git a/src/plugins/qmltooling/qmldbg_debugger/qqmlenginedebugservice.h b/src/plugins/qmltooling/qmldbg_debugger/qqmlenginedebugservice.h new file mode 100644 index 0000000000..19a4827f89 --- /dev/null +++ b/src/plugins/qmltooling/qmldbg_debugger/qqmlenginedebugservice.h @@ -0,0 +1,135 @@ +/**************************************************************************** +** +** 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 QQMLENGINEDEBUGSERVICE_H +#define QQMLENGINEDEBUGSERVICE_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include +#include + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QQmlEngine; +class QQmlContext; +class QQmlWatcher; +class QDataStream; +class QQmlDebugStatesDelegate; + +class QQmlEngineDebugServiceImpl : public QQmlEngineDebugService +{ + Q_OBJECT +public: + QQmlEngineDebugServiceImpl(QObject * = 0); + ~QQmlEngineDebugServiceImpl(); + + struct QQmlObjectData { + QUrl url; + int lineNumber; + int columnNumber; + QString idString; + QString objectName; + QString objectType; + int objectId; + int contextId; + int parentId; + }; + + struct QQmlObjectProperty { + enum Type { Unknown, Basic, Object, List, SignalProperty, Variant }; + Type type; + QString name; + QVariant value; + QString valueTypeName; + QString binding; + bool hasNotifySignal; + }; + + void engineAboutToBeAdded(QQmlEngine *); + void engineAboutToBeRemoved(QQmlEngine *); + void objectCreated(QQmlEngine *, QObject *); + + void setStatesDelegate(QQmlDebugStatesDelegate *); + +protected: + virtual void messageReceived(const QByteArray &); + +private Q_SLOTS: + void processMessage(const QByteArray &msg); + void propertyChanged(int id, int objectId, const QMetaProperty &property, const QVariant &value); + +private: + friend class QQmlDebuggerServiceFactory; + + void prepareDeferredObjects(QObject *); + void buildObjectList(QDataStream &, QQmlContext *, + const QList > &instances); + void buildObjectDump(QDataStream &, QObject *, bool, bool); + void buildStatesList(bool cleanList, const QList > &instances); + QQmlObjectData objectData(QObject *); + QQmlObjectProperty propertyData(QObject *, int); + QVariant valueContents(QVariant defaultValue) const; + bool setBinding(int objectId, const QString &propertyName, const QVariant &expression, bool isLiteralValue, QString filename = QString(), int line = -1, int column = 0); + bool resetBinding(int objectId, const QString &propertyName); + bool setMethodBody(int objectId, const QString &method, const QString &body); + void storeObjectIds(QObject *co); + QList objectForLocationInfo(const QString &filename, int lineNumber, + int columnNumber); + + QList m_engines; + QQmlWatcher *m_watch; + QQmlDebugStatesDelegate *m_statesDelegate; +}; +QDataStream &operator<<(QDataStream &, const QQmlEngineDebugServiceImpl::QQmlObjectData &); +QDataStream &operator>>(QDataStream &, QQmlEngineDebugServiceImpl::QQmlObjectData &); +QDataStream &operator<<(QDataStream &, const QQmlEngineDebugServiceImpl::QQmlObjectProperty &); +QDataStream &operator>>(QDataStream &, QQmlEngineDebugServiceImpl::QQmlObjectProperty &); + +QT_END_NAMESPACE + +#endif // QQMLENGINEDEBUGSERVICE_H + diff --git a/src/plugins/qmltooling/qmldbg_debugger/qqmlwatcher.cpp b/src/plugins/qmltooling/qmldbg_debugger/qqmlwatcher.cpp new file mode 100644 index 0000000000..9f9a6eb33b --- /dev/null +++ b/src/plugins/qmltooling/qmldbg_debugger/qqmlwatcher.cpp @@ -0,0 +1,181 @@ +/**************************************************************************** +** +** 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 "qqmlwatcher.h" + +#include "qqmlexpression.h" +#include "qqmlcontext.h" +#include "qqml.h" + +#include +#include +#include + +#include +#include + +QT_BEGIN_NAMESPACE + + +class QQmlWatchProxy : public QObject +{ + Q_OBJECT +public: + QQmlWatchProxy(int id, + QObject *object, + int debugId, + const QMetaProperty &prop, + QQmlWatcher *parent = 0); + + QQmlWatchProxy(int id, + QQmlExpression *exp, + int debugId, + QQmlWatcher *parent = 0); + +public slots: + void notifyValueChanged(); + +private: + friend class QQmlWatcher; + int m_id; + QQmlWatcher *m_watch; + QObject *m_object; + int m_debugId; + QMetaProperty m_property; + + QQmlExpression *m_expr; +}; + +QQmlWatchProxy::QQmlWatchProxy(int id, + QQmlExpression *exp, + int debugId, + QQmlWatcher *parent) +: QObject(parent), m_id(id), m_watch(parent), m_object(0), m_debugId(debugId), m_expr(exp) +{ + QObject::connect(m_expr, SIGNAL(valueChanged()), this, SLOT(notifyValueChanged())); +} + +QQmlWatchProxy::QQmlWatchProxy(int id, + QObject *object, + int debugId, + const QMetaProperty &prop, + QQmlWatcher *parent) +: QObject(parent), m_id(id), m_watch(parent), m_object(object), m_debugId(debugId), m_property(prop), m_expr(0) +{ + static int refreshIdx = -1; + if(refreshIdx == -1) + refreshIdx = QQmlWatchProxy::staticMetaObject.indexOfMethod("notifyValueChanged()"); + + if (prop.hasNotifySignal()) + QQmlPropertyPrivate::connect(m_object, prop.notifySignalIndex(), this, refreshIdx); +} + +void QQmlWatchProxy::notifyValueChanged() +{ + QVariant v; + if (m_expr) + v = m_expr->evaluate(); + else if (QQmlValueTypeFactory::isValueType(m_property.userType())) + v = m_property.read(m_object); + + emit m_watch->propertyChanged(m_id, m_debugId, m_property, v); +} + + +QQmlWatcher::QQmlWatcher(QObject *parent) + : QObject(parent) +{ +} + +bool QQmlWatcher::addWatch(int id, quint32 debugId) +{ + QObject *object = QQmlDebugService::objectForId(debugId); + if (object) { + int propCount = object->metaObject()->propertyCount(); + for (int ii=0; iimetaObject()->property(ii)); + return true; + } + return false; +} + +bool QQmlWatcher::addWatch(int id, quint32 debugId, const QByteArray &property) +{ + QObject *object = QQmlDebugService::objectForId(debugId); + if (object) { + int index = object->metaObject()->indexOfProperty(property.constData()); + if (index >= 0) { + addPropertyWatch(id, object, debugId, object->metaObject()->property(index)); + return true; + } + } + return false; +} + +bool QQmlWatcher::addWatch(int id, quint32 objectId, const QString &expr) +{ + QObject *object = QQmlDebugService::objectForId(objectId); + QQmlContext *context = qmlContext(object); + if (context) { + QQmlExpression *exprObj = new QQmlExpression(context, object, expr); + exprObj->setNotifyOnValueChanged(true); + QQmlWatchProxy *proxy = new QQmlWatchProxy(id, exprObj, objectId, this); + exprObj->setParent(proxy); + m_proxies[id].append(proxy); + proxy->notifyValueChanged(); + return true; + } + return false; +} + +bool QQmlWatcher::removeWatch(int id) +{ + if (!m_proxies.contains(id)) + return false; + + QList > proxies = m_proxies.take(id); + qDeleteAll(proxies); + return true; +} + +void QQmlWatcher::addPropertyWatch(int id, QObject *object, quint32 debugId, const QMetaProperty &property) +{ + QQmlWatchProxy *proxy = new QQmlWatchProxy(id, object, debugId, property, this); + m_proxies[id].append(proxy); + + proxy->notifyValueChanged(); +} + +QT_END_NAMESPACE + +#include diff --git a/src/plugins/qmltooling/qmldbg_debugger/qqmlwatcher.h b/src/plugins/qmltooling/qmldbg_debugger/qqmlwatcher.h new file mode 100644 index 0000000000..329aee77d2 --- /dev/null +++ b/src/plugins/qmltooling/qmldbg_debugger/qqmlwatcher.h @@ -0,0 +1,86 @@ +/**************************************************************************** +** +** 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 QQMLWATCHER_H +#define QQMLWATCHER_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QQmlWatchProxy; +class QQmlExpression; +class QQmlContext; +class QMetaProperty; + +class QQmlWatcher : public QObject +{ + Q_OBJECT +public: + QQmlWatcher(QObject * = 0); + + bool addWatch(int id, quint32 objectId); + bool addWatch(int id, quint32 objectId, const QByteArray &property); + bool addWatch(int id, quint32 objectId, const QString &expr); + + bool removeWatch(int id); + +Q_SIGNALS: + void propertyChanged(int id, int objectId, const QMetaProperty &property, const QVariant &value); + +private: + friend class QQmlWatchProxy; + void addPropertyWatch(int id, QObject *object, quint32 objectId, const QMetaProperty &property); + + QHash > > m_proxies; +}; + +QT_END_NAMESPACE + +#endif // QQMLWATCHER_H diff --git a/src/plugins/qmltooling/qmldbg_debugger/qv4debugservice.cpp b/src/plugins/qmltooling/qmldbg_debugger/qv4debugservice.cpp new file mode 100644 index 0000000000..0aaa6e7e92 --- /dev/null +++ b/src/plugins/qmltooling/qmldbg_debugger/qv4debugservice.cpp @@ -0,0 +1,1199 @@ +/**************************************************************************** +** +** 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 "qv4debugservice.h" +#include "qqmlengine.h" +#include +#include +#include + +#include + +#include +#include +#include + +const char *const V4_CONNECT = "connect"; +const char *const V4_DISCONNECT = "disconnect"; +const char *const V4_BREAK_ON_SIGNAL = "breakonsignal"; +const char *const V4_PAUSE = "interrupt"; + +#define NO_PROTOCOL_TRACING +#ifdef NO_PROTOCOL_TRACING +# define TRACE_PROTOCOL(x) +#else +#include +# define TRACE_PROTOCOL(x) x +#endif + +QT_BEGIN_NAMESPACE + +class V8CommandHandler; +class UnknownV8CommandHandler; + +class VariableCollector: public QV4::Debugging::Debugger::Collector +{ +public: + VariableCollector(QV4::ExecutionEngine *engine) + : Collector(engine) + , destination(0) + {} + + virtual ~VariableCollector() {} + + void collectScope(QJsonArray *dest, QV4::Debugging::Debugger *debugger, int frameNr, int scopeNr) + { + qSwap(destination, dest); + bool oldIsProp = isProperty(); + setIsProperty(true); + debugger->collectArgumentsInContext(this, frameNr, scopeNr); + debugger->collectLocalsInContext(this, frameNr, scopeNr); + setIsProperty(oldIsProp); + qSwap(destination, dest); + } + + void setDestination(QJsonArray *dest) + { destination = dest; } + + QJsonArray retrieveRefsToInclude() + { + QJsonArray result; + qSwap(refsToInclude, result); + return result; + } + + QJsonValue lookup(int handle, bool addRefs = true) + { + if (handle < 0) + handle = -handle; + + if (addRefs) + foreach (int ref, refsByHandle[handle]) + refsToInclude.append(lookup(ref, false)); + return refs[handle]; + } + + QJsonObject makeRef(int refId) + { + QJsonObject ref; + ref[QLatin1String("ref")] = refId; + return ref; + } + + QJsonObject addFunctionRef(const QString &name) + { + const int refId = newRefId(); + + QJsonObject func; + func[QLatin1String("handle")] = refId; + func[QLatin1String("type")] = QStringLiteral("function"); + func[QLatin1String("className")] = QStringLiteral("Function"); + func[QLatin1String("name")] = name; + insertRef(func, refId); + + return makeRef(refId); + } + + QJsonObject addScriptRef(const QString &name) + { + const int refId = newRefId(); + + QJsonObject func; + func[QLatin1String("handle")] = refId; + func[QLatin1String("type")] = QStringLiteral("script"); + func[QLatin1String("name")] = name; + insertRef(func, refId); + + return makeRef(refId); + } + + QJsonObject addObjectRef(QJsonObject obj, bool anonymous) + { + int ref = newRefId(); + + if (anonymous) + ref = -ref; + obj[QLatin1String("handle")] = ref; + obj[QLatin1String("type")] = QStringLiteral("object"); + insertRef(obj, ref); + QSet used; + qSwap(usedRefs, used); + refsByHandle.insert(ref, used); + + return makeRef(ref); + } + +protected: + virtual void addUndefined(const QString &name) + { + QJsonObject o; + addHandle(name, o, QStringLiteral("undefined")); + } + + virtual void addNull(const QString &name) + { + QJsonObject o; + addHandle(name, o, QStringLiteral("null")); + } + + virtual void addBoolean(const QString &name, bool value) + { + QJsonObject o; + o[QLatin1String("value")] = value; + addHandle(name, o, QStringLiteral("boolean")); + } + + virtual void addString(const QString &name, const QString &value) + { + QJsonObject o; + o[QLatin1String("value")] = value; + addHandle(name, o, QStringLiteral("string")); + } + + virtual void addObject(const QString &name, const QV4::Value &value) + { + QV4::Scope scope(engine()); + QV4::ScopedObject obj(scope, value.as()); + + int ref = cachedObjectRef(obj); + if (ref != -1) { + addNameRefPair(name, ref); + } else { + int ref = newRefId(); + cacheObjectRef(obj, ref); + + QJsonArray properties, *prev = &properties; + QSet used; + qSwap(usedRefs, used); + qSwap(destination, prev); + collect(obj); + qSwap(destination, prev); + qSwap(usedRefs, used); + + QJsonObject o; + o[QLatin1String("properties")] = properties; + addHandle(name, o, QStringLiteral("object"), ref); + refsByHandle.insert(ref, used); + } + } + + virtual void addInteger(const QString &name, int value) + { + QJsonObject o; + o[QLatin1String("value")] = value; + addHandle(name, o, QStringLiteral("number")); + } + + virtual void addDouble(const QString &name, double value) + { + QJsonObject o; + o[QLatin1String("value")] = value; + addHandle(name, o, QStringLiteral("number")); + } + +private: + int addHandle(const QString &name, QJsonObject object, const QString &type, int suppliedRef = -1) + { + Q_ASSERT(destination); + + object[QLatin1String("type")] = type; + + QJsonDocument tmp; + tmp.setObject(object); + QByteArray key = tmp.toJson(QJsonDocument::Compact); + + int ref; + if (suppliedRef == -1) { + ref = refCache.value(key, -1); + if (ref == -1) { + ref = newRefId(); + object[QLatin1String("handle")] = ref; + insertRef(object, ref); + refCache.insert(key, ref); + } + } else { + ref = suppliedRef; + object[QLatin1String("handle")] = ref; + insertRef(object, ref); + refCache.insert(key, ref); + } + + addNameRefPair(name, ref); + return ref; + } + + void addNameRefPair(const QString &name, int ref) + { + QJsonObject nameValuePair; + nameValuePair[QLatin1String("name")] = name; + if (isProperty()) { + nameValuePair[QLatin1String("ref")] = ref; + } else { + QJsonObject refObj; + refObj[QLatin1String("ref")] = ref; + nameValuePair[QLatin1String("value")] = refObj; + } + destination->append(nameValuePair); + usedRefs.insert(ref); + } + + int newRefId() + { + int ref = refs.count(); + refs.insert(ref, QJsonValue()); + return ref; + } + + void insertRef(const QJsonValue &value, int refId) + { + if (refId < 0) + refId = -refId; + + refs.insert(refId, value); + refsToInclude.append(value); + } + + void cacheObjectRef(QV4::Object *obj, int ref) + { + objectRefs.insert(obj, ref); + } + + int cachedObjectRef(QV4::Object *obj) const + { + return objectRefs.value(obj, -1); + } + +private: + QJsonArray refsToInclude; + QHash refs; + QHash refCache; + QJsonArray *destination; + QSet usedRefs; + QHash > refsByHandle; + QHash objectRefs; +}; + +int QV4DebugServiceImpl::debuggerIndex = 0; +int QV4DebugServiceImpl::sequence = 0; + +class V8CommandHandler +{ +public: + V8CommandHandler(const QString &command) + : cmd(command) + {} + + virtual ~V8CommandHandler() + {} + + QString command() const { return cmd; } + + void handle(const QJsonObject &request, QV4DebugServiceImpl *s) + { + TRACE_PROTOCOL(qDebug() << "handling command" << command() << "..."); + + req = request; + seq = req.value(QStringLiteral("seq")); + debugService = s; + + handleRequest(); + if (!response.isEmpty()) { + response[QLatin1String("type")] = QStringLiteral("response"); + debugService->send(response); + } + + debugService = 0; + seq = QJsonValue(); + req = QJsonObject(); + response = QJsonObject(); + } + + virtual void handleRequest() = 0; + +protected: + void addCommand() { response.insert(QStringLiteral("command"), cmd); } + void addRequestSequence() { response.insert(QStringLiteral("request_seq"), seq); } + void addSuccess(bool success) { response.insert(QStringLiteral("success"), success); } + void addBody(const QJsonObject &body) + { + response.insert(QStringLiteral("body"), body); + } + + void addRunning() + { + response.insert(QStringLiteral("running"), debugService->debuggerAgent.isRunning()); + } + + void addRefs() + { + response.insert(QStringLiteral("refs"), debugService->buildRefs()); + } + + void createErrorResponse(const QString &msg) + { + QJsonValue command = req.value(QStringLiteral("command")); + response.insert(QStringLiteral("command"), command); + addRequestSequence(); + addSuccess(false); + addRunning(); + response.insert(QStringLiteral("message"), msg); + } + + int requestSequenceNr() const + { return seq.toInt(-1); } + +protected: + QString cmd; + QJsonObject req; + QJsonValue seq; + QV4DebugServiceImpl *debugService; + QJsonObject response; +}; + +class UnknownV8CommandHandler: public V8CommandHandler +{ +public: + UnknownV8CommandHandler(): V8CommandHandler(QString()) {} + + virtual void handleRequest() + { + QString msg = QStringLiteral("unimplemented command \""); + msg += req.value(QStringLiteral("command")).toString(); + msg += QStringLiteral("\""); + createErrorResponse(msg); + } +}; + +namespace { +class V8VersionRequest: public V8CommandHandler +{ +public: + V8VersionRequest(): V8CommandHandler(QStringLiteral("version")) {} + + virtual void handleRequest() + { + addCommand(); + addRequestSequence(); + addSuccess(true); + addRunning(); + QJsonObject body; + body.insert(QStringLiteral("V8Version"), + QLatin1String("this is not V8, this is V4 in Qt " QT_VERSION_STR)); + addBody(body); + } +}; + +class V8SetBreakPointRequest: public V8CommandHandler +{ +public: + V8SetBreakPointRequest(): V8CommandHandler(QStringLiteral("setbreakpoint")) {} + + virtual void handleRequest() + { + // decypher the payload: + QJsonObject args = req.value(QStringLiteral("arguments")).toObject(); + if (args.isEmpty()) + return; + + QString type = args.value(QStringLiteral("type")).toString(); + if (type != QStringLiteral("scriptRegExp")) { + createErrorResponse(QStringLiteral("breakpoint type \"%1\" is not implemented").arg(type)); + return; + } + + QString fileName = args.value(QStringLiteral("target")).toString(); + if (fileName.isEmpty()) { + createErrorResponse(QStringLiteral("breakpoint has no file name")); + return; + } + + int line = args.value(QStringLiteral("line")).toInt(-1); + if (line < 0) { + createErrorResponse(QStringLiteral("breakpoint has an invalid line number")); + return; + } + + bool enabled = args.value(QStringLiteral("enabled")).toBool(true); + QString condition = args.value(QStringLiteral("condition")).toString(); + + // set the break point: + int id = debugService->debuggerAgent.addBreakPoint(fileName, line + 1, enabled, condition); + + // response: + addCommand(); + addRequestSequence(); + addSuccess(true); + addRunning(); + QJsonObject body; + body.insert(QStringLiteral("type"), type); + body.insert(QStringLiteral("breakpoint"), id); + // It's undocumented, but V8 sends back an actual_locations array too. However, our + // Debugger currently doesn't tell us when it resolved a breakpoint, so we'll leave them + // pending until the breakpoint is hit for the first time. + addBody(body); + } +}; + +class V8ClearBreakPointRequest: public V8CommandHandler +{ +public: + V8ClearBreakPointRequest(): V8CommandHandler(QStringLiteral("clearbreakpoint")) {} + + virtual void handleRequest() + { + // decypher the payload: + QJsonObject args = req.value(QStringLiteral("arguments")).toObject(); + if (args.isEmpty()) + return; + + int id = args.value(QStringLiteral("breakpoint")).toInt(-1); + if (id < 0) { + createErrorResponse(QStringLiteral("breakpoint has an invalid number")); + return; + } + + // remove the break point: + debugService->debuggerAgent.removeBreakPoint(id); + + // response: + addCommand(); + addRequestSequence(); + addSuccess(true); + addRunning(); + QJsonObject body; + body.insert(QStringLiteral("type"), QStringLiteral("scriptRegExp")); + body.insert(QStringLiteral("breakpoint"), id); + addBody(body); + } +}; + +class V8BacktraceRequest: public V8CommandHandler +{ +public: + V8BacktraceRequest(): V8CommandHandler(QStringLiteral("backtrace")) {} + + virtual void handleRequest() + { + // decypher the payload: + + QJsonObject arguments = req.value(QStringLiteral("arguments")).toObject(); + int fromFrame = arguments.value(QStringLiteral("fromFrame")).toInt(0); + 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(); + + QJsonArray frameArray; + QVector frames = debugger->stackTrace(toFrame); + for (int i = fromFrame; i < toFrame && i < frames.size(); ++i) + frameArray.push_back(debugService->buildFrame(frames[i], i, debugger)); + + // response: + addCommand(); + addRequestSequence(); + addSuccess(true); + addRunning(); + QJsonObject body; + if (frameArray.isEmpty()) { + body.insert(QStringLiteral("totalFrames"), 0); + } else { + body.insert(QStringLiteral("fromFrame"), fromFrame); + body.insert(QStringLiteral("toFrame"), fromFrame + frameArray.size()); + body.insert(QStringLiteral("frames"), frameArray); + } + addBody(body); + addRefs(); + } +}; + +class V8FrameRequest: public V8CommandHandler +{ +public: + V8FrameRequest(): V8CommandHandler(QStringLiteral("frame")) {} + + virtual void handleRequest() + { + // decypher the payload: + QJsonObject arguments = req.value(QStringLiteral("arguments")).toObject(); + const int frameNr = arguments.value(QStringLiteral("number")).toInt( + debugService->selectedFrame()); + + QV4::Debugging::Debugger *debugger = debugService->debuggerAgent.firstDebugger(); + QVector frames = debugger->stackTrace(frameNr + 1); + if (frameNr < 0 || frameNr >= frames.size()) { + createErrorResponse(QStringLiteral("frame command has invalid frame number")); + return; + } + + debugService->selectFrame(frameNr); + QJsonObject frame = debugService->buildFrame(frames[frameNr], frameNr, debugger); + + // response: + addCommand(); + addRequestSequence(); + addSuccess(true); + addRunning(); + addBody(frame); + addRefs(); + } +}; + +class V8ScopeRequest: public V8CommandHandler +{ +public: + V8ScopeRequest(): V8CommandHandler(QStringLiteral("scope")) {} + + virtual void handleRequest() + { + // decypher the payload: + QJsonObject arguments = req.value(QStringLiteral("arguments")).toObject(); + const int frameNr = arguments.value(QStringLiteral("frameNumber")).toInt( + debugService->selectedFrame()); + const int scopeNr = arguments.value(QStringLiteral("number")).toInt(0); + + QV4::Debugging::Debugger *debugger = debugService->debuggerAgent.firstDebugger(); + QVector frames = debugger->stackTrace(frameNr + 1); + if (frameNr < 0 || frameNr >= frames.size()) { + createErrorResponse(QStringLiteral("scope command has invalid frame number")); + return; + } + if (scopeNr < 0) { + createErrorResponse(QStringLiteral("scope command has invalid scope number")); + return; + } + + QJsonObject scope = debugService->buildScope(frameNr, scopeNr, debugger); + + // response: + addCommand(); + addRequestSequence(); + addSuccess(true); + addRunning(); + addBody(scope); + addRefs(); + } +}; + +class V8LookupRequest: public V8CommandHandler +{ +public: + V8LookupRequest(): V8CommandHandler(QStringLiteral("lookup")) {} + + virtual void handleRequest() + { + // decypher the payload: + QJsonObject arguments = req.value(QStringLiteral("arguments")).toObject(); + QJsonArray handles = arguments.value(QStringLiteral("handles")).toArray(); + + QJsonObject body; + foreach (const QJsonValue &handle, handles) + body[QString::number(handle.toInt())] = debugService->lookup(handle.toInt()); + + // response: + addCommand(); + addRequestSequence(); + addSuccess(true); + addRunning(); + addBody(body); + addRefs(); + } +}; + +class V8ContinueRequest: public V8CommandHandler +{ +public: + V8ContinueRequest(): V8CommandHandler(QStringLiteral("continue")) {} + + virtual void handleRequest() + { + // decypher the payload: + QJsonObject arguments = req.value(QStringLiteral("arguments")).toObject(); + + QV4::Debugging::Debugger *debugger = debugService->debuggerAgent.firstDebugger(); + + if (arguments.empty()) { + debugger->resume(QV4::Debugging::Debugger::FullThrottle); + } else { + QJsonObject arguments = req.value(QStringLiteral("arguments")).toObject(); + QString stepAction = arguments.value(QStringLiteral("stepaction")).toString(); + const int stepcount = arguments.value(QStringLiteral("stepcount")).toInt(1); + if (stepcount != 1) + qWarning() << "Step count other than 1 is not supported."; + + if (stepAction == QStringLiteral("in")) { + debugger->resume(QV4::Debugging::Debugger::StepIn); + } else if (stepAction == QStringLiteral("out")) { + debugger->resume(QV4::Debugging::Debugger::StepOut); + } else if (stepAction == QStringLiteral("next")) { + debugger->resume(QV4::Debugging::Debugger::StepOver); + } else { + createErrorResponse(QStringLiteral("continue command has invalid stepaction")); + return; + } + } + + // response: + addCommand(); + addRequestSequence(); + addSuccess(true); + addRunning(); + } +}; + +class V8DisconnectRequest: public V8CommandHandler +{ +public: + V8DisconnectRequest(): V8CommandHandler(QStringLiteral("disconnect")) {} + + virtual void handleRequest() + { + debugService->debuggerAgent.removeAllBreakPoints(); + debugService->debuggerAgent.resumeAll(); + + // response: + addCommand(); + addRequestSequence(); + addSuccess(true); + addRunning(); + } +}; + +class V8SetExceptionBreakRequest: public V8CommandHandler +{ +public: + V8SetExceptionBreakRequest(): V8CommandHandler(QStringLiteral("setexceptionbreak")) {} + + virtual void handleRequest() + { + bool wasEnabled = debugService->debuggerAgent.breakOnThrow(); + + //decypher the payload: + QJsonObject arguments = req.value(QStringLiteral("arguments")).toObject(); + QString type = arguments.value(QStringLiteral("type")).toString(); + bool enabled = arguments.value(QStringLiteral("number")).toBool(!wasEnabled); + + if (type == QStringLiteral("all")) { + // that's fine + } else if (type == QStringLiteral("uncaught")) { + createErrorResponse(QStringLiteral("breaking only on uncaught exceptions is not supported yet")); + return; + } else { + createErrorResponse(QStringLiteral("invalid type for break on exception")); + return; + } + + // do it: + debugService->debuggerAgent.setBreakOnThrow(enabled); + + QJsonObject body; + body[QLatin1String("type")] = type; + body[QLatin1String("enabled")] = debugService->debuggerAgent.breakOnThrow(); + + // response: + addBody(body); + addRunning(); + addSuccess(true); + addRequestSequence(); + addCommand(); + } +}; + +class V8ScriptsRequest: public V8CommandHandler +{ +public: + V8ScriptsRequest(): V8CommandHandler(QStringLiteral("scripts")) {} + + virtual void handleRequest() + { + //decypher the payload: + QJsonObject arguments = req.value(QStringLiteral("arguments")).toObject(); + int types = arguments.value(QStringLiteral("types")).toInt(-1); + if (types < 0 || types > 7) { + createErrorResponse(QStringLiteral("invalid types value in scripts command")); + return; + } else if (types != 4) { + createErrorResponse(QStringLiteral("unsupported types value in scripts command")); + return; + } + + // do it: + debugService->debuggerAgent.firstDebugger()->gatherSources(requestSequenceNr()); + + // response will be send by + } +}; + +// Request: +// { +// "seq": 4, +// "type": "request", +// "command": "evaluate", +// "arguments": { +// "expression": "a", +// "frame": 0 +// } +// } +// +// Response: +// { +// "body": { +// "handle": 3, +// "type": "number", +// "value": 1 +// }, +// "command": "evaluate", +// "refs": [], +// "request_seq": 4, +// "running": false, +// "seq": 5, +// "success": true, +// "type": "response" +// } +// +// The "value" key in "body" is the result of evaluating the expression in the request. +class V8EvaluateRequest: public V8CommandHandler +{ +public: + V8EvaluateRequest(): V8CommandHandler(QStringLiteral("evaluate")) {} + + virtual void handleRequest() + { + //decypher the payload: + QJsonObject arguments = req.value(QStringLiteral("arguments")).toObject(); + QString expression = arguments.value(QStringLiteral("expression")).toString(); + const int frame = arguments.value(QStringLiteral("frame")).toInt(0); + + QV4::Debugging::Debugger *debugger = debugService->debuggerAgent.firstDebugger(); + Q_ASSERT(debugger->state() == QV4::Debugging::Debugger::Paused); + + VariableCollector *collector = debugService->collector(); + QJsonArray dest; + collector->setDestination(&dest); + debugger->evaluateExpression(frame, expression, collector); + + const int ref = dest.at(0).toObject().value(QStringLiteral("value")).toObject() + .value(QStringLiteral("ref")).toInt(); + + // response: + addCommand(); + addRequestSequence(); + addSuccess(true); + addRunning(); + addBody(collector->lookup(ref).toObject()); + addRefs(); + } +}; +} // anonymous namespace + +void QV4DebugServiceImpl::addHandler(V8CommandHandler* handler) +{ + handlers[handler->command()] = handler; +} + +V8CommandHandler *QV4DebugServiceImpl::v8CommandHandler(const QString &command) const +{ + V8CommandHandler *handler = handlers.value(command, 0); + if (handler) + return handler; + else + return unknownV8CommandHandler.data(); +} + +QV4DebugServiceImpl::QV4DebugServiceImpl(QObject *parent) : + QQmlConfigurableDebugService(1, parent), + debuggerAgent(this), version(1), theSelectedFrame(0), + unknownV8CommandHandler(new UnknownV8CommandHandler) +{ + addHandler(new V8VersionRequest); + addHandler(new V8SetBreakPointRequest); + addHandler(new V8ClearBreakPointRequest); + addHandler(new V8BacktraceRequest); + addHandler(new V8FrameRequest); + addHandler(new V8ScopeRequest); + addHandler(new V8LookupRequest); + addHandler(new V8ContinueRequest); + addHandler(new V8DisconnectRequest); + addHandler(new V8SetExceptionBreakRequest); + addHandler(new V8ScriptsRequest); + addHandler(new V8EvaluateRequest); +} + +QV4DebugServiceImpl::~QV4DebugServiceImpl() +{ + qDeleteAll(handlers); +} + +void QV4DebugServiceImpl::engineAboutToBeAdded(QQmlEngine *engine) +{ + QMutexLocker lock(&m_configMutex); + if (engine) { + QV4::ExecutionEngine *ee = QV8Engine::getV4(engine->handle()); + if (QQmlDebugConnector *server = QQmlDebugConnector::instance()) { + if (ee) { + ee->enableDebugger(); + QV4::Debugging::Debugger *debugger = ee->debugger; + debuggerMap.insert(debuggerIndex++, debugger); + debuggerAgent.addDebugger(debugger); + debuggerAgent.moveToThread(server->thread()); + } + } + } + QQmlConfigurableDebugService::engineAboutToBeAdded(engine); +} + +void QV4DebugServiceImpl::engineAboutToBeRemoved(QQmlEngine *engine) +{ + QMutexLocker lock(&m_configMutex); + if (engine){ + const QV4::ExecutionEngine *ee = QV8Engine::getV4(engine->handle()); + if (ee) { + QV4::Debugging::Debugger *debugger = ee->debugger; + typedef QMap::const_iterator DebuggerMapIterator; + const DebuggerMapIterator end = debuggerMap.constEnd(); + for (DebuggerMapIterator i = debuggerMap.constBegin(); i != end; ++i) { + if (i.value() == debugger) { + debuggerMap.remove(i.key()); + break; + } + } + debuggerAgent.removeDebugger(debugger); + } + } + QQmlConfigurableDebugService::engineAboutToBeRemoved(engine); +} + +void QV4DebugServiceImpl::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 QV4DebugServiceImpl::messageReceived(const QByteArray &message) +{ + QMutexLocker lock(&m_configMutex); + + QQmlDebugStream ms(message); + QByteArray header; + ms >> header; + + TRACE_PROTOCOL(qDebug() << "received message with header" << header); + + if (header == "V8DEBUG") { + QByteArray type; + QByteArray payload; + ms >> type >> payload; + TRACE_PROTOCOL(qDebug() << "... type:" << type); + + if (type == V4_CONNECT) { + emit messageToClient(name(), packMessage(type)); + stopWaiting(); + } else if (type == V4_PAUSE) { + debuggerAgent.pauseAll(); + sendSomethingToSomebody(type); + } else if (type == V4_BREAK_ON_SIGNAL) { + QByteArray signal; + bool enabled; + ms >> signal >> enabled; + //Normalize to lower case. + QString signalName(QString::fromUtf8(signal).toLower()); + if (enabled) + breakOnSignals.append(signalName); + else + breakOnSignals.removeOne(signalName); + } else if (type == "v8request") { + handleV8Request(payload); + } else if (type == V4_DISCONNECT) { + TRACE_PROTOCOL(qDebug() << "... payload:" << payload.constData()); + handleV8Request(payload); + } else { + sendSomethingToSomebody(type, 0); + } + } +} + +void QV4DebugServiceImpl::sendSomethingToSomebody(const char *type, int magicNumber) +{ + QByteArray response; + QQmlDebugStream rs(&response, QIODevice::WriteOnly); + rs << QByteArray(type) + << QByteArray::number(version) << QByteArray::number(magicNumber); + emit messageToClient(name(), packMessage(type, response)); +} + +QV4DebuggerAgent::QV4DebuggerAgent(QV4DebugServiceImpl *debugService) + : debugService(debugService) +{} + +QV4::Debugging::Debugger *QV4DebuggerAgent::firstDebugger() const +{ + // Currently only 1 single engine is supported, so: + if (m_debuggers.isEmpty()) + return 0; + else + return m_debuggers.first(); +} + +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; + else + return false; +} + +void QV4DebuggerAgent::debuggerPaused(QV4::Debugging::Debugger *debugger, QV4::Debugging::PauseReason reason) +{ + Q_UNUSED(reason); + + debugService->clearHandles(debugger->engine()); + + QJsonObject event, body, script; + event.insert(QStringLiteral("type"), QStringLiteral("event")); + + switch (reason) { + case QV4::Debugging::Step: + case QV4::Debugging::PauseRequest: + case QV4::Debugging::BreakPoint: { + event.insert(QStringLiteral("event"), QStringLiteral("break")); + QVector frames = debugger->stackTrace(1); + if (frames.isEmpty()) + break; + + const QV4::StackFrame &topFrame = frames.first(); + body.insert(QStringLiteral("invocationText"), topFrame.function); + body.insert(QStringLiteral("sourceLine"), topFrame.line - 1); + if (topFrame.column > 0) + body.insert(QStringLiteral("sourceColumn"), topFrame.column); + QJsonArray breakPoints; + foreach (int breakPointId, breakPointIds(topFrame.source, topFrame.line)) + breakPoints.push_back(breakPointId); + body.insert(QStringLiteral("breakpoints"), breakPoints); + script.insert(QStringLiteral("name"), topFrame.source); + } break; + case QV4::Debugging::Throwing: + // TODO: complete this! + event.insert(QStringLiteral("event"), QStringLiteral("exception")); + break; + } + + if (!script.isEmpty()) + body.insert(QStringLiteral("script"), script); + if (!body.isEmpty()) + event.insert(QStringLiteral("body"), body); + debugService->send(event); +} + +void QV4DebuggerAgent::sourcesCollected(QV4::Debugging::Debugger *debugger, QStringList sources, int requestSequenceNr) +{ + QJsonArray body; + foreach (const QString &source, sources) { + QJsonObject src; + src[QLatin1String("name")] = source; + src[QLatin1String("scriptType")] = 4; + body.append(src); + } + + QJsonObject response; + response[QLatin1String("success")] = true; + response[QLatin1String("running")] = debugger->state() == QV4::Debugging::Debugger::Running; + response[QLatin1String("body")] = body; + response[QLatin1String("command")] = QStringLiteral("scripts"); + response[QLatin1String("request_seq")] = requestSequenceNr; + response[QLatin1String("type")] = QStringLiteral("response"); + debugService->send(response); +} + +void QV4DebugServiceImpl::handleV8Request(const QByteArray &payload) +{ + TRACE_PROTOCOL(qDebug() << "v8request, payload:" << payload.constData()); + + QJsonDocument request = QJsonDocument::fromJson(payload); + QJsonObject o = request.object(); + QJsonValue type = o.value(QStringLiteral("type")); + if (type.toString() == QStringLiteral("request")) { + QJsonValue command = o.value(QStringLiteral("command")); + V8CommandHandler *h = v8CommandHandler(command.toString()); + if (h) + h->handle(o, this); + } +} + +QByteArray QV4DebugServiceImpl::packMessage(const QByteArray &command, const QByteArray &message) +{ + QByteArray reply; + QQmlDebugStream rs(&reply, QIODevice::WriteOnly); + static const QByteArray cmd("V8DEBUG"); + rs << cmd << command << message; + return reply; +} + +void QV4DebugServiceImpl::send(QJsonObject v8Payload) +{ + v8Payload[QLatin1String("seq")] = sequence++; + QJsonDocument doc; + doc.setObject(v8Payload); +#ifdef NO_PROTOCOL_TRACING + QByteArray responseData = doc.toJson(QJsonDocument::Compact); +#else + QByteArray responseData = doc.toJson(QJsonDocument::Indented); +#endif + + TRACE_PROTOCOL(qDebug() << "sending response for:" << responseData.constData() << endl); + + emit messageToClient(name(), packMessage("v8message", responseData)); +} + +void QV4DebugServiceImpl::clearHandles(QV4::ExecutionEngine *engine) +{ + theCollector.reset(new VariableCollector(engine)); +} + +QJsonObject QV4DebugServiceImpl::buildFrame(const QV4::StackFrame &stackFrame, int frameNr, + QV4::Debugging::Debugger *debugger) +{ + QJsonObject frame; + frame[QLatin1String("index")] = frameNr; + frame[QLatin1String("debuggerFrame")] = false; + frame[QLatin1String("func")] = theCollector->addFunctionRef(stackFrame.function); + frame[QLatin1String("script")] = theCollector->addScriptRef(stackFrame.source); + frame[QLatin1String("line")] = stackFrame.line - 1; + if (stackFrame.column >= 0) + frame[QLatin1String("column")] = stackFrame.column; + + QJsonArray properties; + theCollector->setDestination(&properties); + if (debugger->collectThisInContext(theCollector.data(), frameNr)) { + QJsonObject obj; + obj[QLatin1String("properties")] = properties; + frame[QLatin1String("receiver")] = theCollector->addObjectRef(obj, false); + } + + QJsonArray scopes; + // Only type and index are used by Qt Creator, so we keep it easy: + QVector scopeTypes = debugger->getScopeTypes(frameNr); + for (int i = 0, ei = scopeTypes.count(); i != ei; ++i) { + int type = encodeScopeType(scopeTypes[i]); + if (type == -1) + continue; + + QJsonObject scope; + scope[QLatin1String("index")] = i; + scope[QLatin1String("type")] = type; + scopes.push_back(scope); + } + frame[QLatin1String("scopes")] = scopes; + + return frame; +} + +int QV4DebugServiceImpl::encodeScopeType(QV4::Heap::ExecutionContext::ContextType scopeType) +{ + switch (scopeType) { + case QV4::Heap::ExecutionContext::Type_GlobalContext: + return 0; + break; + case QV4::Heap::ExecutionContext::Type_CatchContext: + return 4; + break; + case QV4::Heap::ExecutionContext::Type_WithContext: + return 2; + break; + case QV4::Heap::ExecutionContext::Type_SimpleCallContext: + case QV4::Heap::ExecutionContext::Type_CallContext: + return 1; + break; + case QV4::Heap::ExecutionContext::Type_QmlContext: + default: + return -1; + } +} + +QJsonObject QV4DebugServiceImpl::buildScope(int frameNr, int scopeNr, + QV4::Debugging::Debugger *debugger) +{ + QJsonObject scope; + + QJsonArray properties; + theCollector->collectScope(&properties, debugger, frameNr, scopeNr); + + QJsonObject anonymous; + anonymous[QLatin1String("properties")] = properties; + + QVector scopeTypes = debugger->getScopeTypes(frameNr); + scope[QLatin1String("type")] = encodeScopeType(scopeTypes[scopeNr]); + scope[QLatin1String("index")] = scopeNr; + scope[QLatin1String("frameIndex")] = frameNr; + scope[QLatin1String("object")] = theCollector->addObjectRef(anonymous, true); + + return scope; +} + +QJsonValue QV4DebugServiceImpl::lookup(int refId) const +{ + return theCollector->lookup(refId); +} + +QJsonArray QV4DebugServiceImpl::buildRefs() +{ + return theCollector->retrieveRefsToInclude(); +} + +VariableCollector *QV4DebugServiceImpl::collector() const +{ + return theCollector.data(); +} + +void QV4DebugServiceImpl::selectFrame(int frameNr) +{ + theSelectedFrame = frameNr; +} + +int QV4DebugServiceImpl::selectedFrame() const +{ + return theSelectedFrame; +} + +QT_END_NAMESPACE diff --git a/src/plugins/qmltooling/qmldbg_debugger/qv4debugservice.h b/src/plugins/qmltooling/qmldbg_debugger/qv4debugservice.h new file mode 100644 index 0000000000..b009a0fead --- /dev/null +++ b/src/plugins/qmltooling/qmldbg_debugger/qv4debugservice.h @@ -0,0 +1,139 @@ +/**************************************************************************** +** +** 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 QV4DEBUGSERVICE_H +#define QV4DEBUGSERVICE_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include +#include +#include + +#include + +QT_BEGIN_NAMESPACE + +namespace QV4 { struct ExecutionEngine; } + +class QQmlEngine; +class VariableCollector; +class V8CommandHandler; +class UnknownV8CommandHandler; +class QV4DebugServiceImpl; + +class QV4DebuggerAgent : public QV4::Debugging::DebuggerAgent +{ + Q_OBJECT +public: + QV4DebuggerAgent(QV4DebugServiceImpl *debugService); + QV4::Debugging::Debugger *firstDebugger() const; + bool isRunning() const; + +public slots: + virtual void debuggerPaused(QV4::Debugging::Debugger *debugger, + QV4::Debugging::PauseReason reason); + virtual void sourcesCollected(QV4::Debugging::Debugger *debugger, QStringList sources, + int requestSequenceNr); + +private: + QV4DebugServiceImpl *debugService; +}; + +class QV4DebugServiceImpl : public QQmlConfigurableDebugService +{ + Q_OBJECT +public: + explicit QV4DebugServiceImpl(QObject *parent = 0); + ~QV4DebugServiceImpl(); + + void engineAboutToBeAdded(QQmlEngine *engine); + void engineAboutToBeRemoved(QQmlEngine *engine); + + void signalEmitted(const QString &signal); + void send(QJsonObject v8Payload); + + QJsonObject buildScope(int frameNr, int scopeNr, QV4::Debugging::Debugger *debugger); + QJsonArray buildRefs(); + QJsonValue lookup(int refId) const; + + QJsonObject buildFrame(const QV4::StackFrame &stackFrame, int frameNr, + QV4::Debugging::Debugger *debugger); + int selectedFrame() const; + void selectFrame(int frameNr); + + void clearHandles(QV4::ExecutionEngine *engine); + + VariableCollector *collector() const; + QV4DebuggerAgent debuggerAgent; + +protected: + void messageReceived(const QByteArray &); + void sendSomethingToSomebody(const char *type, int magicNumber = 1); + +private: + friend class QQmlDebuggerServiceFactory; + + void handleV8Request(const QByteArray &payload); + static QByteArray packMessage(const QByteArray &command, + const QByteArray &message = QByteArray()); + void processCommand(const QByteArray &command, const QByteArray &data); + V8CommandHandler *v8CommandHandler(const QString &command) const; + int encodeScopeType(QV4::Heap::ExecutionContext::ContextType scopeType); + + QStringList breakOnSignals; + QMap debuggerMap; + static int debuggerIndex; + static int sequence; + const int version; + + QScopedPointer theCollector; + int theSelectedFrame; + + void addHandler(V8CommandHandler* handler); + QHash handlers; + QScopedPointer unknownV8CommandHandler; +}; + +QT_END_NAMESPACE + +#endif // QV4DEBUGSERVICE_H diff --git a/src/plugins/qmltooling/qmltooling.pro b/src/plugins/qmltooling/qmltooling.pro index cfcf631f1a..ae13826a4c 100644 --- a/src/plugins/qmltooling/qmltooling.pro +++ b/src/plugins/qmltooling/qmltooling.pro @@ -1,6 +1,7 @@ TEMPLATE = subdirs SUBDIRS += \ + qmldbg_debugger \ qmldbg_local \ qmldbg_profiler \ qmldbg_server \ -- cgit v1.2.3