Change data collection for debugging to use QV4::Value.
authorUlf Hermann <ulf.hermann@theqtcompany.com>
Mon, 27 Jul 2015 12:40:26 +0000 (14:40 +0200)
committerUlf Hermann <ulf.hermann@theqtcompany.com>
Mon, 10 Aug 2015 10:05:01 +0000 (10:05 +0000)
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>
src/plugins/qmltooling/qmldbg_debugger/qv4debugservice.cpp
src/plugins/qmltooling/qmldbg_debugger/qv4debugservice.h
src/qml/jsruntime/qv4debugging.cpp
src/qml/jsruntime/qv4debugging_p.h
tests/auto/qml/qv4debugger/tst_qv4debugger.cpp

index 0aaa6e7e9290c86d0b3049764190c7c7409899f3..62436f18c9a5506227849fb389582694e0bc982b 100644 (file)
@@ -42,6 +42,7 @@
 #include <QtCore/QJsonArray>
 #include <QtCore/QJsonDocument>
 #include <QtCore/QJsonObject>
+#include <QtCore/QJsonValue>
 
 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<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, const QV4::Value &value)
-    {
-        QV4::Scope scope(engine());
-        QV4::ScopedObject obj(scope, value.as<QV4::Object>());
-
-        int ref = cachedObjectRef(obj);
-        if (ref != -1) {
-            addNameRefPair(name, ref);
-        } else {
-            int ref = newRefId();
-            cacheObjectRef(obj, 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;
-};
-
 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<QV4::Heap::ExecutionContext::ContextType> 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();
 }
index 4829ae414f91c7e91c1a33f724775d7efd508c30..23bffa368a9ad11fb8471378e40be6b2693f0e8a 100644 (file)
@@ -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<VariableCollector> theCollector;
+    QScopedPointer<QV4::Debugging::DataCollector> theCollector;
     int theSelectedFrame;
 
     void addHandler(V8CommandHandler* handler);
index ed8baa3f69a3897c2627b57a0c6fc7a32170c9c7..c48bb038444582301618e4b7aeb0248b7e12dd04 100644 (file)
 #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()
 {
 }
index e6a975035148816f0061f6195f2de80bac0aa17f..424459a7d924a9972b2e35a77e1be5ef7887af04 100644 (file)
@@ -44,6 +44,8 @@
 #include <QMutex>
 #include <QWaitCondition>
 
+#include <QtCore/QJsonObject>
+
 QT_BEGIN_NAMESPACE
 
 namespace QV4 {
@@ -79,6 +81,61 @@ inline bool operator==(const DebuggerBreakPoint &a, const DebuggerBreakPoint &b)
 
 typedef QHash<DebuggerBreakPoint, QString> BreakPoints;
 
+class Q_QML_PRIVATE_EXPORT DataCollector
+{
+public:
+    typedef uint Ref;
+    typedef QVector<uint> 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<Ref, QJsonObject> 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<StackFrame> 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<Heap::ExecutionContext::ContextType> 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();
index ee0f55813db1329119452724668d7f6e3ae6b9ec..78aa1134509029b7d010b162c12e9b9a057ed039 100644 (file)
@@ -38,6 +38,7 @@
 #include <private/qv4engine_p.h>
 #include <private/qv4debugging_p.h>
 #include <private/qv8engine_p.h>
+#include <private/qv4objectiterator_p.h>
 
 using namespace QV4;
 using namespace QV4::Debugging;
@@ -92,92 +93,83 @@ signals:
     void evaluateFinished();
 };
 
-
-namespace {
-class TestCollector: public QV4::Debugging::Debugger::Collector
+class TestAgent : public QV4::Debugging::DebuggerAgent
 {
+    Q_OBJECT
 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);
-    }
+    typedef QV4::Debugging::DataCollector::Refs Refs;
+    typedef QV4::Debugging::DataCollector::Ref Ref;
+    struct NamedRefs {
+        NamedRefs(DataCollector *collector = 0): collector(collector) {}
+
+        QStringList names;
+        Refs refs;
+        DataCollector *collector;
+
+        int size() const {
+            Q_ASSERT(names.size() == refs.size());
+            return names.size();
+        }
 
-    virtual void addString(const QString &name, const QString &value)
-    {
-        destination->insert(name, value);
-    }
+        bool contains(const QString &name) const {
+            return names.contains(name);
+        }
 
-    virtual void addObject(const QString &name, const QV4::Value &value)
-    {
-        QV4::Scope scope(engine());
-        QV4::ScopedObject obj(scope, value.as<Object>());
+#define DUMP_JSON(x) {\
+    QJsonDocument doc(x);\
+    qDebug() << #x << "=" << doc.toJson(QJsonDocument::Indented);\
+}
 
-        QVariantMap props, *prev = &props;
-        qSwap(destination, prev);
-        collect(obj);
-        qSwap(destination, prev);
+        QJsonObject rawValue(const QString &name) const {
+            Q_ASSERT(contains(name));
+            return collector->lookupRef(refs.at(names.indexOf(name)));
+        }
 
-        destination->insert(name, props);
-    }
+        QJsonValue value(const QString &name) const {
+            return rawValue(name).value(QStringLiteral("value"));
+        }
 
-    virtual void addInteger(const QString &name, int value)
-    {
-        destination->insert(name, QVariant::fromValue<double>(static_cast<double>(value)));
-    }
+        QString type(const QString &name) const {
+            return rawValue(name).value(QStringLiteral("type")).toString();
+        }
 
-    virtual void addDouble(const QString &name, double value)
-    {
-        destination->insert(name, QVariant::fromValue<double>(value));
-    }
+        void dump(const QString &name) const {
+            if (!contains(name)) {
+                qDebug() << "no" << name;
+                return;
+            }
 
-private:
-    QVariantMap *destination;
-};
-}
+            QJsonObject o = collector->lookupRef(refs.at(names.indexOf(name)));
+            QJsonDocument d;
+            d.setObject(o);
+            qDebug() << name << "=" << d.toJson(QJsonDocument::Indented);
+        }
+    };
 
