Files
AyuGramDesktop/Telegram/SourceFiles/dialogs/dialogs_pinned_list.cpp
T
John Preston 402cb0e668 Fix crash when removing folders with pinned chats.
ChatFilters::applyRemove() emptied the filter in the list and only then
detached its chats. Unpinning a chat re-caches its siblings' pinned
indices, which runs Session::refreshChatListEntry(); seeing the filter
still listed but emptied, it re-entered removeFromChatList() ->
setChatPinned() -> PinnedList::setPinned() on the same pinned list while
its re-cache loop iterated with a cached size, reading past the end of
the vector. This aborted on closing the Folders editor after removing
two or more folders that had pinned chats.

Detach the filter from the list before applyChange() tears down its
chats, so the re-entrant refresh no longer touches it. Also harden the
setPinned() re-cache loop to re-check the container size each iteration
as defense in depth.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 17:49:07 +04:00

168 lines
4.4 KiB
C++

/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "dialogs/dialogs_pinned_list.h"
#include "data/data_saved_messages.h"
#include "dialogs/dialogs_key.h"
#include "dialogs/dialogs_entry.h"
#include "history/history.h"
#include "data/data_session.h"
#include "data/data_forum.h"
namespace Dialogs {
PinnedList::PinnedList(FilterId filterId, int limit)
: _filterId(filterId)
, _limit(limit) {
Expects(limit > 0);
}
void PinnedList::setLimit(int limit) {
Expects(limit > 0);
if (_limit == limit) {
return;
}
_limit = limit;
applyLimit(_limit);
}
void PinnedList::addPinned(Key key) {
Expects(key.entry()->folderKnown());
addPinnedGetPosition(key);
}
int PinnedList::addPinnedGetPosition(Key key) {
const auto already = ranges::find(_data, key);
if (already != end(_data)) {
return already - begin(_data);
}
applyLimit(_limit - 1);
const auto position = int(_data.size());
_data.push_back(key);
key.entry()->cachePinnedIndex(_filterId, position + 1);
return position;
}
void PinnedList::setPinned(Key key, bool pinned) {
Expects(key.entry()->folderKnown() || _filterId != 0);
if (pinned) {
const int position = addPinnedGetPosition(key);
if (position) {
const auto begin = _data.begin();
std::rotate(begin, begin + position, begin + position + 1);
for (auto i = 0; i != position + 1; ++i) {
_data[i].entry()->cachePinnedIndex(_filterId, i + 1);
}
}
} else if (const auto it = ranges::find(_data, key); it != end(_data)) {
const auto index = int(it - begin(_data));
_data.erase(it);
key.entry()->cachePinnedIndex(_filterId, 0);
// cachePinnedIndex() may re-enter and erase from _data (for example
// while a filter is being removed), so re-check the size each
// iteration instead of caching it to avoid an out-of-bounds access.
for (auto i = index; i < int(size(_data)); ++i) {
_data[i].entry()->cachePinnedIndex(_filterId, i + 1);
}
}
}
void PinnedList::applyLimit(int limit) {
Expects(limit >= 0);
while (_data.size() > limit) {
setPinned(_data.back(), false);
}
}
void PinnedList::clear() {
applyLimit(0);
}
void PinnedList::applyList(
not_null<Data::Session*> owner,
const QVector<MTPDialogPeer> &list) {
Expects(this != owner->savedMessages().chatsList()->pinned());
clear();
for (const auto &peer : list) {
peer.match([&](const MTPDdialogPeer &data) {
if (const auto peerId = peerFromMTP(data.vpeer())) {
addPinned(owner->history(peerId));
}
}, [&](const MTPDdialogPeerFolder &data) {
addPinned(owner->folder(data.vfolder_id().v));
});
}
}
void PinnedList::applyList(
not_null<Data::SavedMessages*> sublistsOwner,
const QVector<MTPDialogPeer> &list) {
Expects(this == sublistsOwner->chatsList()->pinned());
clear();
for (const auto &peer : list) {
peer.match([&](const MTPDdialogPeer &data) {
if (const auto peerId = peerFromMTP(data.vpeer())) {
const auto peer = sublistsOwner->owner().peer(peerId);
addPinned(sublistsOwner->sublist(peer));
}
}, [](const MTPDdialogPeerFolder &data) {
});
}
}
void PinnedList::applyList(
not_null<Data::Forum*> forum,
const QVector<MTPint> &list) {
Expects(this == forum->topicsList()->pinned());
clear();
for (const auto &topicId : list) {
addPinned(forum->topicFor(topicId.v));
}
}
void PinnedList::applyList(const std::vector<not_null<History*>> &list) {
Expects(_filterId != 0);
const auto old = base::take(_data);
const auto count = int(list.size());
_data.reserve(count);
for (auto i = 0; i != count; ++i) {
const auto history = list[i];
_data.emplace_back(history);
history->cachePinnedIndex(_filterId, i + 1);
}
for (const auto &key : old) {
const auto history = key.history();
if (!history || !ranges::contains(_data, history, &Key::history)) {
key.entry()->cachePinnedIndex(_filterId, 0);
}
}
}
void PinnedList::reorder(Key key1, Key key2) {
const auto index1 = ranges::find(_data, key1) - begin(_data);
const auto index2 = ranges::find(_data, key2) - begin(_data);
Assert(index1 >= 0 && index1 < _data.size());
Assert(index2 >= 0 && index2 < _data.size());
Assert(index1 != index2);
std::swap(_data[index1], _data[index2]);
key1.entry()->cachePinnedIndex(_filterId, index2 + 1);
key2.entry()->cachePinnedIndex(_filterId, index1 + 1);
}
} // namespace Dialogs