V4 Debugger: Avoid looking up values in debugger thread

To avoid interaction with the engine from the debugger thread we
move the value lookup functionality into the data collector, and
drop the RefHolder. Also, we define some more debugger jobs to
move the work the request handlers do into the GUI thread.

Task-number: QTBUG-50481
Change-Id: Id93548dc65133246deac71f73188c715e9dc01e4
Reviewed-by: Simon Hausmann <simon.hausmann@theqtcompany.com>
This commit is contained in:
Ulf Hermann 2015-11-30 12:04:59 +01:00 committed by Simon Hausmann
parent 706238e037
commit ebb08ee84e
7 changed files with 305 additions and 329 deletions

View File

@ -104,6 +104,28 @@ QVector<QV4::Heap::ExecutionContext::ContextType> QV4DataCollector::getScopeType
return types; return types;
} }
int QV4DataCollector::encodeScopeType(QV4::Heap::ExecutionContext::ContextType scopeType)
{
switch (scopeType) {
case QV4::Heap::ExecutionContext::Type_GlobalContext:
return 0;
break;
case QV4::Heap::ExecutionContext::Type_CatchContext:
return 4;
break;
case QV4::Heap::ExecutionContext::Type_WithContext:
return 2;
break;
case QV4::Heap::ExecutionContext::Type_SimpleCallContext:
case QV4::Heap::ExecutionContext::Type_CallContext:
return 1;
break;
case QV4::Heap::ExecutionContext::Type_QmlContext:
default:
return -1;
}
}
QV4DataCollector::QV4DataCollector(QV4::ExecutionEngine *engine) : m_engine(engine) QV4DataCollector::QV4DataCollector(QV4::ExecutionEngine *engine) : m_engine(engine)
{ {
m_values.set(engine, engine->newArrayObject()); m_values.set(engine, engine->newArrayObject());
@ -227,19 +249,35 @@ bool QV4DataCollector::isValidRef(QV4DataCollector::Ref ref) const
return ref < array->getLength(); return ref < array->getLength();
} }
void QV4DataCollector::collectScope(QJsonObject *dict, QV4Debugger *debugger, int frameNr, bool QV4DataCollector::collectScope(QJsonObject *dict, int frameNr, int scopeNr)
int scopeNr)
{ {
QStringList names; QStringList names;
if (debugger->state() == QV4Debugger::Paused) { QV4::Scope scope(engine());
ArgumentCollectJob argumentsJob(m_engine, this, &names, frameNr, scopeNr); QV4::Scoped<QV4::CallContext> ctxt(scope, findScope(findContext(engine(), frameNr), scopeNr));
debugger->runInEngine(&argumentsJob); if (!ctxt)
LocalCollectJob localsJob(m_engine, this, &names, frameNr, scopeNr); return false;
debugger->runInEngine(&localsJob);
QV4::ScopedValue v(scope);
int nFormals = ctxt->formalCount();
for (unsigned i = 0, ei = nFormals; i != ei; ++i) {
QString qName;
if (QV4::Identifier *name = ctxt->formals()[nFormals - i - 1])
qName = name->string;
names.append(qName);
v = ctxt->argument(i);
collect(v);
}
for (unsigned i = 0, ei = ctxt->variableCount(); i != ei; ++i) {
QString qName;
if (QV4::Identifier *name = ctxt->variables()[i])
qName = name->string;
names.append(qName);
v = ctxt->d()->locals[i];
collect(v);
} }
QV4::Scope scope(engine());
QV4::ScopedObject scopeObject(scope, engine()->newObject()); QV4::ScopedObject scopeObject(scope, engine()->newObject());
Q_ASSERT(names.size() == m_collectedRefs.size()); Q_ASSERT(names.size() == m_collectedRefs.size());
@ -250,6 +288,62 @@ void QV4DataCollector::collectScope(QJsonObject *dict, QV4Debugger *debugger, in
Ref scopeObjectRef = addRef(scopeObject); Ref scopeObjectRef = addRef(scopeObject);
dict->insert(QStringLiteral("ref"), qint64(scopeObjectRef)); dict->insert(QStringLiteral("ref"), qint64(scopeObjectRef));
m_collectedRefs.append(scopeObjectRef); m_collectedRefs.append(scopeObjectRef);
return true;
}
QJsonObject toRef(QV4DataCollector::Ref ref) {
QJsonObject dict;
dict.insert(QStringLiteral("ref"), qint64(ref));
return dict;
}
QJsonObject QV4DataCollector::buildFrame(const QV4::StackFrame &stackFrame, int frameNr)
{
QV4DataCollector::Ref ref;
QJsonObject frame;
frame[QLatin1String("index")] = frameNr;
frame[QLatin1String("debuggerFrame")] = false;
ref = addFunctionRef(stackFrame.function);
frame[QLatin1String("func")] = toRef(ref);
ref = addScriptRef(stackFrame.source);
frame[QLatin1String("script")] = toRef(ref);
frame[QLatin1String("line")] = stackFrame.line - 1;
if (stackFrame.column >= 0)
frame[QLatin1String("column")] = stackFrame.column;
QJsonArray scopes;
QV4::Scope scope(engine());
QV4::ScopedContext ctxt(scope, findContext(engine(), frameNr));
while (ctxt) {
if (QV4::CallContext *cCtxt = ctxt->asCallContext()) {
if (cCtxt->d()->activation)
break;
}
ctxt = ctxt->d()->outer;
}
if (ctxt) {
QV4::ScopedValue o(scope, ctxt->asCallContext()->d()->activation);
frame[QLatin1String("receiver")] = toRef(collect(o));
}
// Only type and index are used by Qt Creator, so we keep it easy:
QVector<QV4::Heap::ExecutionContext::ContextType> scopeTypes = getScopeTypes(engine(), frameNr);
for (int i = 0, ei = scopeTypes.count(); i != ei; ++i) {
int type = encodeScopeType(scopeTypes[i]);
if (type == -1)
continue;
QJsonObject scope;
scope[QLatin1String("index")] = i;
scope[QLatin1String("type")] = type;
scopes.push_back(scope);
}
frame[QLatin1String("scopes")] = scopes;
return frame;
} }
QJsonArray QV4DataCollector::flushCollectedRefs() QJsonArray QV4DataCollector::flushCollectedRefs()

View File

@ -60,6 +60,7 @@ public:
static QV4::Heap::CallContext *findScope(QV4::ExecutionContext *ctxt, int scope); static QV4::Heap::CallContext *findScope(QV4::ExecutionContext *ctxt, int scope);
static QVector<QV4::Heap::ExecutionContext::ContextType> getScopeTypes( static QVector<QV4::Heap::ExecutionContext::ContextType> getScopeTypes(
QV4::ExecutionEngine *engine, int frame); QV4::ExecutionEngine *engine, int frame);
static int encodeScopeType(QV4::Heap::ExecutionContext::ContextType scopeType);
QV4DataCollector(QV4::ExecutionEngine *engine); QV4DataCollector(QV4::ExecutionEngine *engine);
@ -70,7 +71,8 @@ public:
bool isValidRef(Ref ref) const; bool isValidRef(Ref ref) const;
QJsonObject lookupRef(Ref ref); QJsonObject lookupRef(Ref ref);
void collectScope(QJsonObject *dict, QV4Debugger *debugger, int frameNr, int scopeNr); bool collectScope(QJsonObject *dict, int frameNr, int scopeNr);
QJsonObject buildFrame(const QV4::StackFrame &stackFrame, int frameNr);
QV4::ExecutionEngine *engine() const { return m_engine; } QV4::ExecutionEngine *engine() const { return m_engine; }
QJsonArray flushCollectedRefs(); QJsonArray flushCollectedRefs();

View File

@ -118,8 +118,79 @@ bool JavaScriptJob::hasExeption() const
return resultIsException; return resultIsException;
} }
BacktraceJob::BacktraceJob(QV4DataCollector *collector, int fromFrame, int toFrame) :
CollectJob(collector), fromFrame(fromFrame), toFrame(toFrame)
{
}
void BacktraceJob::run()
{
QJsonArray frameArray;
QVector<QV4::StackFrame> frames = collector->engine()->stackTrace(toFrame);
for (int i = fromFrame; i < toFrame && i < frames.size(); ++i)
frameArray.push_back(collector->buildFrame(frames[i], i));
if (frameArray.isEmpty()) {
result.insert(QStringLiteral("totalFrames"), 0);
} else {
result.insert(QStringLiteral("fromFrame"), fromFrame);
result.insert(QStringLiteral("toFrame"), fromFrame + frameArray.size());
result.insert(QStringLiteral("frames"), frameArray);
}
collectedRefs = collector->flushCollectedRefs();
}
FrameJob::FrameJob(QV4DataCollector *collector, int frameNr) :
CollectJob(collector), frameNr(frameNr), success(false)
{
}
void FrameJob::run()
{
QVector<QV4::StackFrame> frames = collector->engine()->stackTrace(frameNr + 1);
if (frameNr >= frames.length()) {
success = false;
} else {
result = collector->buildFrame(frames[frameNr], frameNr);
collectedRefs = collector->flushCollectedRefs();
success = true;
}
}
bool FrameJob::wasSuccessful() const
{
return success;
}
ScopeJob::ScopeJob(QV4DataCollector *collector, int frameNr, int scopeNr) :
CollectJob(collector), frameNr(frameNr), scopeNr(scopeNr), success(false)
{
}
void ScopeJob::run()
{
QJsonObject object;
success = collector->collectScope(&object, frameNr, scopeNr);
if (success) {
QVector<QV4::Heap::ExecutionContext::ContextType> scopeTypes =
QV4DataCollector::getScopeTypes(collector->engine(), frameNr);
result[QLatin1String("type")] = QV4DataCollector::encodeScopeType(scopeTypes[scopeNr]);
} else {
result[QLatin1String("type")] = -1;
}
result[QLatin1String("index")] = scopeNr;
result[QLatin1String("frameIndex")] = frameNr;
result[QLatin1String("object")] = object;
collectedRefs = collector->flushCollectedRefs();
}
bool ScopeJob::wasSuccessful() const
{
return success;
}
ValueLookupJob::ValueLookupJob(const QJsonArray &handles, QV4DataCollector *collector) : ValueLookupJob::ValueLookupJob(const QJsonArray &handles, QV4DataCollector *collector) :
collector(collector), handles(handles) {} CollectJob(collector), handles(handles) {}
void ValueLookupJob::run() void ValueLookupJob::run()
{ {
@ -152,16 +223,6 @@ const QString &ValueLookupJob::exceptionMessage() const
return exception; return exception;
} }
const QJsonObject &ValueLookupJob::returnValue() const
{
return result;
}
const QJsonArray &ValueLookupJob::refs() const
{
return collectedRefs;
}
ExpressionEvalJob::ExpressionEvalJob(QV4::ExecutionEngine *engine, int frameNr, ExpressionEvalJob::ExpressionEvalJob(QV4::ExecutionEngine *engine, int frameNr,
const QString &expression, QV4DataCollector *collector) : const QString &expression, QV4DataCollector *collector) :
JavaScriptJob(engine, frameNr, expression), collector(collector) JavaScriptJob(engine, frameNr, expression), collector(collector)
@ -209,100 +270,6 @@ const QStringList &GatherSourcesJob::result() const
return sources; return sources;
} }
ArgumentCollectJob::ArgumentCollectJob(QV4::ExecutionEngine *engine, QV4DataCollector *collector,
QStringList *names, int frameNr, int scopeNr)
: engine(engine)
, collector(collector)
, names(names)
, frameNr(frameNr)
, scopeNr(scopeNr)
{}
void ArgumentCollectJob::run()
{
if (frameNr < 0)
return;
QV4::Scope scope(engine);
QV4::Scoped<QV4::CallContext> ctxt(
scope, QV4DataCollector::findScope(QV4DataCollector::findContext(engine, frameNr), scopeNr));
if (!ctxt)
return;
QV4::ScopedValue v(scope);
int nFormals = ctxt->formalCount();
for (unsigned i = 0, ei = nFormals; i != ei; ++i) {
QString qName;
if (QV4::Identifier *name = ctxt->formals()[nFormals - i - 1])
qName = name->string;
names->append(qName);
v = ctxt->argument(i);
collector->collect(v);
}
}
LocalCollectJob::LocalCollectJob(QV4::ExecutionEngine *engine, QV4DataCollector *collector,
QStringList *names, int frameNr, int scopeNr)
: engine(engine)
, collector(collector)
, names(names)
, frameNr(frameNr)
, scopeNr(scopeNr)
{}
void LocalCollectJob::run()
{
if (frameNr < 0)
return;
QV4::Scope scope(engine);
QV4::Scoped<QV4::CallContext> ctxt(
scope, QV4DataCollector::findScope(QV4DataCollector::findContext(engine, frameNr), scopeNr));
if (!ctxt)
return;
QV4::ScopedValue v(scope);
for (unsigned i = 0, ei = ctxt->variableCount(); i != ei; ++i) {
QString qName;
if (QV4::Identifier *name = ctxt->variables()[i])
qName = name->string;
names->append(qName);
v = ctxt->d()->locals[i];
collector->collect(v);
}
}
ThisCollectJob::ThisCollectJob(QV4::ExecutionEngine *engine, QV4DataCollector *collector,
int frameNr)
: engine(engine)
, collector(collector)
, frameNr(frameNr)
, thisRef(QV4DataCollector::s_invalidRef)
{}
void ThisCollectJob::run()
{
QV4::Scope scope(engine);
QV4::ScopedContext ctxt(scope, QV4DataCollector::findContext(engine, frameNr));
while (ctxt) {
if (QV4::CallContext *cCtxt = ctxt->asCallContext())
if (cCtxt->d()->activation)
break;
ctxt = ctxt->d()->outer;
}
if (!ctxt)
return;
QV4::ScopedValue o(scope, ctxt->asCallContext()->d()->activation);
thisRef = collector->collect(o);
}
QV4DataCollector::Ref ThisCollectJob::foundRef() const
{
return thisRef;
}
ExceptionCollectJob::ExceptionCollectJob(QV4::ExecutionEngine *engine, QV4DataCollector *collector) ExceptionCollectJob::ExceptionCollectJob(QV4::ExecutionEngine *engine, QV4DataCollector *collector)
: engine(engine) : engine(engine)
, collector(collector) , collector(collector)

