Clean up data format for V4 debug connection
authorUlf Hermann <ulf.hermann@theqtcompany.com>
Fri, 14 Aug 2015 16:10:38 +0000 (18:10 +0200)
committerUlf Hermann <ulf.hermann@theqtcompany.com>
Wed, 19 Aug 2015 11:28:32 +0000 (11:28 +0000)
This changes the type announced for functions to the actual JavaScript
type "function".

The type for null is also wrong: it should be "object". However, older
QtCreators cannot distinguish between null and {} if null gets the
correct type, unless you explicitly compare x === null in an expression
evaluator. For this reason the fake "null" type is kept for now.

Also, the value field of undefined is now set as QJsonValue::Undefined
which causes it to be omitted when sent over the wire. This is the
logical thing to do.

In addition we add type and value fields for all data members mentioned
in a response, not only the ones specifically asked for. The value
field is the actual value for any primitives (including strings), or the
number of properties for composite types: objects, arrays, functions. In
turn, the "ref" members are omitted for primitive types, so that we
don't have to hold references to them in the debug service anymore. Even
old QtCreators can deal with verbatim data members without "ref".

Task-number: QTBUG-47746
Task-number: QTBUG-47747
Change-Id: I773e6418c39cd9814aadb5fb5ef7e109f9a4e618
Reviewed-by: Simon Hausmann <simon.hausmann@theqtcompany.com>
src/plugins/qmltooling/qmldbg_debugger/qv4datacollector.cpp
src/plugins/qmltooling/qmldbg_debugger/qv4datacollector.h
tests/auto/qml/qv4debugger/tst_qv4debugger.cpp

index 01d2a98a74b7172d4bc0f7822c41ab76f0365299..2ef7713ac79dff518949591a0dbf45cbd025cecf 100644 (file)
@@ -37,6 +37,7 @@
 #include <private/qv4string_p.h>
 #include <private/qv4objectiterator_p.h>
 #include <private/qv4identifier_p.h>
+#include <private/qv4runtime_p.h>
 
 #include <QtCore/qjsonarray.h>
 
@@ -109,51 +110,78 @@ void QV4DataCollector::collect(const QV4::ScopedValue &value)
         m_collectedRefs->append(addRef(value));
 }
 
-QJsonObject QV4DataCollector::lookupRef(Ref ref)
+const QV4::Object *collectProperty(const QV4::ScopedValue &value, QV4::ExecutionEngine *engine,
+                                   QJsonObject &dict)
 {
-    QJsonObject dict;
-    if (lookupSpecialRef(ref, &dict))
-        return dict;
-
-    dict.insert(QStringLiteral("handle"), qint64(ref));
+    QV4::Scope scope(engine);
+    QV4::ScopedValue typeString(scope, QV4::Runtime::typeofValue(engine, value));
+    dict.insert(QStringLiteral("type"), typeString->toQStringNoThrow());
 
-    QV4::Scope scope(engine());
-    QV4::ScopedValue value(scope, getValue(ref));
+    const QLatin1String valueKey("value");
     switch (value->type()) {
     case QV4::Value::Empty_Type:
         Q_ASSERT(!"empty Value encountered");
-        break;
+        return 0;
     case QV4::Value::Undefined_Type:
-        dict.insert(QStringLiteral("type"), QStringLiteral("undefined"));
-        break;
+        dict.insert(valueKey, QJsonValue::Undefined);
+        return 0;
     case QV4::Value::Null_Type:
+        // "null" is not the correct type, but we leave this in until QtC can deal with "object"
         dict.insert(QStringLiteral("type"), QStringLiteral("null"));
-        break;
+        dict.insert(valueKey, QJsonValue::Null);
+        return 0;
     case QV4::Value::Boolean_Type:
-        dict.insert(QStringLiteral("type"), QStringLiteral("boolean"));
-        dict.insert(QStringLiteral("value"), value->booleanValue() ? QStringLiteral("true")
-                                                                   : QStringLiteral("false"));
-        break;
+        dict.insert(valueKey, value->booleanValue());
+        return 0;
     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));
