diff --git a/clang-tools-extra/clangd/GlobalCompilationDatabase.cpp b/clang-tools-extra/clangd/GlobalCompilationDatabase.cpp --- a/clang-tools-extra/clangd/GlobalCompilationDatabase.cpp +++ b/clang-tools-extra/clangd/GlobalCompilationDatabase.cpp @@ -15,6 +15,7 @@ #include "llvm/ADT/None.h" #include "llvm/ADT/Optional.h" #include "llvm/ADT/STLExtras.h" +#include "llvm/ADT/SmallString.h" #include "llvm/Support/FileSystem.h" #include "llvm/Support/Path.h" #include @@ -73,16 +74,20 @@ tooling::CompileCommand GlobalCompilationDatabase::getFallbackCommand(PathRef File) const { + llvm::SmallString<128> CanonPath(File); + llvm::sys::path::remove_dots(CanonPath, true); + std::vector Argv = {getFallbackClangPath()}; // Clang treats .h files as C by default and files without extension as linker // input, resulting in unhelpful diagnostics. // Parsing as Objective C++ is friendly to more cases. - auto FileExtension = llvm::sys::path::extension(File); + auto FileExtension = llvm::sys::path::extension(CanonPath); if (FileExtension.empty() || FileExtension == ".h") Argv.push_back("-xobjective-c++-header"); - Argv.push_back(File); - tooling::CompileCommand Cmd(llvm::sys::path::parent_path(File), - llvm::sys::path::filename(File), std::move(Argv), + Argv.push_back(CanonPath.str().str()); + tooling::CompileCommand Cmd(llvm::sys::path::parent_path(CanonPath), + llvm::sys::path::filename(CanonPath), + std::move(Argv), /*Output=*/""); Cmd.Heuristic = "clangd fallback"; return Cmd; @@ -136,6 +141,8 @@ CDBLookupRequest Request) const { assert(llvm::sys::path::is_absolute(Request.FileName) && "path must be absolute"); + llvm::SmallString<128> CanonPath(Request.FileName); + llvm::sys::path::remove_dots(CanonPath, true); CDBLookupResult Result; bool SentBroadcast = false; @@ -148,7 +155,7 @@ Result.PI.SourceRoot = *CompileCommandsDir; } else { actOnAllParentDirectories( - Request.FileName, [this, &SentBroadcast, &Result](PathRef Path) { + CanonPath, [this, &SentBroadcast, &Result](PathRef Path) { std::tie(Result.CDB, SentBroadcast) = getCDBInDirLocked(Path); Result.PI.SourceRoot = Path; return Result.CDB != nullptr; @@ -203,13 +210,17 @@ } std::vector GovernedFiles; + llvm::SmallString<128> CanonPath; for (llvm::StringRef File : AllFiles) { // A file is governed by this CDB if lookup for the file would find it. // Independent of whether it has an entry for that file or not. actOnAllParentDirectories(File, [&](PathRef Path) { if (DirectoryHasCDB.lookup(Path)) { - if (Path == Result.PI.SourceRoot) - GovernedFiles.push_back(File); + if (Path == Result.PI.SourceRoot) { + CanonPath = File; + llvm::sys::path::remove_dots(CanonPath, true); + GovernedFiles.push_back(CanonPath.str().str()); + } // Stop as soon as we hit a CDB. return true; } @@ -245,10 +256,12 @@ llvm::Optional OverlayCDB::getCompileCommand(PathRef File) const { + llvm::SmallString<128> CanonPath(File); + llvm::sys::path::remove_dots(CanonPath, true); llvm::Optional Cmd; { std::lock_guard Lock(Mutex); - auto It = Commands.find(File); + auto It = Commands.find(CanonPath); if (It != Commands.end()) Cmd = It->second; } @@ -272,20 +285,24 @@ void OverlayCDB::setCompileCommand( PathRef File, llvm::Optional Cmd) { + llvm::SmallString<128> CanonPath(File); + llvm::sys::path::remove_dots(CanonPath, true); { std::unique_lock Lock(Mutex); if (Cmd) - Commands[File] = std::move(*Cmd); + Commands[CanonPath] = std::move(*Cmd); else - Commands.erase(File); + Commands.erase(CanonPath); } - OnCommandChanged.broadcast({File}); + OnCommandChanged.broadcast({CanonPath.str().str()}); } llvm::Optional OverlayCDB::getProjectInfo(PathRef File) const { + llvm::SmallString<128> CanonPath(File); + llvm::sys::path::remove_dots(CanonPath, true); { std::lock_guard Lock(Mutex); - auto It = Commands.find(File); + auto It = Commands.find(CanonPath); if (It != Commands.end()) return ProjectInfo{}; } diff --git a/clang-tools-extra/clangd/unittests/GlobalCompilationDatabaseTests.cpp b/clang-tools-extra/clangd/unittests/GlobalCompilationDatabaseTests.cpp --- a/clang-tools-extra/clangd/unittests/GlobalCompilationDatabaseTests.cpp +++ b/clang-tools-extra/clangd/unittests/GlobalCompilationDatabaseTests.cpp @@ -10,6 +10,7 @@ #include "Path.h" #include "TestFS.h" +#include "clang/Tooling/CompilationDatabase.h" #include "llvm/ADT/Optional.h" #include "llvm/ADT/SmallString.h" #include "llvm/ADT/StringExtras.h" @@ -31,6 +32,7 @@ using ::testing::Contains; using ::testing::ElementsAre; using ::testing::EndsWith; +using ::testing::HasSubstr; using ::testing::IsEmpty; using ::testing::Not; using ::testing::StartsWith; @@ -247,9 +249,10 @@ }); File = FS.Root; - llvm::sys::path::append(File, "a.cc"); + llvm::sys::path::append(File, "build", "..", "a.cc"); DB.getCompileCommand(File.str()); - EXPECT_THAT(DiscoveredFiles, UnorderedElementsAre(EndsWith("a.cc"))); + EXPECT_THAT(DiscoveredFiles, UnorderedElementsAre(AllOf( + EndsWith("a.cc"), Not(HasSubstr(".."))))); DiscoveredFiles.clear(); File = FS.Root; @@ -282,6 +285,28 @@ } } +TEST(GlobalCompilationDatabaseTest, NonCanonicalFilenames) { + OverlayCDB DB(nullptr); + std::vector DiscoveredFiles; + auto Sub = + DB.watch([&DiscoveredFiles](const std::vector Changes) { + DiscoveredFiles = Changes; + }); + + llvm::SmallString<128> Root(testRoot()); + llvm::sys::path::append(Root, "build", "..", "a.cc"); + DB.setCompileCommand(Root.str(), tooling::CompileCommand()); + EXPECT_THAT(DiscoveredFiles, UnorderedElementsAre(testPath("a.cc"))); + DiscoveredFiles.clear(); + + llvm::SmallString<128> File(testRoot()); + llvm::sys::path::append(File, "blabla", "..", "a.cc"); + + EXPECT_TRUE(DB.getCompileCommand(File)); + EXPECT_TRUE(DB.getProjectInfo(File)); + EXPECT_EQ(DB.getFallbackCommand(File).Directory, testRoot()); +} + } // namespace } // namespace clangd } // namespace clang