diff --git a/clang/include/clang/Basic/FileManager.h b/clang/include/clang/Basic/FileManager.h --- a/clang/include/clang/Basic/FileManager.h +++ b/clang/include/clang/Basic/FileManager.h @@ -135,7 +135,7 @@ /// llvm::vfs::getRealFileSystem(). FileManager(const FileSystemOptions &FileSystemOpts, IntrusiveRefCntPtr FS = nullptr); - ~FileManager(); + virtual ~FileManager(); /// Installs the provided FileSystemStatCache object within /// the FileManager. @@ -150,7 +150,9 @@ void clearStatCache(); /// Returns the number of unique real file entries cached by the file manager. - size_t getNumUniqueRealFiles() const { return UniqueRealFiles.size(); } + virtual size_t getNumUniqueRealFiles() const { + return UniqueRealFiles.size(); + } /// Lookup, cache, and verify the specified directory (real or /// virtual). @@ -162,8 +164,8 @@ /// /// \param CacheFailure If true and the file does not exist, we'll cache /// the failure to find this file. - llvm::Expected getDirectoryRef(StringRef DirName, - bool CacheFailure = true); + virtual llvm::Expected + getDirectoryRef(StringRef DirName, bool CacheFailure = true); /// Get a \c DirectoryEntryRef if it exists, without doing anything on error. llvm::Optional @@ -218,9 +220,9 @@ /// /// \param CacheFailure If true and the file does not exist, we'll cache /// the failure to find this file. - llvm::Expected getFileRef(StringRef Filename, - bool OpenFile = false, - bool CacheFailure = true); + virtual llvm::Expected getFileRef(StringRef Filename, + bool OpenFile = false, + bool CacheFailure = true); /// Get the FileEntryRef for stdin, returning an error if stdin cannot be /// read. @@ -252,8 +254,8 @@ /// if there were a file with the given name on disk. /// /// The file itself is not accessed. - FileEntryRef getVirtualFileRef(StringRef Filename, off_t Size, - time_t ModificationTime); + virtual FileEntryRef getVirtualFileRef(StringRef Filename, off_t Size, + time_t ModificationTime); const FileEntry *getVirtualFile(StringRef Filename, off_t Size, time_t ModificationTime); @@ -266,7 +268,7 @@ /// bypasses all mapping and uniquing, blindly creating a new FileEntry. /// There is no attempt to deduplicate these; if you bypass the same file /// twice, you get two new file entries. - llvm::Optional getBypassFile(FileEntryRef VFE); + virtual llvm::Optional getBypassFile(FileEntryRef VFE); /// Open the specified file as a MemoryBuffer, returning a new /// MemoryBuffer if successful, otherwise returning null. @@ -308,24 +310,24 @@ /// Produce an array mapping from the unique IDs assigned to each /// file to the corresponding FileEntry pointer. - void GetUniqueIDMapping( - SmallVectorImpl &UIDToFiles) const; + virtual void + GetUniqueIDMapping(SmallVectorImpl &UIDToFiles) const; /// Retrieve the canonical name for a given directory. /// /// This is a very expensive operation, despite its results being cached, /// and should only be used when the physical layout of the file system is /// required, which is (almost) never. - StringRef getCanonicalName(const DirectoryEntry *Dir); + virtual StringRef getCanonicalName(const DirectoryEntry *Dir); /// Retrieve the canonical name for a given file. /// /// This is a very expensive operation, despite its results being cached, /// and should only be used when the physical layout of the file system is /// required, which is (almost) never. - StringRef getCanonicalName(const FileEntry *File); + virtual StringRef getCanonicalName(const FileEntry *File); - void PrintStats() const; + virtual void PrintStats() const; }; } // end namespace clang diff --git a/clang/include/clang/Tooling/Tooling.h b/clang/include/clang/Tooling/Tooling.h --- a/clang/include/clang/Tooling/Tooling.h +++ b/clang/include/clang/Tooling/Tooling.h @@ -65,6 +65,67 @@ class CompilationDatabase; +class ToolingFileManager : public FileManager { + // Use a FileManager for each different CWD + mutable std::map fileManagers; + +public: + ToolingFileManager(const FileSystemOptions &FileSystemOpts, + IntrusiveRefCntPtr FS = nullptr) + : FileManager(FileSystemOpts, FS){}; + + size_t getNumUniqueRealFiles() const override { + return getRealFileManager()->getNumUniqueRealFiles(); + } + + llvm::Expected + getDirectoryRef(StringRef DirName, bool CacheFailure = true) override { + return getRealFileManager()->getDirectoryRef(DirName, CacheFailure); + } + + llvm::Expected getFileRef(StringRef Filename, bool openFile, + bool CacheFailure) override { + return getRealFileManager()->getFileRef(Filename, openFile, CacheFailure); + } + + FileEntryRef getVirtualFileRef(StringRef Filename, off_t Size, + time_t ModificationTime) override { + return getRealFileManager()->getVirtualFileRef(Filename, Size, + ModificationTime); + } + + llvm::Optional getBypassFile(FileEntryRef VFE) override { + return getRealFileManager()->getBypassFile(VFE); + } + + void GetUniqueIDMapping( + SmallVectorImpl &UIDToFiles) const override { + getRealFileManager()->GetUniqueIDMapping(UIDToFiles); + } + + StringRef getCanonicalName(const DirectoryEntry *Dir) override { + return getRealFileManager()->getCanonicalName(Dir); + } + + StringRef getCanonicalName(const FileEntry *File) override { + return getRealFileManager()->getCanonicalName(File); + } + + void PrintStats() const override { getRealFileManager()->PrintStats(); } + + FileManager *getRealFileManager() const { + auto Success = getVirtualFileSystem().getCurrentWorkingDirectory(); + if (!Success) + llvm::report_fatal_error("Cannot get current working directory!\n"); + std::string Cwd = Success.get(); + if (!fileManagers.count(Cwd)) { + fileManagers[Cwd] = + new FileManager(getFileSystemOpts(), &getVirtualFileSystem()); + } + return fileManagers[Cwd]; + } +}; + /// Retrieves the flags of the `-cc1` job in `Compilation` that has only source /// files as its inputs. /// Returns nullptr if there are no such jobs or multiple of them. Note that diff --git a/clang/lib/Tooling/Tooling.cpp b/clang/lib/Tooling/Tooling.cpp --- a/clang/lib/Tooling/Tooling.cpp +++ b/clang/lib/Tooling/Tooling.cpp @@ -208,7 +208,7 @@ StringRef FileNameRef = FileName.toNullTerminatedStringRef(FileNameStorage); llvm::IntrusiveRefCntPtr Files( - new FileManager(FileSystemOptions(), VFS)); + new ToolingFileManager(FileSystemOptions(), VFS)); ArgumentsAdjuster Adjuster = getClangStripDependencyFileAdjuster(); ToolInvocation Invocation( getSyntaxOnlyToolArgs(ToolName, Adjuster(Args, FileNameRef), FileNameRef), @@ -436,7 +436,8 @@ OverlayFileSystem(new llvm::vfs::OverlayFileSystem(std::move(BaseFS))), InMemoryFileSystem(new llvm::vfs::InMemoryFileSystem), Files(Files ? Files - : new FileManager(FileSystemOptions(), OverlayFileSystem)) { + : new ToolingFileManager(FileSystemOptions(), + OverlayFileSystem)) { OverlayFileSystem->pushOverlay(InMemoryFileSystem); appendArgumentsAdjuster(getClangStripOutputAdjuster()); appendArgumentsAdjuster(getClangSyntaxOnlyAdjuster()); @@ -656,7 +657,7 @@ new llvm::vfs::InMemoryFileSystem); OverlayFileSystem->pushOverlay(InMemoryFileSystem); llvm::IntrusiveRefCntPtr Files( - new FileManager(FileSystemOptions(), OverlayFileSystem)); + new ToolingFileManager(FileSystemOptions(), OverlayFileSystem)); ToolInvocation Invocation( getSyntaxOnlyToolArgs(ToolName, Adjuster(Args, FileName), FileName), diff --git a/clang/test/Tooling/multiple-source-include-different-header-with-same-relative-path.cpp b/clang/test/Tooling/multiple-source-include-different-header-with-same-relative-path.cpp new file mode 100644 --- /dev/null +++ b/clang/test/Tooling/multiple-source-include-different-header-with-same-relative-path.cpp @@ -0,0 +1,42 @@ +// This test case presents a circumstance that the information related one file +// is misused by another. +// +// The path tree of the test directory is as follors. +// . +// ├── a +// │ ├── a.c +// │ └── config.h +// ├── b +// │ ├── b.c +// │ └── config.h +// └── compile_commands.json +// +// Both source files (a/a.c and b/b.c) includes the config.h file of their own +// directory with `#include "config.h"`. However, the two config.h files are +// different. File a/config.h is longer than b/config.h. Both a/a.c and b/b.c +// are compiled in their own directory, which is recorded in the compilation +// database. +// +// When using ClangTool to parse these two source files one by one, since the +// file name of both header files are the same, the FileManager will confuse +// with them and using the file entry of a/config.h for b/config.h. And the +// wrong file length will lead to a buffer overflow when reading the file. +// +// In this test case, to avoid buffer overflow, we use the leading '\0' in an +// empty buffer to trigger the problem. We set a/config.h as an empty line +// comment, and leave b/config.h empty. Firstly, a/config.h is read and cached, +// then when reading b/config.h, if the size of a/config.h is used, the first +// two chars are read and the first one must be a '\0'. + +// RUN: rm -rf %t +// RUN: mkdir -p %t/a +// RUN: mkdir -p %t/b +// RUN: echo '#include "config.h"' > %t/a/a.c +// RUN: echo '#include "config.h"' > %t/b/b.c +// RUN: echo '//' > %t/a/config.h +// RUN: echo '' > %t/b/config.h +// RUN: echo '[{"arguments": ["cc", "-c", "-o", "a.o", "a.c"], "directory": "%t/a", "file": "a.c"}, {"arguments": ["cc", "-c", "-o", "b.o", "b.c"], "directory": "%t/b", "file": "b.c"}]' | sed -e 's/\\/\\\\/g' > %t/compile_commands.json + +// The following two test RUNs should have no output. +// RUN: cd %t && clang-check a/a.c b/b.c 2>&1 | count 0 +// RUN: cd %t && clang-check b/b.c a/a.c 2>&1 | count 0