+        if (const QV4::String *s = value->as<QV4::String>()) {
+            dict.insert(valueKey, s->toQString());
+        } else if (const QV4::ArrayObject *a = value->as<QV4::ArrayObject>()) {
+            // size of an array is number of its numerical properties; We don't consider free form
+            // object properties here.
+            dict.insert(valueKey, qint64(a->getLength()));
+            return a;
+        } else if (const QV4::Object *o = value->as<QV4::Object>()) {
+            int numProperties = 0;
+            QV4::ObjectIterator it(scope, o, QV4::ObjectIterator::EnumerableOnly);
+            QV4::PropertyAttributes attrs;
+            uint index;
+            QV4::ScopedProperty p(scope);
+            QV4::ScopedString name(scope);
+            while (true) {
+                it.next(name.getRef(), &index, p, &attrs);
+                if (attrs.isEmpty())
+                    break;
+                else
+                    ++numProperties;
+            }
+            dict.insert(valueKey, numProperties);
+            return o;
         } else {
             Q_UNREACHABLE();
         }
-        break;
+        return 0;
     case QV4::Value::Integer_Type:
-        dict.insert(QStringLiteral("type"), QStringLiteral("number"));
-        dict.insert(QStringLiteral("value"), value->integerValue());
-        break;
+        dict.insert(valueKey, value->integerValue());
+        return 0;
     default: // double
-        dict.insert(QStringLiteral("type"), QStringLiteral("number"));
-        dict.insert(QStringLiteral("value"), value->doubleValue());
-        break;
+        dict.insert(valueKey, value->doubleValue());
+        return 0;
     }
+}
+
+QJsonObject QV4DataCollector::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));
+
+    if (const QV4::Object *o = collectProperty(value, engine(), dict))
+        dict.insert(QStringLiteral("properties"), collectProperties(o));
 
     return dict;
 }
@@ -165,7 +193,6 @@ QV4DataCollector::Ref QV4DataCollector::addFunctionRef(const QString &functionNa
     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);
 
@@ -264,7 +291,7 @@ bool QV4DataCollector::lookupSpecialRef(Ref ref, QJsonObject *dict)
     return true;
 }
 
-QJsonArray QV4DataCollector::collectProperties(QV4::Object *object)
+QJsonArray QV4DataCollector::collectProperties(const QV4::Object *object)
 {
     QJsonArray res;
 
@@ -290,20 +317,14 @@ QJsonObject QV4DataCollector::collectAsJson(const QString &name, const QV4::Scop
     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()));
-        }
+    if (value->isManaged() && !value->isString()) {
+        Ref ref = addRef(value);
+        dict.insert(QStringLiteral("ref"), qint64(ref));
+        if (m_collectedRefs)
+            m_collectedRefs->append(ref);
     }
 
+    collectProperty(value, engine(), dict);
     return dict;
 }
 
index ebdde8f968518c7e7dec4a652b53161844edd87e..7d26d71bdf38991e962c7b1c31957bccd6b7dfa4 100644 (file)
@@ -72,7 +72,7 @@ private:
     QV4::ReturnedValue getValue(Ref ref);
     bool lookupSpecialRef(Ref ref, QJsonObject *dict);
 
-    QJsonArray collectProperties(QV4::Object *object);
+    QJsonArray collectProperties(const QV4::Object *object);
     QJsonObject collectAsJson(const QString &name, const QV4::ScopedValue &value);
     void collectArgumentsInContext();
 
index ca308a4f4912dc3d20606ae3195c7ccea9b600eb..7772d1623492f51ae7e4eb2fe6404d2aa8aba6e3 100644 (file)
@@ -564,10 +564,8 @@ void tst_qv4debugger::readObject()
     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);
+    QCOMPARE(b_head.value("type").toString(), QStringLiteral("number"));
+    QCOMPARE(b_head.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"));
@@ -580,16 +578,12 @@ void tst_qv4debugger::readObject()
     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"));
+    QCOMPARE(b_tail_head.value("type").toString(), QStringLiteral("string"));
+    QCOMPARE(b_tail_head.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"));
+    QCOMPARE(b_tail_tail.value("type").toString(), QStringLiteral("null"));
+    QVERIFY(b_tail_tail.value("value").isNull());
 }
 
 void tst_qv4debugger::readContextInAllFrames()