//! @file QCollatorSortKeyBug.cpp Demonstrates a bug in QCollatorSortKey under Windows. // Copyright (C) 2023 FotoWare AS. All rights reserved. #ifdef _WIN32 // This is a Qt on Windows bug. #include #include #include #include #include #include "stringapiset.h" //! A class that generates a sort key for a string by means of Windows API LCMapStringW. //! //! This is equal to QCollatorSortKey (file qcollator_win.cpp line 92) except for two things: //! # Sort keys are kept in a std::vector instead of a QString since they are binary. //! # Sort keys are compared with std::memcmp instead of QString::compare(), again because they're binary. class MyWinSortKey { std::vector m_key; public: MyWinSortKey(const std::wstring& str) { auto required = LCMapStringW(LOCALE_NAME_USER_DEFAULT, LCMAP_SORTKEY | SORT_DIGITSASNUMBERS, str.c_str(), -1, nullptr, 0); m_key.resize(required); LCMapStringW(LOCALE_NAME_USER_DEFAULT, LCMAP_SORTKEY | SORT_DIGITSASNUMBERS, str.c_str(), -1, (wchar_t*)m_key.data(), (int)m_key.size()); } int compare(const MyWinSortKey& right) const { auto ll = m_key.size(), rl = right.m_key.size(); int r = std::memcmp(m_key.data(), right.m_key.data(), std::min(ll, rl)); if (r == 0 && ll != rl) { return (ll < rl) ? -1 : 1; } return r; } friend bool operator <(const MyWinSortKey& left, const MyWinSortKey& right) { return left.compare(right) < 0; } const char* data() const { return m_key.data(); } size_t size() const { return m_key.size(); } }; //! Test to check that MyWinSortKey sorts the strings "s12" and "s100" correctly in numeric mode. //! I.e. that the underlying API function LCMapStringW works correctly. TEST(QCollatorTest, WinSortKeys) { std::wstring leftStr(L"s12"), rightStr(L"s100"); MyWinSortKey my_left(leftStr); MyWinSortKey my_right(rightStr); EXPECT_TRUE(my_left.compare(my_right) < 0); EXPECT_TRUE(my_right.compare(my_left) > 0); EXPECT_TRUE(my_left < my_right); EXPECT_FALSE(my_right < my_left); } //! This test illustrates how QCollatorSortKey fails in numeric mode with the strings "s12" and "s100". //! Also how QCollator::compare does not fail for those same strings. TEST(QCollatorTest, QCollatorSortKeysBug) { QCollator collator; // (QLocale(QLocale::Language::NorwegianBokmal, QLocale::Territory::Norway)); collator.setNumericMode(true); QString leftStr("s12"), rightStr("s100"); EXPECT_TRUE(collator.compare(leftStr, rightStr) < 0); // OK! EXPECT_TRUE(collator.compare(rightStr, leftStr) > 0); // OK! auto left = collator.sortKey(leftStr); auto right = collator.sortKey(rightStr); // These all fail. EXPECT_TRUE(left.compare(right) < 0); EXPECT_TRUE(right.compare(left) > 0); EXPECT_TRUE(left < right); EXPECT_FALSE(right < left); } //! A test that illustrates why QCollatorSortKey fails in numeric mode with the strings "s12" and "s100". TEST(QCollatorTest, SourceOfQCollatorSortKeysBug) { std::wstring leftStr(L"s12"), rightStr(L"s100"); MyWinSortKey my_left(leftStr); MyWinSortKey my_right(rightStr); // This is how QString is initialized with sort key data in file qcollator_win.cpp line 103. // Note that the data is binary and may contain 0-bytes(!) QString leftKeyStr(my_left.size(), Qt::Uninitialized); std::memcpy(leftKeyStr.data(), my_left.data(), leftKeyStr.size()); QString rightKeyStr(my_right.size(), Qt::Uninitialized); std::memcpy(rightKeyStr.data(), my_right.data(), rightKeyStr.size()); // QCollatorSortKey::compare is implemented as a call to QString::compare in qcollator_win.cpp line 117. EXPECT_TRUE(leftKeyStr.compare(rightKeyStr) < 0); } #endif // _WIN32