aboutsummaryrefslogtreecommitdiffstats
path: root/src/qml/debugger
diff options
context:
space:
mode:
authorErik Verbruggen <erik.verbruggen@me.com>2013-10-16 12:29:47 +0200
committerThe Qt Project <gerrit-noreply@qt-project.org>2013-11-10 11:01:35 +0100
commit0910a577f4d12eea4a099c989bd58f1dee6c88db (patch)
tree53860b5debf08cef684da1eb387769dbe8ef2d42 /src/qml/debugger
parent1738e4ee119bbcd20d33353e7018f04d92766639 (diff)
Debugging with V4
Currently missing, but coming in subsequent patches: - evaluating expressions - evaluating breakpoint conditions Change-Id: Ib43f2a3aaa252741ea7ce857a274480feb8741aa Reviewed-by: Simon Hausmann <simon.hausmann@digia.com>
Diffstat (limited to 'src/qml/debugger')
-rw-r--r--src/qml/debugger/qqmldebugserver.cpp2
-rw-r--r--src/qml/debugger/qqmldebugservice.cpp27
-rw-r--r--src/qml/debugger/qqmldebugservice_p_p.h1
-rw-r--r--src/qml/debugger/qv4debugservice.cpp1112
-rw-r--r--src/qml/debugger/qv4debugservice_p.h7
5 files changed, 1019 insertions, 130 deletions
diff --git a/src/qml/debugger/qqmldebugserver.cpp b/src/qml/debugger/qqmldebugserver.cpp
index 5286d3e694..0523762971 100644
--- a/src/qml/debugger/qqmldebugserver.cpp
+++ b/src/qml/debugger/qqmldebugserver.cpp
@@ -514,8 +514,6 @@ void QQmlDebugServerPrivate::_q_changeServiceState(const QString &serviceName,
if (service && (service->d_func()->state != newState)) {
service->stateAboutToBeChanged(newState);
service->d_func()->state = newState;
- if (newState == QQmlDebugService::NotConnected)
- service->d_func()->server = 0;
service->stateChanged(newState);
}
diff --git a/src/qml/debugger/qqmldebugservice.cpp b/src/qml/debugger/qqmldebugservice.cpp
index f036dd9d69..d8fc2f2bb2 100644
--- a/src/qml/debugger/qqmldebugservice.cpp
+++ b/src/qml/debugger/qqmldebugservice.cpp
@@ -52,20 +52,18 @@
QT_BEGIN_NAMESPACE
QQmlDebugServicePrivate::QQmlDebugServicePrivate()
- : server(0)
{
}
QQmlDebugService::QQmlDebugService(const QString &name, float version, QObject *parent)
: QObject(*(new QQmlDebugServicePrivate), parent)
{
+ QQmlDebugServer::instance(); // create it when it isn't there yet.
+
Q_D(QQmlDebugService);
d->name = name;
d->version = version;
- d->server = QQmlDebugServer::instance();
d->state = QQmlDebugService::NotConnected;
-
-
}
QQmlDebugService::QQmlDebugService(QQmlDebugServicePrivate &dd,
@@ -75,7 +73,6 @@ QQmlDebugService::QQmlDebugService(QQmlDebugServicePrivate &dd,
Q_D(QQmlDebugService);
d->name = name;
d->version = version;
- d->server = QQmlDebugServer::instance();
d->state = QQmlDebugService::NotConnected;
}
@@ -86,24 +83,23 @@ QQmlDebugService::QQmlDebugService(QQmlDebugServicePrivate &dd,
QQmlDebugService::State QQmlDebugService::registerService()
{
Q_D(QQmlDebugService);
- if (!d->server)
+ QQmlDebugServer *server = QQmlDebugServer::instance();
+
+ if (!server)
return NotConnected;
- if (d->server->serviceNames().contains(d->name)) {
+ if (server->serviceNames().contains(d->name)) {
qWarning() << "QQmlDebugService: Conflicting plugin name" << d->name;
- d->server = 0;
} else {
- d->server->addService(this);
+ server->addService(this);
}
return state();
}
QQmlDebugService::~QQmlDebugService()
{
- Q_D(const QQmlDebugService);
- if (d->server) {
- d->server->removeService(this);
- }
+ if (QQmlDebugServer *inst = QQmlDebugServer::instance())
+ inst->removeService(this);
}
QString QQmlDebugService::name() const
@@ -303,12 +299,11 @@ void QQmlDebugService::sendMessage(const QByteArray &message)
void QQmlDebugService::sendMessages(const QList<QByteArray> &messages)
{
- Q_D(QQmlDebugService);
-
if (state() != Enabled)
return;
- d->server->sendMessages(this, messages);
+ if (QQmlDebugServer *inst = QQmlDebugServer::instance())
+ inst->sendMessages(this, messages);
}
void QQmlDebugService::stateAboutToBeChanged(State)
diff --git a/src/qml/debugger/qqmldebugservice_p_p.h b/src/qml/debugger/qqmldebugservice_p_p.h
index 940990f628..70cf8ef73a 100644
--- a/src/qml/debugger/qqmldebugservice_p_p.h
+++ b/src/qml/debugger/qqmldebugservice_p_p.h
@@ -69,7 +69,6 @@ public:
QString name;
float version;
- QQmlDebugServer *server;
QQmlDebugService::State state;
};
diff --git a/src/qml/debugger/qv4debugservice.cpp b/src/qml/debugger/qv4debugservice.cpp
index ecf7905844..3b6bafdac2 100644
--- a/src/qml/debugger/qv4debugservice.cpp
+++ b/src/qml/debugger/qv4debugservice.cpp
@@ -49,7 +49,13 @@
#include <private/qv8engine_p.h>
+#include <QtCore/QJsonArray>
+#include <QtCore/QJsonDocument>
+#include <QtCore/QJsonObject>
+#include <QtCore/QJsonValue>
+
const char *V4_CONNECT = "connect";
+const char *V4_DISCONNECT = "disconnect";
const char *V4_BREAK_ON_SIGNAL = "breakonsignal";
const char *V4_ADD_BREAKPOINT = "addBreakpoint";
const char *V4_REMOVE_BREAKPOINT = "removeBreakpoint";
@@ -60,14 +66,296 @@ const char *V4_BREAK = "break";
const char *V4_FILENAME = "filename";
const char *V4_LINENUMBER = "linenumber";
+#define NO_PROTOCOL_TRACING
+#ifdef NO_PROTOCOL_TRACING
+# define TRACE_PROTOCOL(x)
+#else
+# define TRACE_PROTOCOL(x) x
+static QTextStream debug(stderr, QIODevice::WriteOnly);
+#endif
+
QT_BEGIN_NAMESPACE
Q_GLOBAL_STATIC(QV4DebugService, v4ServiceInstance)
+class QV4DebugServicePrivate;
+
class QV4DebuggerAgent : public QV4::Debugging::DebuggerAgent
{
+public:
+ QV4DebuggerAgent(QV4DebugServicePrivate *debugServicePrivate)
+ : debugServicePrivate(debugServicePrivate)
+ {}
+
+ QV4::Debugging::Debugger *firstDebugger() const
+ {
+ // Currently only 1 single engine is supported, so:
+ if (m_debuggers.isEmpty())
+ return 0;
+ else
+ return m_debuggers.first();
+ }
+
+ bool 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;
+ }
+
public slots:
- virtual void debuggerPaused(QV4::Debugging::Debugger *debugger);
+ virtual void debuggerPaused(QV4::Debugging::Debugger *debugger, QV4::Debugging::PauseReason reason);
+ virtual void sourcesCollected(QV4::Debugging::Debugger *debugger, QStringList sources, int requestSequenceNr);
+
+private:
+ QV4DebugServicePrivate *debugServicePrivate;
+};
+
+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<int> 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, QV4::ValueRef value)
+ {
+ QV4::Scope scope(engine());
+ QV4::ScopedObject obj(scope, value->asObject());
+
+ int ref = cachedObjectRef(obj.getPointer());
+ if (ref != -1) {
+ addNameRefPair(name, ref);
+ } else {
+ int ref = newRefId();
+ cacheObjectRef(obj.getPointer(), ref);
+
+ QJsonArray properties, *prev = &properties;
+ QSet<int> 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<int, QJsonValue> refs;
+ QHash<QByteArray, int> refCache;
+ QJsonArray *destination;
+ QSet<int> usedRefs;
+ QHash<int, QSet<int> > refsByHandle;
+ QHash<QV4::Object *, int> objectRefs;
};
class QV4DebugServicePrivate : public QQmlDebugServicePrivate
@@ -75,20 +363,35 @@ class QV4DebugServicePrivate : public QQmlDebugServicePrivate
Q_DECLARE_PUBLIC(QV4DebugService)
public:
- QV4DebugServicePrivate() : version(1) {}
+ QV4DebugServicePrivate();
+ ~QV4DebugServicePrivate() { qDeleteAll(handlers.values()); }
- static QByteArray packMessage(const QByteArray &command, int querySequence, const QByteArray &message = QByteArray())
+ static QByteArray packMessage(const QByteArray &command, const QByteArray &message = QByteArray())
{
QByteArray reply;
QQmlDebugStream rs(&reply, QIODevice::WriteOnly);
- const QByteArray cmd("V4DEBUG");
- rs << cmd << QByteArray::number(++sequence) << QByteArray::number(querySequence) << command << message;
+ static const QByteArray cmd("V8DEBUG");
+ rs << cmd << command << message;
return reply;
}
+ void 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(debug << "sending response for: " << responseData << endl);
+
+ q_func()->sendMessage(packMessage("v8message", responseData));
+ }
+
void processCommand(const QByteArray &command, const QByteArray &data);
- void addBreakpoint(const QByteArray &data);
- void removeBreakpoint(const QByteArray &data);
QMutex initializeMutex;
QWaitCondition initializeCondition;
@@ -99,14 +402,605 @@ public:
static int debuggerIndex;
static int sequence;
const int version;
+
+ V8CommandHandler *v8CommandHandler(const QString &command) const;
+
+ void clearHandles(QV4::ExecutionEngine *engine)
+ {
+ collector.reset(new VariableCollector(engine));
+ }
+
+ QJsonObject buildFrame(const QV4::StackFrame &stackFrame, int frameNr,
+ QV4::Debugging::Debugger *debugger)
+ {
+ QJsonObject frame;
+ frame[QLatin1String("index")] = frameNr;
+ frame[QLatin1String("debuggerFrame")] = false;
+ frame[QLatin1String("func")] = collector->addFunctionRef(stackFrame.function);
+ frame[QLatin1String("script")] = collector->addScriptRef(stackFrame.source);
+ frame[QLatin1String("line")] = stackFrame.line - 1;
+ if (stackFrame.column >= 0)
+ frame[QLatin1String("column")] = stackFrame.column;
+
+ QJsonArray properties;
+ collector->setDestination(&properties);
+ if (debugger->collectThisInContext(collector.data(), frameNr)) {
+ QJsonObject obj;
+ obj[QLatin1String("properties")] = properties;
+ frame[QLatin1String("receiver")] = collector->addObjectRef(obj, false);
+ }
+
+ QJsonArray scopes;
+ // Only type and index are used by Qt Creator, so we keep it easy:
+ QVector<QV4::ExecutionContext::Type> 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["index"] = i;
+ scope["type"] = type;
+ scopes.push_back(scope);
+ }
+ frame[QLatin1String("scopes")] = scopes;
+
+ return frame;
+ }
+
+ int encodeScopeType(QV4::ExecutionContext::Type scopeType)
+ {
+ switch (scopeType) {
+ case QV4::ExecutionContext::Type_GlobalContext:
+ return 0;
+ break;
+ case QV4::ExecutionContext::Type_CatchContext:
+ return 4;
+ break;
+ case QV4::ExecutionContext::Type_WithContext:
+ return 2;
+ break;
+ case QV4::ExecutionContext::Type_SimpleCallContext:
+ case QV4::ExecutionContext::Type_CallContext:
+ return 1;
+ break;
+ case QV4::ExecutionContext::Type_QmlContext:
+ default:
+ return -1;
+ }
+ }
+
+ QJsonObject buildScope(int frameNr, int scopeNr, QV4::Debugging::Debugger *debugger)
+ {
+ QJsonObject scope;
+
+ QJsonArray properties;
+ collector->collectScope(&properties, debugger, frameNr, scopeNr);
+
+ QJsonObject anonymous;
+ anonymous[QLatin1String("properties")] = properties;
+
+ QVector<QV4::ExecutionContext::Type> scopeTypes = debugger->getScopeTypes(frameNr);
+ scope[QLatin1String("type")] = encodeScopeType(scopeTypes[scopeNr]);
+ scope[QLatin1String("index")] = scopeNr;
+ scope[QLatin1String("frameIndex")] = frameNr;
+ scope[QLatin1String("object")] = collector->addObjectRef(anonymous, true);
+
+ return scope;
+ }
+
+ QJsonValue lookup(int refId) const { return collector->lookup(refId); }
+
+ QJsonArray buildRefs()
+ {
+ return collector->retrieveRefsToInclude();
+ }
+
+ void selectFrame(int frameNr)
+ { theSelectedFrame = frameNr; }
+
+ int selectedFrame() const
+ { return theSelectedFrame; }
+
+private:
+ QScopedPointer<VariableCollector> collector;
+ int theSelectedFrame;
+
+ void addHandler(V8CommandHandler* handler);
+ QHash<QString, V8CommandHandler*> handlers;
+ QScopedPointer<UnknownV8CommandHandler> unknownV8CommandHandler;
};
int QV4DebugServicePrivate::debuggerIndex = 0;
int QV4DebugServicePrivate::sequence = 0;
+class V8CommandHandler
+{
+public:
+ V8CommandHandler(const QString &command)
+ : cmd(command)
+ {}
+
+ virtual ~V8CommandHandler()
+ {}
+
+ QString command() const { return cmd; }
+
+ void handle(const QJsonObject &request, QQmlDebugService *s, QV4DebugServicePrivate *p)
+ {
+ TRACE_PROTOCOL(debug << "handling command " << command() << "..." << endl);
+
+ req = request;
+ seq = req.value(QStringLiteral("seq"));
+ debugService = s;
+ debugServicePrivate = p;
+
+ handleRequest();
+ if (!response.isEmpty()) {
+ response[QLatin1String("type")] = QStringLiteral("response");
+ debugServicePrivate->send(response);
+ }
+
+ debugServicePrivate = 0;
+ 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"), debugServicePrivate->debuggerAgent.isRunning());
+ }
+
+ void addRefs()
+ {
+ response.insert(QStringLiteral("refs"), debugServicePrivate->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;
+ QQmlDebugService *debugService;
+ QV4DebugServicePrivate *debugServicePrivate;
+ 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"),
+ QStringLiteral("this is not V8, this is V4 in Qt %1").arg(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 = debugServicePrivate->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:
+ debugServicePrivate->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 = debugServicePrivate->debuggerAgent.firstDebugger();
+
+ QJsonArray frameArray;
+ QVector<QV4::StackFrame> frames = debugger->stackTrace(toFrame);
+ for (int i = fromFrame; i < toFrame && i < frames.size(); ++i)
+ frameArray.push_back(debugServicePrivate->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(debugServicePrivate->selectedFrame());
+
+ QV4::Debugging::Debugger *debugger = debugServicePrivate->debuggerAgent.firstDebugger();
+ QVector<QV4::StackFrame> frames = debugger->stackTrace(frameNr + 1);
+ if (frameNr < 0 || frameNr >= frames.size()) {
+ createErrorResponse(QStringLiteral("frame command has invalid frame number"));
+ return;
+ }
+
+ debugServicePrivate->selectFrame(frameNr);
+ QJsonObject frame = debugServicePrivate->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(debugServicePrivate->selectedFrame());
+ const int scopeNr = arguments.value(QStringLiteral("number")).toInt(0);
+
+ QV4::Debugging::Debugger *debugger = debugServicePrivate->debuggerAgent.firstDebugger();
+ QVector<QV4::StackFrame> 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 = debugServicePrivate->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 (QJsonValue handle, handles)
+ body[QString::number(handle.toInt())] = debugServicePrivate->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 = debugServicePrivate->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()
+ {
+ debugServicePrivate->debuggerAgent.removeAllBreakPoints();
+ debugServicePrivate->debuggerAgent.resumeAll();
+
+ // response:
+ addCommand();
+ addRequestSequence();
+ addSuccess(true);
+ addRunning();
+ }
+};
+
+class V8SetExceptionBreakRequest: public V8CommandHandler
+{
+public:
+ V8SetExceptionBreakRequest(): V8CommandHandler(QStringLiteral("setexceptionbreak")) {}
+
+ virtual void handleRequest()
+ {
+ bool wasEnabled = debugServicePrivate->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:
+ debugServicePrivate->debuggerAgent.setBreakOnThrow(enabled);
+
+ QJsonObject body;
+ body[QLatin1String("type")] = type;
+ body[QLatin1String("enabled")] = debugServicePrivate->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:
+ debugServicePrivate->debuggerAgent.firstDebugger()->gatherSources(requestSequenceNr());
+
+ // response will be send by
+ }
+};
+} // anonymous namespace
+
+QV4DebugServicePrivate::QV4DebugServicePrivate()
+ : 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);
+
+ // TODO: evaluate
+}
+
+void QV4DebugServicePrivate::addHandler(V8CommandHandler* handler)
+{
+ handlers[handler->command()] = handler;
+}
+
+V8CommandHandler *QV4DebugServicePrivate::v8CommandHandler(const QString &command) const
+{
+ V8CommandHandler *handler = handlers.value(command, 0);
+ if (handler)
+ return handler;
+ else
+ return unknownV8CommandHandler.data();
+}
+
QV4DebugService::QV4DebugService(QObject *parent)
: QQmlDebugService(*(new QV4DebugServicePrivate()),
- QStringLiteral("V4Debugger"), 1, parent)
+ QStringLiteral("V8Debugger"), 1, parent)
{
Q_D(QV4DebugService);
@@ -131,15 +1025,18 @@ QV4DebugService *QV4DebugService::instance()
void QV4DebugService::addEngine(const QQmlEngine *engine)
{
Q_D(QV4DebugService);
+
if (engine) {
QV4::ExecutionEngine *ee = QV8Engine::getV4(engine->handle());
- if (ee) {
- ee->enableDebugger();
- QV4::Debugging::Debugger *debugger = ee->debugger;
- d->debuggerMap.insert(d->debuggerIndex++, debugger);
- d->debuggerAgent.addDebugger(debugger);
- d->debuggerAgent.moveToThread(d->server->thread());
- moveToThread(d->server->thread());
+ if (QQmlDebugServer *server = QQmlDebugServer::instance()) {
+ if (ee) {
+ ee->enableDebugger();
+ QV4::Debugging::Debugger *debugger = ee->debugger;
+ d->debuggerMap.insert(d->debuggerIndex++, debugger);
+ d->debuggerAgent.addDebugger(debugger);
+ d->debuggerAgent.moveToThread(server->thread());
+ moveToThread(server->thread());
+ }
}
}
}
@@ -204,128 +1101,129 @@ void QV4DebugService::messageReceived(const QByteArray &message)
QByteArray header;
ms >> header;
- if (header == "V4DEBUG") {
- QByteArray sequenceValue;
- QByteArray command;
- QByteArray data;
- ms >> sequenceValue >> command >> data;
+ TRACE_PROTOCOL(debug << "received message with header " << header << endl);
- QQmlDebugStream ds(data);
+ if (header == "V8DEBUG") {
+ QByteArray type;
+ QByteArray payload;
+ ms >> type >> payload;
+ TRACE_PROTOCOL(debug << "... type: "<<type << endl);
- QByteArray versionValue;
- QByteArray debuggerValue;
- ds >> versionValue >> debuggerValue; // unused for now
-
- int querySequence = sequenceValue.toInt();
- if (command == V4_BREAK_ON_SIGNAL) {
+ if (type == V4_CONNECT) {
+ sendMessage(d->packMessage(type));
+ d->initializeCondition.wakeAll();
+ } else if (type == V4_PAUSE) {
+ d->debuggerAgent.pauseAll();
+ sendSomethingToSomebody(type);
+ } else if (type == V4_BREAK_ON_SIGNAL) {
QByteArray signal;
bool enabled;
- ds >> signal >> enabled;
+ ms >> signal >> enabled;
//Normalize to lower case.
QString signalName(QString::fromUtf8(signal).toLower());
if (enabled)
d->breakOnSignals.append(signalName);
else
d->breakOnSignals.removeOne(signalName);
- } else if (command == V4_ADD_BREAKPOINT) {
- QMetaObject::invokeMethod(this, "addBreakpoint", Qt::QueuedConnection,
- Q_ARG(QByteArray, data), Q_ARG(int, querySequence));
- } else if (command == V4_REMOVE_BREAKPOINT) {
- QMetaObject::invokeMethod(this, "removeBreakpoint", Qt::QueuedConnection,
- Q_ARG(QByteArray, data), Q_ARG(int, querySequence));
- } else if (command == V4_PAUSE) {
- int id = ds.atEnd() ? debuggerValue.toInt() : -1;
- QMetaObject::invokeMethod(this, "pause", Qt::QueuedConnection, Q_ARG(int, id),
- Q_ARG(int, querySequence));
- } else if (command == V4_CONNECT) {
- QByteArray response;
- QQmlDebugStream rs(&response, QIODevice::WriteOnly);
- rs << QByteArray::number(d->version) << QByteArray::number(1);
- sendMessage(d->packMessage(command, sequenceValue.toInt(), response));
-
- d->initializeCondition.wakeAll();
+ } else if (type == "v8request") {
+ handleV8Request(payload);
+ } else if (type == V4_DISCONNECT) {
+ TRACE_PROTOCOL(debug << "... payload:"<<payload << endl);
+ handleV8Request(payload);
} else {
- QByteArray response;
- QQmlDebugStream rs(&response, QIODevice::WriteOnly);
- rs << QByteArray::number(d->version) << QByteArray::number(0);
- sendMessage(d->packMessage(command, sequenceValue.toInt(), response));
+ sendSomethingToSomebody(type, 0);
}
}
}
-void QV4DebuggerAgent::debuggerPaused(QV4::Debugging::Debugger *debugger)
+void QV4DebugService::sendSomethingToSomebody(const char *type, int magicNumber)
{
- QByteArray data;
- QQmlDebugStream message(&data, QIODevice::WriteOnly);
-
- QV4::Debugging::Debugger::ExecutionState state = debugger->currentExecutionState();
- message << V4_FILENAME << state.fileName.toLatin1();
- message << V4_LINENUMBER << QByteArray().number(state.lineNumber);
-
- QV4DebugService::instance()->sendMessage(QV4DebugServicePrivate::packMessage(V4_BREAK, -1, data));
- // ### TODO: Once the remote side supports V4_BREAK properly and sends us a V4_CONTINUE, then we
- // can remove the following line:
- resumeAll();
+ Q_D(QV4DebugService);
- qDebug() << Q_FUNC_INFO;
+ QByteArray response;
+ QQmlDebugStream rs(&response, QIODevice::WriteOnly);
+ rs << QByteArray(type)
+ << QByteArray::number(d->version) << QByteArray::number(magicNumber);
+ sendMessage(d->packMessage(type, response));
}
-void QV4DebugService::pause(int debuggerId, int querySequence)
+void QV4DebuggerAgent::debuggerPaused(QV4::Debugging::Debugger *debugger, QV4::Debugging::PauseReason reason)
{
- Q_D(QV4DebugService);
+ Q_UNUSED(reason);
- debuggerId == -1 ? d->debuggerAgent.pauseAll()
- : d->debuggerAgent.pause(d->debuggerMap.value(debuggerId));
- QByteArray response;
- QQmlDebugStream rs(&response, QIODevice::WriteOnly);
- rs << QByteArray::number(d->version) << QByteArray::number(1);
- sendMessage(d->packMessage(V4_PAUSE, querySequence, response));
+ debugServicePrivate->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<QV4::StackFrame> 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);
+ debugServicePrivate->send(event);
}
-void QV4DebugService::addBreakpoint(const QByteArray &data, int querySequence)
+void QV4DebuggerAgent::sourcesCollected(QV4::Debugging::Debugger *debugger, QStringList sources, int requestSequenceNr)
{
- Q_D(QV4DebugService);
+ QJsonArray body;
+ foreach (const QString source, sources) {
+ QJsonObject src;
+ src[QLatin1String("name")] = source;
+ src[QLatin1String("scriptType")] = 4;
+ body.append(src);
+ }
- QQmlDebugStream ds(data);
- QString fileName;
- int lineNumber = -1;
- while (!ds.atEnd()) {
- QByteArray key;
- QByteArray value;
- ds >> key >> value;
- if (key == V4_FILENAME)
- fileName = QString::fromLatin1(value);
- else if (key == V4_LINENUMBER)
- lineNumber = value.toInt();
- }
- d->debuggerAgent.addBreakPoint(fileName, lineNumber);
- QByteArray response;
- QQmlDebugStream rs(&response, QIODevice::WriteOnly);
- rs << QByteArray::number(d->version) << QByteArray::number(1);
- sendMessage(d->packMessage(V4_ADD_BREAKPOINT, querySequence, response));
+ 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");
+ debugServicePrivate->send(response);
}
-void QV4DebugService::removeBreakpoint(const QByteArray &data, int querySequence)
+void QV4DebugService::handleV8Request(const QByteArray &payload)
{
Q_D(QV4DebugService);
- QQmlDebugStream ds(data);
- QString fileName;
- int lineNumber = -1;
- while (!ds.atEnd()) {
- QByteArray key;
- QByteArray value;
- ds >> key >> value;
- if (key == V4_FILENAME)
- fileName = QString::fromLatin1(value);
- else if (key == V4_LINENUMBER)
- lineNumber = value.toInt();
- }
- d->debuggerAgent.removeBreakPoint(fileName, lineNumber);
- QByteArray response;
- QQmlDebugStream rs(&response, QIODevice::WriteOnly);
- rs << QByteArray::number(d->version) << QByteArray::number(1);
- sendMessage(d->packMessage(V4_REMOVE_BREAKPOINT, querySequence, response));
+ TRACE_PROTOCOL(debug << "v8request, payload: " << payload << endl);
+
+ 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 = d->v8CommandHandler(command.toString());
+ if (h)
+ h->handle(o, this, d);
+ }
}
QT_END_NAMESPACE
diff --git a/src/qml/debugger/qv4debugservice_p.h b/src/qml/debugger/qv4debugservice_p.h
index e61bc01d19..e35010bebf 100644
--- a/src/qml/debugger/qv4debugservice_p.h
+++ b/src/qml/debugger/qv4debugservice_p.h
@@ -77,11 +77,10 @@ public:
protected:
void stateChanged(State newState);
void messageReceived(const QByteArray &);
+ void sendSomethingToSomebody(const char *type, int magicNumber = 1);
-private slots:
- void pause(int debuggerId, int querySequence);
- void addBreakpoint(const QByteArray &data, int querySequence);
- void removeBreakpoint(const QByteArray &data, int querySequence);
+private:
+ void handleV8Request(const QByteArray &payload);
private:
Q_DISABLE_COPY(QV4DebugService)