QVariant: use a typedef name when saving user types to QDataStream
Due to the way Qt 5 and 6 registered type names, they end up producing different type names for the same content for a typedef. For example, because Q_DECLARE_METATYPE can't manage a comma (it's a macro), users are forced to write something like: using MyTypeMap = QMap<QString, MyType> Q_DECLARE_METATYPE(MyTypeMap) Qt 5's Q_DECLARE_METATYPE's argument "MyTypeMap" was the only name we knew about the type, so that's what got saved in the stream. However, Qt 6 QtPrivate::typenameHelper is much more clever and obtains the name from the compiler itself, so it "sees through" the typedef and registers "QMap<QString,MyType>" as the official type name. If another library/plugin has a different typedef name for the same type (e.g., StringTypeMap), it's indeterminate which type gets saved and will even change from run to run (depends on the QHash order). [ChangeLog][QtCore][QDataStream] If QDataStream is used with a QDataStream::Version < Qt_6_0 to serialize a user type that was registered via a typedef with the metatype system, the typedef's name is used in the stream instead of the non-typedef name. This restores compatibility with Qt 5, allowing existing content to read the same QDataStreams; reading from older Qt 6 versions should not be affected. (Note: if more than one typedef name is registered, it's indetermine which name gets used) Fixes: QTBUG-96916 Pick-to: 6.3 6.2 Change-Id: I2bbf422288924c198645fffd16a8d811aa58201e Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org> Reviewed-by: Lars Knoll <lars.knoll@qt.io>
This commit is contained in:
parent
af36675afd
commit
46dc8e453a
@ -1,6 +1,8 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2016 The Qt Company Ltd.
|
||||
** Copyright (C) 2021 The Qt Company Ltd.
|
||||
** Copyright (C) 2021 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com
|
||||
** Copyright (C) 2021 Intel Corporation.
|
||||
** Contact: https://www.qt.io/licensing/
|
||||
**
|
||||
** This file is part of the QtCore module of the Qt Toolkit.
|
||||
@ -179,6 +181,42 @@ Q_GLOBAL_STATIC(QMetaTypeCustomRegistry, customTypeRegistry)
|
||||
|
||||
} // namespace
|
||||
|
||||
// used by QVariant::save(): returns the name used in the Q_DECLARE_METATYPE
|
||||
// macro (one of them, indetermine which one)
|
||||
const char *QtMetaTypePrivate::typedefNameForType(const QtPrivate::QMetaTypeInterface *type_d)
|
||||
{
|
||||
const char *name = nullptr;
|
||||
QMetaTypeCustomRegistry *r = customTypeRegistry;
|
||||
if (!r)
|
||||
return name;
|
||||
|
||||
QByteArrayView officialName(type_d->name);
|
||||
QReadLocker l(&r->lock);
|
||||
auto it = r->aliases.constBegin();
|
||||
auto end = r->aliases.constEnd();
|
||||
for ( ; it != end; ++it) {
|
||||
if (it.value() != type_d)
|
||||
continue;
|
||||
if (it.key() == officialName)
|
||||
continue; // skip the official name
|
||||
name = it.key().constData();
|
||||
break;
|
||||
}
|
||||
|
||||
#ifndef QT_NO_DEBUG
|
||||
QByteArrayList otherNames;
|
||||
for ( ; it != end; ++it) {
|
||||
if (it.value() == type_d)
|
||||
otherNames << it.key();
|
||||
}
|
||||
if (!otherNames.isEmpty())
|
||||
qWarning("QMetaType: type %s has more than one typedef alias: %s, %s",
|
||||
type_d->name, name, otherNames.join(", ").constData());
|
||||
#endif
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
/*!
|
||||
\macro Q_DECLARE_OPAQUE_POINTER(PointerType)
|
||||
\relates QMetaType
|
||||
|
@ -233,6 +233,7 @@ static const QT_PREPEND_NAMESPACE(QtPrivate::QMetaTypeInterface) *getInterfaceFr
|
||||
case QMetaType::MetaTypeName: \
|
||||
return QtMetaTypePrivate::getInterfaceFromType<RealName>();
|
||||
|
||||
const char *typedefNameForType(const QtPrivate::QMetaTypeInterface *type_d);
|
||||
} //namespace QtMetaTypePrivate
|
||||
|
||||
QT_END_NAMESPACE
|
||||
|
@ -1,7 +1,7 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2020 The Qt Company Ltd.
|
||||
** Copyright (C) 2018 Intel Corporation.
|
||||
** Copyright (C) 2021 The Qt Company Ltd.
|
||||
** Copyright (C) 2021 Intel Corporation.
|
||||
** Copyright (C) 2015 Olivier Goffart <ogoffart@woboq.com>
|
||||
** Contact: https://www.qt.io/licensing/
|
||||
**
|
||||
@ -1314,13 +1314,17 @@ void QVariant::save(QDataStream &s) const
|
||||
}
|
||||
}
|
||||
const char *typeName = nullptr;
|
||||
if (saveAsUserType)
|
||||
typeName = d.type().name();
|
||||
if (saveAsUserType) {
|
||||
if (s.version() < QDataStream::Qt_6_0)
|
||||
typeName = QtMetaTypePrivate::typedefNameForType(d.type().d_ptr);
|
||||
if (!typeName)
|
||||
typeName = d.type().name();
|
||||
}
|
||||
s << typeId;
|
||||
if (s.version() >= QDataStream::Qt_4_2)
|
||||
s << qint8(d.is_null);
|
||||
if (typeName)
|
||||
s << d.type().name();
|
||||
s << typeName;
|
||||
|
||||
if (!isValid()) {
|
||||
if (s.version() < QDataStream::Qt_5_0)
|
||||
|
@ -6,6 +6,7 @@
|
||||
|
||||
# Collect test data
|
||||
list(APPEND test_data "datastream.q42")
|
||||
list(APPEND test_data "typedef.q5")
|
||||
|
||||
qt_internal_add_test(tst_qdatastream
|
||||
SOURCES
|
||||
@ -22,6 +23,7 @@ if(ANDROID OR INTEGRITY)
|
||||
# Resources:
|
||||
set(testdata_resource_files
|
||||
"datastream.q42"
|
||||
"typedef.q5"
|
||||
)
|
||||
|
||||
qt_internal_add_resource(tst_qdatastream "testdata"
|
||||
|
@ -0,0 +1,53 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2021 The Qt Company Ltd.
|
||||
** Contact: https://www.qt.io/licensing/
|
||||
**
|
||||
** This file is part of the test suite of the Qt Toolkit.
|
||||
**
|
||||
** $QT_BEGIN_LICENSE:GPL-EXCEPT$
|
||||
** Commercial License Usage
|
||||
** Licensees holding valid commercial Qt licenses may use this file in
|
||||
** accordance with the commercial license agreement provided with the
|
||||
** Software or, alternatively, in accordance with the terms contained in
|
||||
** a written agreement between you and The Qt Company. For licensing terms
|
||||
** and conditions see https://www.qt.io/terms-conditions. For further
|
||||
** information use the contact form at https://www.qt.io/contact-us.
|
||||
**
|
||||
** GNU General Public License Usage
|
||||
** Alternatively, this file may be used under the terms of the GNU
|
||||
** General Public License version 3 as published by the Free Software
|
||||
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
|
||||
** included in the packaging of this file. Please review the following
|
||||
** information to ensure the GNU General Public License requirements will
|
||||
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
|
||||
**
|
||||
** $QT_END_LICENSE$
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
|
||||
#include <QDataStream>
|
||||
#include <QPair>
|
||||
#include <QFile>
|
||||
#include <QVariant>
|
||||
#include <QDebug>
|
||||
|
||||
using CustomPair = QPair<int, int>;
|
||||
QDataStream &operator<<(QDataStream &ds, CustomPair pd)
|
||||
{ return ds << pd.first << pd.second; }
|
||||
QDataStream &operator>>(QDataStream &ds, CustomPair &pd)
|
||||
{ return ds >> pd.first >> pd.second; }
|
||||
Q_DECLARE_METATYPE(CustomPair)
|
||||
|
||||
|
||||
int main() {
|
||||
qRegisterMetaTypeStreamOperators<CustomPair>();
|
||||
QFile out("typedef.q5");
|
||||
out.open(QIODevice::ReadWrite);
|
||||
QDataStream stream(&out);
|
||||
stream.setVersion(QDataStream::Qt_5_15);
|
||||
CustomPair p {42, 100};
|
||||
qDebug() << p.first << p.second;
|
||||
stream << QVariant::fromValue(p);
|
||||
}
|
@ -221,6 +221,8 @@ private slots:
|
||||
void nestedTransactionsResult_data();
|
||||
void nestedTransactionsResult();
|
||||
|
||||
void typedefQt5Compat();
|
||||
|
||||
private:
|
||||
void writebool(QDataStream *s);
|
||||
void writeQBitArray(QDataStream *s);
|
||||
@ -3872,6 +3874,48 @@ void tst_QDataStream::nestedTransactionsResult()
|
||||
QCOMPARE(int(stream.status()), expectedStatus);
|
||||
}
|
||||
|
||||
using CustomPair = QPair<int, int>;
|
||||
QDataStream &operator<<(QDataStream &ds, CustomPair pd)
|
||||
{ return ds << pd.first << pd.second; }
|
||||
QDataStream &operator>>(QDataStream &ds, CustomPair &pd)
|
||||
{ return ds >> pd.first >> pd.second; }
|
||||
|
||||
|
||||
void tst_QDataStream::typedefQt5Compat()
|
||||
{
|
||||
qRegisterMetaType<CustomPair>("CustomPair");
|
||||
QByteArray qt5Data;
|
||||
{
|
||||
// we can read the qt5 version
|
||||
QFile in(QFINDTESTDATA("typedef.q5"));
|
||||
QVERIFY(in.open(QIODevice::ReadOnly));
|
||||
qt5Data = in.readAll();
|
||||
QVERIFY(in.seek(0));
|
||||
QDataStream stream(&in);
|
||||
stream.setVersion(QDataStream::Qt_5_15);
|
||||
QVariant var;
|
||||
stream >> var;
|
||||
QCOMPARE(stream.status(), QDataStream::Ok);
|
||||
CustomPair p = var.value<CustomPair>();
|
||||
QCOMPARE(p.first, 42);
|
||||
QCOMPARE(p.second, 100);
|
||||
}
|
||||
{
|
||||
// writing in Qt 6 results in the same file
|
||||
QTemporaryDir dir;
|
||||
QVERIFY(dir.isValid());
|
||||
QFile file(dir.filePath(u"typedef.q6"_qs));
|
||||
file.open(QIODevice::WriteOnly);
|
||||
QDataStream stream(&file);
|
||||
stream.setVersion(QDataStream::Qt_5_15);
|
||||
CustomPair p {42, 100};
|
||||
stream << QVariant::fromValue(p);
|
||||
file.close();
|
||||
file.open(QIODevice::ReadOnly);
|
||||
QCOMPARE(file.readAll(), qt5Data);
|
||||
}
|
||||
}
|
||||
|
||||
QTEST_MAIN(tst_QDataStream)
|
||||
#include "tst_qdatastream.moc"
|
||||
|
||||
|
BIN
tests/auto/corelib/serialization/qdatastream/typedef.q5
Normal file
BIN
tests/auto/corelib/serialization/qdatastream/typedef.q5
Normal file
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user