-class TestAgent : public QV4::Debugging::DebuggerAgent
-{
-    Q_OBJECT
-public:
-    TestAgent()
+    TestAgent(QV4::ExecutionEngine *engine)
         : m_wasPaused(false)
         , m_captureContextInfo(false)
+        , m_thrownValue(-1)
+        , collector(engine)
     {
     }
 
     virtual void debuggerPaused(Debugger *debugger, PauseReason reason)
     {
         Q_ASSERT(m_debuggers.count() == 1 && m_debuggers.first() == debugger);
+        Q_ASSERT(debugger->engine() == collector.engine());
         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"];
+        {
+            Refs refs;
+            QV4::Debugging::RefHolder holder(&collector, &refs);
+            if (debugger->collectThrownValue(&collector)) {
+                Q_ASSERT(refs.size() > 0);
+                m_thrownValue = refs.first();
+            }
+        }
 
         foreach (const TestBreakPoint &bp, m_breakPointsToAddWhenPaused)
             debugger->addBreakPoint(bp.fileName, bp.lineNumber);
@@ -187,10 +179,9 @@ public:
 
         while (!m_expressionRequests.isEmpty()) {
             ExpressionRequest request = m_expressionRequests.takeFirst();
-            QVariantMap result;
-            collector.setDestination(&result);
+            m_expressionResults << Refs();
+            RefHolder holder(&collector, &m_expressionResults.last());
             debugger->evaluateExpression(request.frameNr, request.expression, &collector);
-            m_expressionResults << result[QString::fromLatin1("body")];
         }
 
         if (m_captureContextInfo)
@@ -219,18 +210,14 @@ public:
 
     void captureContextInfo(Debugger *debugger)
     {
-        TestCollector collector(debugger->engine());
-
         for (int i = 0, ei = m_stackTrace.size(); i != ei; ++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);
+            m_capturedArguments.append(NamedRefs(&collector));
+            RefHolder argHolder(&collector, &m_capturedArguments.last().refs);
+            debugger->collectArgumentsInContext(&collector, &m_capturedArguments.last().names, i);
+
+            m_capturedLocals.append(NamedRefs(&collector));
+            RefHolder localHolder(&collector, &m_capturedLocals.last().refs);
+            debugger->collectLocalsInContext(&collector, &m_capturedLocals.last().names, i);
         }
     }
 
@@ -240,16 +227,17 @@ public:
     QList<Debugger::ExecutionState> m_statesWhenPaused;
     QList<TestBreakPoint> m_breakPointsToAddWhenPaused;
     QVector<QV4::StackFrame> m_stackTrace;
-    QList<QVariantMap> m_capturedArguments;
-    QList<QVariantMap> m_capturedLocals;
-    QVariant m_thrownValue;
+    QVector<NamedRefs> m_capturedArguments;
+    QVector<NamedRefs> m_capturedLocals;
+    qint64 m_thrownValue;
+    QV4::Debugging::DataCollector collector;
 
     struct ExpressionRequest {
         QString expression;
         int frameNr;
     };
     QVector<ExpressionRequest> m_expressionRequests;
-    QVector<QVariant> m_expressionResults;
+    QVector<Refs> m_expressionResults;
 
     // Utility methods:
     void dumpStackTrace() const
@@ -315,7 +303,7 @@ void tst_qv4debugger::init()
     m_v4->enableDebugger();
     m_engine->moveToThread(m_javaScriptThread);
     m_javaScriptThread->start();
-    m_debuggerAgent = new TestAgent;
+    m_debuggerAgent = new TestAgent(m_v4);
     m_debuggerAgent->addDebugger(m_v4->debugger);
 }
 
