[clang-tools-extra] r303977 - [clangd] Allow to use vfs::FileSystem for file accesses.
Ilya Biryukov via cfe-commits
cfe-commits at lists.llvm.org
Fri May 26 05:26:51 PDT 2017
Author: ibiryukov
Date: Fri May 26 07:26:51 2017
New Revision: 303977
URL: http://llvm.org/viewvc/llvm-project?rev=303977&view=rev
Log:
[clangd] Allow to use vfs::FileSystem for file accesses.
Summary:
Custom vfs::FileSystem is currently used for unit tests.
This revision depends on https://reviews.llvm.org/D33397.
Reviewers: bkramer, krasimir
Reviewed By: bkramer, krasimir
Subscribers: klimek, cfe-commits, mgorny
Differential Revision: https://reviews.llvm.org/D33416
Added:
clang-tools-extra/trunk/unittests/clangd/
clang-tools-extra/trunk/unittests/clangd/CMakeLists.txt
clang-tools-extra/trunk/unittests/clangd/ClangdTests.cpp
Modified:
clang-tools-extra/trunk/clangd/ClangdLSPServer.cpp
clang-tools-extra/trunk/clangd/ClangdServer.cpp
clang-tools-extra/trunk/clangd/ClangdServer.h
clang-tools-extra/trunk/clangd/ClangdUnit.cpp
clang-tools-extra/trunk/clangd/ClangdUnit.h
clang-tools-extra/trunk/clangd/ClangdUnitStore.h
clang-tools-extra/trunk/unittests/CMakeLists.txt
Modified: clang-tools-extra/trunk/clangd/ClangdLSPServer.cpp
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/clangd/ClangdLSPServer.cpp?rev=303977&r1=303976&r2=303977&view=diff
==============================================================================
--- clang-tools-extra/trunk/clangd/ClangdLSPServer.cpp (original)
+++ clang-tools-extra/trunk/clangd/ClangdLSPServer.cpp Fri May 26 07:26:51 2017
@@ -199,6 +199,7 @@ ClangdLSPServer::ClangdLSPServer(JSONOut
: Out(Out),
Server(llvm::make_unique<DirectoryBasedGlobalCompilationDatabase>(),
llvm::make_unique<LSPDiagnosticsConsumer>(*this),
+ llvm::make_unique<RealFileSystemProvider>(),
RunSynchronously) {}
void ClangdLSPServer::run(std::istream &In) {
Modified: clang-tools-extra/trunk/clangd/ClangdServer.cpp
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/clangd/ClangdServer.cpp?rev=303977&r1=303976&r2=303977&view=diff
==============================================================================
--- clang-tools-extra/trunk/clangd/ClangdServer.cpp (original)
+++ clang-tools-extra/trunk/clangd/ClangdServer.cpp Fri May 26 07:26:51 2017
@@ -58,6 +58,10 @@ Position clangd::offsetToPosition(String
return {Lines, Cols};
}
+IntrusiveRefCntPtr<vfs::FileSystem> RealFileSystemProvider::getFileSystem() {
+ return vfs::getRealFileSystem();
+}
+
ClangdScheduler::ClangdScheduler(bool RunSynchronously)
: RunSynchronously(RunSynchronously) {
if (RunSynchronously) {
@@ -135,8 +139,10 @@ void ClangdScheduler::addToEnd(std::func
ClangdServer::ClangdServer(std::unique_ptr<GlobalCompilationDatabase> CDB,
std::unique_ptr<DiagnosticsConsumer> DiagConsumer,
+ std::unique_ptr<FileSystemProvider> FSProvider,
bool RunSynchronously)
: CDB(std::move(CDB)), DiagConsumer(std::move(DiagConsumer)),
+ FSProvider(std::move(FSProvider)),
PCHs(std::make_shared<PCHContainerOperations>()),
WorkScheduler(RunSynchronously) {}
@@ -150,10 +156,11 @@ void ClangdServer::addDocument(PathRef F
assert(FileContents.Draft &&
"No contents inside a file that was scheduled for reparse");
- Units.runOnUnit(
- FileStr, *FileContents.Draft, *CDB, PCHs, [&](ClangdUnit const &Unit) {
- DiagConsumer->onDiagnosticsReady(FileStr, Unit.getLocalDiagnostics());
- });
+ Units.runOnUnit(FileStr, *FileContents.Draft, *CDB, PCHs,
+ FSProvider->getFileSystem(), [&](ClangdUnit const &Unit) {
+ DiagConsumer->onDiagnosticsReady(
+ FileStr, Unit.getLocalDiagnostics());
+ });
});
}
@@ -168,15 +175,22 @@ void ClangdServer::removeDocument(PathRe
});
}
+void ClangdServer::forceReparse(PathRef File) {
+ // The addDocument schedules the reparse even if the contents of the file
+ // never changed, so we just call it here.
+ addDocument(File, getDocument(File));
+}
+
std::vector<CompletionItem> ClangdServer::codeComplete(PathRef File,
Position Pos) {
auto FileContents = DraftMgr.getDraft(File);
assert(FileContents.Draft && "codeComplete is called for non-added document");
std::vector<CompletionItem> Result;
+ auto VFS = FSProvider->getFileSystem();
Units.runOnUnitWithoutReparse(
- File, *FileContents.Draft, *CDB, PCHs, [&](ClangdUnit &Unit) {
- Result = Unit.codeComplete(*FileContents.Draft, Pos);
+ File, *FileContents.Draft, *CDB, PCHs, VFS, [&](ClangdUnit &Unit) {
+ Result = Unit.codeComplete(*FileContents.Draft, Pos, VFS);
});
return Result;
}
Modified: clang-tools-extra/trunk/clangd/ClangdServer.h
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/clangd/ClangdServer.h?rev=303977&r1=303976&r2=303977&view=diff
==============================================================================
--- clang-tools-extra/trunk/clangd/ClangdServer.h (original)
+++ clang-tools-extra/trunk/clangd/ClangdServer.h Fri May 26 07:26:51 2017
@@ -50,6 +50,17 @@ public:
std::vector<DiagWithFixIts> Diagnostics) = 0;
};
+class FileSystemProvider {
+public:
+ virtual ~FileSystemProvider() = default;
+ virtual IntrusiveRefCntPtr<vfs::FileSystem> getFileSystem() = 0;
+};
+
+class RealFileSystemProvider : public FileSystemProvider {
+public:
+ IntrusiveRefCntPtr<vfs::FileSystem> getFileSystem() override;
+};
+
class ClangdServer;
/// Handles running WorkerRequests of ClangdServer on a separate threads.
@@ -94,6 +105,7 @@ class ClangdServer {
public:
ClangdServer(std::unique_ptr<GlobalCompilationDatabase> CDB,
std::unique_ptr<DiagnosticsConsumer> DiagConsumer,
+ std::unique_ptr<FileSystemProvider> FSProvider,
bool RunSynchronously);
/// Add a \p File to the list of tracked C++ files or update the contents if
@@ -104,6 +116,8 @@ public:
/// Remove \p File from list of tracked files, schedule a request to free
/// resources associated with it.
void removeDocument(PathRef File);
+ /// Force \p File to be reparsed using the latest contents.
+ void forceReparse(PathRef File);
/// Run code completion for \p File at \p Pos.
std::vector<CompletionItem> codeComplete(PathRef File, Position Pos);
@@ -129,6 +143,7 @@ public:
private:
std::unique_ptr<GlobalCompilationDatabase> CDB;
std::unique_ptr<DiagnosticsConsumer> DiagConsumer;
+ std::unique_ptr<FileSystemProvider> FSProvider;
DraftStore DraftMgr;
ClangdUnitStore Units;
std::shared_ptr<PCHContainerOperations> PCHs;
Modified: clang-tools-extra/trunk/clangd/ClangdUnit.cpp
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/clangd/ClangdUnit.cpp?rev=303977&r1=303976&r2=303977&view=diff
==============================================================================
--- clang-tools-extra/trunk/clangd/ClangdUnit.cpp (original)
+++ clang-tools-extra/trunk/clangd/ClangdUnit.cpp Fri May 26 07:26:51 2017
@@ -11,6 +11,7 @@
#include "clang/Frontend/ASTUnit.h"
#include "clang/Frontend/CompilerInstance.h"
#include "clang/Frontend/CompilerInvocation.h"
+#include "clang/Frontend/Utils.h"
#include "clang/Tooling/CompilationDatabase.h"
using namespace clang::clangd;
@@ -18,7 +19,8 @@ using namespace clang;
ClangdUnit::ClangdUnit(PathRef FileName, StringRef Contents,
std::shared_ptr<PCHContainerOperations> PCHs,
- std::vector<tooling::CompileCommand> Commands)
+ std::vector<tooling::CompileCommand> Commands,
+ IntrusiveRefCntPtr<vfs::FileSystem> VFS)
: FileName(FileName), PCHs(PCHs) {
assert(!Commands.empty() && "No compile commands provided");
@@ -48,10 +50,16 @@ ClangdUnit::ClangdUnit(PathRef FileName,
/*PrecompilePreambleAfterNParses=*/1, /*TUKind=*/TU_Prefix,
/*CacheCodeCompletionResults=*/true,
/*IncludeBriefCommentsInCodeCompletion=*/true,
- /*AllowPCHWithCompilerErrors=*/true));
+ /*AllowPCHWithCompilerErrors=*/true,
+ /*SkipFunctionBodies=*/false,
+ /*UserFilesAreVolatile=*/false, /*ForSerialization=*/false,
+ /*ModuleFormat=*/llvm::None,
+ /*ErrAST=*/nullptr, VFS));
+ assert(Unit && "Unit wasn't created");
}
-void ClangdUnit::reparse(StringRef Contents) {
+void ClangdUnit::reparse(StringRef Contents,
+ IntrusiveRefCntPtr<vfs::FileSystem> VFS) {
// Do a reparse if this wasn't the first parse.
// FIXME: This might have the wrong working directory if it changed in the
// meantime.
@@ -59,7 +67,7 @@ void ClangdUnit::reparse(StringRef Conte
FileName,
llvm::MemoryBuffer::getMemBufferCopy(Contents, FileName).release());
- Unit->Reparse(PCHs, RemappedSource);
+ Unit->Reparse(PCHs, RemappedSource, VFS);
}
namespace {
@@ -146,8 +154,9 @@ public:
};
} // namespace
-std::vector<CompletionItem> ClangdUnit::codeComplete(StringRef Contents,
- Position Pos) {
+std::vector<CompletionItem>
+ClangdUnit::codeComplete(StringRef Contents, Position Pos,
+ IntrusiveRefCntPtr<vfs::FileSystem> VFS) {
CodeCompleteOptions CCO;
CCO.IncludeBriefComments = 1;
// This is where code completion stores dirty buffers. Need to free after
@@ -163,8 +172,10 @@ std::vector<CompletionItem> ClangdUnit::
FileName,
llvm::MemoryBuffer::getMemBufferCopy(Contents, FileName).release());
+ IntrusiveRefCntPtr<FileManager> FileMgr(
+ new FileManager(Unit->getFileSystemOpts(), VFS));
IntrusiveRefCntPtr<SourceManager> SourceMgr(
- new SourceManager(*DiagEngine, Unit->getFileManager()));
+ new SourceManager(*DiagEngine, *FileMgr));
// CodeComplete seems to require fresh LangOptions.
LangOptions LangOpts = Unit->getLangOpts();
// The language server protocol uses zero-based line and column numbers.
@@ -172,8 +183,8 @@ std::vector<CompletionItem> ClangdUnit::
Unit->CodeComplete(FileName, Pos.line + 1, Pos.character + 1, RemappedSource,
CCO.IncludeMacros, CCO.IncludeCodePatterns,
CCO.IncludeBriefComments, Collector, PCHs, *DiagEngine,
- LangOpts, *SourceMgr, Unit->getFileManager(),
- StoredDiagnostics, OwnedBuffers);
+ LangOpts, *SourceMgr, *FileMgr, StoredDiagnostics,
+ OwnedBuffers);
for (const llvm::MemoryBuffer *Buffer : OwnedBuffers)
delete Buffer;
return Items;
Modified: clang-tools-extra/trunk/clangd/ClangdUnit.h
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/clangd/ClangdUnit.h?rev=303977&r1=303976&r2=303977&view=diff
==============================================================================
--- clang-tools-extra/trunk/clangd/ClangdUnit.h (original)
+++ clang-tools-extra/trunk/clangd/ClangdUnit.h Fri May 26 07:26:51 2017
@@ -10,8 +10,8 @@
#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_CLANGDUNIT_H
#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_CLANGDUNIT_H
-#include "Protocol.h"
#include "Path.h"
+#include "Protocol.h"
#include "clang/Frontend/ASTUnit.h"
#include "clang/Tooling/Core/Replacement.h"
#include <memory>
@@ -24,6 +24,10 @@ namespace clang {
class ASTUnit;
class PCHContainerOperations;
+namespace vfs {
+class FileSystem;
+}
+
namespace tooling {
struct CompileCommand;
}
@@ -42,16 +46,19 @@ class ClangdUnit {
public:
ClangdUnit(PathRef FileName, StringRef Contents,
std::shared_ptr<PCHContainerOperations> PCHs,
- std::vector<tooling::CompileCommand> Commands);
+ std::vector<tooling::CompileCommand> Commands,
+ IntrusiveRefCntPtr<vfs::FileSystem> VFS);
/// Reparse with new contents.
- void reparse(StringRef Contents);
+ void reparse(StringRef Contents, IntrusiveRefCntPtr<vfs::FileSystem> VFS);
/// Get code completions at a specified \p Line and \p Column in \p File.
///
/// This function is thread-safe and returns completion items that own the
/// data they contain.
- std::vector<CompletionItem> codeComplete(StringRef Contents, Position Pos);
+ std::vector<CompletionItem>
+ codeComplete(StringRef Contents, Position Pos,
+ IntrusiveRefCntPtr<vfs::FileSystem> VFS);
/// Returns diagnostics and corresponding FixIts for each diagnostic that are
/// located in the current file.
std::vector<DiagWithFixIts> getLocalDiagnostics() const;
Modified: clang-tools-extra/trunk/clangd/ClangdUnitStore.h
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/clangd/ClangdUnitStore.h?rev=303977&r1=303976&r2=303977&view=diff
==============================================================================
--- clang-tools-extra/trunk/clangd/ClangdUnitStore.h (original)
+++ clang-tools-extra/trunk/clangd/ClangdUnitStore.h Fri May 26 07:26:51 2017
@@ -32,9 +32,10 @@ public:
template <class Func>
void runOnUnit(PathRef File, StringRef FileContents,
GlobalCompilationDatabase &CDB,
- std::shared_ptr<PCHContainerOperations> PCHs, Func Action) {
+ std::shared_ptr<PCHContainerOperations> PCHs,
+ IntrusiveRefCntPtr<vfs::FileSystem> VFS, Func Action) {
runOnUnitImpl(File, FileContents, CDB, PCHs, /*ReparseBeforeAction=*/true,
- std::forward<Func>(Action));
+ VFS, std::forward<Func>(Action));
}
/// Run specified \p Action on the ClangdUnit for \p File.
@@ -45,9 +46,10 @@ public:
void runOnUnitWithoutReparse(PathRef File, StringRef FileContents,
GlobalCompilationDatabase &CDB,
std::shared_ptr<PCHContainerOperations> PCHs,
+ IntrusiveRefCntPtr<vfs::FileSystem> VFS,
Func Action) {
runOnUnitImpl(File, FileContents, CDB, PCHs, /*ReparseBeforeAction=*/false,
- std::forward<Func>(Action));
+ VFS, std::forward<Func>(Action));
}
/// Run the specified \p Action on the ClangdUnit for \p File.
@@ -71,24 +73,23 @@ private:
void runOnUnitImpl(PathRef File, StringRef FileContents,
GlobalCompilationDatabase &CDB,
std::shared_ptr<PCHContainerOperations> PCHs,
- bool ReparseBeforeAction, Func Action) {
+ bool ReparseBeforeAction,
+ IntrusiveRefCntPtr<vfs::FileSystem> VFS, Func Action) {
std::lock_guard<std::mutex> Lock(Mutex);
auto Commands = getCompileCommands(CDB, File);
assert(!Commands.empty() &&
"getCompileCommands should add default command");
- // chdir. This is thread hostile.
- // FIXME(ibiryukov): get rid of this
- llvm::sys::fs::set_current_path(Commands.front().Directory);
+ VFS->setCurrentWorkingDirectory(Commands.front().Directory);
auto It = OpenedFiles.find(File);
if (It == OpenedFiles.end()) {
It = OpenedFiles
.insert(std::make_pair(
- File, ClangdUnit(File, FileContents, PCHs, Commands)))
+ File, ClangdUnit(File, FileContents, PCHs, Commands, VFS)))
.first;
} else if (ReparseBeforeAction) {
- It->second.reparse(FileContents);
+ It->second.reparse(FileContents, VFS);
}
return Action(It->second);
}
Modified: clang-tools-extra/trunk/unittests/CMakeLists.txt
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/unittests/CMakeLists.txt?rev=303977&r1=303976&r2=303977&view=diff
==============================================================================
--- clang-tools-extra/trunk/unittests/CMakeLists.txt (original)
+++ clang-tools-extra/trunk/unittests/CMakeLists.txt Fri May 26 07:26:51 2017
@@ -11,4 +11,5 @@ add_subdirectory(clang-move)
add_subdirectory(clang-query)
add_subdirectory(clang-tidy)
add_subdirectory(clang-rename)
+add_subdirectory(clangd)
add_subdirectory(include-fixer)
Added: clang-tools-extra/trunk/unittests/clangd/CMakeLists.txt
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/unittests/clangd/CMakeLists.txt?rev=303977&view=auto
==============================================================================
--- clang-tools-extra/trunk/unittests/clangd/CMakeLists.txt (added)
+++ clang-tools-extra/trunk/unittests/clangd/CMakeLists.txt Fri May 26 07:26:51 2017
@@ -0,0 +1,24 @@
+set(LLVM_LINK_COMPONENTS
+ support
+ )
+
+get_filename_component(CLANGD_SOURCE_DIR
+ ${CMAKE_CURRENT_SOURCE_DIR}/../../clangd REALPATH)
+include_directories(
+ ${CLANGD_SOURCE_DIR}
+ )
+
+add_extra_unittest(ClangdTests
+ ClangdTests.cpp
+ )
+
+target_link_libraries(ClangdTests
+ clangBasic
+ clangDaemon
+ clangFormat
+ clangFrontend
+ clangSema
+ clangTooling
+ clangToolingCore
+ LLVMSupport
+ )
Added: clang-tools-extra/trunk/unittests/clangd/ClangdTests.cpp
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/unittests/clangd/ClangdTests.cpp?rev=303977&view=auto
==============================================================================
--- clang-tools-extra/trunk/unittests/clangd/ClangdTests.cpp (added)
+++ clang-tools-extra/trunk/unittests/clangd/ClangdTests.cpp Fri May 26 07:26:51 2017
@@ -0,0 +1,364 @@
+//===-- ClangdTests.cpp - Clangd unit tests ---------------------*- C++ -*-===//
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+#include "ClangdServer.h"
+#include "clang/Basic/VirtualFileSystem.h"
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/ADT/StringMap.h"
+#include "llvm/Config/config.h"
+#include "llvm/Support/Errc.h"
+#include "llvm/Support/Path.h"
+#include "llvm/Support/Regex.h"
+#include "gtest/gtest.h"
+#include <algorithm>
+#include <iostream>
+#include <string>
+#include <vector>
+
+namespace clang {
+namespace vfs {
+
+/// An implementation of vfs::FileSystem that only allows access to
+/// files and folders inside a set of whitelisted directories.
+///
+/// FIXME(ibiryukov): should it also emulate access to parents of whitelisted
+/// directories with only whitelisted contents?
+class FilteredFileSystem : public vfs::FileSystem {
+public:
+ /// The paths inside \p WhitelistedDirs should be absolute
+ FilteredFileSystem(std::vector<std::string> WhitelistedDirs,
+ IntrusiveRefCntPtr<vfs::FileSystem> InnerFS)
+ : WhitelistedDirs(std::move(WhitelistedDirs)), InnerFS(InnerFS) {
+ assert(std::all_of(WhitelistedDirs.begin(), WhitelistedDirs.end(),
+ [](const std::string &Path) -> bool {
+ return llvm::sys::path::is_absolute(Path);
+ }) &&
+ "Not all WhitelistedDirs are absolute");
+ }
+
+ virtual llvm::ErrorOr<Status> status(const Twine &Path) {
+ if (!isInsideWhitelistedDir(Path))
+ return llvm::errc::no_such_file_or_directory;
+ return InnerFS->status(Path);
+ }
+
+ virtual llvm::ErrorOr<std::unique_ptr<File>>
+ openFileForRead(const Twine &Path) {
+ if (!isInsideWhitelistedDir(Path))
+ return llvm::errc::no_such_file_or_directory;
+ return InnerFS->openFileForRead(Path);
+ }
+
+ llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>>
+ getBufferForFile(const Twine &Name, int64_t FileSize = -1,
+ bool RequiresNullTerminator = true,
+ bool IsVolatile = false) {
+ if (!isInsideWhitelistedDir(Name))
+ return llvm::errc::no_such_file_or_directory;
+ return InnerFS->getBufferForFile(Name, FileSize, RequiresNullTerminator,
+ IsVolatile);
+ }
+
+ virtual directory_iterator dir_begin(const Twine &Dir, std::error_code &EC) {
+ if (!isInsideWhitelistedDir(Dir)) {
+ EC = llvm::errc::no_such_file_or_directory;
+ return directory_iterator();
+ }
+ return InnerFS->dir_begin(Dir, EC);
+ }
+
+ virtual std::error_code setCurrentWorkingDirectory(const Twine &Path) {
+ return InnerFS->setCurrentWorkingDirectory(Path);
+ }
+
+ virtual llvm::ErrorOr<std::string> getCurrentWorkingDirectory() const {
+ return InnerFS->getCurrentWorkingDirectory();
+ }
+
+ bool exists(const Twine &Path) {
+ if (!isInsideWhitelistedDir(Path))
+ return false;
+ return InnerFS->exists(Path);
+ }
+
+ std::error_code makeAbsolute(SmallVectorImpl<char> &Path) const {
+ return InnerFS->makeAbsolute(Path);
+ }
+
+private:
+ bool isInsideWhitelistedDir(const Twine &InputPath) const {
+ SmallString<128> Path;
+ InputPath.toVector(Path);
+
+ if (makeAbsolute(Path))
+ return false;
+
+ for (const auto &Dir : WhitelistedDirs) {
+ if (Path.startswith(Dir))
+ return true;
+ }
+ return false;
+ }
+
+ std::vector<std::string> WhitelistedDirs;
+ IntrusiveRefCntPtr<vfs::FileSystem> InnerFS;
+};
+
+/// Create a vfs::FileSystem that has access only to temporary directories
+/// (obtained by calling system_temp_directory).
+IntrusiveRefCntPtr<vfs::FileSystem> getTempOnlyFS() {
+ llvm::SmallString<128> TmpDir1;
+ llvm::sys::path::system_temp_directory(/*erasedOnReboot=*/false, TmpDir1);
+ llvm::SmallString<128> TmpDir2;
+ llvm::sys::path::system_temp_directory(/*erasedOnReboot=*/true, TmpDir2);
+
+ std::vector<std::string> TmpDirs;
+ TmpDirs.push_back(TmpDir1.str());
+ if (TmpDir2 != TmpDir2)
+ TmpDirs.push_back(TmpDir2.str());
+ return new vfs::FilteredFileSystem(std::move(TmpDirs),
+ vfs::getRealFileSystem());
+}
+} // namespace vfs
+
+namespace clangd {
+namespace {
+
+class ErrorCheckingDiagConsumer : public DiagnosticsConsumer {
+public:
+ void onDiagnosticsReady(PathRef File,
+ std::vector<DiagWithFixIts> Diagnostics) override {
+ bool HadError = false;
+ for (const auto &DiagAndFixIts : Diagnostics) {
+ // FIXME: severities returned by clangd should have a descriptive
+ // diagnostic severity enum
+ const int ErrorSeverity = 1;
+ HadError = DiagAndFixIts.Diag.severity == ErrorSeverity;
+ }
+
+ std::lock_guard<std::mutex> Lock(Mutex);
+ HadErrorInLastDiags = HadError;
+ }
+
+ bool hadErrorInLastDiags() {
+ std::lock_guard<std::mutex> Lock(Mutex);
+ return HadErrorInLastDiags;
+ }
+
+private:
+ std::mutex Mutex;
+ bool HadErrorInLastDiags = false;
+};
+
+class MockCompilationDatabase : public GlobalCompilationDatabase {
+public:
+ std::vector<tooling::CompileCommand>
+ getCompileCommands(PathRef File) override {
+ return {};
+ }
+};
+
+class MockFSProvider : public FileSystemProvider {
+public:
+ IntrusiveRefCntPtr<vfs::FileSystem> getFileSystem() override {
+ IntrusiveRefCntPtr<vfs::InMemoryFileSystem> MemFS(
+ new vfs::InMemoryFileSystem);
+ for (auto &FileAndContents : Files)
+ MemFS->addFile(FileAndContents.first(), time_t(),
+ llvm::MemoryBuffer::getMemBuffer(FileAndContents.second,
+ FileAndContents.first()));
+
+ auto OverlayFS = IntrusiveRefCntPtr<vfs::OverlayFileSystem>(
+ new vfs::OverlayFileSystem(vfs::getTempOnlyFS()));
+ OverlayFS->pushOverlay(std::move(MemFS));
+ return OverlayFS;
+ }
+
+ llvm::StringMap<std::string> Files;
+};
+
+/// Replaces all patterns of the form 0x123abc with spaces
+void replacePtrsInDump(std::string &Dump) {
+ llvm::Regex RE("0x[0-9a-fA-F]+");
+ llvm::SmallVector<StringRef, 1> Matches;
+ while (RE.match(Dump, &Matches)) {
+ assert(Matches.size() == 1 && "Exactly one match expected");
+ auto MatchPos = Matches[0].data() - Dump.data();
+ std::fill(Dump.begin() + MatchPos,
+ Dump.begin() + MatchPos + Matches[0].size(), ' ');
+ }
+}
+
+std::string dumpASTWithoutMemoryLocs(ClangdServer &Server, PathRef File) {
+ auto Dump = Server.dumpAST(File);
+ replacePtrsInDump(Dump);
+ return Dump;
+}
+
+template <class T>
+std::unique_ptr<T> getAndMove(std::unique_ptr<T> Ptr, T *&Output) {
+ Output = Ptr.get();
+ return Ptr;
+}
+} // namespace
+
+class ClangdVFSTest : public ::testing::Test {
+protected:
+ SmallString<16> getVirtualTestRoot() {
+#ifdef LLVM_ON_WIN32
+ return SmallString<16>("C:\\clangd-test");
+#else
+ return SmallString<16>("/clangd-test");
+#endif
+ }
+
+ llvm::SmallString<32> getVirtualTestFilePath(PathRef File) {
+ assert(llvm::sys::path::is_relative(File) && "FileName should be relative");
+
+ llvm::SmallString<32> Path;
+ llvm::sys::path::append(Path, getVirtualTestRoot(), File);
+ return Path;
+ }
+
+ std::string parseSourceAndDumpAST(
+ PathRef SourceFileRelPath, StringRef SourceContents,
+ std::vector<std::pair<PathRef, StringRef>> ExtraFiles = {},
+ bool ExpectErrors = false) {
+ MockFSProvider *FS;
+ ErrorCheckingDiagConsumer *DiagConsumer;
+ ClangdServer Server(
+ llvm::make_unique<MockCompilationDatabase>(),
+ getAndMove(llvm::make_unique<ErrorCheckingDiagConsumer>(),
+ DiagConsumer),
+ getAndMove(llvm::make_unique<MockFSProvider>(), FS),
+ /*RunSynchronously=*/false);
+ for (const auto &FileWithContents : ExtraFiles)
+ FS->Files[getVirtualTestFilePath(FileWithContents.first)] =
+ FileWithContents.second;
+
+ auto SourceFilename = getVirtualTestFilePath(SourceFileRelPath);
+ Server.addDocument(SourceFilename, SourceContents);
+ auto Result = dumpASTWithoutMemoryLocs(Server, SourceFilename);
+ EXPECT_EQ(ExpectErrors, DiagConsumer->hadErrorInLastDiags());
+ return Result;
+ }
+};
+
+TEST_F(ClangdVFSTest, Parse) {
+ // FIXME: figure out a stable format for AST dumps, so that we can check the
+ // output of the dump itself is equal to the expected one, not just that it's
+ // different.
+ auto Empty = parseSourceAndDumpAST("foo.cpp", "", {});
+ auto OneDecl = parseSourceAndDumpAST("foo.cpp", "int a;", {});
+ auto SomeDecls = parseSourceAndDumpAST("foo.cpp", "int a; int b; int c;", {});
+ EXPECT_NE(Empty, OneDecl);
+ EXPECT_NE(Empty, SomeDecls);
+ EXPECT_NE(SomeDecls, OneDecl);
+
+ auto Empty2 = parseSourceAndDumpAST("foo.cpp", "");
+ auto OneDecl2 = parseSourceAndDumpAST("foo.cpp", "int a;");
+ auto SomeDecls2 = parseSourceAndDumpAST("foo.cpp", "int a; int b; int c;");
+ EXPECT_EQ(Empty, Empty2);
+ EXPECT_EQ(OneDecl, OneDecl2);
+ EXPECT_EQ(SomeDecls, SomeDecls2);
+}
+
+TEST_F(ClangdVFSTest, ParseWithHeader) {
+ parseSourceAndDumpAST("foo.cpp", "#include \"foo.h\"", {},
+ /*ExpectErrors=*/true);
+ parseSourceAndDumpAST("foo.cpp", "#include \"foo.h\"", {{"foo.h", ""}},
+ /*ExpectErrors=*/false);
+
+ const auto SourceContents = R"cpp(
+#include "foo.h"
+int b = a;
+)cpp";
+ parseSourceAndDumpAST("foo.cpp", SourceContents, {{"foo.h", ""}},
+ /*ExpectErrors=*/true);
+ parseSourceAndDumpAST("foo.cpp", SourceContents, {{"foo.h", "int a;"}},
+ /*ExpectErrors=*/false);
+}
+
+TEST_F(ClangdVFSTest, Reparse) {
+ MockFSProvider *FS;
+ ErrorCheckingDiagConsumer *DiagConsumer;
+ ClangdServer Server(
+ llvm::make_unique<MockCompilationDatabase>(),
+ getAndMove(llvm::make_unique<ErrorCheckingDiagConsumer>(), DiagConsumer),
+ getAndMove(llvm::make_unique<MockFSProvider>(), FS),
+ /*RunSynchronously=*/false);
+
+ const auto SourceContents = R"cpp(
+#include "foo.h"
+int b = a;
+)cpp";
+
+ auto FooCpp = getVirtualTestFilePath("foo.cpp");
+ auto FooH = getVirtualTestFilePath("foo.h");
+
+ FS->Files[FooH] = "int a;";
+ FS->Files[FooCpp] = SourceContents;
+
+ Server.addDocument(FooCpp, SourceContents);
+ auto DumpParse1 = dumpASTWithoutMemoryLocs(Server, FooCpp);
+ EXPECT_FALSE(DiagConsumer->hadErrorInLastDiags());
+
+ Server.addDocument(FooCpp, "");
+ auto DumpParseEmpty = dumpASTWithoutMemoryLocs(Server, FooCpp);
+ EXPECT_FALSE(DiagConsumer->hadErrorInLastDiags());
+
+ Server.addDocument(FooCpp, SourceContents);
+ auto DumpParse2 = dumpASTWithoutMemoryLocs(Server, FooCpp);
+ EXPECT_FALSE(DiagConsumer->hadErrorInLastDiags());
+
+ EXPECT_EQ(DumpParse1, DumpParse2);
+ EXPECT_NE(DumpParse1, DumpParseEmpty);
+}
+
+TEST_F(ClangdVFSTest, ReparseOnHeaderChange) {
+ MockFSProvider *FS;
+ ErrorCheckingDiagConsumer *DiagConsumer;
+
+ ClangdServer Server(
+ llvm::make_unique<MockCompilationDatabase>(),
+ getAndMove(llvm::make_unique<ErrorCheckingDiagConsumer>(), DiagConsumer),
+ getAndMove(llvm::make_unique<MockFSProvider>(), FS),
+ /*RunSynchronously=*/false);
+
+ const auto SourceContents = R"cpp(
+#include "foo.h"
+int b = a;
+)cpp";
+
+ auto FooCpp = getVirtualTestFilePath("foo.cpp");
+ auto FooH = getVirtualTestFilePath("foo.h");
+
+ FS->Files[FooH] = "int a;";
+ FS->Files[FooCpp] = SourceContents;
+
+ Server.addDocument(FooCpp, SourceContents);
+ auto DumpParse1 = dumpASTWithoutMemoryLocs(Server, FooCpp);
+ EXPECT_FALSE(DiagConsumer->hadErrorInLastDiags());
+
+ FS->Files[FooH] = "";
+ Server.forceReparse(FooCpp);
+ auto DumpParseDifferent = dumpASTWithoutMemoryLocs(Server, FooCpp);
+ EXPECT_TRUE(DiagConsumer->hadErrorInLastDiags());
+
+ FS->Files[FooH] = "int a;";
+ Server.forceReparse(FooCpp);
+ auto DumpParse2 = dumpASTWithoutMemoryLocs(Server, FooCpp);
+ EXPECT_FALSE(DiagConsumer->hadErrorInLastDiags());
+
+ EXPECT_EQ(DumpParse1, DumpParse2);
+ EXPECT_NE(DumpParse1, DumpParseDifferent);
+}
+
+} // namespace clangd
+} // namespace clang
More information about the cfe-commits
mailing list