QLibrary: fix loading multiple versions of a library

The libraryMap only stored the file path, so we couldn't load two
versions of the same library as we'd find the other version already
loaded. Change the map to index by file name and version (using a NUL as
separator, since that can't appear in file names).

[ChangeLog][QtCore][QLibrary] Fixed a bug that caused QLibrary to be
unable to load two different versions of a library of a given name at the
same time. Note that this is often inadviseable and loading the second
library may cause a crash.

Pick-to: 6.4
Change-Id: I12a088d1ae424825abd3fffd171ce3bb0590978d
Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org>
Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io>
This commit is contained in:
Thiago Macieira 2022-10-10 19:45:54 -07:00
parent 3731e04668
commit 77eef32917
2 changed files with 75 additions and 5 deletions

View File

@ -391,10 +391,12 @@ inline QLibraryPrivate *QLibraryStore::findOrCreate(const QString &fileName, con
QMutexLocker locker(&qt_library_mutex);
QLibraryStore *data = instance();
QString mapName = version.isEmpty() ? fileName : fileName + u'\0' + version;
// check if this library is already loaded
QLibraryPrivate *lib = nullptr;
if (Q_LIKELY(data)) {
lib = data->libraryMap.value(fileName);
lib = data->libraryMap.value(mapName);
if (lib)
lib->mergeLoadHints(loadHints);
}
@ -403,7 +405,7 @@ inline QLibraryPrivate *QLibraryStore::findOrCreate(const QString &fileName, con
// track this library
if (Q_LIKELY(data) && !fileName.isEmpty())
data->libraryMap.insert(fileName, lib);
data->libraryMap.insert(mapName, lib);
lib->libraryRefCount.ref();
return lib;
@ -423,9 +425,11 @@ inline void QLibraryStore::releaseLibrary(QLibraryPrivate *lib)
Q_ASSERT(lib->libraryUnloadCount.loadRelaxed() == 0);
if (Q_LIKELY(data) && !lib->fileName.isEmpty()) {
QLibraryPrivate *that = data->libraryMap.take(lib->fileName);
Q_ASSERT(lib == that);
Q_UNUSED(that);
qsizetype n = erase_if(data->libraryMap, [lib](LibraryMap::iterator it) {
return it.value() == lib;
});
Q_ASSERT_X(n, "~QLibrary", "Did not find this library in the library map");
Q_UNUSED(n);
}
delete lib;
}

View File

@ -96,6 +96,8 @@ private slots:
void isLibrary();
void version_data();
void version();
void loadTwoVersions();
void setFileNameAndVersionTwice();
void setFileNameAndVersionAfterFailedLoad_data() { version_data(); }
void setFileNameAndVersionAfterFailedLoad();
void errorString_data();
@ -196,6 +198,70 @@ void tst_QLibrary::version()
#endif
}
void tst_QLibrary::loadTwoVersions()
{
#if defined(Q_OS_ANDROID) || defined(Q_OS_WIN)
QSKIP("Versioned files are not generated for this OS, so this test is not applicable.");
#endif
QLibrary lib1(directory + "/mylib", 1);
QLibrary lib2(directory + "/mylib", 2);
QVERIFY(!lib1.isLoaded());
QVERIFY(!lib2.isLoaded());
// load the first one
QVERIFY(lib1.load());
QVERIFY(lib1.isLoaded());
// let's see if we can load the second one too
QVERIFY(lib2.load());
QVERIFY(lib2.isLoaded());
auto p1 = (VersionFunction)lib1.resolve("mylibversion");
QVERIFY(p1);
auto p2 = (VersionFunction)lib2.resolve("mylibversion");
QVERIFY(p2);
QCOMPARE_NE(p1(), p2());
lib2.unload();
lib1.unload();
}
void tst_QLibrary::setFileNameAndVersionTwice()
{
#if defined(Q_OS_ANDROID) || defined(Q_OS_WIN)
QSKIP("Versioned files are not generated for this OS, so this test is not applicable.");
#endif
QLibrary library(directory + "/mylib", 1);
QVERIFY(library.load());
QVERIFY(library.isLoaded());
auto p1 = (VersionFunction)library.resolve("mylibversion");
QVERIFY(p1);
// don't .unload()
library.setFileNameAndVersion(directory + "/mylib", 2);
QVERIFY(!library.isLoaded());
QVERIFY(library.load());
QVERIFY(library.isLoaded());
auto p2 = (VersionFunction)library.resolve("mylibversion");
QVERIFY(p2);
QCOMPARE_NE(p1(), p2());
QVERIFY(library.unload());
QVERIFY(!library.isLoaded());
// set back
library.setFileNameAndVersion(directory + "/mylib", 1);
QVERIFY(library.isLoaded());
QVERIFY(library.unload());
QVERIFY(!library.isLoaded());
}
void tst_QLibrary::load_data()
{
QTest::addColumn<QString>("lib");