mirror of
https://github.com/PrismLauncher/PrismLauncher.git
synced 2026-06-29 01:54:20 +03:00
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 <alexandru.tripon97@gmail.com>
This commit is contained in:
parent
156b7f365e
commit
5a0931d3cf
4 changed files with 175 additions and 226 deletions
|
|
@ -1,111 +1,28 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
/*
|
||||
* Prism Launcher - Minecraft Launcher
|
||||
* Copyright (C) 2023 flowln <flowlnlnln@gmail.com>
|
||||
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
|
||||
* Copyright (c) 2026 Trial97 <alexandru.tripon97@gmail.com>
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include "Version.h"
|
||||
|
||||
#include <QDebug>
|
||||
#include <QRegularExpressionMatch>
|
||||
#include <QUrl>
|
||||
|
||||
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<QChar> 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 <compare>
|
||||
|
||||
/// 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;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue