diff --git a/src/corelib/kernel/qmetatype.cpp b/src/corelib/kernel/qmetatype.cpp index 29e57c659d3..3f47f03f42a 100644 --- a/src/corelib/kernel/qmetatype.cpp +++ b/src/corelib/kernel/qmetatype.cpp @@ -1385,6 +1385,191 @@ int QMetaType::type(const QT_PREPEND_NAMESPACE(QByteArray) &typeName) } #ifndef QT_NO_DATASTREAM + +namespace +{ + +template +class HasStreamOperator +{ + struct Yes { char unused[1]; }; + struct No { char unused[2]; }; + Q_STATIC_ASSERT(sizeof(Yes) != sizeof(No)); + + template static decltype(std::declval().operator>>(std::declval()), Yes()) load(int); + template static decltype(operator>>(std::declval(), std::declval()), Yes()) load(int); + template static No load(...); + template static decltype(operator<<(std::declval(), std::declval()), Yes()) saveFunction(int); + template static decltype(std::declval().operator<<(std::declval()), Yes()) saveMethod(int); + template static No saveMethod(...); + template static No saveFunction(...); + static constexpr bool LoadValue = QtMetaTypePrivate::TypeDefinition::IsAvailable && (sizeof(load(0)) == sizeof(Yes)); + static constexpr bool SaveValue = QtMetaTypePrivate::TypeDefinition::IsAvailable && + ((sizeof(saveMethod(0)) == sizeof(Yes)) || (sizeof(saveFunction(0)) == sizeof(Yes))); +public: + static constexpr bool Value = LoadValue && SaveValue; +}; + +// Quick sanity checks +Q_STATIC_ASSERT(HasStreamOperator::Value); +Q_STATIC_ASSERT(!HasStreamOperator::Value); +Q_STATIC_ASSERT(HasStreamOperator::Value); + +template::IsAccepted && HasStreamOperator::Value> +struct FilteredOperatorSwitch +{ + static bool load(QDataStream &stream, T *data, int) + { + stream >> *data; + return true; + } + static bool save(QDataStream &stream, const T *data, int) + { + stream << *data; + return true; + } +}; +template +struct FilteredOperatorSwitch +{ + static const QMetaTypeInterface* getMetaTypeInterface(int type) + { + if (QModulesPrivate::QTypeModuleInfo::IsGui && qMetaTypeGuiHelper) + return &qMetaTypeGuiHelper[type - QMetaType::FirstGuiType]; + else if (QModulesPrivate::QTypeModuleInfo::IsWidget && qMetaTypeWidgetsHelper) + return &qMetaTypeWidgetsHelper[type - QMetaType::FirstWidgetsType]; + return nullptr; + } + static bool save(QDataStream &stream, const T *data, int type) + { + if (auto interface = getMetaTypeInterface(type)) { + interface->saveOp(stream, data); + return true; + } + return false; + } + static bool load(QDataStream &stream, T *data, int type) + { + if (auto interface = getMetaTypeInterface(type)) { + interface->loadOp(stream, data); + return true; + } + return false; + } +}; + +class SaveOperatorSwitch +{ +public: + QDataStream &stream; + int m_type; + + template + bool delegate(const T *data) + { + return FilteredOperatorSwitch::save(stream, data, m_type); + } + bool delegate(const char *data) + { + // force a char to be signed + stream << qint8(*data); + return true; + } + bool delegate(const long *data) + { + stream << qlonglong(*data); + return true; + } + bool delegate(const unsigned long *data) + { + stream << qulonglong(*data); + return true; + } + bool delegate(const QCborSimpleType *data) + { + // TODO just define a normal QDataStream operator + stream << quint8(*data); + return true; + } + bool delegate(const QMetaTypeSwitcher::NotBuiltinType *data) + { + const QVector * const ct = customTypes(); + if (!ct) + return false; + QMetaType::SaveOperator saveOp = nullptr; + { + QReadLocker locker(customTypesLock()); + saveOp = ct->at(m_type - QMetaType::User).saveOp; + } + if (!saveOp) + return false; + saveOp(stream, data); + return true; + } + bool delegate(const void*) { return false; } + bool delegate(const QMetaTypeSwitcher::UnknownType*) { return false; } +}; +class LoadOperatorSwitch +{ +public: + QDataStream &stream; + int m_type; + + template + bool delegate(const T *data) + { + return FilteredOperatorSwitch::load(stream, const_cast(data), m_type); + } + bool delegate(const char *data) + { + // force a char to be signed + qint8 c; + stream >> c; + *const_cast(data) = c; + return true; + } + bool delegate(const long *data) + { + qlonglong l; + stream >> l; + *const_cast(data) = l; + return true; + } + bool delegate(const unsigned long *data) + { + qlonglong l; + stream >> l; + *const_cast(data) = l; + return true; + } + bool delegate(const QCborSimpleType *data) + { + // TODO just define a normal QDataStream operator + quint8 l; + stream >> l; + *const_cast(data) = QCborSimpleType(l); + return true; + } + bool delegate(const QMetaTypeSwitcher::NotBuiltinType *data) + { + const QVector * const ct = customTypes(); + if (!ct) + return false; + QMetaType::LoadOperator loadOp = nullptr; + { + QReadLocker locker(customTypesLock()); + loadOp = ct->at(m_type - QMetaType::User).loadOp; + } + if (!loadOp) + return false; + loadOp(stream, const_cast(data)); + return true; + } + bool delegate(const void*) { return false; } + bool delegate(const QMetaTypeSwitcher::UnknownType*) { return false; } +}; +} // namespace + /*! Writes the object pointed to by \a data with the ID \a type to the given \a stream. Returns \c true if the object is saved @@ -1401,222 +1586,10 @@ int QMetaType::type(const QT_PREPEND_NAMESPACE(QByteArray) &typeName) */ bool QMetaType::save(QDataStream &stream, int type, const void *data) { - if (!data || !isRegistered(type)) + if (!data) return false; - - switch(type) { - case QMetaType::UnknownType: - case QMetaType::Void: - case QMetaType::VoidStar: - case QMetaType::QObjectStar: -#if QT_CONFIG(itemmodel) - case QMetaType::QModelIndex: - case QMetaType::QPersistentModelIndex: -#endif - case QMetaType::QJsonValue: - case QMetaType::QJsonObject: - case QMetaType::QJsonArray: - case QMetaType::QCborValue: - case QMetaType::QCborArray: - case QMetaType::QCborMap: - return false; - case QMetaType::Nullptr: - stream << *static_cast(data); - return true; - case QMetaType::Long: - stream << qlonglong(*static_cast(data)); - break; - case QMetaType::Int: - stream << *static_cast(data); - break; - case QMetaType::Short: - stream << *static_cast(data); - break; - case QMetaType::Char: - // force a char to be signed - stream << *static_cast(data); - break; - case QMetaType::ULong: - stream << qulonglong(*static_cast(data)); - break; - case QMetaType::UInt: - stream << *static_cast(data); - break; - case QMetaType::LongLong: - stream << *static_cast(data); - break; - case QMetaType::ULongLong: - stream << *static_cast(data); - break; - case QMetaType::UShort: - stream << *static_cast(data); - break; - case QMetaType::SChar: - stream << *static_cast(data); - break; - case QMetaType::UChar: - stream << *static_cast(data); - break; - case QMetaType::Bool: - stream << qint8(*static_cast(data)); - break; - case QMetaType::Float: - stream << *static_cast(data); - break; - case QMetaType::Double: - stream << *static_cast(data); - break; - case QMetaType::QChar: - stream << *static_cast(data); - break; -#ifndef QT_BOOTSTRAPPED - case QMetaType::QVariantMap: - stream << *static_cast(data); - break; - case QMetaType::QVariantHash: - stream << *static_cast(data); - break; - case QMetaType::QVariantList: - stream << *static_cast(data); - break; - case QMetaType::QVariant: - stream << *static_cast(data); - break; - case QMetaType::QByteArrayList: - stream << *static_cast(data); - break; -#endif - case QMetaType::QByteArray: - stream << *static_cast(data); - break; - case QMetaType::QString: - stream << *static_cast(data); - break; - case QMetaType::QStringList: - stream << *static_cast(data); - break; -#ifndef QT_BOOTSTRAPPED - case QMetaType::QBitArray: - stream << *static_cast(data); - break; -#endif - case QMetaType::QDate: - stream << *static_cast(data); - break; - case QMetaType::QTime: - stream << *static_cast(data); - break; - case QMetaType::QDateTime: - stream << *static_cast(data); - break; -#ifndef QT_BOOTSTRAPPED - case QMetaType::QUrl: - stream << *static_cast(data); - break; -#endif - case QMetaType::QLocale: - stream << *static_cast(data); - break; -#ifndef QT_NO_GEOM_VARIANT - case QMetaType::QRect: - stream << *static_cast(data); - break; - case QMetaType::QRectF: - stream << *static_cast(data); - break; - case QMetaType::QSize: - stream << *static_cast(data); - break; - case QMetaType::QSizeF: - stream << *static_cast(data); - break; - case QMetaType::QLine: - stream << *static_cast(data); - break; - case QMetaType::QLineF: - stream << *static_cast(data); - break; - case QMetaType::QPoint: - stream << *static_cast(data); - break; - case QMetaType::QPointF: - stream << *static_cast(data); - break; -#endif -#ifndef QT_NO_REGEXP - case QMetaType::QRegExp: - stream << *static_cast(data); - break; -#endif -#if QT_CONFIG(regularexpression) - case QMetaType::QRegularExpression: - stream << *static_cast(data); - break; -#endif // QT_CONFIG(regularexpression) -#ifndef QT_BOOTSTRAPPED - case QMetaType::QEasingCurve: - stream << *static_cast(data); - break; - case QMetaType::QCborSimpleType: - stream << *static_cast(data); - break; - case QMetaType::QJsonDocument: - stream << *static_cast(data); - break; -#endif // QT_BOOTSTRAPPED - case QMetaType::QFont: - case QMetaType::QPixmap: - case QMetaType::QBrush: - case QMetaType::QColor: - case QMetaType::QPalette: - case QMetaType::QImage: - case QMetaType::QPolygon: - case QMetaType::QPolygonF: - case QMetaType::QRegion: - case QMetaType::QBitmap: - case QMetaType::QCursor: - case QMetaType::QKeySequence: - case QMetaType::QPen: - case QMetaType::QTextLength: - case QMetaType::QTextFormat: - case QMetaType::QMatrix: - case QMetaType::QTransform: - case QMetaType::QMatrix4x4: - case QMetaType::QVector2D: - case QMetaType::QVector3D: - case QMetaType::QVector4D: - case QMetaType::QQuaternion: - case QMetaType::QIcon: - if (!qMetaTypeGuiHelper) - return false; - qMetaTypeGuiHelper[type - FirstGuiType].saveOp(stream, data); - break; - case QMetaType::QSizePolicy: - if (!qMetaTypeWidgetsHelper) - return false; - qMetaTypeWidgetsHelper[type - FirstWidgetsType].saveOp(stream, data); - break; - case QMetaType::QUuid: - stream << *static_cast(data); - break; - default: { - const QVector * const ct = customTypes(); - if (!ct) - return false; - - SaveOperator saveOp = 0; - { - QReadLocker locker(customTypesLock()); - saveOp = ct->at(type - User).saveOp; - } - - if (!saveOp) - return false; - saveOp(stream, data); - break; } - } - - return true; + SaveOperatorSwitch saveOp{stream, type}; + return QMetaTypeSwitcher::switcher(saveOp, type, data); } /*! @@ -1635,227 +1608,10 @@ bool QMetaType::save(QDataStream &stream, int type, const void *data) */ bool QMetaType::load(QDataStream &stream, int type, void *data) { - if (!data || !isRegistered(type)) + if (!data) return false; - - switch(type) { - case QMetaType::UnknownType: - case QMetaType::Void: - case QMetaType::VoidStar: - case QMetaType::QObjectStar: -#if QT_CONFIG(itemmodel) - case QMetaType::QModelIndex: - case QMetaType::QPersistentModelIndex: -#endif - case QMetaType::QJsonValue: - case QMetaType::QJsonObject: - case QMetaType::QJsonArray: - case QMetaType::QCborValue: - case QMetaType::QCborArray: - case QMetaType::QCborMap: - return false; - case QMetaType::Nullptr: - stream >> *static_cast(data); - return true; - case QMetaType::Long: { - qlonglong l; - stream >> l; - *static_cast(data) = long(l); - break; } - case QMetaType::Int: - stream >> *static_cast(data); - break; - case QMetaType::Short: - stream >> *static_cast(data); - break; - case QMetaType::Char: - // force a char to be signed - stream >> *static_cast(data); - break; - case QMetaType::ULong: { - qulonglong ul; - stream >> ul; - *static_cast(data) = ulong(ul); - break; } - case QMetaType::UInt: - stream >> *static_cast(data); - break; - case QMetaType::LongLong: - stream >> *static_cast(data); - break; - case QMetaType::ULongLong: - stream >> *static_cast(data); - break; - case QMetaType::UShort: - stream >> *static_cast(data); - break; - case QMetaType::SChar: - stream >> *static_cast(data); - break; - case QMetaType::UChar: - stream >> *static_cast(data); - break; - case QMetaType::Bool: { - qint8 b; - stream >> b; - *static_cast(data) = b; - break; } - case QMetaType::Float: - stream >> *static_cast(data); - break; - case QMetaType::Double: - stream >> *static_cast(data); - break; - case QMetaType::QChar: - stream >> *static_cast< NS(QChar)*>(data); - break; -#ifndef QT_BOOTSTRAPPED - case QMetaType::QVariantMap: - stream >> *static_cast< NS(QVariantMap)*>(data); - break; - case QMetaType::QVariantHash: - stream >> *static_cast< NS(QVariantHash)*>(data); - break; - case QMetaType::QVariantList: - stream >> *static_cast< NS(QVariantList)*>(data); - break; - case QMetaType::QVariant: - stream >> *static_cast< NS(QVariant)*>(data); - break; - case QMetaType::QByteArrayList: - stream >> *static_cast< NS(QByteArrayList)*>(data); - break; -#endif - case QMetaType::QByteArray: - stream >> *static_cast< NS(QByteArray)*>(data); - break; - case QMetaType::QString: - stream >> *static_cast< NS(QString)*>(data); - break; - case QMetaType::QStringList: - stream >> *static_cast< NS(QStringList)*>(data); - break; -#ifndef QT_BOOTSTRAPPED - case QMetaType::QBitArray: - stream >> *static_cast< NS(QBitArray)*>(data); - break; -#endif - case QMetaType::QDate: - stream >> *static_cast< NS(QDate)*>(data); - break; - case QMetaType::QTime: - stream >> *static_cast< NS(QTime)*>(data); - break; - case QMetaType::QDateTime: - stream >> *static_cast< NS(QDateTime)*>(data); - break; -#ifndef QT_BOOTSTRAPPED - case QMetaType::QUrl: - stream >> *static_cast< NS(QUrl)*>(data); - break; -#endif - case QMetaType::QLocale: - stream >> *static_cast< NS(QLocale)*>(data); - break; -#ifndef QT_NO_GEOM_VARIANT - case QMetaType::QRect: - stream >> *static_cast< NS(QRect)*>(data); - break; - case QMetaType::QRectF: - stream >> *static_cast< NS(QRectF)*>(data); - break; - case QMetaType::QSize: - stream >> *static_cast< NS(QSize)*>(data); - break; - case QMetaType::QSizeF: - stream >> *static_cast< NS(QSizeF)*>(data); - break; - case QMetaType::QLine: - stream >> *static_cast< NS(QLine)*>(data); - break; - case QMetaType::QLineF: - stream >> *static_cast< NS(QLineF)*>(data); - break; - case QMetaType::QPoint: - stream >> *static_cast< NS(QPoint)*>(data); - break; - case QMetaType::QPointF: - stream >> *static_cast< NS(QPointF)*>(data); - break; -#endif -#ifndef QT_NO_REGEXP - case QMetaType::QRegExp: - stream >> *static_cast< NS(QRegExp)*>(data); - break; -#endif -#if QT_CONFIG(regularexpression) - case QMetaType::QRegularExpression: - stream >> *static_cast< NS(QRegularExpression)*>(data); - break; -#endif // QT_CONFIG(regularexpression) -#ifndef QT_BOOTSTRAPPED - case QMetaType::QEasingCurve: - stream >> *static_cast< NS(QEasingCurve)*>(data); - break; - case QMetaType::QCborSimpleType: - stream >> *static_cast(data); - break; - case QMetaType::QJsonDocument: - stream >> *static_cast(data); - break; -#endif // QT_BOOTSTRAPPED - case QMetaType::QFont: - case QMetaType::QPixmap: - case QMetaType::QBrush: - case QMetaType::QColor: - case QMetaType::QPalette: - case QMetaType::QImage: - case QMetaType::QPolygon: - case QMetaType::QPolygonF: - case QMetaType::QRegion: - case QMetaType::QBitmap: - case QMetaType::QCursor: - case QMetaType::QKeySequence: - case QMetaType::QPen: - case QMetaType::QTextLength: - case QMetaType::QTextFormat: - case QMetaType::QMatrix: - case QMetaType::QTransform: - case QMetaType::QMatrix4x4: - case QMetaType::QVector2D: - case QMetaType::QVector3D: - case QMetaType::QVector4D: - case QMetaType::QQuaternion: - case QMetaType::QIcon: - if (!qMetaTypeGuiHelper) - return false; - qMetaTypeGuiHelper[type - FirstGuiType].loadOp(stream, data); - break; - case QMetaType::QSizePolicy: - if (!qMetaTypeWidgetsHelper) - return false; - qMetaTypeWidgetsHelper[type - FirstWidgetsType].loadOp(stream, data); - break; - case QMetaType::QUuid: - stream >> *static_cast< NS(QUuid)*>(data); - break; - default: { - const QVector * const ct = customTypes(); - if (!ct) - return false; - - LoadOperator loadOp = 0; - { - QReadLocker locker(customTypesLock()); - loadOp = ct->at(type - User).loadOp; - } - - if (!loadOp) - return false; - loadOp(stream, data); - break; } - } - return true; + LoadOperatorSwitch loadOp{stream, type}; + return QMetaTypeSwitcher::switcher(loadOp, type, data); } #endif // QT_NO_DATASTREAM diff --git a/tests/auto/gui/kernel/qguimetatype/tst_qguimetatype.cpp b/tests/auto/gui/kernel/qguimetatype/tst_qguimetatype.cpp index cca0e95c295..b2572188b93 100644 --- a/tests/auto/gui/kernel/qguimetatype/tst_qguimetatype.cpp +++ b/tests/auto/gui/kernel/qguimetatype/tst_qguimetatype.cpp @@ -49,6 +49,8 @@ private slots: void construct(); void constructCopy_data(); void constructCopy(); + void saveAndLoadBuiltin_data(); + void saveAndLoadBuiltin(); }; #define FOR_EACH_GUI_METATYPE_BASE(F) \ @@ -442,5 +444,49 @@ FOR_EACH_GUI_METATYPE(RETURN_CONSTRUCT_COPY_FUNCTION) TypeTestFunctionGetter::get(type)(); } +template +struct StreamingTraits +{ + // Streamable by default, as currently all gui built-in types are streamable + enum { isStreamable = 1 }; +}; + +void tst_QGuiMetaType::saveAndLoadBuiltin_data() +{ + QTest::addColumn("type"); + QTest::addColumn("isStreamable"); + +#define ADD_METATYPE_TEST_ROW(MetaTypeName, MetaTypeId, RealType) \ + QTest::newRow(#RealType) << MetaTypeId << bool(StreamingTraits::isStreamable); + QT_FOR_EACH_STATIC_GUI_CLASS(ADD_METATYPE_TEST_ROW) +#undef ADD_METATYPE_TEST_ROW +} + +void tst_QGuiMetaType::saveAndLoadBuiltin() +{ + QFETCH(int, type); + QFETCH(bool, isStreamable); + + void *value = QMetaType::create(type); + + QByteArray ba; + QDataStream stream(&ba, QIODevice::ReadWrite); + QCOMPARE(QMetaType::save(stream, type, value), isStreamable); + QCOMPARE(stream.status(), QDataStream::Ok); + + if (isStreamable) + QVERIFY(QMetaType::load(stream, type, value)); + + stream.device()->seek(0); + stream.resetStatus(); + QCOMPARE(QMetaType::load(stream, type, value), isStreamable); + QCOMPARE(stream.status(), QDataStream::Ok); + + if (isStreamable) + QVERIFY(QMetaType::load(stream, type, value)); + + QMetaType::destroy(type, value); +} + QTEST_MAIN(tst_QGuiMetaType) #include "tst_qguimetatype.moc" diff --git a/tests/auto/widgets/kernel/qwidgetmetatype/tst_qwidgetmetatype.cpp b/tests/auto/widgets/kernel/qwidgetmetatype/tst_qwidgetmetatype.cpp index 077e8de3282..06522b2bd36 100644 --- a/tests/auto/widgets/kernel/qwidgetmetatype/tst_qwidgetmetatype.cpp +++ b/tests/auto/widgets/kernel/qwidgetmetatype/tst_qwidgetmetatype.cpp @@ -41,6 +41,8 @@ public: private slots: void metaObject(); + void saveAndLoadBuiltin_data(); + void saveAndLoadBuiltin(); }; class CustomWidget : public QWidget @@ -68,5 +70,50 @@ void tst_QWidgetMetaType::metaObject() QCOMPARE(QMetaType::metaObjectForType(qMetaTypeId()), &QSizePolicy::staticMetaObject); } +template +struct StreamingTraits +{ + // Streamable by default, as currently all widgets built-in types are streamable + enum { isStreamable = 1 }; +}; + +void tst_QWidgetMetaType::saveAndLoadBuiltin_data() +{ + QTest::addColumn("type"); + QTest::addColumn("isStreamable"); + +#define ADD_METATYPE_TEST_ROW(MetaTypeName, MetaTypeId, RealType) \ + QTest::newRow(#RealType) << MetaTypeId << bool(StreamingTraits::isStreamable); + QT_FOR_EACH_STATIC_WIDGETS_CLASS(ADD_METATYPE_TEST_ROW) +#undef ADD_METATYPE_TEST_ROW +} + +void tst_QWidgetMetaType::saveAndLoadBuiltin() +{ + QFETCH(int, type); + QFETCH(bool, isStreamable); + + void *value = QMetaType::create(type); + + QByteArray ba; + QDataStream stream(&ba, QIODevice::ReadWrite); + QCOMPARE(QMetaType::save(stream, type, value), isStreamable); + QCOMPARE(stream.status(), QDataStream::Ok); + + if (isStreamable) + QVERIFY(QMetaType::load(stream, type, value)); + + stream.device()->seek(0); + stream.resetStatus(); + QCOMPARE(QMetaType::load(stream, type, value), isStreamable); + QCOMPARE(stream.status(), QDataStream::Ok); + + if (isStreamable) + QVERIFY(QMetaType::load(stream, type, value)); + + QMetaType::destroy(type, value); +} + + QTEST_MAIN(tst_QWidgetMetaType) #include "tst_qwidgetmetatype.moc"