@@ -446,9 +434,12 @@ void tst_qv4debugger::conditionalBreakPoint()
     QV4::Debugging::Debugger::ExecutionState state = m_debuggerAgent->m_statesWhenPaused.first();
     QCOMPARE(state.fileName, QString("conditionalBreakPoint"));
     QCOMPARE(state.lineNumber, 3);
-    QCOMPARE(m_debuggerAgent->m_capturedLocals[0].size(), 2);
-    QVERIFY(m_debuggerAgent->m_capturedLocals[0].contains(QStringLiteral("i")));
-    QCOMPARE(m_debuggerAgent->m_capturedLocals[0]["i"].toInt(), 11);
+
+    QVERIFY(m_debuggerAgent->m_capturedLocals.size() > 1);
+    const TestAgent::NamedRefs &frame0 = m_debuggerAgent->m_capturedLocals.at(0);
+    QCOMPARE(frame0.size(), 2);
+    QVERIFY(frame0.contains("i"));
+    QCOMPARE(frame0.value("i").toInt(), 11);
 }
 
 void tst_qv4debugger::conditionalBreakPointInQml()
@@ -459,7 +450,7 @@ void tst_qv4debugger::conditionalBreakPointInQml()
 
     QScopedPointer<QThread> debugThread(new QThread);
     debugThread->start();
-    QScopedPointer<TestAgent> debuggerAgent(new TestAgent);
+    QScopedPointer<TestAgent> debuggerAgent(new TestAgent(v4));
     debuggerAgent->addDebugger(v4->debugger);
     debuggerAgent->moveToThread(debugThread.data());
 
@@ -499,13 +490,15 @@ void tst_qv4debugger::readArguments()
     m_debuggerAgent->addBreakPoint("readArguments", 2);
     evaluateJavaScript(script, "readArguments");
     QVERIFY(m_debuggerAgent->m_wasPaused);
-    QCOMPARE(m_debuggerAgent->m_capturedArguments[0].size(), 4);
-    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"));
+    QVERIFY(m_debuggerAgent->m_capturedArguments.size() > 1);
+    const TestAgent::NamedRefs &frame0 = m_debuggerAgent->m_capturedArguments.at(0);
+    QCOMPARE(frame0.size(), 4);
+    QVERIFY(frame0.contains(QStringLiteral("a")));
+    QCOMPARE(frame0.type(QStringLiteral("a")), QStringLiteral("number"));
+    QCOMPARE(frame0.value(QStringLiteral("a")).toDouble(), 1.0);
+    QVERIFY(frame0.names.contains("b"));
+    QCOMPARE(frame0.type(QStringLiteral("b")), QStringLiteral("string"));
+    QCOMPARE(frame0.value(QStringLiteral("b")).toString(), QStringLiteral("two"));
 }
 
 void tst_qv4debugger::readLocals()
@@ -521,12 +514,14 @@ void tst_qv4debugger::readLocals()
     m_debuggerAgent->addBreakPoint("readLocals", 3);
     evaluateJavaScript(script, "readLocals");
     QVERIFY(m_debuggerAgent->m_wasPaused);
