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/
|
** Contact: https://www.qt.io/licensing/
|
||||||
**
|
**
|
||||||
** This file is part of the QtCore module of the Qt Toolkit.
|
** This file is part of the QtCore module of the Qt Toolkit.
|
||||||
@ -179,6 +181,42 @@ Q_GLOBAL_STATIC(QMetaTypeCustomRegistry, customTypeRegistry)
|
|||||||
|
|
||||||
} // namespace
|
} // 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)
|
\macro Q_DECLARE_OPAQUE_POINTER(PointerType)
|
||||||
\relates QMetaType
|
\relates QMetaType
|
||||||
|
@ -233,6 +233,7 @@ static const QT_PREPEND_NAMESPACE(QtPrivate::QMetaTypeInterface) *getInterfaceFr
|
|||||||
case QMetaType::MetaTypeName: \
|
case QMetaType::MetaTypeName: \
|
||||||
return QtMetaTypePrivate::getInterfaceFromType<RealName>();
|
return QtMetaTypePrivate::getInterfaceFromType<RealName>();
|
||||||
|
|
||||||
|
const char *typedefNameForType(const QtPrivate::QMetaTypeInterface *type_d);
|
||||||
} //namespace QtMetaTypePrivate
|
} //namespace QtMetaTypePrivate
|
||||||
|
|
||||||
QT_END_NAMESPACE
|
QT_END_NAMESPACE
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
/****************************************************************************
|
/****************************************************************************
|
||||||
**
|
**
|
||||||
** Copyright (C) 2020 The Qt Company Ltd.
|
** Copyright (C) 2021 The Qt Company Ltd.
|
||||||
** Copyright (C) 2018 Intel Corporation.
|
** Copyright (C) 2021 Intel Corporation.
|
||||||
** Copyright (C) 2015 Olivier Goffart <ogoffart@woboq.com>
|
** Copyright (C) 2015 Olivier Goffart <ogoffart@woboq.com>
|
||||||
** Contact: https://www.qt.io/licensing/
|
** Contact: https://www.qt.io/licensing/
|
||||||
**
|
**
|
||||||
@ -1314,13 +1314,17 @@ void QVariant::save(QDataStream &s) const
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
const char *typeName = nullptr;
|
const char *typeName = nullptr;
|
||||||
if (saveAsUserType)
|
if (saveAsUserType) {
|
||||||
typeName = d.type().name();
|
if (s.version() < QDataStream::Qt_6_0)
|
||||||
|
typeName = QtMetaTypePrivate::typedefNameForType(d.type().d_ptr);
|
||||||
|
if (!typeName)
|
||||||
|
typeName = d.type().name();
|
||||||
|
}
|
||||||
s << typeId;
|
s << typeId;
|
||||||
if (s.version() >= QDataStream::Qt_4_2)
|
if (s.version() >= QDataStream::Qt_4_2)
|
||||||
s << qint8(d.is_null);
|
s << qint8(d.is_null);
|
||||||
if (typeName)
|
if (typeName)
|
||||||
s << d.type().name();
|
s << typeName;
|
||||||
|
|
||||||
if (!isValid()) {
|
if (!isValid()) {
|
||||||
if (s.version() < QDataStream::Qt_5_0)
|
if (s.version() < QDataStream::Qt_5_0)
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
|
|
||||||
# Collect test data
|
# Collect test data
|
||||||
list(APPEND test_data "datastream.q42")
|
list(APPEND test_data "datastream.q42")
|
||||||
|
list(APPEND test_data "typedef.q5")
|
||||||
|
|
||||||
qt_internal_add_test(tst_qdatastream
|
qt_internal_add_test(tst_qdatastream
|
||||||
SOURCES
|
SOURCES
|
||||||
@ -22,6 +23,7 @@ if(ANDROID OR INTEGRITY)
|
|||||||
# Resources:
|
# Resources:
|
||||||
set(testdata_resource_files
|
set(testdata_resource_files
|
||||||
"datastream.q42"
|
"datastream.q42"
|
||||||
|
"typedef.q5"
|
||||||
)
|
)
|
||||||
|
|
||||||
qt_internal_add_resource(tst_qdatastream "testdata"
|
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_data();
|
||||||
void nestedTransactionsResult();
|
void nestedTransactionsResult();
|
||||||
|
|
||||||
|
void typedefQt5Compat();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void writebool(QDataStream *s);
|
void writebool(QDataStream *s);
|
||||||
void writeQBitArray(QDataStream *s);
|
void writeQBitArray(QDataStream *s);
|
||||||
@ -3872,6 +3874,48 @@ void tst_QDataStream::nestedTransactionsResult()
|
|||||||
QCOMPARE(int(stream.status()), expectedStatus);
|
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)
|
QTEST_MAIN(tst_QDataStream)
|
||||||
#include "tst_qdatastream.moc"
|
#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