From 5a0931d3cf0f514700115c195b7961c5f4b57fb5 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Tue, 24 Mar 2026 20:05:37 +0200 Subject: [PATCH 1/3] fix heap overflow with unstable version comparation fixes #5210 fixes #5251 (the removeDuplicates line) The issue was mostly with the Version parsing and compring implementation. Refactored that based on the https://git.sleeping.town/exa/FlexVer examples. Signed-off-by: Trial97 --- launcher/Version.cpp | 238 +++++++++++++---------- launcher/Version.h | 135 ++----------- launcher/modplatform/packwiz/Packwiz.cpp | 19 +- tests/testdata/Version/test_vectors.txt | 9 +- 4 files changed, 175 insertions(+), 226 deletions(-) diff --git a/launcher/Version.cpp b/launcher/Version.cpp index bffe5d58a..fdfd2721b 100644 --- a/launcher/Version.cpp +++ b/launcher/Version.cpp @@ -1,111 +1,28 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (C) 2023 flowln + * Copyright (C) 2022 Sefa Eyeoglu + * Copyright (c) 2026 Trial97 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ #include "Version.h" #include #include #include - -Version::Version(QString str) : m_string(std::move(str)) -{ - parse(); -} - -#define VERSION_OPERATOR(return_on_different) \ - bool exclude_our_sections = false; \ - bool exclude_their_sections = false; \ - \ - const auto size = qMax(m_sections.size(), other.m_sections.size()); \ - for (int i = 0; i < size; ++i) { \ - Section sec1 = (i >= m_sections.size()) ? Section() : m_sections.at(i); \ - Section sec2 = (i >= other.m_sections.size()) ? Section() : other.m_sections.at(i); \ - \ - { /* Don't include appendixes in the comparison */ \ - if (sec1.isAppendix()) \ - exclude_our_sections = true; \ - if (sec2.isAppendix()) \ - exclude_their_sections = true; \ - \ - if (exclude_our_sections) { \ - sec1 = Section(); \ - if (sec2.m_isNull) \ - break; \ - } \ - \ - if (exclude_their_sections) { \ - sec2 = Section(); \ - if (sec1.m_isNull) \ - break; \ - } \ - } \ - \ - if (sec1 != sec2) \ - return return_on_different; \ - } - -bool Version::operator<(const Version& other) const -{ - VERSION_OPERATOR(sec1 < sec2) - - return false; -} -bool Version::operator==(const Version& other) const -{ - VERSION_OPERATOR(false) - - return true; -} -bool Version::operator!=(const Version& other) const -{ - return !operator==(other); -} -bool Version::operator<=(const Version& other) const -{ - return *this < other || *this == other; -} -bool Version::operator>(const Version& other) const -{ - return !(*this <= other); -} -bool Version::operator>=(const Version& other) const -{ - return !(*this < other); -} - -void Version::parse() -{ - m_sections.clear(); - QString currentSection; - - if (m_string.isEmpty()) - return; - - auto classChange = [¤tSection](QChar lastChar, QChar currentChar) { - if (lastChar.isNull()) - return false; - if (lastChar.isDigit() != currentChar.isDigit()) - return true; - - const QList s_separators{ '.', '-', '+' }; - if (s_separators.contains(currentChar) && currentSection.at(0) != currentChar) - return true; - - return false; - }; - - currentSection += m_string.at(0); - for (int i = 1; i < m_string.size(); ++i) { - const auto& current_char = m_string.at(i); - if (classChange(m_string.at(i - 1), current_char)) { - if (!currentSection.isEmpty()) - m_sections.append(Section(currentSection)); - currentSection = ""; - } - - currentSection += current_char; - } - - if (!currentSection.isEmpty()) - m_sections.append(Section(currentSection)); -} +#include /// qDebug print support for the Version class QDebug operator<<(QDebug debug, const Version& v) @@ -115,10 +32,11 @@ QDebug operator<<(QDebug debug, const Version& v) debug.nospace() << "Version{ string: " << v.toString() << ", sections: [ "; bool first = true; - for (auto s : v.m_sections) { - if (!first) + for (const auto& s : v.m_sections) { + if (!first) { debug.nospace() << ", "; - debug.nospace() << s.m_fullString; + } + debug.nospace() << s.value; first = false; } @@ -126,3 +44,111 @@ QDebug operator<<(QDebug debug, const Version& v) return debug; } + +std::strong_ordering Version::Section::operator<=>(const Section& other) const +{ + // If both components are numeric, compare numerically (codepoint-wise) + if (this->t == Type::Numeric && other.t == Type::Numeric) { + auto aLen = this->value.size(); + if (aLen != other.value.size()) { + // Lengths differ; compare by length + return aLen <=> other.value.size(); + } + // Compare by digits + auto cmp = QString::compare(this->value, other.value); + if (cmp < 0) { + return std::strong_ordering::less; + } + if (cmp > 0) { + return std::strong_ordering::greater; + } + return std::strong_ordering::equal; + } + // One or both are null + if (this->t == Type::Null) { + if (other.t == Type::PreRelease) { + return std::strong_ordering::greater; + } + return std::strong_ordering::less; + } + if (other.t == Type::Null) { + if (this->t == Type::PreRelease) { + return std::strong_ordering::less; + } + return std::strong_ordering::greater; + } + // Textual comparison (differing type, or both textual/pre-release) + auto minLen = qMin(this->value.size(), other.value.size()); + for (int i = 0; i < minLen; i++) { + auto a = this->value.at(i); + auto b = other.value.at(i); + if (a != b) { + // Compare by rune + return a.unicode() <=> b.unicode(); + } + } + // Compare by length + return this->value.size() <=> other.value.size(); +} + +namespace { +void removeLeadingZeros(QString& s) +{ + s.remove(0, std::distance(s.begin(), std::ranges::find_if_not(s, [](QChar c) { return c == '0'; }))); +} +} // namespace + +void Version::parse() +{ + auto len = m_string.size(); + for (int i = 0; i < len;) { + Section cur(Section::Type::Textual); + auto c = m_string.at(i); + if (c == '+') { + break; // Ignore appendices + } + if (c == '-') { + // Add dash to component + cur.value += '-'; + i++; + // If the next rune is non-digit, mark as pre-release (requires >= 1 non-digit after dash so the component has length > 1) + if (i < len && !m_string.at(i).isDigit()) { + cur.t = Section::Type::PreRelease; + } + } else if (c.isDigit()) { + // Mark as numeric + cur.t = Section::Type::Numeric; + } + for (; i < len; i++) { + auto r = m_string.at(i); + if ((r.isDigit() != (cur.t == Section::Type::Numeric)) || + (r == '-' && cur.t != Section::Type::PreRelease) // "---" is a valid pre-release component + || r == '+') { + // Run completed (do not consume this rune) + break; + } + // Add rune to current run + cur.value += r; + } + if (!cur.value.isEmpty()) { + if (cur.t == Section::Type::Numeric) { + removeLeadingZeros(cur.value); + } + m_sections.append(cur); + } + } +} + +std::strong_ordering Version::operator<=>(const Version& other) const +{ + const auto size = qMax(m_sections.size(), other.m_sections.size()); + for (int i = 0; i < size; ++i) { + auto sec1 = (i >= m_sections.size()) ? Section() : m_sections.at(i); + auto sec2 = (i >= other.m_sections.size()) ? Section() : other.m_sections.at(i); + + if (auto cmp = sec1 <=> sec2; cmp != std::strong_ordering::equal) { + return cmp; + } + } + return std::strong_ordering::equal; +} diff --git a/launcher/Version.h b/launcher/Version.h index 9933fecc3..d123f8348 100644 --- a/launcher/Version.h +++ b/launcher/Version.h @@ -3,6 +3,7 @@ * Prism Launcher - Minecraft Launcher * Copyright (C) 2023 flowln * Copyright (C) 2022 Sefa Eyeoglu + * Copyright (c) 2026 Trial97 * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -15,23 +16,6 @@ * * You should have received a copy of the GNU General Public License * along with this program. If not, see . - * - * This file incorporates work covered by the following copyright and - * permission notice: - * - * Copyright 2013-2021 MultiMC Contributors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. */ #pragma once @@ -41,115 +25,36 @@ #include #include -class QUrl; - +// this implements the FlexVer +// https://git.sleeping.town/exa/FlexVer class Version { public: - Version(QString str); + Version(QString str) : m_string(std::move(str)) { parse(); } Version() = default; - bool operator<(const Version& other) const; - bool operator<=(const Version& other) const; - bool operator>(const Version& other) const; - bool operator>=(const Version& other) const; - bool operator==(const Version& other) const; - bool operator!=(const Version& other) const; + private: + struct Section { + enum class Type : std::uint8_t { Null, Textual, Numeric, PreRelease }; + explicit Section(Type t = Type::Null, QString value = "") : t(t), value(std::move(value)) {} + Type t; + QString value; + bool operator==(const Section& other) const = default; + std::strong_ordering operator<=>(const Section& other) const; + }; + private: + void parse(); + + public: QString toString() const { return m_string; } bool isEmpty() const { return m_string.isEmpty(); } friend QDebug operator<<(QDebug debug, const Version& v); - private: - struct Section { - explicit Section(QString fullString) : m_fullString(std::move(fullString)) - { - qsizetype cutoff = m_fullString.size(); - for (int i = 0; i < m_fullString.size(); i++) { - if (!m_fullString[i].isDigit()) { - cutoff = i; - break; - } - } - - auto numPart = QStringView{ m_fullString }.left(cutoff); - - if (!numPart.isEmpty()) { - m_isNull = false; - m_numPart = numPart.toInt(); - } - - auto stringPart = QStringView{ m_fullString }.mid(cutoff); - - if (!stringPart.isEmpty()) { - m_isNull = false; - m_stringPart = stringPart.toString(); - } - } - - explicit Section() = default; - - bool m_isNull = true; - - int m_numPart = 0; - QString m_stringPart; - - QString m_fullString; - - inline bool isAppendix() const { return m_stringPart.startsWith('+'); } - inline bool isPreRelease() const { return m_stringPart.startsWith('-') && m_stringPart.length() > 1; } - - inline bool operator==(const Section& other) const - { - if (m_isNull && !other.m_isNull) - return false; - if (!m_isNull && other.m_isNull) - return false; - - if (!m_isNull && !other.m_isNull) { - return (m_numPart == other.m_numPart) && (m_stringPart == other.m_stringPart); - } - - return true; - } - - inline bool operator<(const Section& other) const - { - static auto unequal_is_less = [](const Section& non_null) -> bool { - if (non_null.m_stringPart.isEmpty()) - return non_null.m_numPart == 0; - return (non_null.m_stringPart != QLatin1Char('.')) && non_null.isPreRelease(); - }; - - if (!m_isNull && other.m_isNull) - return unequal_is_less(*this); - if (m_isNull && !other.m_isNull) - return !unequal_is_less(other); - - if (!m_isNull && !other.m_isNull) { - if (m_numPart < other.m_numPart) - return true; - if (m_numPart == other.m_numPart && m_stringPart < other.m_stringPart) - return true; - - if (!m_stringPart.isEmpty() && other.m_stringPart.isEmpty()) - return false; - if (m_stringPart.isEmpty() && !other.m_stringPart.isEmpty()) - return true; - - return false; - } - - return m_fullString < other.m_fullString; - } - - inline bool operator!=(const Section& other) const { return !(*this == other); } - inline bool operator>(const Section& other) const { return !(*this < other || *this == other); } - }; + bool operator==(const Version& other) const { return (*this <=> other) == std::strong_ordering::equal; } + std::strong_ordering operator<=>(const Version& other) const; private: QString m_string; QList
m_sections; - - void parse(); -}; +}; \ No newline at end of file diff --git a/launcher/modplatform/packwiz/Packwiz.cpp b/launcher/modplatform/packwiz/Packwiz.cpp index 4b3d75169..aa1f01601 100644 --- a/launcher/modplatform/packwiz/Packwiz.cpp +++ b/launcher/modplatform/packwiz/Packwiz.cpp @@ -22,6 +22,8 @@ #include #include #include +#include +#include #include #include #include @@ -91,6 +93,15 @@ auto intEntry(toml::table table, QString entry_name) -> int return node.value_or(0); } +bool sortMCVersions(const QString& a, const QString& b) +{ + auto cmp = Version(a) <=> Version(b); + if (cmp == std::strong_ordering::equal) { + return a < b; + } + return cmp == std::strong_ordering::less; +}; + auto V1::createModFormat([[maybe_unused]] const QDir& index_dir, ModPlatform::IndexedPack& mod_pack, ModPlatform::IndexedVersion& mod_version) -> Mod @@ -117,8 +128,8 @@ auto V1::createModFormat([[maybe_unused]] const QDir& index_dir, mod.side = mod_version.side == ModPlatform::Side::NoSide ? mod_pack.side : mod_version.side; mod.loaders = mod_version.loaders; mod.mcVersions = mod_version.mcVersion; - std::sort(mod.mcVersions.begin(), mod.mcVersions.end(), - [](QString a, QString b) { return Version(std::move(a)) < Version(std::move(b)); }); + mod.mcVersions.removeDuplicates(); + std::ranges::sort(mod.mcVersions, sortMCVersions); mod.releaseType = mod_version.version_type; mod.version_number = mod_version.version_number; @@ -304,8 +315,8 @@ auto V1::getIndexForMod(const QDir& index_dir, QString slug) -> Mod } } } - std::sort(mod.mcVersions.begin(), mod.mcVersions.end(), - [](QString a, QString b) { return Version(std::move(a)) < Version(std::move(b)); }); + mod.mcVersions.removeDuplicates(); + std::ranges::sort(mod.mcVersions, sortMCVersions); } } mod.version_number = table["x-prismlauncher-version-number"].value_or(""); diff --git a/tests/testdata/Version/test_vectors.txt b/tests/testdata/Version/test_vectors.txt index e6c6507cf..971f23daf 100644 --- a/tests/testdata/Version/test_vectors.txt +++ b/tests/testdata/Version/test_vectors.txt @@ -1,5 +1,5 @@ # Test vector from: -# https://github.com/unascribed/FlexVer/blob/704e12759b6e59220ff888f8bf2ec15b8f8fd969/test/test_vectors.txt +# https://git.sleeping.town/exa/FlexVer/src/branch/trunk/test/test_vectors.txt # # This test file is formatted as " ", seperated by the space character # Implementations should ignore lines starting with "#" and lines that have a length of 0 @@ -61,3 +61,10 @@ a1.1.2 < a1.1.2_01 13w02a < c0.3.0_01 0.6.0-1.18.x < 0.9.beta-1.18.x +# removeLeadingZeroes (#17) +0000.0.0 = 0.0.0 +0000.00.0 = 0.00.0 +0.0.0 = 0.00.0000 +# General leading zeroes +1.0.01 = 1.0.1 +1.0.0001 = 1.0.01 From 8427626e5615e3c54a172a4779bd81e1c8ade2ce Mon Sep 17 00:00:00 2001 From: Trial97 Date: Wed, 1 Apr 2026 19:16:29 +0300 Subject: [PATCH 2/3] add modrinth pre-release support to flexVer implementation extended the flexVer implementation to consider any space that is after a numeric section as a pre-release. Signed-off-by: Trial97 --- launcher/Version.cpp | 11 +++++++---- tests/Version_test.cpp | 27 ++++++++++++++++++++++++++- 2 files changed, 33 insertions(+), 5 deletions(-) diff --git a/launcher/Version.cpp b/launcher/Version.cpp index fdfd2721b..ebe31b12a 100644 --- a/launcher/Version.cpp +++ b/launcher/Version.cpp @@ -107,9 +107,11 @@ void Version::parse() if (c == '+') { break; // Ignore appendices } - if (c == '-') { + // custom: the space is special to handle the strings like "1.20 Pre-Release 1" + // this is needed to support Modrinth versions + if (c == '-' || c == ' ') { // Add dash to component - cur.value += '-'; + cur.value += c; i++; // If the next rune is non-digit, mark as pre-release (requires >= 1 non-digit after dash so the component has length > 1) if (i < len && !m_string.at(i).isDigit()) { @@ -121,8 +123,9 @@ void Version::parse() } for (; i < len; i++) { auto r = m_string.at(i); - if ((r.isDigit() != (cur.t == Section::Type::Numeric)) || - (r == '-' && cur.t != Section::Type::PreRelease) // "---" is a valid pre-release component + if ((r.isDigit() != (cur.t == Section::Type::Numeric)) // starts a new section + || (r == ' ' && cur.t == Section::Type::Numeric) // custom: numeric section then a space is a pre-release + || (r == '-' && cur.t != Section::Type::PreRelease) // "---" is a valid pre-release component || r == '+') { // Run completed (do not consume this rune) break; diff --git a/tests/Version_test.cpp b/tests/Version_test.cpp index f3bdfec14..f6ac6f951 100644 --- a/tests/Version_test.cpp +++ b/tests/Version_test.cpp @@ -181,8 +181,33 @@ class VersionTest : public QObject { QCOMPARE(v1 > v2, !lessThan && !equal); QCOMPARE(v1 == v2, equal); } + + void test_strict_weak_order() + { + // this tests the strict_weak_order + // https://en.cppreference.com/w/cpp/concepts/strict_weak_order.html + Version a("1.10 Pre-Release 1"); // this is a pre-relese is before b because ' ' is lower than '-' + Version b("1.10-pre1"); // this is a pre-release is before c that is an actual release + Version c("1.10"); + + auto r = [](const Version& a, const Version& b) { return a < b; }; + auto e = [&r](const Version& a, const Version& b) { return !r(a, b) && !r(b, a); }; + + qCritical() << a << b << c; + + // irreflexive + QCOMPARE(r(a, a), false); + QCOMPARE(r(b, b), false); + QCOMPARE(r(c, c), false); + // transitive + QCOMPARE(r(a, b), true); + QCOMPARE(r(b, c), true); + QCOMPARE(r(a, c), true); + // transitive equivalence + QCOMPARE(e(a, b) && e(b, c), e(a, c)); + } }; QTEST_GUILESS_MAIN(VersionTest) -#include "Version_test.moc" +#include "Version_test.moc" \ No newline at end of file From 087ffb26ba2703b3e285d4f6fb43249d65db5a4e Mon Sep 17 00:00:00 2001 From: Trial97 Date: Wed, 1 Apr 2026 23:46:18 +0300 Subject: [PATCH 3/3] clang-tidy: fix warnings Signed-off-by: Trial97 --- launcher/Version.cpp | 2 +- launcher/Version.h | 2 +- launcher/modplatform/packwiz/Packwiz.cpp | 49 +++++++++++++----------- launcher/modplatform/packwiz/Packwiz.h | 2 - tests/Version_test.cpp | 8 ++-- 5 files changed, 32 insertions(+), 31 deletions(-) diff --git a/launcher/Version.cpp b/launcher/Version.cpp index ebe31b12a..d5496ce1c 100644 --- a/launcher/Version.cpp +++ b/launcher/Version.cpp @@ -27,7 +27,7 @@ /// qDebug print support for the Version class QDebug operator<<(QDebug debug, const Version& v) { - QDebugStateSaver saver(debug); + const QDebugStateSaver saver(debug); debug.nospace() << "Version{ string: " << v.toString() << ", sections: [ "; diff --git a/launcher/Version.h b/launcher/Version.h index d123f8348..c0f70f487 100644 --- a/launcher/Version.h +++ b/launcher/Version.h @@ -29,7 +29,7 @@ // https://git.sleeping.town/exa/FlexVer class Version { public: - Version(QString str) : m_string(std::move(str)) { parse(); } + Version(QString str) : m_string(std::move(str)) { parse(); } // NOLINT(hicpp-explicit-conversions) Version() = default; private: diff --git a/launcher/modplatform/packwiz/Packwiz.cpp b/launcher/modplatform/packwiz/Packwiz.cpp index aa1f01601..4d162a90d 100644 --- a/launcher/modplatform/packwiz/Packwiz.cpp +++ b/launcher/modplatform/packwiz/Packwiz.cpp @@ -38,59 +38,61 @@ namespace Packwiz { -auto getRealIndexName(const QDir& index_dir, QString normalized_fname, bool should_find_match) -> QString +namespace { +auto getRealIndexName(const QDir& indexDir, const QString& normalizedFname, bool shouldFindMatch = false) -> QString { - QFile index_file(index_dir.absoluteFilePath(normalized_fname)); + const QFile indexFile(indexDir.absoluteFilePath(normalizedFname)); - QString real_fname = normalized_fname; - if (!index_file.exists()) { + QString realFname = normalizedFname; + if (!indexFile.exists()) { // Tries to get similar entries - for (auto& file_name : index_dir.entryList(QDir::Filter::Files)) { - if (!QString::compare(normalized_fname, file_name, Qt::CaseInsensitive)) { - real_fname = file_name; + for (auto& fileName : indexDir.entryList(QDir::Filter::Files)) { + if (QString::compare(normalizedFname, fileName, Qt::CaseInsensitive) == 0) { + realFname = fileName; break; } } - if (should_find_match && !QString::compare(normalized_fname, real_fname, Qt::CaseSensitive)) { + if (shouldFindMatch && (QString::compare(normalizedFname, realFname, Qt::CaseSensitive) == 0)) { qCritical() << "Could not find a match for a valid metadata file!"; - qCritical() << "File:" << normalized_fname; + qCritical() << "File:" << normalizedFname; return {}; } } - return real_fname; + return realFname; } // Helpers -static inline auto indexFileName(const QString& mod_slug) -> QString +auto indexFileName(const QString& modSlug) -> QString { - if (mod_slug.endsWith(".pw.toml")) - return mod_slug; - return QString("%1.pw.toml").arg(mod_slug); + if (modSlug.endsWith(".pw.toml")) { + return modSlug; + } + return QString("%1.pw.toml").arg(modSlug); } // Helper functions for extracting data from the TOML file -auto stringEntry(toml::table table, QString entry_name) -> QString +auto stringEntry(toml::table table, const QString& entryName) -> QString { - auto node = table[StringUtils::toStdString(entry_name)]; + auto* node = table.get(StringUtils::toStdString(entryName)); if (!node) { - qDebug() << "Failed to read str property '" + entry_name + "' in mod metadata."; + qDebug() << "Failed to read str property '" + entryName + "' in mod metadata."; return {}; } - return node.value_or(""); + return node->value_or(""); } -auto intEntry(toml::table table, QString entry_name) -> int +auto intEntry(toml::table table, const QString& entryName) -> int { - auto node = table[StringUtils::toStdString(entry_name)]; + auto* node = table.get(StringUtils::toStdString(entryName)); if (!node) { - qDebug() << "Failed to read int property '" + entry_name + "' in mod metadata."; + qDebug() << "Failed to read int property '" + entryName + "' in mod metadata."; return {}; } - return node.value_or(0); + return node->value_or(0); } bool sortMCVersions(const QString& a, const QString& b) @@ -100,8 +102,9 @@ bool sortMCVersions(const QString& a, const QString& b) return a < b; } return cmp == std::strong_ordering::less; -}; +} +} // namespace auto V1::createModFormat([[maybe_unused]] const QDir& index_dir, ModPlatform::IndexedPack& mod_pack, ModPlatform::IndexedVersion& mod_version) -> Mod diff --git a/launcher/modplatform/packwiz/Packwiz.h b/launcher/modplatform/packwiz/Packwiz.h index b5b8894f3..b5b817755 100644 --- a/launcher/modplatform/packwiz/Packwiz.h +++ b/launcher/modplatform/packwiz/Packwiz.h @@ -29,8 +29,6 @@ class QDir; namespace Packwiz { -auto getRealIndexName(const QDir& index_dir, QString normalized_index_name, bool should_match = false) -> QString; - class V1 { public: // can also represent other resources beside loader mods - but this is what packwiz calls it diff --git a/tests/Version_test.cpp b/tests/Version_test.cpp index f6ac6f951..3e52f4eb9 100644 --- a/tests/Version_test.cpp +++ b/tests/Version_test.cpp @@ -182,13 +182,13 @@ class VersionTest : public QObject { QCOMPARE(v1 == v2, equal); } - void test_strict_weak_order() + static void test_strict_weak_order() { // this tests the strict_weak_order // https://en.cppreference.com/w/cpp/concepts/strict_weak_order.html - Version a("1.10 Pre-Release 1"); // this is a pre-relese is before b because ' ' is lower than '-' - Version b("1.10-pre1"); // this is a pre-release is before c that is an actual release - Version c("1.10"); + const Version a("1.10 Pre-Release 1"); // this is a pre-relese is before b because ' ' is lower than '-' + const Version b("1.10-pre1"); // this is a pre-release is before c that is an actual release + const Version c("1.10"); auto r = [](const Version& a, const Version& b) { return a < b; }; auto e = [&r](const Version& a, const Version& b) { return !r(a, b) && !r(b, a); };