diff options
author | Erik Verbruggen <[email protected]> | 2013-10-16 12:29:47 +0200 |
---|---|---|
committer | The Qt Project <[email protected]> | 2013-11-10 11:01:35 +0100 |
commit | 0910a577f4d12eea4a099c989bd58f1dee6c88db (patch) | |
tree | 53860b5debf08cef684da1eb387769dbe8ef2d42 /src/qml/debugger | |
parent | 1738e4ee119bbcd20d33353e7018f04d92766639 (diff) |
Debugging with V4
Currently missing, but coming in subsequent patches:
- evaluating expressions
- evaluating breakpoint conditions
Change-Id: Ib43f2a3aaa252741ea7ce857a274480feb8741aa
Reviewed-by: Simon Hausmann <[email protected]>
Diffstat (limited to 'src/qml/debugger')
-rw-r--r-- | src/qml/debugger/qqmldebugserver.cpp | 2 | ||||
-rw-r--r-- | src/qml/debugger/qqmldebugservice.cpp | 27 | ||||
-rw-r--r-- | src/qml/debugger/qqmldebugservice_p_p.h | 1 | ||||
-rw-r--r-- | src/qml/debugger/qv4debugservice.cpp | 1112 | ||||
-rw-r--r-- | src/qml/debugger/qv4debugservice_p.h | 7 |
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) |