View File

@ -72,20 +72,59 @@ protected:
virtual void handleResult(QV4::ScopedValue &result) = 0; virtual void handleResult(QV4::ScopedValue &result) = 0;
}; };
class ValueLookupJob: public QV4DebugJob class CollectJob : public QV4DebugJob
{ {
protected:
QV4DataCollector *collector; QV4DataCollector *collector;
const QJsonArray handles;
QJsonObject result; QJsonObject result;
QJsonArray collectedRefs; QJsonArray collectedRefs;
public:
CollectJob(QV4DataCollector *collector) : collector(collector) {}
const QJsonObject &returnValue() const { return result; }
const QJsonArray &refs() const { return collectedRefs; }
};
class BacktraceJob: public CollectJob
{
int fromFrame;
int toFrame;
public:
BacktraceJob(QV4DataCollector *collector, int fromFrame, int toFrame);
void run();
};
class FrameJob: public CollectJob
{
int frameNr;
bool success;
public:
FrameJob(QV4DataCollector *collector, int frameNr);
void run();
bool wasSuccessful() const;
};
class ScopeJob: public CollectJob
{
int frameNr;
int scopeNr;
bool success;
public:
ScopeJob(QV4DataCollector *collector, int frameNr, int scopeNr);
void run();
bool wasSuccessful() const;
};
class ValueLookupJob: public CollectJob
{
const QJsonArray handles;
QString exception; QString exception;
public: public:
ValueLookupJob(const QJsonArray &handles, QV4DataCollector *collector); ValueLookupJob(const QJsonArray &handles, QV4DataCollector *collector);
void run(); void run();
const QString &exceptionMessage() const; const QString &exceptionMessage() const;
const QJsonObject &returnValue() const;
const QJsonArray &refs() const;
}; };
class ExpressionEvalJob: public JavaScriptJob class ExpressionEvalJob: public JavaScriptJob
@ -115,47 +154,6 @@ public:
const QStringList &result() const; const QStringList &result() const;
}; };
class ArgumentCollectJob: public QV4DebugJob
{
QV4::ExecutionEngine *engine;
QV4DataCollector *collector;
QStringList *names;
int frameNr;
int scopeNr;
public:
ArgumentCollectJob(QV4::ExecutionEngine *engine, QV4DataCollector *collector,
QStringList *names, int frameNr, int scopeNr);
void run();
};
class LocalCollectJob: public QV4DebugJob
{
QV4::ExecutionEngine *engine;
QV4DataCollector *collector;
QStringList *names;
int frameNr;
int scopeNr;
public:
LocalCollectJob(QV4::ExecutionEngine *engine, QV4DataCollector *collector, QStringList *names,
int frameNr, int scopeNr);
void run();
};
class ThisCollectJob: public QV4DebugJob
{
QV4::ExecutionEngine *engine;
QV4DataCollector *collector;
int frameNr;
QV4DataCollector::Ref thisRef;
public:
ThisCollectJob(QV4::ExecutionEngine *engine, QV4DataCollector *collector, int frameNr);
void run();
QV4DataCollector::Ref foundRef() const;
};
class ExceptionCollectJob: public QV4DebugJob class ExceptionCollectJob: public QV4DebugJob
{ {
QV4::ExecutionEngine *engine; QV4::ExecutionEngine *engine;
@ -176,8 +174,7 @@ public:
EvalJob(QV4::ExecutionEngine *engine, const QString &script); EvalJob(QV4::ExecutionEngine *engine, const QString &script);
virtual void handleResult(QV4::ScopedValue &result); virtual void handleResult(QV4::ScopedValue &result);
bool resultAsBoolean() const bool resultAsBoolean() const;
;
}; };
QT_END_NAMESPACE QT_END_NAMESPACE

