Skip to content

Commit

Permalink
QVariant: use a typedef name when saving user types to QDataStream
Browse files Browse the repository at this point in the history
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 <[email protected]>
Reviewed-by: Lars Knoll <[email protected]>
  • Loading branch information
thiagomacieira authored and isolovev committed Dec 17, 2021
1 parent af36675 commit 46dc8e4
Show file tree
Hide file tree
Showing 7 changed files with 148 additions and 6 deletions.
40 changes: 39 additions & 1 deletion src/corelib/kernel/qmetatype.cpp
Original file line number Diff line number Diff line change
@@ -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, [email protected]
** Copyright (C) 2021 Intel Corporation.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the QtCore module of the Qt Toolkit.
Expand Down Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions src/corelib/kernel/qmetatype_p.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
14 changes: 9 additions & 5 deletions src/corelib/kernel/qvariant.cpp
Original file line number Diff line number Diff line change
@@ -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 <[email protected]>
** Contact: https://www.qt.io/licensing/
**
Expand Down Expand Up @@ -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)
Expand Down
2 changes: 2 additions & 0 deletions tests/auto/corelib/serialization/qdatastream/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -22,6 +23,7 @@ if(ANDROID OR INTEGRITY)
# Resources:
set(testdata_resource_files
"datastream.q42"
"typedef.q5"
)

qt_internal_add_resource(tst_qdatastream "testdata"
Expand Down
53 changes: 53 additions & 0 deletions tests/auto/corelib/serialization/qdatastream/gen_typedefq5.cpp
Original file line number Diff line number Diff line change
@@ -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);
}
44 changes: 44 additions & 0 deletions tests/auto/corelib/serialization/qdatastream/tst_qdatastream.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,8 @@ private slots:
void nestedTransactionsResult_data();
void nestedTransactionsResult();

void typedefQt5Compat();

private:
void writebool(QDataStream *s);
void writeQBitArray(QDataStream *s);
Expand Down Expand Up @@ -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"

Binary file not shown.

0 comments on commit 46dc8e4

Please sign in to comment.