Bring qmetaobjectbuilder in sync with moc
authorKent Hansen <kent.hansen@nokia.com>
Thu, 2 Feb 2012 13:20:24 +0000 (14:20 +0100)
committerQt by Nokia <qt-info@nokia.com>
Fri, 3 Feb 2012 14:09:03 +0000 (15:09 +0100)
qmetaobjectbuilder should generate meta-objects of the same version
as moc; in the future, when the moc version is bumped, QMOB has to
be adapted at the same time.

QMOB was generating version 4 meta-objects. This patch makes it
generate version 6 (the current version). This also fixes a bug with
using qt_static_metacall with QMOB (setStaticMetacallFunction()); it
was already using the version 6 qt_static_metacall signature, which
isn't compatible with version 4.

Also add tests that ensure that the QMOB-generated meta-object works
with real objects; in particular we want to test the codepaths in Qt
that check for version >= 4.

Change-Id: I64a151ea5c947a6f8b7a00e85a39866446c735e9
Reviewed-by: Bradley T. Hughes <bradley.hughes@nokia.com>
src/corelib/kernel/qmetaobject_p.h
src/corelib/kernel/qmetaobjectbuilder.cpp
src/tools/moc/generator.cpp
tests/auto/corelib/kernel/qmetaobjectbuilder/tst_qmetaobjectbuilder.cpp

