diff options
author | Ulf Hermann <ulf.hermann@theqtcompany.com> | 2015-07-27 14:40:26 +0200 |
---|---|---|
committer | Ulf Hermann <ulf.hermann@theqtcompany.com> | 2015-08-10 10:05:01 +0000 |
commit | dc341e6c1c524330b838a62ceeaa148a01dc0729 (patch) | |
tree | e42e93b6af0ecb347d4f3850b8e04acd796a4b66 /src/qml/jsruntime/qv4debugging.cpp | |
parent | e01bea8999d2f58add58874bd3e6792f509b131b (diff) |
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 <erik.verbruggen@theqtcompany.com>
Diffstat (limited to 'src/qml/jsruntime/qv4debugging.cpp')
-rw-r--r-- | src/qml/jsruntime/qv4debugging.cpp | 353 |
1 files changed, 259 insertions, 94 deletions
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 <iostream> +#include "qv4objectiterator_p.h" +#include <iostream> #include <algorithm> +#include <QtCore/QJsonArray> +#include <QtCore/QJsonDocument> +#include <QtCore/QJsonValue> + 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<QV4::String>()) { + dict.insert(QStringLiteral("type"), QStringLiteral("string")); + dict.insert(QStringLiteral("value"), s->toQString()); + } else if (QV4::Object *o = value->as<QV4::Object>()) { + 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<QV4::Object>()); + 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<Heap::ExecutionContext::ContextType> Debugger::getScopeTypes(int frame) const @@ -511,7 +732,8 @@ QVector<Heap::ExecutionContext::ContextType> 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<String>()) - 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() { } |