aboutsummaryrefslogtreecommitdiffstats
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
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>
-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
-rw-r--r--tests/auto/qml/debugger/debugger.pro2
-rw-r--r--tests/auto/qml/debugger/qqmldebugjs/tst_qqmldebugjs.cpp284
-rw-r--r--tests/auto/qml/qv4debugger/tst_qv4debugger.cpp210
14 files changed, 2013 insertions, 693 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;
}
diff --git a/tests/auto/qml/debugger/debugger.pro b/tests/auto/qml/debugger/debugger.pro
index 15abbcc7ab..aa3ad6a3a3 100644
--- a/tests/auto/qml/debugger/debugger.pro
+++ b/tests/auto/qml/debugger/debugger.pro
@@ -2,7 +2,7 @@ TEMPLATE = subdirs
PUBLICTESTS += \
qqmlenginedebugservice \
-# qqmldebugjs \
+ qqmldebugjs \
qpacketprotocol \
# qv8profilerservice \
# qdebugmessageservice \
diff --git a/tests/auto/qml/debugger/qqmldebugjs/tst_qqmldebugjs.cpp b/tests/auto/qml/debugger/qqmldebugjs/tst_qqmldebugjs.cpp
index 28b7775fc6..fb6b4eee65 100644
--- a/tests/auto/qml/debugger/qqmldebugjs/tst_qqmldebugjs.cpp
+++ b/tests/auto/qml/debugger/qqmldebugjs/tst_qqmldebugjs.cpp
@@ -100,7 +100,6 @@ const char *SCOPES = "scopes";
const char *SCRIPTS = "scripts";
const char *SOURCE = "source";
const char *SETBREAKPOINT = "setbreakpoint";
-const char *CHANGEBREAKPOINT = "changebreakpoint";
const char *CLEARBREAKPOINT = "clearbreakpoint";
const char *SETEXCEPTIONBREAK = "setexceptionbreak";
const char *VERSION = "version";
@@ -127,7 +126,6 @@ const char *UNCAUGHT = "uncaught";
//const char *PAUSE = "pause";
//const char *RESUME = "resume";
-const char *ENABLE_DEBUG= "-enable-debugger";//flag needed for debugger with qml binary
const char *BLOCKMODE = "-qmljsdebugger=port:3771,3800,block";
const char *NORMALMODE = "-qmljsdebugger=port:3771,3800";
const char *TEST_QMLFILE = "test.qml";
@@ -189,21 +187,17 @@ private slots:
void setBreakpointInScriptOnComment();
void setBreakpointInScriptOnEmptyLine();
void setBreakpointInScriptOnOptimizedBinding();
- void setBreakpointInScriptWithCondition();
+// void setBreakpointInScriptWithCondition(); // Not supported yet.
void setBreakpointInScriptThatQuits();
//void setBreakpointInFunction(); //NOT SUPPORTED
- void setBreakpointOnEvent();
+// void setBreakpointOnEvent();
// void setBreakpointWhenAttaching();
- void changeBreakpoint();
- void changeBreakpointOnCondition();
-
void clearBreakpoint();
void setExceptionBreak();
void stepNext();
- void stepNextWithCount();
void stepIn();
void stepOut();
void continueDebugging();
@@ -214,15 +208,11 @@ private slots:
void getScopeDetails();
- void evaluateInGlobalScope();
- void evaluateInLocalScope();
-
- void getScopes();
+// void evaluateInGlobalScope(); // Not supported yet.
+// void evaluateInLocalScope(); // Not supported yet.
void getScripts();
- void getSource();
-
// void profile(); //NOT SUPPORTED
// void verifyQMLOptimizerDisabled();
@@ -269,17 +259,14 @@ public:
void connect();
void interrupt();
- void continueDebugging(StepAction stepAction, int stepCount = 1);
+ void continueDebugging(StepAction stepAction);
void evaluate(QString expr, bool global = false, bool disableBreak = false, int frame = -1, const QVariantMap &addContext = QVariantMap());
void lookup(QList<int> handles, bool includeSource = false);
void backtrace(int fromFrame = -1, int toFrame = -1, bool bottom = false);
void frame(int number = -1);
void scope(int number = -1, int frameNumber = -1);
- void scopes(int frameNumber = -1);
void scripts(int types = 4, QList<int> ids = QList<int>(), bool includeSource = false, QVariant filter = QVariant());
- void source(int frame = -1, int fromLine = -1, int toLine = -1);
void setBreakpoint(QString type, QString target, int line = -1, int column = -1, bool enabled = true, QString condition = QString(), int ignoreCount = -1);
- void changeBreakpoint(int breakpoint, bool enabled = true, QString condition = QString(), int ignoreCount = -1);
void clearBreakpoint(int breakpoint);
void setExceptionBreak(Exception type, bool enabled = false);
void version();
@@ -327,7 +314,7 @@ void QJSDebugClient::interrupt()
sendMessage(packMessage(INTERRUPT));
}
-void QJSDebugClient::continueDebugging(StepAction action, int count)
+void QJSDebugClient::continueDebugging(StepAction action)
{
// { "seq" : <number>,
// "type" : "request",
@@ -351,8 +338,6 @@ void QJSDebugClient::continueDebugging(StepAction action, int count)
default:break;
}
if (!args.isUndefined()) {
- if (count != 1)
- args.setProperty(QLatin1String(STEPCOUNT),QJSValue(count));
jsonVal.setProperty(QLatin1String(ARGUMENTS),args);
}
}
@@ -516,30 +501,6 @@ void QJSDebugClient::scope(int number, int frameNumber)
sendMessage(packMessage(V8REQUEST, json.toString().toUtf8()));
}
-void QJSDebugClient::scopes(int frameNumber)
-{
- // { "seq" : <number>,
- // "type" : "request",
- // "command" : "scopes",
- // "arguments" : { "frameNumber" : <frame number, optional uses selected frame if missing>
- // }
- // }
- VARIANTMAPINIT;
- jsonVal.setProperty(QLatin1String(COMMAND),QJSValue(QLatin1String(SCOPES)));
-
- if (frameNumber != -1) {
- QJSValue args = parser.call(QJSValueList() << obj);
- args.setProperty(QLatin1String(FRAMENUMBER),QJSValue(frameNumber));
-
- if (!args.isUndefined()) {
- jsonVal.setProperty(QLatin1String(ARGUMENTS),args);
- }
- }
-
- QJSValue json = stringify.call(QJSValueList() << jsonVal);
- sendMessage(packMessage(V8REQUEST, json.toString().toUtf8()));
-}
-
void QJSDebugClient::scripts(int types, QList<int> ids, bool includeSource, QVariant /*filter*/)
{
// { "seq" : <number>,
@@ -584,39 +545,6 @@ void QJSDebugClient::scripts(int types, QList<int> ids, bool includeSource, QVar
sendMessage(packMessage(V8REQUEST, json.toString().toUtf8()));
}
-void QJSDebugClient::source(int frame, int fromLine, int toLine)
-{
- // { "seq" : <number>,
- // "type" : "request",
- // "command" : "source",
- // "arguments" : { "frame" : <frame number (default selected frame)>
- // "fromLine" : <from line within the source default is line 0>
- // "toLine" : <to line within the source this line is not included in
- // the result default is the number of lines in the script>
- // }
- // }
- VARIANTMAPINIT;
- jsonVal.setProperty(QLatin1String(COMMAND),QJSValue(QLatin1String(SOURCE)));
-
- QJSValue args = parser.call(QJSValueList() << obj);
-
- if (frame != -1)
- args.setProperty(QLatin1String(FRAME),QJSValue(frame));
-
- if (fromLine != -1)
- args.setProperty(QLatin1String(FROMLINE),QJSValue(fromLine));
-
- if (toLine != -1)
- args.setProperty(QLatin1String(TOLINE),QJSValue(toLine));
-
- if (!args.isUndefined()) {
- jsonVal.setProperty(QLatin1String(ARGUMENTS),args);
- }
-
- QJSValue json = stringify.call(QJSValueList() << jsonVal);
- sendMessage(packMessage(V8REQUEST, json.toString().toUtf8()));
-}
-
void QJSDebugClient::setBreakpoint(QString type, QString target, int line, int column, bool enabled, QString condition, int ignoreCount)
{
// { "seq" : <number>,
@@ -670,39 +598,6 @@ void QJSDebugClient::setBreakpoint(QString type, QString target, int line, int c
}
}
-void QJSDebugClient::changeBreakpoint(int breakpoint, bool enabled, QString condition, int ignoreCount)
-{
- // { "seq" : <number>,
- // "type" : "request",
- // "command" : "changebreakpoint",
- // "arguments" : { "breakpoint" : <number of the break point to clear>
- // "enabled" : <initial enabled state. True or false, default is true>
- // "condition" : <string with break point condition>
- // "ignoreCount" : <number specifying the number of break point hits }
- // }
- VARIANTMAPINIT;
- jsonVal.setProperty(QLatin1String(COMMAND),QJSValue(QLatin1String(CHANGEBREAKPOINT)));
-
- QJSValue args = parser.call(QJSValueList() << obj);
-
- args.setProperty(QLatin1String(BREAKPOINT),QJSValue(breakpoint));
-
- args.setProperty(QLatin1String(ENABLED),QJSValue(enabled));
-
- if (!condition.isEmpty())
- args.setProperty(QLatin1String(CONDITION),QJSValue(condition));
-
- if (ignoreCount != -1)
- args.setProperty(QLatin1String(IGNORECOUNT),QJSValue(ignoreCount));
-
- if (!args.isUndefined()) {
- jsonVal.setProperty(QLatin1String(ARGUMENTS),args);
- }
-
- QJSValue json = stringify.call(QJSValueList() << jsonVal);
- sendMessage(packMessage(V8REQUEST, json.toString().toUtf8()));
-}
-
void QJSDebugClient::clearBreakpoint(int breakpoint)
{
// { "seq" : <number>,
@@ -938,13 +833,13 @@ void tst_QQmlDebugJS::cleanupTestCase()
bool tst_QQmlDebugJS::init(const QString &qmlFile, bool blockMode)
{
connection = new QQmlDebugConnection();
- process = new QQmlDebugProcess(QLibraryInfo::location(QLibraryInfo::BinariesPath) + "/qml", this);
+ process = new QQmlDebugProcess(QLibraryInfo::location(QLibraryInfo::BinariesPath) + "/qmlscene", this);
client = new QJSDebugClient(connection);
if (blockMode)
- process->start(QStringList() << QLatin1String(ENABLE_DEBUG) << QLatin1String(BLOCKMODE) << testFile(qmlFile));
+ process->start(QStringList() << QLatin1String(BLOCKMODE) << testFile(qmlFile));
else
- process->start(QStringList() << QLatin1String(ENABLE_DEBUG) << QLatin1String(NORMALMODE) << testFile(qmlFile));
+ process->start(QStringList() << QLatin1String(NORMALMODE) << testFile(qmlFile));
if (!process->waitForSessionStart()) {
qDebug() << "could not launch application, or did not get 'Waiting for connection'.";
@@ -1186,8 +1081,11 @@ void tst_QQmlDebugJS::setBreakpointInScriptOnOptimizedBinding()
QCOMPARE(QFileInfo(body.value("script").toMap().value("name").toString()).fileName(), QLatin1String(BREAKPOINTRELOCATION_QMLFILE));
}
+#if 0
void tst_QQmlDebugJS::setBreakpointInScriptWithCondition()
{
+ QFAIL("conditional breakpoints are not yet supported");
+
//void setBreakpoint(QString type, QString target, int line = -1, int column = -1, bool enabled = false, QString condition = QString(), int ignoreCount = -1)
int out = 10;
@@ -1218,6 +1116,7 @@ void tst_QQmlDebugJS::setBreakpointInScriptWithCondition()
QVERIFY(body.value("value").toInt() > out);
}
+#endif
void tst_QQmlDebugJS::setBreakpointInScriptThatQuits()
{
@@ -1274,8 +1173,11 @@ void tst_QQmlDebugJS::setBreakpointWhenAttaching()
// QCOMPARE(QFileInfo(body.value("script").toMap().value("name").toString()).fileName(), QLatin1String(QMLFILE));
//}
+#if 0
void tst_QQmlDebugJS::setBreakpointOnEvent()
{
+ QFAIL("Not implemented in V4.");
+
//void setBreakpoint(QString type, QString target, int line = -1, int column = -1, bool enabled = false, QString condition = QString(), int ignoreCount = -1)
QVERIFY(init(TIMER_QMLFILE));
@@ -1292,97 +1194,7 @@ void tst_QQmlDebugJS::setBreakpointOnEvent()
QCOMPARE(QFileInfo(body.value("script").toMap().value("name").toString()).fileName(), QLatin1String(TIMER_QMLFILE));
}
-
-
-void tst_QQmlDebugJS::changeBreakpoint()
-{
- //void changeBreakpoint(int breakpoint, bool enabled = false, QString condition = QString(), int ignoreCount = -1)
-
- int sourceLine1 = 50;
- int sourceLine2 = 51;
- QVERIFY(init(CHANGEBREAKPOINT_QMLFILE));
-
- client->connect();
- client->setBreakpoint(QLatin1String(SCRIPTREGEXP), QLatin1String(CHANGEBREAKPOINT_QMLFILE), sourceLine2, -1, true);
- client->setBreakpoint(QLatin1String(SCRIPTREGEXP), QLatin1String(CHANGEBREAKPOINT_QMLFILE), sourceLine1, -1, true);
-
- QVERIFY(QQmlDebugTest::waitForSignal(client, SIGNAL(stopped())));
-
- //Will hit 1st brakpoint, change this breakpoint enable = false
- QString jsonString(client->response);
- QVariantMap value = client->parser.call(QJSValueList() << QJSValue(jsonString)).toVariant().toMap();
-
- QVariantMap body = value.value("body").toMap();
- QList<QVariant> breakpointsHit = body.value("breakpoints").toList();
-
- int breakpoint = breakpointsHit.at(0).toInt();
- client->changeBreakpoint(breakpoint,false);
-
- QVERIFY(QQmlDebugTest::waitForSignal(client, SIGNAL(result())));
-
- //Continue with debugging
- client->continueDebugging(QJSDebugClient::Continue);
- //Hit 2nd breakpoint
- QVERIFY(QQmlDebugTest::waitForSignal(client, SIGNAL(stopped())));
-
- //Continue with debugging
- client->continueDebugging(QJSDebugClient::Continue);
- //Should stop at 2nd breakpoint
- QVERIFY(QQmlDebugTest::waitForSignal(client, SIGNAL(stopped())));
-
- jsonString = client->response;
- value = client->parser.call(QJSValueList() << QJSValue(jsonString)).toVariant().toMap();
-
- body = value.value("body").toMap();
-
- QCOMPARE(body.value("sourceLine").toInt(), sourceLine2);
-}
-
-void tst_QQmlDebugJS::changeBreakpointOnCondition()
-{
- //void changeBreakpoint(int breakpoint, bool enabled = false, QString condition = QString(), int ignoreCount = -1)
-
- int sourceLine1 = 50;
- int sourceLine2 = 51;
-
- QVERIFY(init(CHANGEBREAKPOINT_QMLFILE));
-
- client->connect();
- client->setBreakpoint(QLatin1String(SCRIPTREGEXP), QLatin1String(CHANGEBREAKPOINT_QMLFILE), sourceLine1, -1, true);
- client->setBreakpoint(QLatin1String(SCRIPTREGEXP), QLatin1String(CHANGEBREAKPOINT_QMLFILE), sourceLine2, -1, true);
-
- QVERIFY(QQmlDebugTest::waitForSignal(client, SIGNAL(stopped())));
-
- //Will hit 1st brakpoint, change this breakpoint enable = false
- QString jsonString(client->response);
- QVariantMap value = client->parser.call(QJSValueList() << QJSValue(jsonString)).toVariant().toMap();
-
- QVariantMap body = value.value("body").toMap();
- QList<QVariant> breakpointsHit = body.value("breakpoints").toList();
-
- int breakpoint = breakpointsHit.at(0).toInt();
- client->changeBreakpoint(breakpoint, false, QLatin1String("d == 0"));
-
- QVERIFY(QQmlDebugTest::waitForSignal(client, SIGNAL(result())));
-
- //Continue with debugging
- client->continueDebugging(QJSDebugClient::Continue);
- //Hit 2nd breakpoint
- QVERIFY(QQmlDebugTest::waitForSignal(client, SIGNAL(stopped())));
-
- //Continue with debugging
- client->continueDebugging(QJSDebugClient::Continue);
- //Should stop at 2nd breakpoint
- QVERIFY(QQmlDebugTest::waitForSignal(client, SIGNAL(stopped())));
-
- jsonString = client->response;
- value = client->parser.call(QJSValueList() << QJSValue(jsonString)).toVariant().toMap();
-
- body = value.value("body").toMap();
-
- QCOMPARE(body.value("sourceLine").toInt(), sourceLine2);
-
-}
+#endif
void tst_QQmlDebugJS::clearBreakpoint()
{
@@ -1461,29 +1273,6 @@ void tst_QQmlDebugJS::stepNext()
QCOMPARE(QFileInfo(body.value("script").toMap().value("name").toString()).fileName(), QLatin1String(STEPACTION_QMLFILE));
}
-void tst_QQmlDebugJS::stepNextWithCount()
-{
- //void continueDebugging(StepAction stepAction, int stepCount = 1);
-
- int sourceLine = 50;
- QVERIFY(init(STEPACTION_QMLFILE));
-
- client->connect();
- client->setBreakpoint(QLatin1String(SCRIPTREGEXP), QLatin1String(STEPACTION_QMLFILE), sourceLine, -1, true);
- QVERIFY(QQmlDebugTest::waitForSignal(client, SIGNAL(stopped())));
-
- client->continueDebugging(QJSDebugClient::Next, 2);
- QVERIFY(QQmlDebugTest::waitForSignal(client, SIGNAL(stopped())));
-
- QString jsonString(client->response);
- QVariantMap value = client->parser.call(QJSValueList() << QJSValue(jsonString)).toVariant().toMap();
-
- QVariantMap body = value.value("body").toMap();
-
- QCOMPARE(body.value("sourceLine").toInt(), sourceLine + 2);
- QCOMPARE(QFileInfo(body.value("script").toMap().value("name").toString()).fileName(), QLatin1String(STEPACTION_QMLFILE));
-}
-
void tst_QQmlDebugJS::stepIn()
{
//void continueDebugging(StepAction stepAction, int stepCount = 1);
@@ -1602,6 +1391,7 @@ void tst_QQmlDebugJS::getScopeDetails()
QVERIFY(QQmlDebugTest::waitForSignal(client, SIGNAL(result())));
}
+#if 0
void tst_QQmlDebugJS::evaluateInGlobalScope()
{
//void evaluate(QString expr, bool global = false, bool disableBreak = false, int frame = -1, const QVariantMap &addContext = QVariantMap());
@@ -1620,7 +1410,9 @@ void tst_QQmlDebugJS::evaluateInGlobalScope()
QCOMPARE(body.value("text").toString(),QLatin1String("undefined"));
}
+#endif
+#if 0
void tst_QQmlDebugJS::evaluateInLocalScope()
{
//void evaluate(QString expr, bool global = false, bool disableBreak = false, int frame = -1, const QVariantMap &addContext = QVariantMap());
@@ -1654,21 +1446,7 @@ void tst_QQmlDebugJS::evaluateInLocalScope()
QCOMPARE(body.value("value").toInt(),10);
}
-
-void tst_QQmlDebugJS::getScopes()
-{
- //void scopes(int frameNumber = -1);
-
- int sourceLine = 47;
- QVERIFY(init(ONCOMPLETED_QMLFILE));
-
- client->setBreakpoint(QLatin1String(SCRIPTREGEXP), QLatin1String(ONCOMPLETED_QMLFILE), sourceLine, -1, true);
- client->connect();
- QVERIFY(QQmlDebugTest::waitForSignal(client, SIGNAL(stopped())));
-
- client->scopes();
- QVERIFY(QQmlDebugTest::waitForSignal(client, SIGNAL(result())));
-}
+#endif
void tst_QQmlDebugJS::getScripts()
{
@@ -1677,6 +1455,8 @@ void tst_QQmlDebugJS::getScripts()
QVERIFY(init());
client->connect();
+ client->interrupt();
+ QVERIFY(QQmlDebugTest::waitForSignal(client, SIGNAL(interruptRequested())));
client->scripts();
QVERIFY(QQmlDebugTest::waitForSignal(client, SIGNAL(scriptsResult())));
@@ -1686,22 +1466,8 @@ void tst_QQmlDebugJS::getScripts()
QList<QVariant> scripts = value.value("body").toList();
- QCOMPARE(scripts.count(), 3);
-}
-
-void tst_QQmlDebugJS::getSource()
-{
- //void source(int frame = -1, int fromLine = -1, int toLine = -1);
-
- int sourceLine = 47;
- QVERIFY(init(ONCOMPLETED_QMLFILE));
-
- client->setBreakpoint(QLatin1String(SCRIPTREGEXP), QLatin1String(ONCOMPLETED_QMLFILE), sourceLine, -1, true);
- client->connect();
- QVERIFY(QQmlDebugTest::waitForSignal(client, SIGNAL(stopped())));
-
- client->source();
- QVERIFY(QQmlDebugTest::waitForSignal(client, SIGNAL(result())));
+ QCOMPARE(scripts.count(), 1);
+ QVERIFY(scripts.first().toMap()[QStringLiteral("name")].toString().endsWith(QStringLiteral("data/test.qml")));
}
QTEST_MAIN(tst_QQmlDebugJS)
diff --git a/tests/auto/qml/qv4debugger/tst_qv4debugger.cpp b/tests/auto/qml/qv4debugger/tst_qv4debugger.cpp
index 56df3a469a..15a6acc272 100644
--- a/tests/auto/qml/qv4debugger/tst_qv4debugger.cpp
+++ b/tests/auto/qml/qv4debugger/tst_qv4debugger.cpp
@@ -45,6 +45,9 @@
#include <private/qv4debugging_p.h>
#include <private/qv8engine_p.h>
+using namespace QV4;
+using namespace QV4::Debugging;
+
static bool waitForSignal(QObject* obj, const char* signal, int timeout = 10000)
{
QEventLoop loop;
@@ -95,32 +98,109 @@ signals:
Q_DECLARE_METATYPE(TestEngine::InjectedFunction)
+namespace {
+class TestCollector: public QV4::Debugging::Debugger::Collector
+{
+public:
+ TestCollector(QV4::ExecutionEngine *engine)
+ : Collector(engine)
+ , destination(0)
+ {}
+
+ virtual ~TestCollector() {}
+
+ void setDestination(QVariantMap *dest)
+ { destination = dest; }
+
+protected:
+ virtual void addUndefined(const QString &name)
+ {
+ destination->insert(name, QStringLiteral("undefined")); // TODO: add a user-defined type for this
+ }
+
+ virtual void addNull(const QString &name)
+ {
+ destination->insert(name, QStringLiteral("null")); // TODO: add a user-defined type for this
+ }
+
+ virtual void addBoolean(const QString &name, bool value)
+ {
+ destination->insert(name, value);
+ }
+
+ virtual void addString(const QString &name, const QString &value)
+ {
+ destination->insert(name, value);
+ }
+
+ virtual void addObject(const QString &name, QV4::ValueRef value)
+ {
+ QV4::Scope scope(engine());
+ QV4::ScopedObject obj(scope, value->asObject());
+
+ QVariantMap props, *prev = &props;
+ qSwap(destination, prev);
+ collect(obj);
+ qSwap(destination, prev);
+
+ destination->insert(name, props);
+ }
+
+ virtual void addInteger(const QString &name, int value)
+ {
+ destination->insert(name, QVariant::fromValue<double>(static_cast<double>(value)));
+ }
+
+ virtual void addDouble(const QString &name, double value)
+ {
+ destination->insert(name, QVariant::fromValue<double>(value));
+ }
+
+private:
+ QVariantMap *destination;
+};
+}
+
class TestAgent : public QV4::Debugging::DebuggerAgent
{
Q_OBJECT
public:
- typedef QV4::Debugging::Debugger Debugger;
-
TestAgent()
: m_wasPaused(false)
, m_captureContextInfo(false)
{
}
- virtual void debuggerPaused(Debugger *debugger)
+ virtual void debuggerPaused(Debugger *debugger, PauseReason reason)
{
Q_ASSERT(m_debuggers.count() == 1 && m_debuggers.first() == debugger);
m_wasPaused = true;
+ m_pauseReason = reason;
m_statesWhenPaused << debugger->currentExecutionState();
+ TestCollector collector(debugger->engine());
+ QVariantMap tmp;
+ collector.setDestination(&tmp);
+ debugger->collectThrownValue(&collector);
+ m_thrownValue = tmp["exception"];
+
foreach (const TestBreakPoint &bp, m_breakPointsToAddWhenPaused)
debugger->addBreakPoint(bp.fileName, bp.lineNumber);
m_breakPointsToAddWhenPaused.clear();
+ m_stackTrace = debugger->stackTrace();
+
if (m_captureContextInfo)
captureContextInfo(debugger);
- debugger->resume();
+ debugger->resume(Debugger::FullThrottle);
+ }
+
+ virtual void sourcesCollected(Debugger *debugger, QStringList sources, int requestSequenceNr)
+ {
+ Q_UNUSED(debugger);
+ Q_UNUSED(sources);
+ Q_UNUSED(requestSequenceNr);
}
int debuggerCount() const { return m_debuggers.count(); }
@@ -136,25 +216,30 @@ public:
void captureContextInfo(Debugger *debugger)
{
- m_stackTrace = debugger->stackTrace();
+ TestCollector collector(debugger->engine());
+
for (int i = 0, ei = m_stackTrace.size(); i != ei; ++i) {
- m_capturedArguments.append(debugger->retrieveArgumentsFromContext(QStringList(), i));
- m_capturedLocals.append(debugger->retrieveLocalsFromContext(QStringList(), i));
+ QVariantMap args;
+ collector.setDestination(&args);
+ debugger->collectArgumentsInContext(&collector, i);
+ m_capturedArguments.append(args);
+
+ QVariantMap locals;
+ collector.setDestination(&locals);
+ debugger->collectLocalsInContext(&collector, i);
+ m_capturedLocals.append(locals);
}
-
- foreach (const QStringList &path, m_localPathsToRead)
- m_localPathResults += debugger->retrieveLocalsFromContext(path);
}
bool m_wasPaused;
+ PauseReason m_pauseReason;
bool m_captureContextInfo;
- QList<QV4::Debugging::Debugger::ExecutionState> m_statesWhenPaused;
+ QList<Debugger::ExecutionState> m_statesWhenPaused;
QList<TestBreakPoint> m_breakPointsToAddWhenPaused;
QVector<QV4::StackFrame> m_stackTrace;
- QList<QList<Debugger::VarInfo> > m_capturedArguments;
- QList<QList<Debugger::VarInfo> > m_capturedLocals;
- QList<QStringList> m_localPathsToRead;
- QList<QList<Debugger::VarInfo> > m_localPathResults;
+ QList<QVariantMap> m_capturedArguments;
+ QList<QVariantMap> m_capturedLocals;
+ QVariant m_thrownValue;
// Utility methods:
void dumpStackTrace() const
@@ -170,8 +255,6 @@ class tst_qv4debugger : public QObject
{
Q_OBJECT
- typedef QV4::Debugging::Debugger::VarInfo VarInfo;
-
private slots:
void init();
void cleanup();
@@ -190,6 +273,9 @@ private slots:
void readObject();
void readContextInAllFrames();
+ // exceptions:
+ void pauseOnThrow();
+
private:
void evaluateJavaScript(const QString &script, const QString &fileName, int lineNumber = 1)
{
@@ -278,8 +364,8 @@ void tst_qv4debugger::removePendingBreakPoint()
"var i = 42;\n"
"var j = i + 1\n"
"var k = i\n";
- m_debuggerAgent->addBreakPoint("removePendingBreakPoint", 2);
- m_debuggerAgent->removeBreakPoint("removePendingBreakPoint", 2);
+ int id = m_debuggerAgent->addBreakPoint("removePendingBreakPoint", 2);
+ m_debuggerAgent->removeBreakPoint(id);
evaluateJavaScript(script, "removePendingBreakPoint");
QVERIFY(!m_debuggerAgent->m_wasPaused);
}
@@ -339,16 +425,12 @@ void tst_qv4debugger::readArguments()
evaluateJavaScript(script, "readArguments");
QVERIFY(m_debuggerAgent->m_wasPaused);
QCOMPARE(m_debuggerAgent->m_capturedArguments[0].size(), 4);
- QCOMPARE(m_debuggerAgent->m_capturedArguments[0][0].name, QString("a"));
- QCOMPARE(m_debuggerAgent->m_capturedArguments[0][0].type, VarInfo::Number);
- QCOMPARE(m_debuggerAgent->m_capturedArguments[0][0].value.toDouble(), 1.0);
- QCOMPARE(m_debuggerAgent->m_capturedArguments[0][1].name, QString("b"));
- QCOMPARE(m_debuggerAgent->m_capturedArguments[0][1].type, VarInfo::String);
- QCOMPARE(m_debuggerAgent->m_capturedArguments[0][1].value.toString(), QLatin1String("two"));
- QCOMPARE(m_debuggerAgent->m_capturedArguments[0][2].name, QString("c"));
- QCOMPARE(m_debuggerAgent->m_capturedArguments[0][2].type, VarInfo::Null);
- QCOMPARE(m_debuggerAgent->m_capturedArguments[0][3].name, QString("d"));
- QCOMPARE(m_debuggerAgent->m_capturedArguments[0][3].type, VarInfo::Undefined);
+ QVERIFY(m_debuggerAgent->m_capturedArguments[0].contains(QStringLiteral("a")));
+ QCOMPARE(m_debuggerAgent->m_capturedArguments[0]["a"].type(), QVariant::Double);
+ QCOMPARE(m_debuggerAgent->m_capturedArguments[0]["a"].toDouble(), 1.0);
+ QVERIFY(m_debuggerAgent->m_capturedArguments[0].contains("b"));
+ QCOMPARE(m_debuggerAgent->m_capturedArguments[0]["b"].type(), QVariant::String);
+ QCOMPARE(m_debuggerAgent->m_capturedArguments[0]["b"].toString(), QLatin1String("two"));
}
void tst_qv4debugger::readLocals()
@@ -365,11 +447,11 @@ void tst_qv4debugger::readLocals()
evaluateJavaScript(script, "readLocals");
QVERIFY(m_debuggerAgent->m_wasPaused);
QCOMPARE(m_debuggerAgent->m_capturedLocals[0].size(), 2);
- QCOMPARE(m_debuggerAgent->m_capturedLocals[0][0].name, QString("c"));
- QCOMPARE(m_debuggerAgent->m_capturedLocals[0][0].type, VarInfo::Number);
- QCOMPARE(m_debuggerAgent->m_capturedLocals[0][0].value.toDouble(), 3.0);
- QCOMPARE(m_debuggerAgent->m_capturedLocals[0][1].name, QString("d"));
- QCOMPARE(m_debuggerAgent->m_capturedLocals[0][1].type, VarInfo::Undefined);
+ QVERIFY(m_debuggerAgent->m_capturedLocals[0].contains("c"));
+ QCOMPARE(m_debuggerAgent->m_capturedLocals[0]["c"].type(), QVariant::Double);
+ QCOMPARE(m_debuggerAgent->m_capturedLocals[0]["c"].toDouble(), 3.0);
+ QVERIFY(m_debuggerAgent->m_capturedLocals[0].contains("d"));
+ QCOMPARE(m_debuggerAgent->m_capturedLocals[0]["d"].toString(), QString("undefined"));
}
void tst_qv4debugger::readObject()
@@ -382,31 +464,25 @@ void tst_qv4debugger::readObject()
"}\n"
"f({head: 1, tail: { head: 'asdf', tail: null }});\n";
m_debuggerAgent->addBreakPoint("readObject", 3);
- m_debuggerAgent->m_localPathsToRead.append(QStringList() << QLatin1String("b"));
- m_debuggerAgent->m_localPathsToRead.append(QStringList() << QLatin1String("b") << QLatin1String("tail"));
evaluateJavaScript(script, "readObject");
QVERIFY(m_debuggerAgent->m_wasPaused);
QCOMPARE(m_debuggerAgent->m_capturedLocals[0].size(), 1);
- QCOMPARE(m_debuggerAgent->m_capturedLocals[0][0].name, QString("b"));
- QCOMPARE(m_debuggerAgent->m_capturedLocals[0][0].type, VarInfo::Object);
-
- QCOMPARE(m_debuggerAgent->m_localPathResults.size(), 2);
+ QVERIFY(m_debuggerAgent->m_capturedLocals[0].contains("b"));
+ QCOMPARE(m_debuggerAgent->m_capturedLocals[0]["b"].type(), QVariant::Map);
- QList<VarInfo> b = m_debuggerAgent->m_localPathResults[0];
+ QVariantMap b = m_debuggerAgent->m_capturedLocals[0]["b"].toMap();
QCOMPARE(b.size(), 2);
- QCOMPARE(b[0].name, QLatin1String("head"));
- QCOMPARE(b[0].type, VarInfo::Number);
- QCOMPARE(b[0].value.toDouble(), 1.0);
- QCOMPARE(b[1].name, QLatin1String("tail"));
- QCOMPARE(b[1].type, VarInfo::Object);
+ QVERIFY(b.contains("head"));
+ QCOMPARE(b["head"].type(), QVariant::Double);
+ QCOMPARE(b["head"].toDouble(), 1.0);
+ QVERIFY(b.contains("tail"));
+ QCOMPARE(b["tail"].type(), QVariant::Map);
- QList<VarInfo> b_tail = m_debuggerAgent->m_localPathResults[1];
+ QVariantMap b_tail = b["tail"].toMap();
QCOMPARE(b_tail.size(), 2);
- QCOMPARE(b_tail[0].name, QLatin1String("head"));
- QCOMPARE(b_tail[0].type, VarInfo::String);
- QCOMPARE(b_tail[0].value.toString(), QLatin1String("asdf"));
- QCOMPARE(b_tail[1].name, QLatin1String("tail"));
- QCOMPARE(b_tail[1].type, VarInfo::Null);
+ QVERIFY(b_tail.contains("head"));
+ QCOMPARE(b_tail["head"].type(), QVariant::String);
+ QCOMPARE(b_tail["head"].toString(), QString("asdf"));
}
void tst_qv4debugger::readContextInAllFrames()
@@ -431,23 +507,39 @@ void tst_qv4debugger::readContextInAllFrames()
for (int i = 0; i < 12; ++i) {
QCOMPARE(m_debuggerAgent->m_capturedArguments[i].size(), 1);
- QCOMPARE(m_debuggerAgent->m_capturedArguments[i][0].name, QString("n"));
- QCOMPARE(m_debuggerAgent->m_capturedArguments[i][0].type, VarInfo::Number);
- QCOMPARE(m_debuggerAgent->m_capturedArguments[i][0].value.toInt(), i + 1);
+ QVERIFY(m_debuggerAgent->m_capturedArguments[i].contains("n"));
+ QCOMPARE(m_debuggerAgent->m_capturedArguments[i]["n"].type(), QVariant::Double);
+ QCOMPARE(m_debuggerAgent->m_capturedArguments[i]["n"].toDouble(), i + 1.0);
QCOMPARE(m_debuggerAgent->m_capturedLocals[i].size(), 1);
- QCOMPARE(m_debuggerAgent->m_capturedLocals[i][0].name, QString("n_1"));
+ QVERIFY(m_debuggerAgent->m_capturedLocals[i].contains("n_1"));
if (i == 0) {
- QCOMPARE(m_debuggerAgent->m_capturedLocals[i][0].type, VarInfo::Undefined);
+ QCOMPARE(m_debuggerAgent->m_capturedLocals[i]["n_1"].toString(), QString("undefined"));
} else {
- QCOMPARE(m_debuggerAgent->m_capturedLocals[i][0].type, VarInfo::Number);
- QCOMPARE(m_debuggerAgent->m_capturedLocals[i][0].value.toInt(), i);
+ QCOMPARE(m_debuggerAgent->m_capturedLocals[i]["n_1"].type(), QVariant::Double);
+ QCOMPARE(m_debuggerAgent->m_capturedLocals[i]["n_1"].toInt(), i);
}
}
QCOMPARE(m_debuggerAgent->m_capturedArguments[12].size(), 0);
QCOMPARE(m_debuggerAgent->m_capturedLocals[12].size(), 0);
}
+void tst_qv4debugger::pauseOnThrow()
+{
+ QString script =
+ "function die(n) {\n"
+ " throw n\n"
+ "}\n"
+ "die('hard');\n";
+ m_debuggerAgent->setBreakOnThrow(true);
+ evaluateJavaScript(script, "pauseOnThrow");
+ QVERIFY(m_debuggerAgent->m_wasPaused);
+ QCOMPARE(m_debuggerAgent->m_pauseReason, Throwing);
+ QCOMPARE(m_debuggerAgent->m_stackTrace.size(), 2);
+ QCOMPARE(m_debuggerAgent->m_thrownValue.type(), QVariant::String);
+ QCOMPARE(m_debuggerAgent->m_thrownValue.toString(), QString("hard"));
+}
+
QTEST_MAIN(tst_qv4debugger)
#include "tst_qv4debugger.moc"