LanguageClient: Prevent duplicate references due to file aliasing

This problem has been observed with clangd, but it's probably a good idea to apply the check generally.
Note that in the case of renaming, omitting the filtering can lead to file corruption.

Task-number: QTCREATORBUG-30546
Change-Id: I007edbae2cba5f59e427ab07e183162df9e99367
Reviewed-by: David Schulz <david.schulz@qt.io>
This commit is contained in:
Christian Kandeler
2024-03-18 16:58:32 +01:00
committed by David Schulz
parent e3302afd09
commit 5afdc47760
2 changed files with 30 additions and 3 deletions

View File

@@ -291,12 +291,17 @@ void ClangdFindReferences::Private::handleFindUsagesResult(const QList<Location>
for (const Location &loc : locations) for (const Location &loc : locations)
fileData[loc.uri()].rangesAndLineText.push_back({loc.range(), {}}); fileData[loc.uri()].rangesAndLineText.push_back({loc.range(), {}});
QSet<FilePath> canonicalFilePaths;
for (auto it = fileData.begin(); it != fileData.end();) { for (auto it = fileData.begin(); it != fileData.end();) {
const Utils::FilePath filePath = client()->serverUriToHostPath(it.key()); const Utils::FilePath filePath = client()->serverUriToHostPath(it.key());
if (!filePath.exists()) { // https://github.com/clangd/clangd/issues/935 if (!filePath.exists()) { // https://github.com/clangd/clangd/issues/935
it = fileData.erase(it); it = fileData.erase(it);
continue; continue;
} }
if (!Utils::insert(canonicalFilePaths, filePath.canonicalPath())) { // QTCREATORBUG-30546
it = fileData.erase(it);
continue;
}
const QStringList lines = SymbolSupport::getFileContents(filePath); const QStringList lines = SymbolSupport::getFileContents(filePath);
it->fileContent = lines.join('\n'); it->fileContent = lines.join('\n');
for (auto &rangeWithText : it.value().rangesAndLineText) { for (auto &rangeWithText : it.value().rangesAndLineText) {

View File

@@ -274,6 +274,10 @@ struct ItemData
Utils::Text::Range range; Utils::Text::Range range;
QVariant userData; QVariant userData;
}; };
bool operator==(const ItemData &id1, const ItemData &id2)
{
return id1.range == id2.range && id1.userData == id2.userData;
}
QStringList SymbolSupport::getFileContents(const Utils::FilePath &filePath) QStringList SymbolSupport::getFileContents(const Utils::FilePath &filePath)
{ {
@@ -342,15 +346,32 @@ Utils::SearchResultItems generateSearchResultItems(
return result; return result;
} }
using ItemDataPerPath = QMap<Utils::FilePath, QList<ItemData>>;
void filterFileAliases(ItemDataPerPath &itemDataPerPath)
{
QSet<Utils::FilePath> canonicalPaths;
for (auto it = itemDataPerPath.begin(); it != itemDataPerPath.end(); ) {
const Utils::FilePath canonicalPath = it.key().canonicalPath();
if (!Utils::insert(canonicalPaths, canonicalPath)
&& it.value() == itemDataPerPath.value(canonicalPath)) { // QTCREATORBUG-30546
it = itemDataPerPath.erase(it);
} else {
++it;
}
}
}
Utils::SearchResultItems generateSearchResultItems( Utils::SearchResultItems generateSearchResultItems(
const LanguageClientArray<Location> &locations, const DocumentUri::PathMapper &pathMapper) const LanguageClientArray<Location> &locations, const DocumentUri::PathMapper &pathMapper)
{ {
if (locations.isNull()) if (locations.isNull())
return {}; return {};
QMap<Utils::FilePath, QList<ItemData>> rangesInDocument; ItemDataPerPath rangesInDocument;
for (const Location &location : locations.toList()) for (const Location &location : locations.toList()) {
rangesInDocument[location.uri().toFilePath(pathMapper)] rangesInDocument[location.uri().toFilePath(pathMapper)]
<< ItemData{SymbolSupport::convertRange(location.range()), {}}; << ItemData{SymbolSupport::convertRange(location.range()), {}};
}
filterFileAliases(rangesInDocument);
return generateSearchResultItems(rangesInDocument); return generateSearchResultItems(rangesInDocument);
} }
@@ -552,7 +573,7 @@ Utils::SearchResultItems generateReplaceItems(const WorkspaceEdit &edits,
return ItemData{SymbolSupport::convertRange(edit.range()), QVariant(edit)}; return ItemData{SymbolSupport::convertRange(edit.range()), QVariant(edit)};
}); });
}; };
QMap<Utils::FilePath, QList<ItemData>> rangesInDocument; ItemDataPerPath rangesInDocument;
auto documentChanges = edits.documentChanges().value_or(QList<DocumentChange>()); auto documentChanges = edits.documentChanges().value_or(QList<DocumentChange>());
if (!documentChanges.isEmpty()) { if (!documentChanges.isEmpty()) {
for (const DocumentChange &documentChange : std::as_const(documentChanges)) { for (const DocumentChange &documentChange : std::as_const(documentChanges)) {
@@ -588,6 +609,7 @@ Utils::SearchResultItems generateReplaceItems(const WorkspaceEdit &edits,
for (auto it = changes.begin(), end = changes.end(); it != end; ++it) for (auto it = changes.begin(), end = changes.end(); it != end; ++it)
rangesInDocument[it.key().toFilePath(pathMapper)] = convertEdits(it.value()); rangesInDocument[it.key().toFilePath(pathMapper)] = convertEdits(it.value());
} }
filterFileAliases(rangesInDocument);
items += generateSearchResultItems(rangesInDocument, search, limitToProjects); items += generateSearchResultItems(rangesInDocument, search, limitToProjects);
return items; return items;
} }