View File

@ -285,26 +285,16 @@ public:
return; return;
} }
QJsonArray frameArray; BacktraceJob job(debugger->collector(), fromFrame, toFrame);
QVector<QV4::StackFrame> frames = debugger->stackTrace(toFrame); debugger->runInEngine(&job);
for (int i = fromFrame; i < toFrame && i < frames.size(); ++i)
frameArray.push_back(debugService->buildFrame(frames[i], i, debugger));
// response: // response:
addCommand(); addCommand();
addRequestSequence(); addRequestSequence();
addSuccess(true); addSuccess(true);
addRunning(); addRunning();
QJsonObject body; addBody(job.returnValue());
if (frameArray.isEmpty()) { addRefs(job.refs());
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(debugger->collector()->flushCollectedRefs());
} }
}; };
@ -326,22 +316,27 @@ public:
return; return;
} }
QVector<QV4::StackFrame> frames = debugger->stackTrace(frameNr + 1); if (frameNr < 0) {
if (frameNr < 0 || frameNr >= frames.size()) {
createErrorResponse(QStringLiteral("frame command has invalid frame number")); createErrorResponse(QStringLiteral("frame command has invalid frame number"));
return; return;
} }
FrameJob job(debugger->collector(), frameNr);
debugger->runInEngine(&job);
if (!job.wasSuccessful()) {
createErrorResponse(QStringLiteral("frame retrieval failed"));
return;
}
debugService->selectFrame(frameNr); debugService->selectFrame(frameNr);
QJsonObject frame = debugService->buildFrame(frames[frameNr], frameNr, debugger);
// response: // response:
addCommand(); addCommand();
addRequestSequence(); addRequestSequence();
addSuccess(true); addSuccess(true);
addRunning(); addRunning();
addBody(frame); addBody(job.returnValue());
addRefs(debugger->collector()->flushCollectedRefs()); addRefs(job.refs());
} }
}; };
@ -364,8 +359,7 @@ public:
return; return;
} }
QVector<QV4::StackFrame> frames = debugger->stackTrace(frameNr + 1); if (frameNr < 0) {
if (frameNr < 0 || frameNr >= frames.size()) {
createErrorResponse(QStringLiteral("scope command has invalid frame number")); createErrorResponse(QStringLiteral("scope command has invalid frame number"));
return; return;
} }
@ -374,15 +368,20 @@ public:
return; return;
} }
QJsonObject scope = debugService->buildScope(frameNr, scopeNr, debugger); ScopeJob job(debugger->collector(), frameNr, scopeNr);
debugger->runInEngine(&job);
if (!job.wasSuccessful()) {
createErrorResponse(QStringLiteral("scope retrieval failed"));
return;
}
// response: // response:
addCommand(); addCommand();
addRequestSequence(); addRequestSequence();
addSuccess(true); addSuccess(true);
addRunning(); addRunning();
addBody(scope); addBody(job.returnValue());
addRefs(debugger->collector()->flushCollectedRefs()); addRefs(job.refs());
} }
}; };
@ -836,98 +835,6 @@ void QV4DebugServiceImpl::send(QJsonObject v8Payload)
emit messageToClient(name(), packMessage("v8message", responseData)); emit messageToClient(name(), packMessage("v8message", responseData));
} }
QJsonObject QV4DebugServiceImpl::buildFrame(const QV4::StackFrame &stackFrame, int frameNr,
QV4Debugger *debugger)
{
QV4DataCollector::Ref ref;
QJsonObject frame;
frame[QLatin1String("index")] = frameNr;
frame[QLatin1String("debuggerFrame")] = false;
ref = debugger->collector()->addFunctionRef(stackFrame.function);
frame[QLatin1String("func")] = toRef(ref);
ref = debugger->collector()->addScriptRef(stackFrame.source);
frame[QLatin1String("script")] = toRef(ref);
frame[QLatin1String("line")] = stackFrame.line - 1;
if (stackFrame.column >= 0)
frame[QLatin1String("column")] = stackFrame.column;
QJsonArray scopes;
Q_ASSERT (debugger->state() == QV4Debugger::Paused);
ThisCollectJob job(debugger->engine(), debugger->collector(), frameNr);
debugger->runInEngine(&job);
if (job.foundRef() != QV4DataCollector::s_invalidRef)
frame[QLatin1String("receiver")] = toRef(job.foundRef());
// Only type and index are used by Qt Creator, so we keep it easy:
QVector<QV4::Heap::ExecutionContext::ContextType> scopeTypes =
QV4DataCollector::getScopeTypes(debugger->engine(), frameNr);
for (int i = 0, ei = scopeTypes.count(); i != ei; ++i) {
int type = encodeScopeType(scopeTypes[i]);
if (type == -1)
continue;
QJsonObject scope;
scope[QLatin1String("index")] = i;
scope[QLatin1String("type")] = type;
scopes.push_back(scope);
}
frame[QLatin1String("scopes")] = scopes;
return frame;
}
int QV4DebugServiceImpl::encodeScopeType(QV4::Heap::ExecutionContext::ContextType scopeType)
{
switch (scopeType) {
case QV4::Heap::ExecutionContext::Type_GlobalContext:
return 0;
break;
case QV4::Heap::ExecutionContext::Type_CatchContext:
return 4;
break;
case QV4::Heap::ExecutionContext::Type_WithContext:
return 2;
break;
case QV4::Heap::ExecutionContext::Type_SimpleCallContext:
case QV4::Heap::ExecutionContext::Type_CallContext:
return 1;
break;
case QV4::Heap::ExecutionContext::Type_QmlContext:
default:
return -1;
}
}
QJsonObject QV4DebugServiceImpl::buildScope(int frameNr, int scopeNr, QV4Debugger *debugger)
{
QJsonObject scope;
QJsonObject object;
debugger->collector()->collectScope(&object, debugger, frameNr, scopeNr);
if (debugger->state() == QV4Debugger::Paused) {
QVector<QV4::Heap::ExecutionContext::ContextType> scopeTypes =
QV4DataCollector::getScopeTypes(debugger->engine(), frameNr);
scope[QLatin1String("type")] = encodeScopeType(scopeTypes[scopeNr]);
} else {
scope[QLatin1String("type")] = -1;
}
scope[QLatin1String("index")] = scopeNr;
scope[QLatin1String("frameIndex")] = frameNr;
scope[QLatin1String("object")] = object;
return scope;
}
QJsonValue QV4DebugServiceImpl::toRef(QV4DataCollector::Ref ref)
{
QJsonObject dict;
dict.insert(QStringLiteral("ref"), qint64(ref));
return dict;
}
void QV4DebugServiceImpl::selectFrame(int frameNr) void QV4DebugServiceImpl::selectFrame(int frameNr)
{ {
theSelectedFrame = frameNr; theSelectedFrame = frameNr;

View File

@ -83,10 +83,6 @@ public:
void signalEmitted(const QString &signal) Q_DECL_OVERRIDE; void signalEmitted(const QString &signal) Q_DECL_OVERRIDE;
void send(QJsonObject v8Payload); void send(QJsonObject v8Payload);
QJsonObject buildScope(int frameNr, int scopeNr, QV4Debugger *debugger);
QJsonValue toRef(QV4DataCollector::Ref ref);
QJsonObject buildFrame(const QV4::StackFrame &stackFrame, int frameNr, QV4Debugger *debugger);
int selectedFrame() const; int selectedFrame() const;
void selectFrame(int frameNr); void selectFrame(int frameNr);
@ -104,11 +100,9 @@ private:
const QByteArray &message = QByteArray()); const QByteArray &message = QByteArray());
void processCommand(const QByteArray &command, const QByteArray &data); void processCommand(const QByteArray &command, const QByteArray &data);
V8CommandHandler *v8CommandHandler(const QString &command) const; V8CommandHandler *v8CommandHandler(const QString &command) const;
int encodeScopeType(QV4::Heap::ExecutionContext::ContextType scopeType);
QStringList breakOnSignals; QStringList breakOnSignals;
static int sequence; static int sequence;
int theSelectedFrame; int theSelectedFrame;
void addHandler(V8CommandHandler* handler); void addHandler(V8CommandHandler* handler);

