From 0910a577f4d12eea4a099c989bd58f1dee6c88db Mon Sep 17 00:00:00 2001 From: Erik Verbruggen Date: Wed, 16 Oct 2013 12:29:47 +0200 Subject: Debugging with V4 Currently missing, but coming in subsequent patches: - evaluating expressions - evaluating breakpoint conditions Change-Id: Ib43f2a3aaa252741ea7ce857a274480feb8741aa Reviewed-by: Simon Hausmann --- src/qml/debugger/qqmldebugserver.cpp | 2 - src/qml/debugger/qqmldebugservice.cpp | 27 +- src/qml/debugger/qqmldebugservice_p_p.h | 1 - src/qml/debugger/qv4debugservice.cpp | 1112 ++++++++++++++++++++++++++++--- src/qml/debugger/qv4debugservice_p.h | 7 +- src/qml/jsruntime/qv4debugging.cpp | 816 +++++++++++++++++------ src/qml/jsruntime/qv4debugging_p.h | 179 ++++- src/qml/jsruntime/qv4engine.cpp | 2 +- src/qml/jsruntime/qv4function.cpp | 41 +- src/qml/jsruntime/qv4function_p.h | 1 + src/qml/jsruntime/qv4vme_moth.cpp | 22 +- 11 files changed, 1836 insertions(+), 374 deletions(-) (limited to 'src') 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 &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 +#include +#include +#include +#include + 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 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 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; }; 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 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 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 collector; + int theSelectedFrame; + + void addHandler(V8CommandHandler* handler); + QHash handlers; + QScopedPointer 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 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 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 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: "<> 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:"<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 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) diff --git a/src/qml/jsruntime/qv4debugging.cpp b/src/qml/jsruntime/qv4debugging.cpp index 24879e9597..0ba37fa547 100644 --- a/src/qml/jsruntime/qv4debugging.cpp +++ b/src/qml/jsruntime/qv4debugging.cpp @@ -52,15 +52,81 @@ using namespace QV4; using namespace QV4::Debugging; +namespace { +class EvalJob: public Debugger::Job +{ + QV4::ExecutionEngine *engine; + const QString &script; + +public: + EvalJob(QV4::ExecutionEngine *engine, const QString &script) + : engine(engine) + , script(script) + {} + + ~EvalJob() {} + + void run() + { + // TODO + qDebug() << "Evaluating script:" << script; + Q_UNUSED(engine); + } + + bool resultAsBoolean() const + { + return true; + } +}; + +class GatherSourcesJob: public Debugger::Job +{ + QV4::ExecutionEngine *engine; + const int seq; + +public: + GatherSourcesJob(QV4::ExecutionEngine *engine, int seq) + : engine(engine) + , seq(seq) + {} + + ~GatherSourcesJob() {} + + void run() + { + QStringList sources; + + foreach (QV4::CompiledData::CompilationUnit *unit, engine->compilationUnits) { + QString fileName = unit->fileName(); + if (!fileName.isEmpty()) + sources.append(fileName); + } + + Debugger *debugger = engine->debugger; + QMetaObject::invokeMethod(debugger->agent(), "sourcesCollected", Qt::QueuedConnection, + Q_ARG(QV4::Debugging::Debugger*, debugger), + Q_ARG(QStringList, sources), + Q_ARG(int, seq)); + } +}; +} + Debugger::Debugger(QV4::ExecutionEngine *engine) : m_engine(engine) , m_agent(0) , m_state(Running) , m_pauseRequested(false) + , m_gatherSources(0) , m_havePendingBreakPoints(false) , m_currentInstructionPointer(0) + , m_stepping(NotStepping) + , m_stopForStepping(false) + , m_returnedValue(Primitive::undefinedValue()) + , m_breakOnThrow(false) + , m_runningJob(0) { qMetaTypeId(); + qMetaTypeId(); } Debugger::~Debugger() @@ -86,6 +152,18 @@ void Debugger::detachFromAgent() agent->removeDebugger(this); } +void Debugger::gatherSources(int requestSequenceNr) +{ + QMutexLocker locker(&m_lock); + + m_gatherSources = new GatherSourcesJob(m_engine, requestSequenceNr); + if (m_state == Paused) { + runInEngine_havingLock(m_gatherSources); + delete m_gatherSources; + m_gatherSources = 0; + } +} + void Debugger::pause() { QMutexLocker locker(&m_lock); @@ -94,19 +172,33 @@ void Debugger::pause() m_pauseRequested = true; } -void Debugger::resume() +void Debugger::resume(Speed speed) { QMutexLocker locker(&m_lock); - Q_ASSERT(m_state == Paused); + if (m_state != Paused) + return; + + if (!m_returnedValue.isUndefined()) + m_returnedValue = Primitive::undefinedValue(); + + clearTemporaryBreakPoint(); + if (speed == StepOver) + setTemporaryBreakPointOnNextLine(); + if (speed == StepOut) + m_temporaryBreakPoint.function = getFunction(); + + m_stepping = speed; m_runningCondition.wakeAll(); } -void Debugger::addBreakPoint(const QString &fileName, int lineNumber) +void Debugger::addBreakPoint(const QString &fileName, int lineNumber, const QString &condition) { QMutexLocker locker(&m_lock); if (!m_pendingBreakPointsToRemove.remove(fileName, lineNumber)) m_pendingBreakPointsToAdd.add(fileName, lineNumber); m_havePendingBreakPoints = !m_pendingBreakPointsToAdd.isEmpty() || !m_pendingBreakPointsToRemove.isEmpty(); + if (!condition.isEmpty()) + m_breakPointConditions.add(fileName, lineNumber, condition); } void Debugger::removeBreakPoint(const QString &fileName, int lineNumber) @@ -115,6 +207,14 @@ void Debugger::removeBreakPoint(const QString &fileName, int lineNumber) if (!m_pendingBreakPointsToAdd.remove(fileName, lineNumber)) m_pendingBreakPointsToRemove.add(fileName, lineNumber); m_havePendingBreakPoints = !m_pendingBreakPointsToAdd.isEmpty() || !m_pendingBreakPointsToRemove.isEmpty(); + m_breakPointConditions.remove(fileName, lineNumber); +} + +void Debugger::setBreakOnThrow(bool onoff) +{ + QMutexLocker locker(&m_lock); + + m_breakOnThrow = onoff; } Debugger::ExecutionState Debugger::currentExecutionState(const uchar *code) const @@ -124,21 +224,11 @@ Debugger::ExecutionState Debugger::currentExecutionState(const uchar *code) cons // ### Locking ExecutionState state; - QV4::ExecutionContext *context = m_engine->current; - QV4::Function *function = 0; - CallContext *callCtx = context->asCallContext(); - if (callCtx && callCtx->function) - function = callCtx->function->function; - else { - Q_ASSERT(context->type == QV4::ExecutionContext::Type_GlobalContext); - function = context->engine->globalCode; - } - - state.function = function; - state.fileName = function->sourceFile(); + state.function = getFunction(); + state.fileName = state.function->sourceFile(); - qptrdiff relativeProgramCounter = code - function->codeData; - state.lineNumber = function->lineNumberForProgramCounter(relativeProgramCounter); + qptrdiff relativeProgramCounter = code - state.function->codeData; + state.lineNumber = state.function->lineNumberForProgramCounter(relativeProgramCounter); return state; } @@ -153,226 +243,376 @@ QVector Debugger::stackTrace(int frameLimit) const return m_engine->stackTrace(frameLimit); } -QList Debugger::retrieveFromValue(const ObjectRef o, const QStringList &path) const +static inline CallContext *findContext(ExecutionContext *ctxt, int frame) { - QList props; - if (!o) - return props; - - Scope scope(m_engine); - ObjectIterator it(scope, o, ObjectIterator::EnumerableOnly); - ScopedValue name(scope); - ScopedValue val(scope); - while (true) { - Value v; - name = it.nextPropertyNameAsString(&v); - if (name->isNull()) - break; - QString key = name->toQStringNoThrow(); - if (path.isEmpty()) { - val = v; - QVariant varValue; - VarInfo::Type type; - convert(val, &varValue, &type); - props.append(VarInfo(key, varValue, type)); - } else if (path.first() == key) { - QStringList pathTail = path; - pathTail.pop_front(); - return retrieveFromValue(ScopedObject(scope, v), pathTail); + while (ctxt) { + CallContext *cCtxt = ctxt->asCallContext(); + if (cCtxt && cCtxt->function) { + if (frame < 1) + return cCtxt; + --frame; } + ctxt = ctxt->parent; } - return props; + return 0; } -void Debugger::convert(ValueRef v, QVariant *varValue, VarInfo::Type *type) const +static inline CallContext *findScope(ExecutionContext *ctxt, int scope) { - Q_ASSERT(varValue); - Q_ASSERT(type); + for (; scope > 0 && ctxt; --scope) + ctxt = ctxt->outer; - switch (v->type()) { - case Value::Empty_Type: - Q_ASSERT(!"empty Value encountered"); - break; - case Value::Undefined_Type: - *type = VarInfo::Undefined; - varValue->setValue(0); - break; - case Value::Null_Type: - *type = VarInfo::Null; - varValue->setValue(0); - break; - case Value::Boolean_Type: - *type = VarInfo::Bool; - varValue->setValue(v->booleanValue()); - break; - case Value::Managed_Type: - if (v->isString()) { - *type = VarInfo::String; - varValue->setValue(v->stringValue()->toQString()); - } else { - *type = VarInfo::Object; - ExecutionContext *ctx = v->objectValue()->internalClass->engine->current; - Scope scope(ctx); - ScopedValue prim(scope, __qmljs_to_primitive(v, STRING_HINT)); - varValue->setValue(prim->toQString()); - } - break; - case Value::Integer_Type: - *type = VarInfo::Number; - varValue->setValue((double)v->int_32); - break; - default: // double - *type = VarInfo::Number; - varValue->setValue(v->doubleValue()); - break; - } + return ctxt ? ctxt->asCallContext() : 0; } -static CallContext *findContext(ExecutionContext *ctxt, int frame) +void Debugger::collectArgumentsInContext(Collector *collector, int frameNr, int scopeNr) { - while (ctxt) { - CallContext *cCtxt = ctxt->asCallContext(); - if (cCtxt && cCtxt->function) { - if (frame < 1) - return cCtxt; - --frame; + if (state() != Paused) + return; + + class ArgumentCollectJob: public Job + { + QV4::ExecutionEngine *engine; + Collector *collector; + int frameNr; + int scopeNr; + + public: + ArgumentCollectJob(QV4::ExecutionEngine *engine, Collector *collector, int frameNr, int scopeNr) + : engine(engine) + , collector(collector) + , frameNr(frameNr) + , scopeNr(scopeNr) + {} + + ~ArgumentCollectJob() {} + + void run() + { + if (frameNr < 0) + return; + + CallContext *ctxt = findScope(findContext(engine->current, frameNr), scopeNr); + if (!ctxt) + return; + + Scope scope(engine); + ScopedValue v(scope); + for (unsigned i = 0, ei = ctxt->formalCount(); i != ei; ++i) { + QString qName; + if (String *name = ctxt->formals()[i]) + qName = name->toQString(); + v = ctxt->argument(i); + collector->collect(qName, v); + } } - ctxt = ctxt->parent; - } + }; - return 0; + ArgumentCollectJob job(m_engine, collector, frameNr, scopeNr); + runInEngine(&job); } -/// Retrieves all arguments from a context, or all properties in an object passed in an argument. -/// -/// \arg frame specifies the frame number: 0 is top of stack, 1 is the parent of the current frame, etc. -/// \arg path when empty, retrieve all arguments in the specified frame. When not empty, find the -/// argument with the same name as the first element in the path (in the specified frame, of -/// course), and then use the rest of the path to walk nested objects. When the path is empty, -/// retrieve all properties in that object. If an intermediate non-object is specified by the -/// path, or non of the property names match, an empty list is returned. -QList Debugger::retrieveArgumentsFromContext(const QStringList &path, int frame) +/// Same as \c retrieveArgumentsFromContext, but now for locals. +void Debugger::collectLocalsInContext(Collector *collector, int frameNr, int scopeNr) { - QList args; + if (state() != Paused) + return; + class LocalCollectJob: public Job + { + QV4::ExecutionEngine *engine; + Collector *collector; + int frameNr; + int scopeNr; + + public: + LocalCollectJob(QV4::ExecutionEngine *engine, Collector *collector, int frameNr, int scopeNr) + : engine(engine) + , collector(collector) + , frameNr(frameNr) + , scopeNr(scopeNr) + {} + + void run() + { + if (frameNr < 0) + return; + + CallContext *ctxt = findScope(findContext(engine->current, frameNr), scopeNr); + if (!ctxt) + return; + + Scope scope(engine); + ScopedValue v(scope); + for (unsigned i = 0, ei = ctxt->variableCount(); i != ei; ++i) { + QString qName; + if (String *name = ctxt->variables()[i]) + qName = name->toQString(); + v = ctxt->locals[i]; + collector->collect(qName, v); + } + } + }; + + LocalCollectJob job(m_engine, collector, frameNr, scopeNr); + runInEngine(&job); +} + +bool Debugger::collectThisInContext(Debugger::Collector *collector, int frame) +{ if (state() != Paused) - return args; + return false; + + class ThisCollectJob: public Job + { + QV4::ExecutionEngine *engine; + Collector *collector; + int frameNr; + bool *foundThis; + + public: + ThisCollectJob(QV4::ExecutionEngine *engine, Collector *collector, int frameNr, bool *foundThis) + : engine(engine) + , collector(collector) + , frameNr(frameNr) + , foundThis(foundThis) + {} + + void run() + { + *foundThis = myRun(); + } - if (frame < 0) - return args; + bool myRun() + { + ExecutionContext *ctxt = findContext(engine->current, frameNr); + while (ctxt) { + if (CallContext *cCtxt = ctxt->asCallContext()) + if (cCtxt->activation) + break; + ctxt = ctxt->outer; + } - CallContext *ctxt = findContext(m_engine->current, frame); - if (!ctxt) - return args; + if (!ctxt) + return false; - Scope scope(m_engine); - ScopedValue v(scope); - for (unsigned i = 0, ei = ctxt->formalCount(); i != ei; ++i) { - // value = ctxt->argument(i); - String *name = ctxt->formals()[i]; - QString qName; - if (name) - qName = name->toQString(); - if (path.isEmpty()) { - v = ctxt->argument(i); - QVariant value; - VarInfo::Type type; - convert(v, &value, &type); - args.append(VarInfo(qName, value, type)); - } else if (path.first() == qName) { - ScopedObject o(scope, ctxt->argument(i)); - QStringList pathTail = path; - pathTail.pop_front(); - return retrieveFromValue(o, pathTail); + Scope scope(engine); + ScopedObject o(scope, ctxt->asCallContext()->activation); + collector->collect(o); + return true; } - } + }; - return args; + bool foundThis = false; + ThisCollectJob job(m_engine, collector, frame, &foundThis); + runInEngine(&job); + return foundThis; } -/// Same as \c retrieveArgumentsFromContext, but now for locals. -QList Debugger::retrieveLocalsFromContext(const QStringList &path, int frame) +void Debugger::collectThrownValue(Collector *collector) { - QList args; + if (state() != Paused || !m_engine->hasException) + return; + class ThisCollectJob: public Job + { + QV4::ExecutionEngine *engine; + Collector *collector; + + public: + ThisCollectJob(QV4::ExecutionEngine *engine, Collector *collector) + : engine(engine) + , collector(collector) + {} + + void run() + { + Scope scope(engine); + ScopedValue v(scope, engine->exceptionValue); + collector->collect(QStringLiteral("exception"), v); + } + }; + + ThisCollectJob job(m_engine, collector); + runInEngine(&job); +} + +void Debugger::collectReturnedValue(Collector *collector) const +{ if (state() != Paused) - return args; + return; + + Scope scope(m_engine); + ScopedObject o(scope, m_returnedValue); + collector->collect(o); +} + +QVector Debugger::getScopeTypes(int frame) const +{ + QVector types; - if (frame < 0) - return args; + if (state() != Paused) + return types; CallContext *sctxt = findContext(m_engine->current, frame); if (!sctxt || sctxt->type < ExecutionContext::Type_SimpleCallContext) - return args; + return types; CallContext *ctxt = static_cast(sctxt); - Scope scope(m_engine); - ScopedValue v(scope); - for (unsigned i = 0, ei = ctxt->variableCount(); i != ei; ++i) { - String *name = ctxt->variables()[i]; - QString qName; - if (name) - qName = name->toQString(); - if (path.isEmpty()) { - v = ctxt->locals[i]; - QVariant value; - VarInfo::Type type; - convert(v, &value, &type); - args.append(VarInfo(qName, value, type)); - } else if (path.first() == qName) { - ScopedObject o(scope, ctxt->locals[i]); - QStringList pathTail = path; - pathTail.pop_front(); - return retrieveFromValue(o, pathTail); - } - } + for (ExecutionContext *it = ctxt; it; it = it->outer) + types.append(it->type); - return args; + return types; } void Debugger::maybeBreakAtInstruction(const uchar *code, bool breakPointHit) { + if (m_runningJob) // do not re-enter when we're doing a job for the debugger. + return; + QMutexLocker locker(&m_lock); m_currentInstructionPointer = code; + ExecutionState state = currentExecutionState(); + // Do debugger internal work if (m_havePendingBreakPoints) { - - if (breakPointHit) { - ExecutionState state = currentExecutionState(); + if (breakPointHit) breakPointHit = !m_pendingBreakPointsToRemove.contains(state.fileName, state.lineNumber); - } applyPendingBreakPoints(); } - // Serve debugging requests from the agent - if (m_pauseRequested) { + if (m_gatherSources) { + m_gatherSources->run(); + delete m_gatherSources; + m_gatherSources = 0; + } + + if (m_stopForStepping) { + clearTemporaryBreakPoint(); + m_stopForStepping = false; m_pauseRequested = false; - pauseAndWait(); - } else if (breakPointHit) - pauseAndWait(); + pauseAndWait(Step); + } else if (m_pauseRequested) { // Serve debugging requests from the agent + m_pauseRequested = false; + pauseAndWait(PauseRequest); + } else if (breakPointHit) { + if (m_stepping == StepOver) + pauseAndWait(Step); + else if (reallyHitTheBreakPoint(state.fileName, state.lineNumber)) + pauseAndWait(BreakPoint); + } if (!m_pendingBreakPointsToAdd.isEmpty() || !m_pendingBreakPointsToRemove.isEmpty()) applyPendingBreakPoints(); } -void Debugger::aboutToThrow(const QV4::ValueRef value) +void Debugger::enteringFunction() { - Q_UNUSED(value); + QMutexLocker locker(&m_lock); - qDebug() << "*** We are about to throw..."; + if (m_stepping == StepIn) { + m_stepping = NotStepping; + m_stopForStepping = true; + m_pauseRequested = true; + } } -void Debugger::pauseAndWait() +void Debugger::leavingFunction(const ReturnedValue &retVal) { + Q_UNUSED(retVal); // TODO + + QMutexLocker locker(&m_lock); + + if (m_stepping == StepOut && temporaryBreakPointInFunction()) { + clearTemporaryBreakPoint(); + m_stepping = NotStepping; + m_stopForStepping = true; + m_pauseRequested = true; + m_returnedValue = retVal; + } +} + +void Debugger::aboutToThrow() +{ + if (!m_breakOnThrow) + return; + + if (m_runningJob) // do not re-enter when we're doing a job for the debugger. + return; + + QMutexLocker locker(&m_lock); + clearTemporaryBreakPoint(); + pauseAndWait(Throwing); +} + +Function *Debugger::getFunction() const +{ + ExecutionContext *context = m_engine->current; + if (CallContext *callCtx = context->asCallContext()) + return callCtx->function->function; + else { + Q_ASSERT(context->type == QV4::ExecutionContext::Type_GlobalContext); + return context->engine->globalCode; + } +} + +void Debugger::pauseAndWait(PauseReason reason) +{ + if (m_runningJob) + return; + m_state = Paused; - QMetaObject::invokeMethod(m_agent, "debuggerPaused", Qt::QueuedConnection, Q_ARG(QV4::Debugging::Debugger*, this)); - m_runningCondition.wait(&m_lock); + QMetaObject::invokeMethod(m_agent, "debuggerPaused", Qt::QueuedConnection, + Q_ARG(QV4::Debugging::Debugger*, this), + Q_ARG(QV4::Debugging::PauseReason, reason)); + + while (true) { + m_runningCondition.wait(&m_lock); + if (m_runningJob) { + m_runningJob->run(); + m_jobIsRunning.wakeAll(); + } else { + break; + } + } + m_state = Running; } +void Debugger::setTemporaryBreakPointOnNextLine() +{ + ExecutionState state = currentExecutionState(); + Function *function = state.function; + if (!function) + return; + + qptrdiff offset = function->programCounterForLine(state.lineNumber + 1); + if (offset < 0) + return; + + if (hasBreakOnInstruction(function, offset)) + return; + + setBreakOnInstruction(function, offset, true); + m_temporaryBreakPoint = TemporaryBreakPoint(function, offset); +} + +void Debugger::clearTemporaryBreakPoint() +{ + if (m_temporaryBreakPoint.function && m_temporaryBreakPoint.codeOffset) { + setBreakOnInstruction(m_temporaryBreakPoint.function, m_temporaryBreakPoint.codeOffset, false); + m_temporaryBreakPoint = TemporaryBreakPoint(); + } +} + +bool Debugger::temporaryBreakPointInFunction() const +{ + return m_temporaryBreakPoint.function == getFunction(); +} + void Debugger::applyPendingBreakPoints() { foreach (QV4::CompiledData::CompilationUnit *unit, m_engine->compilationUnits) { @@ -393,11 +633,62 @@ void Debugger::applyPendingBreakPoints() m_havePendingBreakPoints = false; } +void Debugger::setBreakOnInstruction(Function *function, qptrdiff codeOffset, bool onoff) +{ + uchar *codePtr = const_cast(function->codeData) + codeOffset; + QQmlJS::Moth::Instr *instruction = reinterpret_cast(codePtr); + instruction->common.breakPoint = onoff; +} + +bool Debugger::hasBreakOnInstruction(Function *function, qptrdiff codeOffset) +{ + uchar *codePtr = const_cast(function->codeData) + codeOffset; + QQmlJS::Moth::Instr *instruction = reinterpret_cast(codePtr); + return instruction->common.breakPoint; +} + +bool Debugger::reallyHitTheBreakPoint(const QString &filename, int linenr) +{ + QString condition = m_breakPointConditions.condition(filename, linenr); + if (condition.isEmpty()) + return true; + + Q_ASSERT(m_runningJob == 0); + EvalJob evilJob(m_engine, condition); + m_runningJob = &evilJob; + m_runningJob->run(); + + return evilJob.resultAsBoolean(); +} + +void Debugger::runInEngine(Debugger::Job *job) +{ + QMutexLocker locker(&m_lock); + runInEngine_havingLock(job); +} + +void Debugger::runInEngine_havingLock(Debugger::Job *job) +{ + Q_ASSERT(job); + Q_ASSERT(m_runningJob == 0); + + m_runningJob = job; + m_runningCondition.wakeAll(); + m_jobIsRunning.wait(&m_lock); + m_runningJob = 0; +} + void DebuggerAgent::addDebugger(Debugger *debugger) { Q_ASSERT(!m_debuggers.contains(debugger)); m_debuggers << debugger; debugger->attachToAgent(this); + + debugger->setBreakOnThrow(m_breakOnThrow); + + foreach (const BreakPoint &breakPoint, m_breakPoints.values()) + if (breakPoint.enabled) + debugger->addBreakPoint(breakPoint.fileName, breakPoint.lineNr, breakPoint.condition); } void DebuggerAgent::removeDebugger(Debugger *debugger) @@ -421,19 +712,73 @@ void DebuggerAgent::resumeAll() const { foreach (Debugger *debugger, m_debuggers) if (debugger->state() == Debugger::Paused) - debugger->resume(); + debugger->resume(Debugger::FullThrottle); } -void DebuggerAgent::addBreakPoint(const QString &fileName, int lineNumber) const +int DebuggerAgent::addBreakPoint(const QString &fileName, int lineNumber, bool enabled, const QString &condition) { - foreach (Debugger *debugger, m_debuggers) - debugger->addBreakPoint(fileName, lineNumber); + if (enabled) + foreach (Debugger *debugger, m_debuggers) + debugger->addBreakPoint(fileName, lineNumber, condition); + + int id = m_breakPoints.size(); + m_breakPoints.insert(id, BreakPoint(fileName, lineNumber, enabled, condition)); + return id; } -void DebuggerAgent::removeBreakPoint(const QString &fileName, int lineNumber) const +void DebuggerAgent::removeBreakPoint(int id) { - foreach (Debugger *debugger, m_debuggers) - debugger->removeBreakPoint(fileName, lineNumber); + BreakPoint breakPoint = m_breakPoints.value(id); + if (!breakPoint.isValid()) + return; + + m_breakPoints.remove(id); + + if (breakPoint.enabled) + foreach (Debugger *debugger, m_debuggers) + debugger->removeBreakPoint(breakPoint.fileName, breakPoint.lineNr); +} + +void DebuggerAgent::removeAllBreakPoints() +{ + QList ids = m_breakPoints.keys(); + foreach (int id, ids) + removeBreakPoint(id); +} + +void DebuggerAgent::enableBreakPoint(int id, bool onoff) +{ + BreakPoint &breakPoint = m_breakPoints[id]; + if (!breakPoint.isValid() || breakPoint.enabled == onoff) + return; + breakPoint.enabled = onoff; + + foreach (Debugger *debugger, m_debuggers) { + if (onoff) + debugger->addBreakPoint(breakPoint.fileName, breakPoint.lineNr, breakPoint.condition); + else + debugger->removeBreakPoint(breakPoint.fileName, breakPoint.lineNr); + } +} + +QList DebuggerAgent::breakPointIds(const QString &fileName, int lineNumber) const +{ + QList ids; + + for (QHash::const_iterator i = m_breakPoints.begin(), ei = m_breakPoints.end(); i != ei; ++i) + if (i->lineNr == lineNumber && fileName.endsWith(i->fileName)) + ids.push_back(i.key()); + + return ids; +} + +void DebuggerAgent::setBreakOnThrow(bool onoff) +{ + if (onoff != m_breakOnThrow) { + m_breakOnThrow = onoff; + foreach (Debugger *debugger, m_debuggers) + debugger->setBreakOnThrow(onoff); + } } DebuggerAgent::~DebuggerAgent() @@ -468,32 +813,99 @@ bool Debugger::BreakPoints::contains(const QString &fileName, int lineNumber) co void Debugger::BreakPoints::applyToFunction(Function *function, bool removeBreakPoints) { - Iterator breakPointsForFile = find(function->sourceFile()); - if (breakPointsForFile == end()) - return; + Iterator breakPointsForFile = begin(); + + while (breakPointsForFile != end()) { + if (!function->sourceFile().endsWith(breakPointsForFile.key())) { + ++breakPointsForFile; + continue; + } - QList::Iterator breakPoint = breakPointsForFile->begin(); - while (breakPoint != breakPointsForFile->end()) { - bool breakPointFound = false; - const quint32 *lineNumberMappings = function->compiledFunction->lineNumberMapping(); - for (quint32 i = 0; i < function->compiledFunction->nLineNumberMappingEntries; ++i) { - const int codeOffset = lineNumberMappings[i * 2]; - const int lineNumber = lineNumberMappings[i * 2 + 1]; - if (lineNumber == *breakPoint) { - uchar *codePtr = const_cast(function->codeData) + codeOffset; - QQmlJS::Moth::Instr *instruction = reinterpret_cast(codePtr); - instruction->common.breakPoint = !removeBreakPoints; - // Continue setting the next break point. - breakPointFound = true; - break; + QList::Iterator breakPoint = breakPointsForFile->begin(); + while (breakPoint != breakPointsForFile->end()) { + bool breakPointFound = false; + const quint32 *lineNumberMappings = function->compiledFunction->lineNumberMapping(); + for (quint32 i = 0; i < function->compiledFunction->nLineNumberMappingEntries; ++i) { + const int codeOffset = lineNumberMappings[i * 2]; + const int lineNumber = lineNumberMappings[i * 2 + 1]; + if (lineNumber == *breakPoint) { + setBreakOnInstruction(function, codeOffset, !removeBreakPoints); + // Continue setting the next break point. + breakPointFound = true; + break; + } } + if (breakPointFound) + breakPoint = breakPointsForFile->erase(breakPoint); + else + ++breakPoint; } - if (breakPointFound) - breakPoint = breakPointsForFile->erase(breakPoint); + + if (breakPointsForFile->isEmpty()) + breakPointsForFile = erase(breakPointsForFile); + else + ++breakPointsForFile; + } +} + + +Debugger::Collector::~Collector() +{ +} + +void Debugger::Collector::collect(const QString &name, const ScopedValue &value) +{ + switch (value->type()) { + case Value::Empty_Type: + Q_ASSERT(!"empty Value encountered"); + break; + case Value::Undefined_Type: + addUndefined(name); + break; + case Value::Null_Type: + addNull(name); + break; + case Value::Boolean_Type: + addBoolean(name, value->booleanValue()); + break; + case Value::Managed_Type: + if (String *s = value->asString()) + addString(name, s->toQString()); else - ++breakPoint; + addObject(name, value); + break; + case Value::Integer_Type: + addInteger(name, value->int_32); + break; + default: // double + addDouble(name, value->doubleValue()); + break; } +} + +void Debugger::Collector::collect(const ObjectRef object) +{ + bool property = true; + qSwap(property, m_isProperty); - if (breakPointsForFile->isEmpty()) - erase(breakPointsForFile); + Scope scope(m_engine); + ObjectIterator it(scope, object, ObjectIterator::EnumerableOnly); + ScopedValue name(scope); + ScopedValue value(scope); + while (true) { + Value v; + name = it.nextPropertyNameAsString(&v); + if (name->isNull()) + break; + QString key = name->toQStringNoThrow(); + value = v; + collect(key, value); + } + + qSwap(property, m_isProperty); +} + + +Debugger::Job::~Job() +{ } diff --git a/src/qml/jsruntime/qv4debugging_p.h b/src/qml/jsruntime/qv4debugging_p.h index 133cc3e17c..b6f39d86ba 100644 --- a/src/qml/jsruntime/qv4debugging_p.h +++ b/src/qml/jsruntime/qv4debugging_p.h @@ -45,6 +45,7 @@ #include "qv4global_p.h" #include "qv4engine_p.h" #include "qv4context_p.h" +#include "qv4scopedvalue_p.h" #include #include @@ -59,32 +60,51 @@ struct Function; namespace Debugging { +enum PauseReason { + PauseRequest, + BreakPoint, + Throwing, + Step +}; + class DebuggerAgent; class Q_QML_EXPORT Debugger { public: - struct VarInfo { - enum Type { - Invalid = 0, - Undefined = 1, - Null, - Number, - String, - Bool, - Object - }; - - QString name; - QVariant value; - Type type; - - VarInfo(): type(Invalid) {} - VarInfo(const QString &name, const QVariant &value, Type type) - : name(name), value(value), type(type) - {} + class Job + { + public: + virtual ~Job() = 0; + virtual void run() = 0; + }; - bool isValid() const { return type != Invalid; } + class Q_QML_EXPORT Collector + { + public: + Collector(ExecutionEngine *engine): m_engine(engine), m_isProperty(false) {} + virtual ~Collector(); + + void collect(const QString &name, const ScopedValue &value); + void collect(const ObjectRef object); + + protected: + virtual void addUndefined(const QString &name) = 0; + virtual void addNull(const QString &name) = 0; + virtual void addBoolean(const QString &name, bool value) = 0; + virtual void addString(const QString &name, const QString &value) = 0; + virtual void addObject(const QString &name, ValueRef value) = 0; + virtual void addInteger(const QString &name, int value) = 0; + virtual void addDouble(const QString &name, double value) = 0; + + QV4::ExecutionEngine *engine() const { return m_engine; } + + bool isProperty() const { return m_isProperty; } + void setIsProperty(bool onoff) { m_isProperty = onoff; } + + private: + QV4::ExecutionEngine *m_engine; + bool m_isProperty; }; enum State { @@ -92,20 +112,36 @@ public: Paused }; + enum Speed { + FullThrottle = 0, + StepIn, + StepOut, + StepOver, + + NotStepping = FullThrottle + }; + Debugger(ExecutionEngine *engine); ~Debugger(); + ExecutionEngine *engine() const + { return m_engine; } + void attachToAgent(DebuggerAgent *agent); void detachFromAgent(); + DebuggerAgent *agent() const { return m_agent; } + void gatherSources(int requestSequenceNr); void pause(); - void resume(); + void resume(Speed speed); State state() const { return m_state; } - void addBreakPoint(const QString &fileName, int lineNumber); + void addBreakPoint(const QString &fileName, int lineNumber, const QString &condition = QString()); void removeBreakPoint(const QString &fileName, int lineNumber); + void setBreakOnThrow(bool onoff); + struct ExecutionState { ExecutionState() : lineNumber(-1), function(0) {} @@ -117,29 +153,47 @@ public: ExecutionState currentExecutionState(const uchar *code = 0) const; bool pauseAtNextOpportunity() const { - return m_pauseRequested || m_havePendingBreakPoints; + return m_pauseRequested || m_havePendingBreakPoints || m_gatherSources; } void setPendingBreakpoints(Function *function); QVector stackTrace(int frameLimit = -1) const; - QList retrieveArgumentsFromContext(const QStringList &path, int frame = 0); - QList retrieveLocalsFromContext(const QStringList &path, int frame = 0); + void collectArgumentsInContext(Collector *collector, int frameNr = 0, int scopeNr = 0); + void collectLocalsInContext(Collector *collector, int frameNr = 0, int scopeNr = 0); + bool collectThisInContext(Collector *collector, int frame = 0); + void collectThrownValue(Collector *collector); + void collectReturnedValue(Collector *collector) const; + QVector getScopeTypes(int frame = 0) const; public: // compile-time interface void maybeBreakAtInstruction(const uchar *code, bool breakPointHit); public: // execution hooks - void aboutToThrow(const ValueRef value); + void enteringFunction(); + void leavingFunction(const ReturnedValue &retVal); + void aboutToThrow(); private: + Function *getFunction() const; + // requires lock to be held - void pauseAndWait(); + void pauseAndWait(PauseReason reason); + // requires lock to be held + void setTemporaryBreakPointOnNextLine(); + // requires lock to be held + void clearTemporaryBreakPoint(); + // requires lock to be held + bool temporaryBreakPointInFunction() const; void applyPendingBreakPoints(); + static void setBreakOnInstruction(Function *function, qptrdiff codeOffset, bool onoff); + static bool hasBreakOnInstruction(Function *function, qptrdiff codeOffset); + bool reallyHitTheBreakPoint(const QString &filename, int linenr); - QList retrieveFromValue(const ObjectRef o, const QStringList &path) const; - void convert(ValueRef v, QVariant *varValue, VarInfo::Type *type) const; + void runInEngine(Job *job); + void runInEngine_havingLock(Debugger::Job *job); +private: struct BreakPoints : public QHash > { void add(const QString &fileName, int lineNumber); @@ -154,17 +208,51 @@ private: QWaitCondition m_runningCondition; State m_state; bool m_pauseRequested; + Job *m_gatherSources; bool m_havePendingBreakPoints; BreakPoints m_pendingBreakPointsToAdd; BreakPoints m_pendingBreakPointsToAddToFutureCode; BreakPoints m_pendingBreakPointsToRemove; const uchar *m_currentInstructionPointer; + Speed m_stepping; + bool m_stopForStepping; + QV4::PersistentValue m_returnedValue; + + struct TemporaryBreakPoint { + Function *function; + qptrdiff codeOffset; + TemporaryBreakPoint(Function *function = 0, qptrdiff codeOffset = 0) + : function(function), codeOffset(codeOffset) + {} + } m_temporaryBreakPoint; + + bool m_breakOnThrow; + + Job *m_runningJob; + QWaitCondition m_jobIsRunning; + + struct BreakPointConditions: public QHash + { + static QString genKey(const QString &fileName, int lineNumber) + { + return fileName + QLatin1Char(':') + QString::number(lineNumber); + } + + QString condition(const QString &fileName, int lineNumber) + { return value(genKey(fileName, lineNumber)); } + void add(const QString &fileName, int lineNumber, const QString &condition) + { insert(genKey(fileName, lineNumber), condition); } + void remove(const QString &fileName, int lineNumber) + { take(genKey(fileName, lineNumber)); } + }; + BreakPointConditions m_breakPointConditions; }; class Q_QML_EXPORT DebuggerAgent : public QObject { Q_OBJECT public: + DebuggerAgent(): m_breakOnThrow(false) {} ~DebuggerAgent(); void addDebugger(Debugger *debugger); @@ -173,13 +261,39 @@ public: void pause(Debugger *debugger) const; void pauseAll() const; void resumeAll() const; - void addBreakPoint(const QString &fileName, int lineNumber) const; - void removeBreakPoint(const QString &fileName, int lineNumber) const; + int addBreakPoint(const QString &fileName, int lineNumber, bool enabled = true, const QString &condition = QString()); + void removeBreakPoint(int id); + void removeAllBreakPoints(); + void enableBreakPoint(int id, bool onoff); + QList breakPointIds(const QString &fileName, int lineNumber) const; - Q_INVOKABLE virtual void debuggerPaused(QV4::Debugging::Debugger *debugger) = 0; + bool breakOnThrow() const { return m_breakOnThrow; } + void setBreakOnThrow(bool onoff); + + Q_INVOKABLE virtual void debuggerPaused(QV4::Debugging::Debugger *debugger, + QV4::Debugging::PauseReason reason) = 0; + Q_INVOKABLE virtual void sourcesCollected(QV4::Debugging::Debugger *debugger, + QStringList sources, int requestSequenceNr) = 0; protected: QList m_debuggers; + + struct BreakPoint { + QString fileName; + int lineNr; + bool enabled; + QString condition; + + BreakPoint(): lineNr(-1), enabled(false) {} + BreakPoint(const QString &fileName, int lineNr, bool enabled, const QString &condition) + : fileName(fileName), lineNr(lineNr), enabled(enabled), condition(condition) + {} + + bool isValid() const { return lineNr >= 0 && !fileName.isEmpty(); } + }; + + QHash m_breakPoints; + bool m_breakOnThrow; }; } // namespace Debugging @@ -188,5 +302,6 @@ protected: QT_END_NAMESPACE Q_DECLARE_METATYPE(QV4::Debugging::Debugger*) +Q_DECLARE_METATYPE(QV4::Debugging::PauseReason) #endif // DEBUGGING_H diff --git a/src/qml/jsruntime/qv4engine.cpp b/src/qml/jsruntime/qv4engine.cpp index 8da593f8e3..7af313307e 100644 --- a/src/qml/jsruntime/qv4engine.cpp +++ b/src/qml/jsruntime/qv4engine.cpp @@ -826,7 +826,7 @@ ReturnedValue ExecutionEngine::throwException(const ValueRef value) exceptionStackTrace = stackTrace(); if (debugger) - debugger->aboutToThrow(value); + debugger->aboutToThrow(); return Encode::undefined(); } diff --git a/src/qml/jsruntime/qv4function.cpp b/src/qml/jsruntime/qv4function.cpp index c3d2165fb4..291d4d37d4 100644 --- a/src/qml/jsruntime/qv4function.cpp +++ b/src/qml/jsruntime/qv4function.cpp @@ -91,10 +91,11 @@ void Function::mark(ExecutionEngine *e) } namespace QV4 { +template struct LineNumberMappingHelper { const quint32 *table; - int lowerBound(int begin, int end, quint32 offset) { + int lowerBound(int begin, int end, SearchType value) { int middle; int n = int(end - begin); int half; @@ -102,7 +103,7 @@ struct LineNumberMappingHelper while (n > 0) { half = n >> 1; middle = begin + half; - if (table[middle * 2] < offset) { + if (table[middle * 2 + field] < value) { begin = middle + 1; n -= half + 1; } else { @@ -111,13 +112,30 @@ struct LineNumberMappingHelper } return begin; } -}; + int upperBound(int begin, int end, SearchType value) { + int middle; + int n = int(end - begin); + int half; + while (n > 0) { + half = n >> 1; + middle = begin + half; + if (value < table[middle * 2 + field]) { + n = half; + } else { + begin = middle + 1; + n -= half + 1; + } + } + return begin; + } +}; } int Function::lineNumberForProgramCounter(qptrdiff offset) const { - LineNumberMappingHelper helper; + // Access the first field, the program counter + LineNumberMappingHelper<0, qptrdiff> helper; helper.table = compiledFunction->lineNumberMapping(); const uint count = compiledFunction->nLineNumberMappingEntries; @@ -129,4 +147,19 @@ int Function::lineNumberForProgramCounter(qptrdiff offset) const return helper.table[pos * 2 + 1]; } +qptrdiff Function::programCounterForLine(quint32 line) const +{ + // Access the second field, the line number + LineNumberMappingHelper<1, quint32> helper; + helper.table = compiledFunction->lineNumberMapping(); + const int count = static_cast(compiledFunction->nLineNumberMappingEntries); + + int pos = helper.upperBound(0, count, line); + if (pos != 0 && count > 0) + --pos; + if (pos == count) + return -1; + return helper.table[pos * 2]; +} + QT_END_NAMESPACE diff --git a/src/qml/jsruntime/qv4function_p.h b/src/qml/jsruntime/qv4function_p.h index e9e59928ed..8a8f6a5d79 100644 --- a/src/qml/jsruntime/qv4function_p.h +++ b/src/qml/jsruntime/qv4function_p.h @@ -112,6 +112,7 @@ struct Function { void mark(ExecutionEngine *e); int lineNumberForProgramCounter(qptrdiff offset) const; + qptrdiff programCounterForLine(quint32 line) const; }; } diff --git a/src/qml/jsruntime/qv4vme_moth.cpp b/src/qml/jsruntime/qv4vme_moth.cpp index 3121f178a0..68f791c87d 100644 --- a/src/qml/jsruntime/qv4vme_moth.cpp +++ b/src/qml/jsruntime/qv4vme_moth.cpp @@ -64,8 +64,8 @@ using namespace QQmlJS::Moth; #define MOTH_BEGIN_INSTR_COMMON(I) { \ const InstrMeta<(int)Instr::I>::DataType &instr = InstrMeta<(int)Instr::I>::data(*genericInstr); \ code += InstrMeta<(int)Instr::I>::Size; \ - if (context->engine->debugger && (instr.breakPoint || context->engine->debugger->pauseAtNextOpportunity())) \ - context->engine->debugger->maybeBreakAtInstruction(code, instr.breakPoint); \ + if (debugger && (instr.breakPoint || debugger->pauseAtNextOpportunity())) \ + debugger->maybeBreakAtInstruction(code, instr.breakPoint); \ Q_UNUSED(instr); \ TRACE_INSTR(I) @@ -176,8 +176,6 @@ QV4::ReturnedValue VME::run(QV4::ExecutionContext *context, const uchar *code, #endif ) { - const uchar *exceptionHandler = 0; - #ifdef DO_TRACE_INSTR qDebug("Starting VME with context=%p and code=%p", context, code); #endif // DO_TRACE_INSTR @@ -194,6 +192,14 @@ QV4::ReturnedValue VME::run(QV4::ExecutionContext *context, const uchar *code, } #endif + const uchar *exceptionHandler = 0; + + QV4::Debugging::Debugger *debugger = context->engine->debugger; + +#ifdef DO_TRACE_INSTR + qDebug("Starting VME with context=%p and code=%p", context, code); +#endif // DO_TRACE_INSTR + QV4::SafeString * const runtimeStrings = context->compilationUnit->runtimeStrings; context->interpreterInstructionPointer = &code; @@ -695,5 +701,11 @@ void **VME::instructionJumpTable() QV4::ReturnedValue VME::exec(QV4::ExecutionContext *ctxt, const uchar *code) { VME vme; - return vme.run(ctxt, code); + QV4::Debugging::Debugger *debugger = ctxt->engine->debugger; + if (debugger) + debugger->enteringFunction(); + QV4::ReturnedValue retVal = vme.run(ctxt, code); + if (debugger) + debugger->leavingFunction(retVal); + return retVal; } -- cgit v1.2.3