summaryrefslogtreecommitdiffstats
path: root/src/qscriptdebuggerconnector.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/qscriptdebuggerconnector.cpp')
-rw-r--r--src/qscriptdebuggerconnector.cpp395
1 files changed, 395 insertions, 0 deletions
diff --git a/src/qscriptdebuggerconnector.cpp b/src/qscriptdebuggerconnector.cpp
new file mode 100644
index 0000000..830b0db
--- /dev/null
+++ b/src/qscriptdebuggerconnector.cpp
@@ -0,0 +1,395 @@
+#include "qscriptdebuggerconnector.h"
+#include <QtCore/qeventloop.h>
+#include <QtNetwork/qtcpserver.h>
+#include <QtNetwork/qtcpsocket.h>
+#include <QtScript/qscriptengine.h>
+#include <private/qscriptdebuggerbackend_p.h>
+#include <private/qscriptdebuggercommand_p.h>
+#include <private/qscriptdebuggerevent_p.h>
+#include <private/qscriptdebuggerresponse_p.h>
+#include <private/qscriptdebuggercommandexecutor_p.h>
+#include <private/qscriptbreakpointdata_p.h>
+#include <private/qscriptdebuggerobjectsnapshotdelta_p.h>
+
+// #define DEBUGGERCONNECTOR_DEBUG
+
+void qScriptDebugRegisterMetaTypes();
+
+class QScriptRemoteTargetDebuggerBackend : public QObject,
+ public QScriptDebuggerBackend
+{
+ Q_OBJECT
+public:
+ enum Error {
+ NoError,
+ HostNotFoundError,
+ ConnectionRefusedError,
+ HandshakeError,
+ SocketError
+ };
+
+ QScriptRemoteTargetDebuggerBackend();
+ ~QScriptRemoteTargetDebuggerBackend();
+
+ void connectToDebugger(const QHostAddress &address, quint16 port);
+ void disconnectFromDebugger();
+
+ bool listen(const QHostAddress &address, quint16 port);
+
+ void resume();
+
+Q_SIGNALS:
+ void connected();
+ void disconnected();
+ void error(Error error);
+
+protected:
+ void event(const QScriptDebuggerEvent &event);
+
+private Q_SLOTS:
+ void onSocketStateChanged(QAbstractSocket::SocketState);
+ void onSocketError(QAbstractSocket::SocketError);
+ void onReadyRead();
+ void onNewConnection();
+
+private:
+ enum State {
+ UnconnectedState,
+ HandshakingState,
+ ConnectedState
+ };
+
+private:
+ State m_state;
+ QTcpSocket *m_socket;
+ int m_blockSize;
+ QTcpServer *m_server;
+ QList<QEventLoop*> m_eventLoopPool;
+ QList<QEventLoop*> m_eventLoopStack;
+
+private:
+ Q_DISABLE_COPY(QScriptRemoteTargetDebuggerBackend)
+};
+
+QScriptRemoteTargetDebuggerBackend::QScriptRemoteTargetDebuggerBackend()
+ : m_state(UnconnectedState), m_socket(0), m_blockSize(0), m_server(0)
+{
+ qScriptDebugRegisterMetaTypes();
+}
+
+QScriptRemoteTargetDebuggerBackend::~QScriptRemoteTargetDebuggerBackend()
+{
+}
+
+void QScriptRemoteTargetDebuggerBackend::connectToDebugger(const QHostAddress &address, quint16 port)
+{
+ Q_ASSERT(m_state == UnconnectedState);
+ if (!m_socket) {
+ m_socket = new QTcpSocket(this);
+ QObject::connect(m_socket, SIGNAL(stateChanged(QAbstractSocket::SocketState)),
+ this, SLOT(onSocketSateChanged(QAbstractSocket::SocketState)));
+ QObject::connect(m_socket, SIGNAL(error(QAbstractSocket::SocketError)),
+ this, SLOT(onSocketError(QAbstractSocket::SocketError)));
+ QObject::connect(m_socket, SIGNAL(readyRead()), this, SLOT(onReadyRead()));
+ }
+ m_socket->connectToHost(address, port);
+}
+
+void QScriptRemoteTargetDebuggerBackend::disconnectFromDebugger()
+{
+ if (!m_socket)
+ return;
+ m_socket->disconnectFromHost();
+}
+
+bool QScriptRemoteTargetDebuggerBackend::listen(const QHostAddress &address, quint16 port)
+{
+ if (m_socket)
+ return false;
+ if (!m_server) {
+ m_server = new QTcpServer();
+ QObject::connect(m_server, SIGNAL(newConnection()),
+ this, SLOT(onNewConnection()));
+ }
+ return m_server->listen(address, port);
+}
+
+void QScriptRemoteTargetDebuggerBackend::onSocketStateChanged(QAbstractSocket::SocketState s)
+{
+ if (s == QAbstractSocket::ConnectedState) {
+ m_state = HandshakingState;
+ } else if (s == QAbstractSocket::UnconnectedState) {
+ engine()->setAgent(0);
+ m_state = UnconnectedState;
+ emit disconnected();
+ }
+}
+
+void QScriptRemoteTargetDebuggerBackend::onSocketError(QAbstractSocket::SocketError err)
+{
+ Q_ASSERT_X(false, Q_FUNC_INFO, "implement me");
+}
+
+void QScriptRemoteTargetDebuggerBackend::onNewConnection()
+{
+ m_socket = m_server->nextPendingConnection();
+ m_server->close();
+ QObject::connect(m_socket, SIGNAL(stateChanged(QAbstractSocket::SocketState)),
+ this, SLOT(onSocketStateChanged(QAbstractSocket::SocketState)));
+ QObject::connect(m_socket, SIGNAL(error(QAbstractSocket::SocketError)),
+ this, SLOT(onSocketError(QAbstractSocket::SocketError)));
+ QObject::connect(m_socket, SIGNAL(readyRead()), this, SLOT(onReadyRead()));
+ // the handshake is initiated by the debugger side, so wait for it
+ m_state = HandshakingState;
+}
+
+void QScriptRemoteTargetDebuggerBackend::onReadyRead()
+{
+ switch (m_state) {
+ case UnconnectedState:
+ Q_ASSERT(0);
+ break;
+
+ case HandshakingState: {
+ QByteArray handshakeData("QtScriptDebug-Handshake");
+ if (m_socket->bytesAvailable() == handshakeData.size()) {
+ QByteArray ba = m_socket->read(handshakeData.size());
+ if (ba == handshakeData) {
+#ifdef DEBUGGERCONNECTOR_DEBUG
+ qDebug() << "sending handshake reply (" << handshakeData.size() << "bytes )";
+#endif
+ m_socket->write(handshakeData);
+ // handshaking complete
+ // ### a way to specify if a break should be triggered immediately,
+ // or only if an uncaught exception is triggered
+ interruptEvaluation();
+ m_state = ConnectedState;
+ emit connected();
+ } else {
+// d->error = QScriptDebuggerConnector::HandshakeError;
+// d->errorString = QString::fromLatin1("Incorrect handshake data received");
+ m_state = UnconnectedState;
+ emit error(HandshakeError);
+ m_socket->close();
+ }
+ }
+ } break;
+
+ case ConnectedState: {
+#ifdef DEBUGGERCONNECTOR_DEBUG
+ qDebug() << "received data. bytesAvailable:" << m_socket->bytesAvailable();
+#endif
+ QDataStream in(m_socket);
+ in.setVersion(QDataStream::Qt_4_5);
+ if (m_blockSize == 0) {
+ if (m_socket->bytesAvailable() < (int)sizeof(quint32))
+ return;
+ in >> m_blockSize;
+#ifdef DEBUGGERCONNECTOR_DEBUG
+ qDebug() << " blockSize:" << m_blockSize;
+#endif
+ }
+ if (m_socket->bytesAvailable() < m_blockSize)
+ return;
+
+#ifdef DEBUGGERCONNECTOR_DEBUG
+ qDebug() << "deserializing command";
+#endif
+ int wasAvailable = m_socket->bytesAvailable();
+ qint32 id;
+ in >> id;
+ QScriptDebuggerCommand command(QScriptDebuggerCommand::None);
+ in >> command;
+ Q_ASSERT(m_socket->bytesAvailable() == wasAvailable - m_blockSize);
+
+#ifdef DEBUGGERCONNECTOR_DEBUG
+ qDebug("executing command (id=%d, type=%d)", id, command.type());
+#endif
+ QScriptDebuggerResponse response = commandExecutor()->execute(this, command);
+
+#ifdef DEBUGGERCONNECTOR_DEBUG
+ qDebug() << "serializing response";
+#endif
+ QByteArray block;
+ QDataStream out(&block, QIODevice::WriteOnly);
+ out.setVersion(QDataStream::Qt_4_5);
+ out << (quint32)0; // reserve 4 bytes for block size
+ out << (quint8)1; // type = command response
+ out << id;
+ out << response;
+ out.device()->seek(0);
+ out << (quint32)(block.size() - sizeof(quint32));
+#ifdef DEBUGGERCONNECTOR_DEBUG
+ qDebug() << "writing response (" << block.size() << "bytes )" << block.toHex();
+#endif
+ m_socket->write(block);
+ m_blockSize = 0;
+
+#ifdef DEBUGGERCONNECTOR_DEBUG
+ qDebug() << "bytes available is now" << m_socket->bytesAvailable();
+#endif
+ if (m_socket->bytesAvailable() != 0)
+ QMetaObject::invokeMethod(this, "onReadyRead", Qt::QueuedConnection);
+ } break;
+
+ }
+}
+
+/*!
+ \reimp
+*/
+void QScriptRemoteTargetDebuggerBackend::event(const QScriptDebuggerEvent &event)
+{
+ if (m_state != ConnectedState)
+ return;
+ if (m_eventLoopPool.isEmpty())
+ m_eventLoopPool.append(new QEventLoop());
+ QEventLoop *eventLoop = m_eventLoopPool.takeFirst();
+ Q_ASSERT(!eventLoop->isRunning());
+ m_eventLoopStack.prepend(eventLoop);
+
+#ifdef DEBUGGERCONNECTOR_DEBUG
+ qDebug() << "serializing event of type" << event.type();
+#endif
+ QByteArray block;
+ QDataStream out(&block, QIODevice::WriteOnly);
+ out.setVersion(QDataStream::Qt_4_5);
+ out << (quint32)0; // reserve 4 bytes for block size
+ out << (quint8)0; // type = event
+ out << event;
+ out.device()->seek(0);
+ out << (quint32)(block.size() - sizeof(quint32));
+
+#ifdef DEBUGGERCONNECTOR_DEBUG
+ qDebug() << "writing event (" << block.size() << " bytes )";
+#endif
+ m_socket->write(block);
+
+ // run an event loop until the debugger triggers a resume
+#ifdef DEBUGGERCONNECTOR_DEBUG
+ qDebug("entering event loop");
+#endif
+ eventLoop->exec();
+#ifdef DEBUGGERCONNECTOR_DEBUG
+ qDebug("returned from event loop");
+#endif
+
+ if (!m_eventLoopStack.isEmpty()) {
+ // the event loop was quit directly (i.e. not via resume())
+ m_eventLoopStack.takeFirst();
+ }
+ m_eventLoopPool.append(eventLoop);
+ doPendingEvaluate(/*postEvent=*/false);
+}
+
+/*!
+ \reimp
+*/
+void QScriptRemoteTargetDebuggerBackend::resume()
+{
+ // quitting the event loops will cause event() to return (see above)
+ while (!m_eventLoopStack.isEmpty()) {
+ QEventLoop *eventLoop = m_eventLoopStack.takeFirst();
+ if (eventLoop->isRunning())
+ eventLoop->quit();
+ }
+}
+
+/*!
+ Constructs a new QScriptDebuggerConnector object with the given \a
+ parent.
+*/
+QScriptDebuggerConnector::QScriptDebuggerConnector(QObject *parent)
+ : QObject(parent), m_backend(0)
+{
+}
+
+/*!
+ Destroys this QScriptDebuggerConnector.
+*/
+QScriptDebuggerConnector::~QScriptDebuggerConnector()
+{
+ delete m_backend;
+}
+
+/*!
+ Sets the \a engine that this connector will manage a connection to.
+*/
+void QScriptDebuggerConnector::setEngine(QScriptEngine *engine)
+{
+ if (m_backend) {
+ m_backend->detach();
+ } else {
+ m_backend = new QScriptRemoteTargetDebuggerBackend();
+ QObject::connect(m_backend, SIGNAL(connected()),
+ this, SIGNAL(connected()));
+ QObject::connect(m_backend, SIGNAL(disconnected()),
+ this, SIGNAL(disconnected()));
+ }
+ m_backend->attachTo(engine);
+}
+
+/*!
+ Returns the \a engine that this connector manages a connection to,
+ or 0 if no engine has been set.
+*/
+QScriptEngine *QScriptDebuggerConnector::engine() const
+{
+ if (!m_backend)
+ return 0;
+ return m_backend->engine();
+}
+
+/*!
+ Attempts to make a connection to the given \a address on the given
+ \a port.
+
+ The connected() signal is emitted when the connection has been
+ established.
+
+ \sa disconnectFromDebugger(), listen()
+*/
+void QScriptDebuggerConnector::connectToDebugger(const QHostAddress &address, quint16 port)
+{
+ if (!m_backend) {
+ qWarning("QScriptDebuggerConnector::connectToDebugger(): no engine has been set (call setEngine() first)");
+ return;
+ }
+ m_backend->connectToDebugger(address, port);
+}
+
+/*!
+ Attempts to close the connection.
+
+ The disconnected() signal is emitted when the connection has been
+ closed.
+
+ \sa connectToDebugger()
+*/
+void QScriptDebuggerConnector::disconnectFromDebugger()
+{
+ if (m_backend)
+ m_backend->disconnectFromDebugger();
+}
+
+/*!
+ Listens for an incoming connection on the given \a address and \a
+ port.
+
+ Returns true on success; otherwise returns false.
+
+ The connected() signal is emitted when a connection has been
+ established.
+
+ \sa connectToDebugger()
+*/
+bool QScriptDebuggerConnector::listen(const QHostAddress &address, quint16 port)
+{
+ if (!m_backend) {
+ qWarning("QScriptDebuggerConnector::listen(): no engine has been set (call setEngine() first)");
+ return false;
+ }
+ return m_backend->listen(address, port);
+}
+
+#include "qscriptdebuggerconnector.moc"