Fix crash caused by unregistered enum types
authorChris Adams <christopher.adams@nokia.com>
Fri, 25 May 2012 07:23:42 +0000 (17:23 +1000)
committerQt by Nokia <qt-info@nokia.com>
Mon, 28 May 2012 00:42:55 +0000 (02:42 +0200)
If the enum type isn't registered with Q_ENUMS, the metatype lookup
fails, which results in a crash in certain circumstances.
This commit ensures that when assigning an undefined value to an
unregistered-enum-type property, no crash occurs.

Change-Id: I0b539b591c9c9d6262c748300e4f4b6813d4f9a6
Reviewed-by: Martin Jones <martin.jones@nokia.com>

src/qml/qml/qqmlproperty.cpp
src/qml/qml/v8/qv8qobjectwrapper.cpp
tests/auto/qml/qqmlecmascript/data/enums.2.qml
tests/auto/qml/qqmlecmascript/testtypes.cpp
tests/auto/qml/qqmlecmascript/testtypes.h
tests/auto/qml/qqmlecmascript/tst_qqmlecmascript.cpp

index 5ef833c..b3f5d5f 100644 (file)
@@ -1566,7 +1566,12 @@ bool QQmlPropertyPrivate::writeBinding(QObject *object,
         }
         writeValueProperty(object, engine, core, QVariant::fromValue(v8engine->scriptValueFromInternal(result)), context, flags);
     } else if (isUndefined) {
-        expression->delayedError()->error.setDescription(QLatin1String("Unable to assign [undefined] to ") + QLatin1String(QMetaType::typeName(type)));
+        QString errorStr = QLatin1String("Unable to assign [undefined] to ");
+        if (!QMetaType::typeName(type))
+            errorStr += QLatin1String("[unknown property type]");
+        else
+            errorStr += QLatin1String(QMetaType::typeName(type));
+        expression->delayedError()->error.setDescription(errorStr);
         return false;
     } else if (result->IsFunction()) {
         if (!result->ToObject()->GetHiddenValue(v8engine->bindingFlagKey()).IsEmpty())
index 0c65663..600d98d 100644 (file)
@@ -596,8 +596,11 @@ static inline void StoreProperty(QV8Engine *engine, QObject *object, QQmlPropert
         if (value->ToObject()->GetHiddenValue(engine->bindingFlagKey()).IsEmpty()) {
             if (!property->isVarProperty() && property->propType != qMetaTypeId<QJSValue>()) {
                 // assigning a JS function to a non var or QJSValue property or is not allowed.
-                QString error = QLatin1String("Cannot assign JavaScript function to ") +
-                                QLatin1String(QMetaType::typeName(property->propType));
+                QString error = QLatin1String("Cannot assign JavaScript function to ");
+                if (!QMetaType::typeName(property->propType))
+                    error += QLatin1String("[unknown property type]");
+                else
+                    error += QLatin1String(QMetaType::typeName(property->propType));
                 v8::ThrowException(v8::Exception::Error(engine->toString(error)));
                 return;
             }
@@ -653,8 +656,11 @@ static inline void StoreProperty(QV8Engine *engine, QObject *object, QQmlPropert
     } else if (!newBinding && property->propType == qMetaTypeId<QJSValue>()) {
         PROPERTY_STORE(QJSValue, engine->scriptValueFromInternal(value));
     } else if (value->IsUndefined()) {
-        QString error = QLatin1String("Cannot assign [undefined] to ") +
-                        QLatin1String(QMetaType::typeName(property->propType));
+        QString error = QLatin1String("Cannot assign [undefined] to ");
+        if (!QMetaType::typeName(property->propType))
+            error += QLatin1String("[unknown property type]");
+        else
+            error += QLatin1String(QMetaType::typeName(property->propType));
         v8::ThrowException(v8::Exception::Error(engine->toString(error)));
     } else if (value->IsFunction()) {
         // this is handled by the binding creation above
index bdc672f..ae8efc4 100644 (file)
@@ -4,5 +4,22 @@ import Qt.test 1.0 as Namespace
 MyQmlObject {
     property int a: MyQmlObject.EnumValue10
     property int b: Namespace.MyQmlObject.EnumValue10
+    property int c: child.enumProperty
+
+    // test binding codepath
+    property MyUnregisteredEnumTypeObject child: MyUnregisteredEnumTypeObject {
+        enumProperty: undefined
+    }
+
+    // test assignment codepaths
+    function testAssignmentOne() {
+        child.enumProperty = function() { return "Hello, world!" };   // cannot assign function to non-var prop.
+    }
+    function testAssignmentTwo() {
+        child.enumProperty = MyUnregisteredEnumTypeObject.Firstvalue; // note: incorrect capitalisation
+    }
+    function testAssignmentThree() {
+        child.enumProperty = undefined;                               // directly set undefined value
+    }
 }
 
index d3eff3b..fba6cb3 100644 (file)
@@ -226,6 +226,8 @@ void registerTypes()
     qmlRegisterType<WriteCounter>("Qt.test", 1, 0, "WriteCounter");
 
     qmlRegisterType<MySequenceConversionObject>("Qt.test", 1, 0, "MySequenceConversionObject");
+
+    qmlRegisterType<MyUnregisteredEnumTypeObject>("Qt.test", 1, 0, "MyUnregisteredEnumTypeObject");
 }
 
 #include "testtypes.moc"
index d02f982..8e2b68f 100644 (file)
@@ -1412,6 +1412,27 @@ Q_SIGNALS:
     void done(const QString &result);
 };
 
+class MyUnregisteredEnumTypeObject : public QObject
+{
+    Q_OBJECT
+    Q_PROPERTY(MyEnum enumProperty READ enumProperty WRITE setEnumProperty)
+
+public:
+    MyUnregisteredEnumTypeObject() : QObject(), m_ev(FirstValue) {}
+    ~MyUnregisteredEnumTypeObject() {}
+
+    enum MyEnum {
+        FirstValue = 1,
+        SecondValue = 2
+    };
+
+    MyEnum enumProperty() const { return m_ev; }
+    void setEnumProperty(MyEnum v) { m_ev = v; }
+
+private:
+    MyEnum m_ev;
+};
+
 void registerTypes();
 
 #endif // TESTTYPES_H
index e1a59ed..a460206 100644 (file)
@@ -923,15 +923,36 @@ void tst_qqmlecmascript::enums()
     {
     QQmlComponent component(&engine, testFileUrl("enums.2.qml"));
 
-    QString warning1 = component.url().toString() + ":5: Unable to assign [undefined] to int";
-    QString warning2 = component.url().toString() + ":6: Unable to assign [undefined] to int";
-    QTest::ignoreMessage(QtWarningMsg, qPrintable(warning1));
-    QTest::ignoreMessage(QtWarningMsg, qPrintable(warning2));
+    QString w1 = QLatin1String("QMetaProperty::read: Unable to handle unregistered datatype 'MyEnum' for property 'MyUnregisteredEnumTypeObject::enumProperty'");
+    QString w2 = QLatin1String("QQmlExpression: Expression ") + testFileUrl("enums.2.qml").toString() + QLatin1String(":7 depends on non-NOTIFYable properties:");
+    QString w3 = QLatin1String("    MyUnregisteredEnumTypeObject::enumProperty");
+    QString w4 = component.url().toString() + ":5: Unable to assign [undefined] to int";
+    QString w5 = component.url().toString() + ":6: Unable to assign [undefined] to int";
+    QString w6 = component.url().toString() + ":7: Unable to assign [undefined] to int";
+    QString w7 = component.url().toString() + ":11: Unable to assign [undefined] to [unknown property type]";
+    QString w8 = component.url().toString() + ":16: Error: Cannot assign JavaScript function to [unknown property type]";
+    QString w9 = component.url().toString() + ":19: Error: Cannot assign [undefined] to [unknown property type]";
+    QString w10 = component.url().toString() + ":22: Error: Cannot assign [undefined] to [unknown property type]";
+    QTest::ignoreMessage(QtWarningMsg, qPrintable(w1));
+    QTest::ignoreMessage(QtWarningMsg, qPrintable(w2));
+    QTest::ignoreMessage(QtWarningMsg, qPrintable(w3));
+    QTest::ignoreMessage(QtWarningMsg, qPrintable(w4));
+    QTest::ignoreMessage(QtWarningMsg, qPrintable(w5));
+    QTest::ignoreMessage(QtWarningMsg, qPrintable(w6));
+    QTest::ignoreMessage(QtWarningMsg, qPrintable(w7));
+    QTest::ignoreMessage(QtWarningMsg, qPrintable(w8));
+    QTest::ignoreMessage(QtWarningMsg, qPrintable(w9));
+    QTest::ignoreMessage(QtWarningMsg, qPrintable(w10));
 
     QObject *object = component.create();
     QVERIFY(object != 0);
     QCOMPARE(object->property("a").toInt(), 0);
     QCOMPARE(object->property("b").toInt(), 0);
+    QCOMPARE(object->property("c").toInt(), 0);
+
+    QMetaObject::invokeMethod(object, "testAssignmentOne");
+    QMetaObject::invokeMethod(object, "testAssignmentTwo");
+    QMetaObject::invokeMethod(object, "testAssignmentThree");
 
     delete object;
     }