View File

@ -100,16 +100,24 @@ public:
typedef QV4DataCollector::Refs Refs; typedef QV4DataCollector::Refs Refs;
typedef QV4DataCollector::Ref Ref; typedef QV4DataCollector::Ref Ref;
struct NamedRefs { struct NamedRefs {
QStringList names;
QJsonArray refs; QJsonArray refs;
QJsonObject scope;
int size() const { int size() const {
Q_ASSERT(names.size() == refs.size()); return scope.size();
return names.size();
} }
bool contains(const QString &name) const { bool contains(const QString &name) const {
return names.contains(name); return scope.contains(name);
}
QJsonObject resolveRef(int ref) const {
foreach (const QJsonValue &val, refs) {
QJsonObject obj = val.toObject();
if (obj.value(QLatin1String("handle")).toInt() == ref)
return obj;
}
return QJsonObject();
} }
#define DUMP_JSON(x) {\ #define DUMP_JSON(x) {\
@ -119,7 +127,7 @@ public:
QJsonObject rawValue(const QString &name) const { QJsonObject rawValue(const QString &name) const {
Q_ASSERT(contains(name)); Q_ASSERT(contains(name));
return refs.at(names.indexOf(name)).toObject(); return scope[name].toObject();
} }
QJsonValue value(const QString &name) const { QJsonValue value(const QString &name) const {
@ -136,7 +144,7 @@ public:
return; return;
} }
QJsonObject o = refs.at(names.indexOf(name)).toObject(); QJsonObject o = scope[name].toObject();
QJsonDocument d; QJsonDocument d;
d.setObject(o); d.setObject(o);
qDebug() << name << "=" << d.toJson(QJsonDocument::Indented); qDebug() << name << "=" << d.toJson(QJsonDocument::Indented);
@ -202,17 +210,30 @@ public:
void captureContextInfo(QV4Debugger *debugger) void captureContextInfo(QV4Debugger *debugger)
{ {
for (int i = 0, ei = m_stackTrace.size(); i != ei; ++i) { for (int i = 0, ei = m_stackTrace.size(); i != ei; ++i) {
m_capturedArguments.append(NamedRefs()); m_capturedScope.append(NamedRefs());
ArgumentCollectJob argumentsJob(debugger->engine(), &collector, ScopeJob job(&collector, i, 0);
&m_capturedArguments.last().names, i, 0); debugger->runInEngine(&job);
debugger->runInEngine(&argumentsJob); NamedRefs &refs = m_capturedScope.last();
m_capturedArguments.last().refs = collector.flushCollectedRefs(); refs.refs = job.refs();
QJsonObject object = job.returnValue();
m_capturedLocals.append(NamedRefs()); object = object.value(QLatin1String("object")).toObject();
LocalCollectJob localsJob(debugger->engine(), &collector, int ref = object.value(QLatin1String("ref")).toInt();
&m_capturedLocals.last().names, i, 0); object = refs.resolveRef(ref);
debugger->runInEngine(&localsJob); foreach (const QJsonValue &value, object.value(QLatin1String("properties")).toArray()) {
m_capturedLocals.last().refs = collector.flushCollectedRefs(); QJsonObject property = value.toObject();
QString name = property.value(QLatin1String("name")).toString();
property.remove(QLatin1String("name"));
if (property.contains(QLatin1String("ref"))) {
int childRef = property.value(QLatin1String("ref")).toInt();
if (childRef >= 0 && refs.refs.size() > childRef) {
property.remove(QLatin1String("ref"));
property.insert(QLatin1String("properties"),
refs.resolveRef(childRef).value(
QLatin1String("properties")).toArray());
}
}
refs.scope.insert(name, property);
}
} }
} }
@ -229,8 +250,7 @@ public:
QList<QV4Debugger::ExecutionState> m_statesWhenPaused; QList<QV4Debugger::ExecutionState> m_statesWhenPaused;
QList<TestBreakPoint> m_breakPointsToAddWhenPaused; QList<TestBreakPoint> m_breakPointsToAddWhenPaused;
QVector<QV4::StackFrame> m_stackTrace; QVector<QV4::StackFrame> m_stackTrace;
QVector<NamedRefs> m_capturedArguments; QVector<NamedRefs> m_capturedScope;
QVector<NamedRefs> m_capturedLocals;
qint64 m_thrownValue; qint64 m_thrownValue;
QV4DataCollector collector; QV4DataCollector collector;
@ -444,8 +464,8 @@ void tst_qv4debugger::conditionalBreakPoint()
QCOMPARE(state.fileName, QString("conditionalBreakPoint")); QCOMPARE(state.fileName, QString("conditionalBreakPoint"));
QCOMPARE(state.lineNumber, 3); QCOMPARE(state.lineNumber, 3);
QVERIFY(m_debuggerAgent->m_capturedLocals.size() > 1); QVERIFY(m_debuggerAgent->m_capturedScope.size() > 1);
const TestAgent::NamedRefs &frame0 = m_debuggerAgent->m_capturedLocals.at(0); const TestAgent::NamedRefs &frame0 = m_debuggerAgent->m_capturedScope.at(0);
QCOMPARE(frame0.size(), 2); QCOMPARE(frame0.size(), 2);
QVERIFY(frame0.contains("i")); QVERIFY(frame0.contains("i"));
QCOMPARE(frame0.value("i").toInt(), 11); QCOMPARE(frame0.value("i").toInt(), 11);
@ -501,13 +521,13 @@ void tst_qv4debugger::readArguments()
debugger()->addBreakPoint("readArguments", 2); debugger()->addBreakPoint("readArguments", 2);
evaluateJavaScript(script, "readArguments"); evaluateJavaScript(script, "readArguments");
QVERIFY(m_debuggerAgent->m_wasPaused); QVERIFY(m_debuggerAgent->m_wasPaused);
QVERIFY(m_debuggerAgent->m_capturedArguments.size() > 1); QVERIFY(m_debuggerAgent->m_capturedScope.size() > 1);
const TestAgent::NamedRefs &frame0 = m_debuggerAgent->m_capturedArguments.at(0); const TestAgent::NamedRefs &frame0 = m_debuggerAgent->m_capturedScope.at(0);
QCOMPARE(frame0.size(), 4); QCOMPARE(frame0.size(), 4);
QVERIFY(frame0.contains(QStringLiteral("a"))); QVERIFY(frame0.contains(QStringLiteral("a")));
QCOMPARE(frame0.type(QStringLiteral("a")), QStringLiteral("number")); QCOMPARE(frame0.type(QStringLiteral("a")), QStringLiteral("number"));
QCOMPARE(frame0.value(QStringLiteral("a")).toDouble(), 1.0); QCOMPARE(frame0.value(QStringLiteral("a")).toDouble(), 1.0);
QVERIFY(frame0.names.contains("b")); QVERIFY(frame0.scope.contains("b"));
QCOMPARE(frame0.type(QStringLiteral("b")), QStringLiteral("string")); QCOMPARE(frame0.type(QStringLiteral("b")), QStringLiteral("string"));
QCOMPARE(frame0.value(QStringLiteral("b")).toString(), QStringLiteral("two")); QCOMPARE(frame0.value(QStringLiteral("b")).toString(), QStringLiteral("two"));
} }
@ -525,9 +545,9 @@ void tst_qv4debugger::readLocals()
debugger()->addBreakPoint("readLocals", 3); debugger()->addBreakPoint("readLocals", 3);
evaluateJavaScript(script, "readLocals"); evaluateJavaScript(script, "readLocals");
QVERIFY(m_debuggerAgent->m_wasPaused); QVERIFY(m_debuggerAgent->m_wasPaused);
QVERIFY(m_debuggerAgent->m_capturedLocals.size() > 1); QVERIFY(m_debuggerAgent->m_capturedScope.size() > 1);
const TestAgent::NamedRefs &frame0 = m_debuggerAgent->m_capturedLocals.at(0); const TestAgent::NamedRefs &frame0 = m_debuggerAgent->m_capturedScope.at(0);
QCOMPARE(frame0.size(), 2); QCOMPARE(frame0.size(), 4); // locals and parameters
QVERIFY(frame0.contains("c")); QVERIFY(frame0.contains("c"));
QCOMPARE(frame0.type("c"), QStringLiteral("number")); QCOMPARE(frame0.type("c"), QStringLiteral("number"));
QCOMPARE(frame0.value("c").toDouble(), 3.0); QCOMPARE(frame0.value("c").toDouble(), 3.0);
@ -547,9 +567,9 @@ void tst_qv4debugger::readObject()
debugger()->addBreakPoint("readObject", 3); debugger()->addBreakPoint("readObject", 3);
evaluateJavaScript(script, "readObject"); evaluateJavaScript(script, "readObject");
QVERIFY(m_debuggerAgent->m_wasPaused); QVERIFY(m_debuggerAgent->m_wasPaused);
QVERIFY(m_debuggerAgent->m_capturedLocals.size() > 1); QVERIFY(m_debuggerAgent->m_capturedScope.size() > 1);
const TestAgent::NamedRefs &frame0 = m_debuggerAgent->m_capturedLocals.at(0); const TestAgent::NamedRefs &frame0 = m_debuggerAgent->m_capturedScope.at(0);
QCOMPARE(frame0.size(), 1); QCOMPARE(frame0.size(), 2);
QVERIFY(frame0.contains("b")); QVERIFY(frame0.contains("b"));
QCOMPARE(frame0.type("b"), QStringLiteral("object")); QCOMPARE(frame0.type("b"), QStringLiteral("object"));
QJsonObject b = frame0.rawValue("b"); QJsonObject b = frame0.rawValue("b");
@ -600,28 +620,23 @@ void tst_qv4debugger::readContextInAllFrames()
evaluateJavaScript(script, "readFormalsInAllFrames"); evaluateJavaScript(script, "readFormalsInAllFrames");
QVERIFY(m_debuggerAgent->m_wasPaused); QVERIFY(m_debuggerAgent->m_wasPaused);
QCOMPARE(m_debuggerAgent->m_stackTrace.size(), 13); QCOMPARE(m_debuggerAgent->m_stackTrace.size(), 13);
QCOMPARE(m_debuggerAgent->m_capturedArguments.size(), 13); QCOMPARE(m_debuggerAgent->m_capturedScope.size(), 13);
QCOMPARE(m_debuggerAgent->m_capturedLocals.size(), 13);
for (int i = 0; i < 12; ++i) { for (int i = 0; i < 12; ++i) {
const TestAgent::NamedRefs &args = m_debuggerAgent->m_capturedArguments.at(i); const TestAgent::NamedRefs &scope = m_debuggerAgent->m_capturedScope.at(i);
QCOMPARE(args.size(), 1); QCOMPARE(scope.size(), 2);
QVERIFY(args.contains("n")); QVERIFY(scope.contains("n"));
QCOMPARE(args.type("n"), QStringLiteral("number")); QCOMPARE(scope.type("n"), QStringLiteral("number"));
QCOMPARE(args.value("n").toDouble(), i + 1.0); QCOMPARE(scope.value("n").toDouble(), i + 1.0);
QVERIFY(scope.contains("n_1"));
const TestAgent::NamedRefs &locals = m_debuggerAgent->m_capturedLocals.at(i);
QCOMPARE(locals.size(), 1);
QVERIFY(locals.contains("n_1"));
if (i == 0) { if (i == 0) {
QCOMPARE(locals.type("n_1"), QStringLiteral("undefined")); QCOMPARE(scope.type("n_1"), QStringLiteral("undefined"));
} else { } else {
QCOMPARE(locals.type("n_1"), QStringLiteral("number")); QCOMPARE(scope.type("n_1"), QStringLiteral("number"));
QCOMPARE(locals.value("n_1").toInt(), i); QCOMPARE(scope.value("n_1").toInt(), i);
} }
} }
QCOMPARE(m_debuggerAgent->m_capturedArguments[12].size(), 0); QCOMPARE(m_debuggerAgent->m_capturedScope[12].size(), 0);
QCOMPARE(m_debuggerAgent->m_capturedLocals[12].size(), 0);
} }
void tst_qv4debugger::pauseOnThrow() void tst_qv4debugger::pauseOnThrow()