index 9f87a2a..5fc0555 100644 (file)
@@ -109,6 +109,8 @@ class QMutex;
 
 struct QMetaObjectPrivate
 {
+    enum { OutputRevision = 6 }; // Used by moc and qmetaobjectbuilder
+
     int revision;
     int className;
     int classInfoCount, classInfoData;
index 61ffe74..529ca22 100644 (file)
@@ -1136,7 +1136,8 @@ static int buildMetaObject(QMetaObjectBuilderPrivate *d, char *buf,
         }
     }
     if (buf) {
-        pmeta->revision = 4;
+        Q_STATIC_ASSERT_X(QMetaObjectPrivate::OutputRevision == 6, "QMetaObjectBuilder should generate the same version as moc");
+        pmeta->revision = QMetaObjectPrivate::OutputRevision;
         pmeta->flags = d->flags;
         pmeta->className = 0;   // Class name is always the first string.
         //pmeta->signalCount is handled in the "output method loop" as an optimization.
index 3595719..b4f3d23 100644 (file)
@@ -162,7 +162,7 @@ void Generator::generateCode()
     int index = 14;
     fprintf(out, "static const uint qt_meta_data_%s[] = {\n", qualifiedClassNameIdentifier.constData());
     fprintf(out, "\n // content:\n");
-    fprintf(out, "    %4d,       // revision\n", 6);
+    fprintf(out, "    %4d,       // revision\n", int(QMetaObjectPrivate::OutputRevision));
     fprintf(out, "    %4d,       // classname\n", strreg(cdef->qualified));
     fprintf(out, "    %4d, %4d, // classinfo\n", cdef->classInfoList.count(), cdef->classInfoList.count() ? index : 0);
     index += cdef->classInfoList.count() * 2;
index 450d427..def1b74 100644 (file)
@@ -47,7 +47,6 @@ class tst_QMetaObjectBuilder : public QObject
 {
     Q_OBJECT
 private slots:
-    void mocVersionCheck();
     void create();
     void className();
     void superClass();
@@ -67,6 +66,14 @@ private slots:
     void serialize();
     void removeNotifySignal();
 
+    void usage_signal();
+    void usage_property();
+    void usage_slot();
+    void usage_method();
+    void usage_constructor();
+    void usage_connect();
+    void usage_templateConnect();
+
 private:
     static bool checkForSideEffects
         (const QMetaObjectBuilder& builder,
@@ -130,18 +137,6 @@ signals:
     void propChanged(const QString&);
 };
 
-void tst_QMetaObjectBuilder::mocVersionCheck()
-{
-    // This test will fail when the moc version number is changed.
-    // It is intended as a reminder to also update QMetaObjectBuilder
-    // whenenver moc changes.  Once QMetaObjectBuilder has been
-    // updated, this test can be changed to check for the next version.
-    int version = int(QObject::staticMetaObject.d.data[0]);
-    QVERIFY(version == 4 || version == 5 || version == 6);
-    version = int(staticMetaObject.d.data[0]);
-    QVERIFY(version == 4 || version == 5 || version == 6);
-}
-
 void tst_QMetaObjectBuilder::create()
 {
     QMetaObjectBuilder builder;
@@ -1274,6 +1269,353 @@ bool tst_QMetaObjectBuilder::sameMetaObject
     return true;
 }
 
+
+// This class is used to test that the meta-object generated by QMOB can be
+// used by a real object.
+// The class manually implements the functions normally generated by moc, and
+// creates the corresponding meta-object using QMOB. The autotests check that
+// this object can be used by QObject/QMetaObject functionality (property
+// access, signals & slots, constructing instances, ...).
+
+class TestObject : public QObject
+{
+    // Manually expanded from Q_OBJECT macro
+public:
+    Q_OBJECT_CHECK
+    virtual const QMetaObject *metaObject() const;
+    virtual void *qt_metacast(const char *);
+    virtual int qt_metacall(QMetaObject::Call, int, void **);
+private:
+    Q_DECL_HIDDEN static const QMetaObjectExtraData staticMetaObjectExtraData;
+    Q_DECL_HIDDEN static void qt_static_metacall(QObject *, QMetaObject::Call, int, void **);
+
+    //Q_PROPERTY(int intProp READ intProp WRITE setIntProp NOTIFY intPropChanged)
+public:
+    TestObject(QObject *parent = 0); // Q_INVOKABLE
+    ~TestObject();
+
+    // Property accessors
+    int intProp() const;
+    void setIntProp(int v);
+
+    void emitIntPropChanged();
+
+    int voidSlotIntArgument() const;
+
+// Q_INVOKABLE
+    QVariantList listInvokableQRealQString(qreal, const QString &);
+
+//public Q_SLOTS:
+    void voidSlotInt(int);
+
+//Q_SIGNALS:
+    void intPropChanged(int);
+
+private:
+    static QMetaObject *buildMetaObject();
+
+    QMetaObject *m_metaObject;
+    int m_intProp;
+    int m_voidSlotIntArg;
+};
+
+const QMetaObjectExtraData TestObject::staticMetaObjectExtraData = {
+    0,  qt_static_metacall
+};
+
+TestObject::TestObject(QObject *parent)
+    : QObject(parent), m_metaObject(buildMetaObject()),
+      m_intProp(-1), m_voidSlotIntArg(-1)
+{
+}
+
+TestObject::~TestObject()
+{
+    qFree(m_metaObject);
+}
+
+QMetaObject *TestObject::buildMetaObject()
+{
+    QMetaObjectBuilder builder;
+    // NOTE: If you change the meta-object, remember to adapt qt_metacall and
+    // friends below accordingly.
+
+    builder.setClassName("TestObject");
+
+    builder.setStaticMetacallFunction(qt_static_metacall);
+
+    QMetaMethodBuilder intPropChanged = builder.addSignal("intPropChanged(int)");
+    intPropChanged.setParameterNames(QList<QByteArray>() << "newIntPropValue");
+
+    QMetaPropertyBuilder prop = builder.addProperty("intProp", "int");
+    prop.setNotifySignal(intPropChanged);
+
+    QMetaMethodBuilder voidSlotInt = builder.addSlot("voidSlotInt(int)");
+    voidSlotInt.setParameterNames(QList<QByteArray>() << "slotIntArg");
+
+    QMetaMethodBuilder listInvokableQRealQString = builder.addMethod("listInvokableQRealQString(qreal,QString)");
+    listInvokableQRealQString.setReturnType("QVariantList");
+    listInvokableQRealQString.setParameterNames(QList<QByteArray>() << "qrealArg" << "qstringArg");
+
+    builder.addConstructor("TestObject(QObject*)");
+    builder.addConstructor("TestObject()");
+
+    return builder.toMetaObject();
+}
+
+int TestObject::intProp() const
+{
+    return m_intProp;
+}
+
+void TestObject::setIntProp(int value)
+{
+    if (m_intProp != value) {
+        m_intProp = value;
+        emit intPropChanged(value);
+    }
+}
+
+void TestObject::emitIntPropChanged()
+{
+    emit intPropChanged(m_intProp);
+}
+
+QVariantList TestObject::listInvokableQRealQString(qreal r, const QString &s)
+{
+    return QVariantList() << r << s;
+}
+
+void TestObject::voidSlotInt(int value)
+{
+    m_voidSlotIntArg = value;
+}
+
+int TestObject::voidSlotIntArgument() const
+{
+    return m_voidSlotIntArg;
+}
+
+void TestObject::qt_static_metacall(QObject *_o, QMetaObject::Call _c, int _id, void **_a)
+{
+    if (_c == QMetaObject::CreateInstance) {
+        switch (_id) {
+        case 0: { TestObject *_r = new TestObject((*reinterpret_cast< QObject*(*)>(_a[1])));
+            if (_a[0]) *reinterpret_cast<QObject**>(_a[0]) = _r; } break;
+        case 1: { TestObject *_r = new TestObject();
+            if (_a[0]) *reinterpret_cast<QObject**>(_a[0]) = _r; } break;
+        default: {
+            QMetaMethod ctor = _o->metaObject()->constructor(_id);
+            qFatal("You forgot to add a case for CreateInstance %s", ctor.signature());
+          }
+        }
+    } else if (_c == QMetaObject::InvokeMetaMethod) {
+        Q_ASSERT(_o->metaObject()->cast(_o));
+        TestObject *_t = static_cast<TestObject *>(_o);
+        switch (_id) {
+        case 0: _t->intPropChanged((*reinterpret_cast< int(*)>(_a[1]))); break;
+        case 1: _t->voidSlotInt((*reinterpret_cast< int(*)>(_a[1]))); break;
+        case 2: *reinterpret_cast<QVariantList(*)>(_a[0]) = _t->listInvokableQRealQString(*reinterpret_cast<qreal(*)>(_a[1]), *reinterpret_cast<QString(*)>(_a[2])); break;
+        default: {
+            QMetaMethod method = _o->metaObject()->method(_o->metaObject()->methodOffset() + _id);
+            qFatal("You forgot to add a case for InvokeMetaMethod %s", method.signature());
+          }
+        }
+    } else if (_c == QMetaObject::IndexOfMethod) {
+        // This code is currently unreachable because it's only used by the
+        // template-based versions of connect() and disconnect(), which don't
+        // work with dynamically generated meta-objects (see test).
+        int *result = reinterpret_cast<int *>(_a[0]);
+        void **func = reinterpret_cast<void **>(_a[1]);
+        {
+            typedef void (TestObject::*_t)(int );
+            if (*reinterpret_cast<_t *>(func) == static_cast<_t>(&TestObject::intPropChanged)) {
+                *result = 0;
+            }
+        }
+        {
+            typedef void (TestObject::*_t)(int );
+            if (*reinterpret_cast<_t *>(func) == static_cast<_t>(&TestObject::voidSlotInt)) {
+                *result = 1;
+            }
+        }
+        {
+            typedef QVariantList (TestObject::*_t)(qreal, const QString &);
+            if (*reinterpret_cast<_t *>(func) == static_cast<_t>(&TestObject::listInvokableQRealQString)) {
+                *result = 2;
+            }
+        }
+        qFatal("You forgot to add one or more IndexOfMethod cases");
+    }
+}
+
+const QMetaObject *TestObject::metaObject() const
+{
+    return m_metaObject;
+}
+
+void *TestObject::qt_metacast(const char *_clname)
+{
+    if (!_clname) return 0;
+    if (!strcmp(_clname, "TestObject"))
+        return static_cast<void*>(const_cast< TestObject*>(this));
+    return QObject::qt_metacast(_clname);
+}
+
+int TestObject::qt_metacall(QMetaObject::Call _c, int _id, void **_a)
+{
+    _id = QObject::qt_metacall(_c, _id, _a);
+    if (_id < 0)
+        return _id;
+    int ownMethodCount = m_metaObject->methodCount() - m_metaObject->methodOffset();
+    int ownPropertyCount = m_metaObject->propertyCount() - m_metaObject->propertyOffset();
+    if (_c == QMetaObject::InvokeMetaMethod) {
+        if (_id < ownMethodCount)
+            qt_static_metacall(this, _c, _id, _a);
+        _id -= ownMethodCount;
+    }
+#ifndef QT_NO_PROPERTIES
+      else if (_c == QMetaObject::ReadProperty) {
+        void *_v = _a[0];
+        switch (_id) {
+        case 0: *reinterpret_cast< int*>(_v) = intProp(); break;
+        default: if (_id < ownPropertyCount) {
+            QMetaProperty prop = m_metaObject->property(m_metaObject->propertyOffset() + _id);
+            qFatal("You forgot to add a case for ReadProperty %s", prop.name());
+          }
+        }
+        _id -= ownPropertyCount;
+    } else if (_c == QMetaObject::WriteProperty) {
+        void *_v = _a[0];
+        switch (_id) {
+        case 0: setIntProp(*reinterpret_cast< int*>(_v)); break;
+        default: if (_id < ownPropertyCount) {
+            QMetaProperty prop = m_metaObject->property(m_metaObject->propertyOffset() + _id);
+            qFatal("You forgot to add a case for WriteProperty %s", prop.name());
+          }
+        }
+        _id -= ownPropertyCount;
+    } else if (_c == QMetaObject::ResetProperty) {
+        _id -= ownPropertyCount;
+    } else if (_c == QMetaObject::QueryPropertyDesignable) {
+        _id -= ownPropertyCount;
+    } else if (_c == QMetaObject::QueryPropertyScriptable) {
+        _id -= ownPropertyCount;
+    } else if (_c == QMetaObject::QueryPropertyStored) {
+        _id -= ownPropertyCount;
+    } else if (_c == QMetaObject::QueryPropertyEditable) {
+        _id -= ownPropertyCount;
+    } else if (_c == QMetaObject::QueryPropertyUser) {
+        _id -= ownPropertyCount;
+    }
+#endif // QT_NO_PROPERTIES
+    return _id;
+}
+
+// SIGNAL 0
+void TestObject::intPropChanged(int _t1)
+{
+    void *_a[] = { 0, const_cast<void*>(reinterpret_cast<const void*>(&_t1)) };
+    QMetaObject::activate(this, m_metaObject, 0, _a);
+}
+
+
+void tst_QMetaObjectBuilder::usage_signal()
+{
+    QScopedPointer<TestObject> testObject(new TestObject);
+
+    QSignalSpy propChangedSpy(testObject.data(), SIGNAL(intPropChanged(int)));
+    testObject->emitIntPropChanged();
+    QCOMPARE(propChangedSpy.count(), 1);
+    QCOMPARE(propChangedSpy.at(0).count(), 1);
+    QCOMPARE(propChangedSpy.at(0).at(0).toInt(), testObject->intProp());
+}
+
+void tst_QMetaObjectBuilder::usage_property()
+{
+    QScopedPointer<TestObject> testObject(new TestObject);
+
+    QVariant prop = testObject->property("intProp");
+    QCOMPARE(prop.type(), QVariant::Int);
+    QCOMPARE(prop.toInt(), testObject->intProp());
+
+    QSignalSpy propChangedSpy(testObject.data(), SIGNAL(intPropChanged(int)));
+    QVERIFY(testObject->intProp() != 123);
+    testObject->setProperty("intProp", 123);
+    QCOMPARE(propChangedSpy.count(), 1);
+    prop = testObject->property("intProp");
+    QCOMPARE(prop.type(), QVariant::Int);
+    QCOMPARE(prop.toInt(), 123);
+}
+
+void tst_QMetaObjectBuilder::usage_slot()
+{
+    QScopedPointer<TestObject> testObject(new TestObject);
+
+    int index = testObject->metaObject()->indexOfMethod("voidSlotInt(int)");
+    QVERIFY(index != -1);
+    QMetaMethod voidSlotInt = testObject->metaObject()->method(index);
+
+    QVERIFY(testObject->voidSlotIntArgument() == -1);
+    QVERIFY(voidSlotInt.invoke(testObject.data(), Q_ARG(int, 123)));
+    QCOMPARE(testObject->voidSlotIntArgument(), 123);
+}
+
+void tst_QMetaObjectBuilder::usage_method()
+{
+    QScopedPointer<TestObject> testObject(new TestObject);
+
+    int index = testObject->metaObject()->indexOfMethod("listInvokableQRealQString(qreal,QString)");
+    QVERIFY(index != -1);
+    QMetaMethod listInvokableQRealQString = testObject->metaObject()->method(index);
+    QVariantList list;
+    QVERIFY(listInvokableQRealQString.invoke(testObject.data(), Q_RETURN_ARG(QVariantList, list),
+                                             Q_ARG(qreal, 123.0), Q_ARG(QString, "ciao")));
+    QCOMPARE(list.size(), 2);
+    QCOMPARE(list.at(0).type(), QVariant::Type(QMetaType::QReal));
+    QCOMPARE(list.at(0).toDouble(), double(123));
+    QCOMPARE(list.at(1).type(), QVariant::String);
+    QCOMPARE(list.at(1).toString(), QString::fromLatin1("ciao"));
+}
+
+void tst_QMetaObjectBuilder::usage_constructor()
+{
+    QScopedPointer<TestObject> testObject(new TestObject);
+
+    QCOMPARE(testObject->metaObject()->constructorCount(), 2);
+    QScopedPointer<QObject> testInstance(testObject->metaObject()->newInstance());
+    QVERIFY(testInstance != 0);
+    QScopedPointer<QObject> testInstance2(testObject->metaObject()->newInstance(Q_ARG(QObject*, testInstance.data())));
+    QVERIFY(testInstance2 != 0);
+    QCOMPARE(testInstance2->parent(), testInstance.data());
+}
+
+void tst_QMetaObjectBuilder::usage_connect()
+{
+    QScopedPointer<TestObject> testObject(new TestObject);
+
+    QVERIFY(QObject::connect(testObject.data(), SIGNAL(intPropChanged(int)),
+                             testObject.data(), SLOT(voidSlotInt(int))));
+
+    QVERIFY(testObject->voidSlotIntArgument() == -1);
+    testObject->setProperty("intProp", 123);
+    QCOMPARE(testObject->voidSlotIntArgument(), 123);
+
+    QVERIFY(QObject::disconnect(testObject.data(), SIGNAL(intPropChanged(int)),
+                                testObject.data(), SLOT(voidSlotInt(int))));
+}
+
+void tst_QMetaObjectBuilder::usage_templateConnect()
+{
+    QScopedPointer<TestObject> testObject(new TestObject);
+
+    QTest::ignoreMessage(QtWarningMsg, "QObject::connect: signal not found in QObject");
+    QMetaObject::Connection con = QObject::connect(testObject.data(), &TestObject::intPropChanged,
+                                                   testObject.data(), &TestObject::voidSlotInt);
+    QEXPECT_FAIL("", "template-based connect() fails because meta-object is deduced at compile-time", Abort);
+    QVERIFY(con);
+}
+
 QTEST_MAIN(tst_QMetaObjectBuilder)
 
 #include "tst_qmetaobjectbuilder.moc"