aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorErik Verbruggen <erik.verbruggen@me.com>2013-10-16 12:29:47 +0200
committerThe Qt Project <gerrit-noreply@qt-project.org>2013-11-10 11:01:35 +0100
commit0910a577f4d12eea4a099c989bd58f1dee6c88db (patch)
tree53860b5debf08cef684da1eb387769dbe8ef2d42 /src
parent1738e4ee119bbcd20d33353e7018f04d92766639 (diff)
Debugging with V4
Currently missing, but coming in subsequent patches: - evaluating expressions - evaluating breakpoint conditions Change-Id: Ib43f2a3aaa252741ea7ce857a274480feb8741aa Reviewed-by: Simon Hausmann <simon.hausmann@digia.com>
Diffstat (limited to 'src')
-rw-r--r--src/qml/debugger/qqmldebugserver.cpp2
-rw-r--r--src/qml/debugger/qqmldebugservice.cpp27
-rw-r--r--src/qml/debugger/qqmldebugservice_p_p.h1
-rw-r--r--src/qml/debugger/qv4debugservice.cpp1112
-rw-r--r--src/qml/debugger/qv4debugservice_p.h7
-rw-r--r--src/qml/jsruntime/qv4debugging.cpp816
-rw-r--r--src/qml/jsruntime/qv4debugging_p.h179
-rw-r--r--src/qml/jsruntime/qv4engine.cpp2
-rw-r--r--src/qml/jsruntime/qv4function.cpp41
-rw-r--r--src/qml/jsruntime/qv4function_p.h1
-rw-r--r--src/qml/jsruntime/qv4vme_moth.cpp22
11 files changed, 1836 insertions, 374 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)
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<Debugger*>();
+ qMetaTypeId<PauseReason>();
}
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<StackFrame> Debugger::stackTrace(int frameLimit) const
return m_engine->stackTrace(frameLimit);
}
-QList<Debugger::VarInfo> Debugger::retrieveFromValue(const ObjectRef o, const QStringList &path) const
+static inline CallContext *findContext(ExecutionContext *ctxt, int frame)
{
- QList<Debugger::VarInfo> 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<int>(0);
- break;
- case Value::Null_Type:
- *type = VarInfo::Null;
- varValue->setValue<int>(0);
- break;
- case Value::Boolean_Type:
- *type = VarInfo::Bool;
- varValue->setValue<bool>(v->booleanValue());
- break;
- case Value::Managed_Type:
- if (v->isString()) {
- *type = VarInfo::String;
- varValue->setValue<QString>(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<QString>(prim->toQString());
- }
- break;
- case Value::Integer_Type:
- *type = VarInfo::Number;
- varValue->setValue<double>((double)v->int_32);
- break;
- default: // double
- *type = VarInfo::Number;
- varValue->setValue<double>(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::VarInfo> 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<VarInfo> 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::VarInfo> Debugger::retrieveLocalsFromContext(const QStringList &path, int frame)
+void Debugger::collectThrownValue(Collector *collector)
{
- QList<VarInfo> 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<ExecutionContext::Type> Debugger::getScopeTypes(int frame) const
+{
+ QVector<ExecutionContext::Type> 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<CallContext *>(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<uchar *>(function->codeData) + codeOffset;
+ QQmlJS::Moth::Instr *instruction = reinterpret_cast<QQmlJS::Moth::Instr*>(codePtr);
+ instruction->common.breakPoint = onoff;
+}
+
+bool Debugger::hasBreakOnInstruction(Function *function, qptrdiff codeOffset)
+{
+ uchar *codePtr = const_cast<uchar *>(function->codeData) + codeOffset;
+ QQmlJS::Moth::Instr *instruction = reinterpret_cast<QQmlJS::Moth::Instr*>(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<int> 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<int> DebuggerAgent::breakPointIds(const QString &fileName, int lineNumber) const
+{
+ QList<int> ids;
+
+ for (QHash<int, BreakPoint>::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<int>::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<uchar *>(function->codeData) + codeOffset;
- QQmlJS::Moth::Instr *instruction = reinterpret_cast<QQmlJS::Moth::Instr*>(codePtr);
- instruction->common.breakPoint = !removeBreakPoints;
- // Continue setting the next break point.
- breakPointFound = true;
- break;
+ QList<int>::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 <QHash>
#include <QThread>
@@ -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<StackFrame> stackTrace(int frameLimit = -1) const;
- QList<VarInfo> retrieveArgumentsFromContext(const QStringList &path, int frame = 0);
- QList<VarInfo> 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<ExecutionContext::Type> 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<Debugger::VarInfo> 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<QString, QList<int> >
{
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<QString, QString>
+ {
+ 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<int> 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<Debugger *> 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<int, BreakPoint> 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 <int field, typename SearchType>
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<int>(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;
}