From dc341e6c1c524330b838a62ceeaa148a01dc0729 Mon Sep 17 00:00:00 2001 From: Ulf Hermann Date: Mon, 27 Jul 2015 14:40:26 +0200 Subject: Change data collection for debugging to use QV4::Value. This patch changes the variable collection to store QV4::Value values into a JS array, which is retained by the collector. This prevents any GC issues, and gives a nice mapping from handle (used in the debugging protocol) to JS value. It also allows for easy "shallow" object serialization: any lookup can start with the QV4::Value, and add any values it encounters to the array. Testing is changed to use this collector directly, thereby testing the class that is actually used to generate protocol data. Task-number: QTBUG-47061 Change-Id: Iec75c4f74c08495e2a8af0fedf304f76f8385fd7 Reviewed-by: Erik Verbruggen --- .../qmltooling/qmldbg_debugger/qv4debugservice.cpp | 312 +++--------------- .../qmltooling/qmldbg_debugger/qv4debugservice.h | 8 +- src/qml/jsruntime/qv4debugging.cpp | 353 +++++++++++++++------ src/qml/jsruntime/qv4debugging_p.h | 99 ++++-- 4 files changed, 374 insertions(+), 398 deletions(-) (limited to 'src') diff --git a/src/plugins/qmltooling/qmldbg_debugger/qv4debugservice.cpp b/src/plugins/qmltooling/qmldbg_debugger/qv4debugservice.cpp index 0aaa6e7e92..62436f18c9 100644 --- a/src/plugins/qmltooling/qmldbg_debugger/qv4debugservice.cpp +++ b/src/plugins/qmltooling/qmldbg_debugger/qv4debugservice.cpp @@ -42,6 +42,7 @@ #include #include #include +#include const char *const V4_CONNECT = "connect"; const char *const V4_DISCONNECT = "disconnect"; @@ -61,248 +62,6 @@ QT_BEGIN_NAMESPACE class V8CommandHandler; class UnknownV8CommandHandler; -class VariableCollector: public QV4::Debugging::Debugger::Collector -{ -public: - VariableCollector(QV4::ExecutionEngine *engine) - : Collector(engine) - , destination(0) - {} - - virtual ~VariableCollector() {} - - void collectScope(QJsonArray *dest, QV4::Debugging::Debugger *debugger, int frameNr, int scopeNr) - { - qSwap(destination, dest); - bool oldIsProp = isProperty(); - setIsProperty(true); - debugger->collectArgumentsInContext(this, frameNr, scopeNr); - debugger->collectLocalsInContext(this, frameNr, scopeNr); - setIsProperty(oldIsProp); - qSwap(destination, dest); - } - - void setDestination(QJsonArray *dest) - { destination = dest; } - - QJsonArray retrieveRefsToInclude() - { - QJsonArray result; - qSwap(refsToInclude, result); - return result; - } - - QJsonValue lookup(int handle, bool addRefs = true) - { - if (handle < 0) - handle = -handle; - - if (addRefs) - foreach (int ref, refsByHandle[handle]) - refsToInclude.append(lookup(ref, false)); - return refs[handle]; - } - - QJsonObject makeRef(int refId) - { - QJsonObject ref; - ref[QLatin1String("ref")] = refId; - return ref; - } - - QJsonObject addFunctionRef(const QString &name) - { - const int refId = newRefId(); - - QJsonObject func; - func[QLatin1String("handle")] = refId; - func[QLatin1String("type")] = QStringLiteral("function"); - func[QLatin1String("className")] = QStringLiteral("Function"); - func[QLatin1String("name")] = name; - insertRef(func, refId); - - return makeRef(refId); - } - - QJsonObject addScriptRef(const QString &name) - { - const int refId = newRefId(); - - QJsonObject func; - func[QLatin1String("handle")] = refId; - func[QLatin1String("type")] = QStringLiteral("script"); - func[QLatin1String("name")] = name; - insertRef(func, refId); - - return makeRef(refId); - } - - QJsonObject addObjectRef(QJsonObject obj, bool anonymous) - { - int ref = newRefId(); - - if (anonymous) - ref = -ref; - obj[QLatin1String("handle")] = ref; - obj[QLatin1String("type")] = QStringLiteral("object"); - insertRef(obj, ref); - QSet used; - qSwap(usedRefs, used); - refsByHandle.insert(ref, used); - - return makeRef(ref); - } - -protected: - virtual void addUndefined(const QString &name) - { - QJsonObject o; - addHandle(name, o, QStringLiteral("undefined")); - } - - virtual void addNull(const QString &name) - { - QJsonObject o; - addHandle(name, o, QStringLiteral("null")); - } - - virtual void addBoolean(const QString &name, bool value) - { - QJsonObject o; - o[QLatin1String("value")] = value; - addHandle(name, o, QStringLiteral("boolean")); - } - - virtual void addString(const QString &name, const QString &value) - { - QJsonObject o; - o[QLatin1String("value")] = value; - addHandle(name, o, QStringLiteral("string")); - } - - virtual void addObject(const QString &name, const QV4::Value &value) - { - QV4::Scope scope(engine()); - QV4::ScopedObject obj(scope, value.as()); - - int ref = cachedObjectRef(obj); - if (ref != -1) { - addNameRefPair(name, ref); - } else { - int ref = newRefId(); - cacheObjectRef(obj, ref); - - QJsonArray properties, *prev = &properties; - QSet used; - qSwap(usedRefs, used); - qSwap(destination, prev); - collect(obj); - qSwap(destination, prev); - qSwap(usedRefs, used); - - QJsonObject o; - o[QLatin1String("properties")] = properties; - addHandle(name, o, QStringLiteral("object"), ref); - refsByHandle.insert(ref, used); - } - } - - virtual void addInteger(const QString &name, int value) - { - QJsonObject o; - o[QLatin1String("value")] = value; - addHandle(name, o, QStringLiteral("number")); - } - - virtual void addDouble(const QString &name, double value) - { - QJsonObject o; - o[QLatin1String("value")] = value; - addHandle(name, o, QStringLiteral("number")); - } - -private: - int addHandle(const QString &name, QJsonObject object, const QString &type, int suppliedRef = -1) - { - Q_ASSERT(destination); - - object[QLatin1String("type")] = type; - - QJsonDocument tmp; - tmp.setObject(object); - QByteArray key = tmp.toJson(QJsonDocument::Compact); - - int ref; - if (suppliedRef == -1) { - ref = refCache.value(key, -1); - if (ref == -1) { - ref = newRefId(); - object[QLatin1String("handle")] = ref; - insertRef(object, ref); - refCache.insert(key, ref); - } - } else { - ref = suppliedRef; - object[QLatin1String("handle")] = ref; - insertRef(object, ref); - refCache.insert(key, ref); - } - - addNameRefPair(name, ref); - return ref; - } - - void addNameRefPair(const QString &name, int ref) - { - QJsonObject nameValuePair; - nameValuePair[QLatin1String("name")] = name; - if (isProperty()) { - nameValuePair[QLatin1String("ref")] = ref; - } else { - QJsonObject refObj; - refObj[QLatin1String("ref")] = ref; - nameValuePair[QLatin1String("value")] = refObj; - } - destination->append(nameValuePair); - usedRefs.insert(ref); - } - - int newRefId() - { - int ref = refs.count(); - refs.insert(ref, QJsonValue()); - return ref; - } - - void insertRef(const QJsonValue &value, int refId) - { - if (refId < 0) - refId = -refId; - - refs.insert(refId, value); - refsToInclude.append(value); - } - - void cacheObjectRef(QV4::Object *obj, int ref) - { - objectRefs.insert(obj, ref); - } - - int cachedObjectRef(QV4::Object *obj) const - { - return objectRefs.value(obj, -1); - } - -private: - QJsonArray refsToInclude; - QHash refs; - QHash refCache; - QJsonArray *destination; - QSet usedRefs; - QHash > refsByHandle; - QHash objectRefs; -}; - int QV4DebugServiceImpl::debuggerIndex = 0; int QV4DebugServiceImpl::sequence = 0; @@ -796,20 +555,19 @@ public: QV4::Debugging::Debugger *debugger = debugService->debuggerAgent.firstDebugger(); Q_ASSERT(debugger->state() == QV4::Debugging::Debugger::Paused); - VariableCollector *collector = debugService->collector(); - QJsonArray dest; - collector->setDestination(&dest); + QV4::Debugging::DataCollector *collector = debugService->collector(); + QV4::Debugging::DataCollector::Refs refs; + QV4::Debugging::RefHolder holder(collector, &refs); debugger->evaluateExpression(frame, expression, collector); - const int ref = dest.at(0).toObject().value(QStringLiteral("value")).toObject() - .value(QStringLiteral("ref")).toInt(); + Q_ASSERT(refs.size() == 1); // response: addCommand(); addRequestSequence(); addSuccess(true); addRunning(); - addBody(collector->lookup(ref).toObject()); + addBody(collector->lookupRef(refs.first())); addRefs(); } }; @@ -1088,27 +846,31 @@ void QV4DebugServiceImpl::send(QJsonObject v8Payload) void QV4DebugServiceImpl::clearHandles(QV4::ExecutionEngine *engine) { - theCollector.reset(new VariableCollector(engine)); + theCollector.reset(new QV4::Debugging::DataCollector(engine)); } QJsonObject QV4DebugServiceImpl::buildFrame(const QV4::StackFrame &stackFrame, int frameNr, QV4::Debugging::Debugger *debugger) { + QV4::Debugging::DataCollector::Ref ref; + QJsonObject frame; frame[QLatin1String("index")] = frameNr; frame[QLatin1String("debuggerFrame")] = false; - frame[QLatin1String("func")] = theCollector->addFunctionRef(stackFrame.function); - frame[QLatin1String("script")] = theCollector->addScriptRef(stackFrame.source); + ref = theCollector->addFunctionRef(stackFrame.function); + collectedRefs.append(ref); + frame[QLatin1String("func")] = toRef(ref); + ref = theCollector->addScriptRef(stackFrame.source); + collectedRefs.append(ref); + frame[QLatin1String("script")] = toRef(ref); frame[QLatin1String("line")] = stackFrame.line - 1; if (stackFrame.column >= 0) frame[QLatin1String("column")] = stackFrame.column; - QJsonArray properties; - theCollector->setDestination(&properties); - if (debugger->collectThisInContext(theCollector.data(), frameNr)) { - QJsonObject obj; - obj[QLatin1String("properties")] = properties; - frame[QLatin1String("receiver")] = theCollector->addObjectRef(obj, false); + { + QV4::Debugging::RefHolder holder(theCollector.data(), &collectedRefs); + if (debugger->collectThisInContext(theCollector.data(), frameNr)) + frame[QLatin1String("receiver")] = toRef(collectedRefs.last()); } QJsonArray scopes; @@ -1156,32 +918,48 @@ QJsonObject QV4DebugServiceImpl::buildScope(int frameNr, int scopeNr, { QJsonObject scope; - QJsonArray properties; - theCollector->collectScope(&properties, debugger, frameNr, scopeNr); - - QJsonObject anonymous; - anonymous[QLatin1String("properties")] = properties; + QJsonObject object; + QV4::Debugging::RefHolder holder(theCollector.data(), &collectedRefs); + theCollector->collectScope(&object, debugger, frameNr, scopeNr); QVector scopeTypes = debugger->getScopeTypes(frameNr); scope[QLatin1String("type")] = encodeScopeType(scopeTypes[scopeNr]); scope[QLatin1String("index")] = scopeNr; scope[QLatin1String("frameIndex")] = frameNr; - scope[QLatin1String("object")] = theCollector->addObjectRef(anonymous, true); + scope[QLatin1String("object")] = object; return scope; } -QJsonValue QV4DebugServiceImpl::lookup(int refId) const +QJsonValue QV4DebugServiceImpl::lookup(QV4::Debugging::DataCollector::Ref refId) { - return theCollector->lookup(refId); + QV4::Debugging::RefHolder holder(theCollector.data(), &collectedRefs); + return theCollector->lookupRef(refId); } QJsonArray QV4DebugServiceImpl::buildRefs() { - return theCollector->retrieveRefsToInclude(); + QJsonArray refs; + std::sort(collectedRefs.begin(), collectedRefs.end()); + for (int i = 0, ei = collectedRefs.size(); i != ei; ++i) { + QV4::Debugging::DataCollector::Ref ref = collectedRefs.at(i); + if (i > 0 && ref == collectedRefs.at(i - 1)) + continue; + refs.append(lookup(ref)); + } + + collectedRefs.clear(); + return refs; +} + +QJsonValue QV4DebugServiceImpl::toRef(QV4::Debugging::DataCollector::Ref ref) +{ + QJsonObject dict; + dict.insert(QStringLiteral("ref"), qint64(ref)); + return dict; } -VariableCollector *QV4DebugServiceImpl::collector() const +QV4::Debugging::DataCollector *QV4DebugServiceImpl::collector() const { return theCollector.data(); } diff --git a/src/plugins/qmltooling/qmldbg_debugger/qv4debugservice.h b/src/plugins/qmltooling/qmldbg_debugger/qv4debugservice.h index 4829ae414f..23bffa368a 100644 --- a/src/plugins/qmltooling/qmldbg_debugger/qv4debugservice.h +++ b/src/plugins/qmltooling/qmldbg_debugger/qv4debugservice.h @@ -94,7 +94,8 @@ public: QJsonObject buildScope(int frameNr, int scopeNr, QV4::Debugging::Debugger *debugger); QJsonArray buildRefs(); - QJsonValue lookup(int refId) const; + QJsonValue lookup(QV4::Debugging::DataCollector::Ref refId); + QJsonValue toRef(QV4::Debugging::DataCollector::Ref ref); QJsonObject buildFrame(const QV4::StackFrame &stackFrame, int frameNr, QV4::Debugging::Debugger *debugger); @@ -103,7 +104,7 @@ public: void clearHandles(QV4::ExecutionEngine *engine); - VariableCollector *collector() const; + QV4::Debugging::DataCollector *collector() const; QV4DebuggerAgent debuggerAgent; protected: @@ -125,8 +126,9 @@ private: static int debuggerIndex; static int sequence; const int version; + QV4::Debugging::DataCollector::Refs collectedRefs; - QScopedPointer theCollector; + QScopedPointer theCollector; int theSelectedFrame; void addHandler(V8CommandHandler* handler); diff --git a/src/qml/jsruntime/qv4debugging.cpp b/src/qml/jsruntime/qv4debugging.cpp index ed8baa3f69..c48bb03844 100644 --- a/src/qml/jsruntime/qv4debugging.cpp +++ b/src/qml/jsruntime/qv4debugging.cpp @@ -38,13 +38,17 @@ #include "qv4instr_moth_p.h" #include "qv4runtime_p.h" #include "qv4script_p.h" -#include "qv4objectiterator_p.h" #include "qv4identifier_p.h" #include "qv4string_p.h" -#include +#include "qv4objectiterator_p.h" +#include #include +#include +#include +#include + QT_BEGIN_NAMESPACE using namespace QV4; @@ -120,10 +124,11 @@ public: class ExpressionEvalJob: public JavaScriptJob { - Debugger::Collector *collector; + QV4::Debugging::DataCollector *collector; public: - ExpressionEvalJob(ExecutionEngine *engine, int frameNr, const QString &expression, Debugger::Collector *collector) + ExpressionEvalJob(ExecutionEngine *engine, int frameNr, const QString &expression, + QV4::Debugging::DataCollector *collector) : JavaScriptJob(engine, frameNr, expression) , collector(collector) { @@ -131,7 +136,7 @@ public: virtual void handleResult(QV4::ScopedValue &result) { - collector->collect(QStringLiteral("body"), result); + collector->collect(result); } }; @@ -167,6 +172,218 @@ public: }; } + +DataCollector::DataCollector(QV4::ExecutionEngine *engine) + : m_engine(engine), m_collectedRefs(Q_NULLPTR) +{ + values.set(engine, engine->newArrayObject()); +} + +DataCollector::~DataCollector() +{ +} + +void DataCollector::collect(const ScopedValue &value) +{ + if (m_collectedRefs) + m_collectedRefs->append(addRef(value)); +} + +QJsonObject DataCollector::lookupRef(Ref ref) +{ + QJsonObject dict; + if (lookupSpecialRef(ref, &dict)) + return dict; + + dict.insert(QStringLiteral("handle"), qint64(ref)); + + QV4::Scope scope(engine()); + QV4::ScopedValue value(scope, getValue(ref)); + switch (value->type()) { + case QV4::Value::Empty_Type: + Q_ASSERT(!"empty Value encountered"); + break; + case QV4::Value::Undefined_Type: + dict.insert(QStringLiteral("type"), QStringLiteral("undefined")); + break; + case QV4::Value::Null_Type: + dict.insert(QStringLiteral("type"), QStringLiteral("null")); + break; + case QV4::Value::Boolean_Type: + dict.insert(QStringLiteral("type"), QStringLiteral("boolean")); + dict.insert(QStringLiteral("value"), value->booleanValue() ? QStringLiteral("true") + : QStringLiteral("false")); + break; + case QV4::Value::Managed_Type: + if (QV4::String *s = value->as()) { + dict.insert(QStringLiteral("type"), QStringLiteral("string")); + dict.insert(QStringLiteral("value"), s->toQString()); + } else if (QV4::Object *o = value->as()) { + dict.insert(QStringLiteral("type"), QStringLiteral("object")); + dict.insert(QStringLiteral("properties"), collectProperties(o)); + } else { + Q_UNREACHABLE(); + } + break; + case QV4::Value::Integer_Type: + dict.insert(QStringLiteral("type"), QStringLiteral("number")); + dict.insert(QStringLiteral("value"), value->integerValue()); + break; + default: // double + dict.insert(QStringLiteral("type"), QStringLiteral("number")); + dict.insert(QStringLiteral("value"), value->doubleValue()); + break; + } + + return dict; +} + +DataCollector::Ref DataCollector::addFunctionRef(const QString &functionName) +{ + Ref ref = addRef(QV4::Primitive::emptyValue(), false); + + QJsonObject dict; + dict.insert(QStringLiteral("handle"), qint64(ref)); + dict.insert(QStringLiteral("type"), QStringLiteral("function")); + dict.insert(QStringLiteral("className"), QStringLiteral("Function")); + dict.insert(QStringLiteral("name"), functionName); + specialRefs.insert(ref, dict); + + return ref; +} + +DataCollector::Ref DataCollector::addScriptRef(const QString &scriptName) +{ + Ref ref = addRef(QV4::Primitive::emptyValue(), false); + + QJsonObject dict; + dict.insert(QStringLiteral("handle"), qint64(ref)); + dict.insert(QStringLiteral("type"), QStringLiteral("script")); + dict.insert(QStringLiteral("name"), scriptName); + specialRefs.insert(ref, dict); + + return ref; +} + +void DataCollector::collectScope(QJsonObject *dict, Debugger *debugger, int frameNr, int scopeNr) +{ + QStringList names; + + Refs refs; + { + RefHolder holder(this, &refs); + debugger->collectArgumentsInContext(this, &names, frameNr, scopeNr); + debugger->collectLocalsInContext(this, &names, frameNr, scopeNr); + } + + QV4::Scope scope(engine()); + QV4::ScopedObject scopeObject(scope, engine()->newObject()); + + Q_ASSERT(names.size() == refs.size()); + for (int i = 0, ei = refs.size(); i != ei; ++i) + scopeObject->put(engine(), names.at(i), + QV4::Value::fromReturnedValue(getValue(refs.at(i)))); + + Ref scopeObjectRef = addRef(scopeObject); + dict->insert(QStringLiteral("ref"), qint64(scopeObjectRef)); + if (m_collectedRefs) + m_collectedRefs->append(scopeObjectRef); +} + +DataCollector::Ref DataCollector::addRef(Value value, bool deduplicate) +{ + class ExceptionStateSaver + { + quint32 *hasExceptionLoc; + quint32 hadException; + + public: + ExceptionStateSaver(QV4::ExecutionEngine *engine) + : hasExceptionLoc(&engine->hasException) + , hadException(false) + { std::swap(*hasExceptionLoc, hadException); } + + ~ExceptionStateSaver() + { std::swap(*hasExceptionLoc, hadException); } + }; + + // if we wouldn't do this, the putIndexed won't work. + ExceptionStateSaver resetExceptionState(engine()); + QV4::Scope scope(engine()); + QV4::ScopedObject array(scope, values.value()); + if (deduplicate) { + for (Ref i = 0; i < array->getLength(); ++i) { + if (array->getIndexed(i) == value.rawValue()) + return i; + } + } + Ref ref = array->getLength(); + array->putIndexed(ref, value); + Q_ASSERT(array->getLength() - 1 == ref); + return ref; +} + +ReturnedValue DataCollector::getValue(Ref ref) +{ + QV4::Scope scope(engine()); + QV4::ScopedObject array(scope, values.value()); + Q_ASSERT(ref < array->getLength()); + return array->getIndexed(ref, Q_NULLPTR); +} + +bool DataCollector::lookupSpecialRef(Ref ref, QJsonObject *dict) +{ + SpecialRefs::const_iterator it = specialRefs.find(ref); + if (it == specialRefs.end()) + return false; + + *dict = it.value(); + return true; +} + +QJsonArray DataCollector::collectProperties(Object *object) +{ + QJsonArray res; + + QV4::Scope scope(engine()); + QV4::ObjectIterator it(scope, object, QV4::ObjectIterator::EnumerableOnly); + QV4::ScopedValue name(scope); + QV4::ScopedValue value(scope); + while (true) { + QV4::Value v; + name = it.nextPropertyNameAsString(&v); + if (name->isNull()) + break; + QString key = name->toQStringNoThrow(); + value = v; + res.append(collectAsJson(key, value)); + } + + return res; +} + +QJsonObject DataCollector::collectAsJson(const QString &name, const ScopedValue &value) +{ + QJsonObject dict; + if (!name.isNull()) + dict.insert(QStringLiteral("name"), name); + Ref ref = addRef(value); + dict.insert(QStringLiteral("ref"), qint64(ref)); + if (m_collectedRefs) + m_collectedRefs->append(ref); + + // TODO: enable this when creator can handle it. + if (false) { + if (value->isManaged() && !value->isString()) { + QV4::Scope scope(engine()); + QV4::ScopedObject obj(scope, value->as()); + dict.insert(QStringLiteral("propertycount"), qint64(obj->getLength())); + } + } + + return dict; +} + Debugger::Debugger(QV4::ExecutionEngine *engine) : m_engine(engine) , m_agent(0) @@ -308,7 +525,8 @@ static inline Heap::CallContext *findScope(Heap::ExecutionContext *ctxt, int sco return (ctx && ctx->d()) ? ctx->asCallContext()->d() : 0; } -void Debugger::collectArgumentsInContext(Collector *collector, int frameNr, int scopeNr) +void Debugger::collectArgumentsInContext(DataCollector *collector, QStringList *names, int frameNr, + int scopeNr) { if (state() != Paused) return; @@ -316,14 +534,17 @@ void Debugger::collectArgumentsInContext(Collector *collector, int frameNr, int class ArgumentCollectJob: public Job { QV4::ExecutionEngine *engine; - Collector *collector; + QV4::Debugging::DataCollector *collector; + QStringList *names; int frameNr; int scopeNr; public: - ArgumentCollectJob(QV4::ExecutionEngine *engine, Collector *collector, int frameNr, int scopeNr) + ArgumentCollectJob(QV4::ExecutionEngine *engine, DataCollector *collector, + QStringList *names, int frameNr, int scopeNr) : engine(engine) , collector(collector) + , names(names) , frameNr(frameNr) , scopeNr(scopeNr) {} @@ -346,33 +567,40 @@ void Debugger::collectArgumentsInContext(Collector *collector, int frameNr, int QString qName; if (Identifier *name = ctxt->formals()[nFormals - i - 1]) qName = name->string; + names->append(qName); v = ctxt->argument(i); - collector->collect(qName, v); + collector->collect(v); } } }; - ArgumentCollectJob job(m_engine, collector, frameNr, scopeNr); + ArgumentCollectJob job(m_engine, collector, names, frameNr, scopeNr); runInEngine(&job); } /// Same as \c retrieveArgumentsFromContext, but now for locals. -void Debugger::collectLocalsInContext(Collector *collector, int frameNr, int scopeNr) +void Debugger::collectLocalsInContext(DataCollector *collector, QStringList *names, int frameNr, + int scopeNr) { + Q_ASSERT(collector); + Q_ASSERT(names); + if (state() != Paused) return; class LocalCollectJob: public Job { QV4::ExecutionEngine *engine; - Collector *collector; + DataCollector *collector; + QStringList *names; int frameNr; int scopeNr; public: - LocalCollectJob(QV4::ExecutionEngine *engine, Collector *collector, int frameNr, int scopeNr) + LocalCollectJob(QV4::ExecutionEngine *engine, DataCollector *collector, QStringList *names, int frameNr, int scopeNr) : engine(engine) , collector(collector) + , names(names) , frameNr(frameNr) , scopeNr(scopeNr) {} @@ -392,17 +620,18 @@ void Debugger::collectLocalsInContext(Collector *collector, int frameNr, int sco QString qName; if (Identifier *name = ctxt->variables()[i]) qName = name->string; + names->append(qName); v = ctxt->d()->locals[i]; - collector->collect(qName, v); + collector->collect(v); } } }; - LocalCollectJob job(m_engine, collector, frameNr, scopeNr); + LocalCollectJob job(m_engine, collector, names, frameNr, scopeNr); runInEngine(&job); } -bool Debugger::collectThisInContext(Debugger::Collector *collector, int frame) +bool Debugger::collectThisInContext(DataCollector *collector, int frame) { if (state() != Paused) return false; @@ -410,12 +639,13 @@ bool Debugger::collectThisInContext(Debugger::Collector *collector, int frame) class ThisCollectJob: public Job { QV4::ExecutionEngine *engine; - Collector *collector; + DataCollector *collector; int frameNr; bool *foundThis; public: - ThisCollectJob(QV4::ExecutionEngine *engine, Collector *collector, int frameNr, bool *foundThis) + ThisCollectJob(QV4::ExecutionEngine *engine, DataCollector *collector, int frameNr, + bool *foundThis) : engine(engine) , collector(collector) , frameNr(frameNr) @@ -441,7 +671,7 @@ bool Debugger::collectThisInContext(Debugger::Collector *collector, int frame) if (!ctxt) return false; - ScopedObject o(scope, ctxt->asCallContext()->d()->activation); + ScopedValue o(scope, ctxt->asCallContext()->d()->activation); collector->collect(o); return true; } @@ -453,18 +683,18 @@ bool Debugger::collectThisInContext(Debugger::Collector *collector, int frame) return foundThis; } -void Debugger::collectThrownValue(Collector *collector) +bool Debugger::collectThrownValue(DataCollector *collector) { if (state() != Paused || !m_engine->hasException) - return; + return false; - class ThisCollectJob: public Job + class ExceptionCollectJob: public Job { QV4::ExecutionEngine *engine; - Collector *collector; + DataCollector *collector; public: - ThisCollectJob(QV4::ExecutionEngine *engine, Collector *collector) + ExceptionCollectJob(QV4::ExecutionEngine *engine, DataCollector *collector) : engine(engine) , collector(collector) {} @@ -473,22 +703,13 @@ void Debugger::collectThrownValue(Collector *collector) { Scope scope(engine); ScopedValue v(scope, *engine->exceptionValue); - collector->collect(QStringLiteral("exception"), v); + collector->collect(v); } }; - ThisCollectJob job(m_engine, collector); + ExceptionCollectJob job(m_engine, collector); runInEngine(&job); -} - -void Debugger::collectReturnedValue(Collector *collector) const -{ - if (state() != Paused) - return; - - Scope scope(m_engine); - ScopedObject o(scope, m_returnedValue.valueRef()); - collector->collect(o); + return true; } QVector Debugger::getScopeTypes(int frame) const @@ -511,7 +732,8 @@ QVector Debugger::getScopeTypes(int frame) } -void Debugger::evaluateExpression(int frameNr, const QString &expression, Debugger::Collector *resultsCollector) +void Debugger::evaluateExpression(int frameNr, const QString &expression, + DataCollector *resultsCollector) { Q_ASSERT(state() == Paused); @@ -776,63 +998,6 @@ DebuggerAgent::~DebuggerAgent() Q_ASSERT(m_debuggers.isEmpty()); } -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 (const String *s = value->as()) - addString(name, s->toQString()); - else - 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(Object *object) -{ - bool property = true; - qSwap(property, m_isProperty); - - 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 e6a9750351..424459a7d9 100644 --- a/src/qml/jsruntime/qv4debugging_p.h +++ b/src/qml/jsruntime/qv4debugging_p.h @@ -44,6 +44,8 @@ #include #include +#include + QT_BEGIN_NAMESPACE namespace QV4 { @@ -79,6 +81,61 @@ inline bool operator==(const DebuggerBreakPoint &a, const DebuggerBreakPoint &b) typedef QHash BreakPoints; +class Q_QML_PRIVATE_EXPORT DataCollector +{ +public: + typedef uint Ref; + typedef QVector Refs; + + DataCollector(QV4::ExecutionEngine *engine); + ~DataCollector(); + + void collect(const QV4::ScopedValue &value); + + QJsonObject lookupRef(Ref ref); + + Ref addFunctionRef(const QString &functionName); + Ref addScriptRef(const QString &scriptName); + + void collectScope(QJsonObject *dict, QV4::Debugging::Debugger *debugger, int frameNr, + int scopeNr); + + QV4::ExecutionEngine *engine() const { return m_engine; } + +private: + friend class RefHolder; + + Ref addRef(QV4::Value value, bool deduplicate = true); + QV4::ReturnedValue getValue(Ref ref); + bool lookupSpecialRef(Ref ref, QJsonObject *dict); + + QJsonArray collectProperties(QV4::Object *object); + QJsonObject collectAsJson(const QString &name, const QV4::ScopedValue &value); + + QV4::ExecutionEngine *m_engine; + Refs *m_collectedRefs; + QV4::PersistentValue values; + typedef QHash SpecialRefs; + SpecialRefs specialRefs; +}; + +class RefHolder { +public: + RefHolder(DataCollector *collector, DataCollector::Refs *target) : + m_collector(collector), m_previousRefs(collector->m_collectedRefs) + { + m_collector->m_collectedRefs = target; + } + + ~RefHolder() + { + std::swap(m_collector->m_collectedRefs, m_previousRefs); + } + +private: + DataCollector *m_collector; + DataCollector::Refs *m_previousRefs; +}; class Q_QML_EXPORT Debugger { @@ -90,34 +147,6 @@ public: virtual void run() = 0; }; - 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(Object *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, const Value &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 { Running, Paused @@ -166,14 +195,16 @@ public: } QVector stackTrace(int frameLimit = -1) const; - 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; + void collectArgumentsInContext(DataCollector *collector, QStringList *names, int frameNr = 0, + int scopeNr = 0); + void collectLocalsInContext(DataCollector *collector, QStringList *names, int frameNr = 0, + int scopeNr = 0); + bool collectThisInContext(DataCollector *collector, int frame = 0); + bool collectThrownValue(DataCollector *collector); QVector getScopeTypes(int frame = 0) const; - void evaluateExpression(int frameNr, const QString &expression, Collector *resultsCollector); + void evaluateExpression(int frameNr, const QString &expression, + DataCollector *resultsCollector); public: // compile-time interface void maybeBreakAtInstruction(); -- cgit v1.2.3