-    QCOMPARE(m_debuggerAgent->m_capturedLocals[0].size(), 2);
-    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"));
+    QVERIFY(m_debuggerAgent->m_capturedLocals.size() > 1);
+    const TestAgent::NamedRefs &frame0 = m_debuggerAgent->m_capturedLocals.at(0);
+    QCOMPARE(frame0.size(), 2);
+    QVERIFY(frame0.contains("c"));
+    QCOMPARE(frame0.type("c"), QStringLiteral("number"));
+    QCOMPARE(frame0.value("c").toDouble(), 3.0);
+    QVERIFY(frame0.contains("d"));
+    QCOMPARE(frame0.type("d"), QStringLiteral("undefined"));
 }
 
 void tst_qv4debugger::readObject()
@@ -541,23 +536,46 @@ void tst_qv4debugger::readObject()
     m_debuggerAgent->addBreakPoint("readObject", 3);
     evaluateJavaScript(script, "readObject");
     QVERIFY(m_debuggerAgent->m_wasPaused);
-    QCOMPARE(m_debuggerAgent->m_capturedLocals[0].size(), 1);
-    QVERIFY(m_debuggerAgent->m_capturedLocals[0].contains("b"));
-    QCOMPARE(m_debuggerAgent->m_capturedLocals[0]["b"].type(), QVariant::Map);
-
-    QVariantMap b = m_debuggerAgent->m_capturedLocals[0]["b"].toMap();
-    QCOMPARE(b.size(), 2);
-    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);
-
-    QVariantMap b_tail = b["tail"].toMap();
-    QCOMPARE(b_tail.size(), 2);
-    QVERIFY(b_tail.contains("head"));
-    QCOMPARE(b_tail["head"].type(), QVariant::String);
-    QCOMPARE(b_tail["head"].toString(), QString("asdf"));
+    QVERIFY(m_debuggerAgent->m_capturedLocals.size() > 1);
+    const TestAgent::NamedRefs &frame0 = m_debuggerAgent->m_capturedLocals.at(0);
+    QCOMPARE(frame0.size(), 1);
+    QVERIFY(frame0.contains("b"));
+    QCOMPARE(frame0.type("b"), QStringLiteral("object"));
+    QJsonObject b = frame0.rawValue("b");
+    QVERIFY(b.contains(QStringLiteral("properties")));
+    QVERIFY(b.value("properties").isArray());
+    QJsonArray b_props = b.value("properties").toArray();
+    QCOMPARE(b_props.size(), 2);
+
+    QVERIFY(b_props.at(0).isObject());
+    QJsonObject b_head = b_props.at(0).toObject();
+    QCOMPARE(b_head.value("name").toString(), QStringLiteral("head"));
+    QVERIFY(b_head.contains("ref"));
+    QJsonObject b_head_value = frame0.collector->lookupRef(b_head.value("ref").toInt());
+    QCOMPARE(b_head_value.value("type").toString(), QStringLiteral("number"));
+    QCOMPARE(b_head_value.value("value").toDouble(), 1.0);
+    QVERIFY(b_props.at(1).isObject());
+    QJsonObject b_tail = b_props.at(1).toObject();
+    QCOMPARE(b_tail.value("name").toString(), QStringLiteral("tail"));
+    QVERIFY(b_tail.contains("ref"));
+
+    QJsonObject b_tail_value = frame0.collector->lookupRef(b_tail.value("ref").toInt());
+    QCOMPARE(b_tail_value.value("type").toString(), QStringLiteral("object"));
+    QVERIFY(b_tail_value.contains("properties"));
+    QJsonArray b_tail_props = b_tail_value.value("properties").toArray();
+    QCOMPARE(b_tail_props.size(), 2);
+    QJsonObject b_tail_head = b_tail_props.at(0).toObject();
+    QCOMPARE(b_tail_head.value("name").toString(), QStringLiteral("head"));
+    QVERIFY(b_tail_head.contains("ref"));
+    QJsonObject b_tail_head_value = frame0.collector->lookupRef(b_tail_head.value("ref").toInt());
+    QCOMPARE(b_tail_head_value.value("type").toString(), QStringLiteral("string"));
+    QCOMPARE(b_tail_head_value.value("value").toString(), QStringLiteral("asdf"));
+    QJsonObject b_tail_tail = b_tail_props.at(1).toObject();
+    QCOMPARE(b_tail_tail.value("name").toString(), QStringLiteral("tail"));
+    QVERIFY(b_tail_tail.contains("ref"));
+
+    QJsonObject b_tail_tail_value = frame0.collector->lookupRef(b_tail_tail.value("ref").toInt());
+    QCOMPARE(b_tail_tail_value.value("type").toString(), QStringLiteral("null"));
 }
 
 void tst_qv4debugger::readContextInAllFrames()
