From fe40d856e275a2715277fab38db004953f2e6dce Mon Sep 17 00:00:00 2001 From: Takuro Ashie Date: Tue, 9 Jun 2026 17:05:58 +0900 Subject: [PATCH] Fix LRUCache::insert() handling for existing entries and empty caches LRUCache::insert() returned nullptr when the key already existed in the cache. Some callers assumed that a prior find() guaranteed that insert() would always succeed, which could lead to nullptr dereference and crashes during pinyin matching. Update insert() to return the existing value for duplicate keys instead of returning nullptr. Also add handling for zero-capacity caches and assert that eviction is never attempted on an empty list. Additionally, add a defensive nullptr check in PinyinDictionaryPrivate::matchWordsForOnePath() to avoid crashing if insert() unexpectedly fails. This fixes crashes observed during incremental pinyin input such as typing "nih". --- src/libime/core/lrucache.h | 33 ++++++++++++++++++-------- src/libime/pinyin/pinyindictionary.cpp | 4 ++++ 2 files changed, 27 insertions(+), 10 deletions(-) diff --git a/src/libime/core/lrucache.h b/src/libime/core/lrucache.h index ce8d447..795cd28 100644 --- a/src/libime/core/lrucache.h +++ b/src/libime/core/lrucache.h @@ -40,19 +40,31 @@ class LRUCache { template value_type *insert(const key_type &key, Args &&...args) { + if (sz_ == 0) { + return nullptr; + } + auto iter = dict_.find(key); - if (iter == dict_.end()) { - if (size() >= sz_) { - evict(); - } - - order_.push_front(key); - auto r = dict_.emplace( - key, std::make_pair(value_type(std::forward(args)...), - order_.begin())); + if (iter != dict_.end()) { + return &iter->second.first; + } + + if (size() >= sz_) { + evict(); + } + + auto listIter = order_.insert(order_.begin(), key); + + auto r = dict_.emplace(key, + std::make_pair(value_type(std::forward(args)...), + listIter)); + + if (!r.second) { + order_.erase(listIter); return &r.first->second.first; } - return nullptr; + + return &r.first->second.first; } void erase(const key_type &key) { @@ -86,6 +98,7 @@ class LRUCache { private: void evict() { // evict item from the end of most recently used list + assert(!order_.empty()); auto i = std::prev(order_.end()); dict_.erase(*i); order_.erase(i); diff --git a/src/libime/pinyin/pinyindictionary.cpp b/src/libime/pinyin/pinyindictionary.cpp index 3a8a870..ebc543f 100644 --- a/src/libime/pinyin/pinyindictionary.cpp +++ b/src/libime/pinyin/pinyindictionary.cpp @@ -544,6 +544,10 @@ bool PinyinDictionaryPrivate::matchWordsForOnePath( if (!result) { result = matchCache.insert(context.hasher_.pathToPinyins(path.path_)); + if (!result) { + FCITX_ERROR() << "Unexpected nullptr from insert"; + return false; + } result->clear(); auto &items = *result;