@@ -581,18 +599,20 @@ void tst_qv4debugger::readContextInAllFrames()
     QCOMPARE(m_debuggerAgent->m_capturedLocals.size(), 13);
 
     for (int i = 0; i < 12; ++i) {
-        QCOMPARE(m_debuggerAgent->m_capturedArguments[i].size(), 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);
-        QVERIFY(m_debuggerAgent->m_capturedLocals[i].contains("n_1"));
+        const TestAgent::NamedRefs &args = m_debuggerAgent->m_capturedArguments.at(i);
+        QCOMPARE(args.size(), 1);
+        QVERIFY(args.contains("n"));
+        QCOMPARE(args.type("n"), QStringLiteral("number"));
+        QCOMPARE(args.value("n").toDouble(), i + 1.0);
+
+        const TestAgent::NamedRefs &locals = m_debuggerAgent->m_capturedLocals.at(i);
+        QCOMPARE(locals.size(), 1);
+        QVERIFY(locals.contains("n_1"));
         if (i == 0) {
-            QCOMPARE(m_debuggerAgent->m_capturedLocals[i]["n_1"].toString(), QString("undefined"));
+            QCOMPARE(locals.type("n_1"), QStringLiteral("undefined"));
         } else {
-            QCOMPARE(m_debuggerAgent->m_capturedLocals[i]["n_1"].type(), QVariant::Double);
-            QCOMPARE(m_debuggerAgent->m_capturedLocals[i]["n_1"].toInt(), i);
+            QCOMPARE(locals.type("n_1"), QStringLiteral("number"));
+            QCOMPARE(locals.value("n_1").toInt(), i);
         }
     }
     QCOMPARE(m_debuggerAgent->m_capturedArguments[12].size(), 0);
@@ -611,8 +631,11 @@ void tst_qv4debugger::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"));
+    QVERIFY(m_debuggerAgent->m_thrownValue >= qint64(0));
+    QJsonObject exception = m_debuggerAgent->collector.lookupRef(m_debuggerAgent->m_thrownValue);
+//    DUMP_JSON(exception);
+    QCOMPARE(exception.value("type").toString(), QStringLiteral("string"));
+    QCOMPARE(exception.value("value").toString(), QStringLiteral("hard"));
 }
 
 void tst_qv4debugger::breakInCatch()
@@ -674,8 +697,16 @@ void tst_qv4debugger::evaluateExpression()
     evaluateJavaScript(script, "evaluateExpression");
 
     QCOMPARE(m_debuggerAgent->m_expressionResults.count(), 2);
-    QCOMPARE(m_debuggerAgent->m_expressionResults[0].toInt(), 10);
-    QCOMPARE(m_debuggerAgent->m_expressionResults[1].toInt(), 20);
+    QCOMPARE(m_debuggerAgent->m_expressionResults[0].size(), 1);
+    QJsonObject result0 =
+            m_debuggerAgent->collector.lookupRef(m_debuggerAgent->m_expressionResults[0].first());
+    QCOMPARE(result0.value("type").toString(), QStringLiteral("number"));
+    QCOMPARE(result0.value("value").toInt(), 10);
+    QCOMPARE(m_debuggerAgent->m_expressionResults[1].size(), 1);
+    QJsonObject result1 =
+            m_debuggerAgent->collector.lookupRef(m_debuggerAgent->m_expressionResults[1].first());
+    QCOMPARE(result1.value("type").toString(), QStringLiteral("number"));
+    QCOMPARE(result1.value("value").toInt(), 20);
 }
 
 QTEST_MAIN(tst_qv4debugger)