diff --git a/clang-tools-extra/clangd/Headers.cpp b/clang-tools-extra/clangd/Headers.cpp --- a/clang-tools-extra/clangd/Headers.cpp +++ b/clang-tools-extra/clangd/Headers.cpp @@ -10,6 +10,7 @@ #include "Compiler.h" #include "SourceCode.h" #include "support/Logger.h" +#include "clang/Basic/SourceLocation.h" #include "clang/Frontend/CompilerInstance.h" #include "clang/Frontend/CompilerInvocation.h" #include "clang/Frontend/FrontendActions.h" @@ -30,12 +31,12 @@ // in the main file are collected. void InclusionDirective(SourceLocation HashLoc, const Token &IncludeTok, llvm::StringRef FileName, bool IsAngled, - CharSourceRange FilenameRange, const FileEntry *File, - llvm::StringRef /*SearchPath*/, + CharSourceRange /*FilenameRange*/, + const FileEntry *File, llvm::StringRef /*SearchPath*/, llvm::StringRef /*RelativePath*/, const Module * /*Imported*/, SrcMgr::CharacteristicKind FileKind) override { - if (isInsideMainFile(HashLoc, SM)) { + auto AddMainFileInc = [&](SourceLocation HashLoc) { Out->MainFileIncludes.emplace_back(); auto &Inc = Out->MainFileIncludes.back(); Inc.Written = @@ -47,22 +48,64 @@ SM.getLineNumber(SM.getFileID(HashLoc), Inc.HashOffset) - 1; Inc.FileKind = FileKind; Inc.Directive = IncludeTok.getIdentifierInfo()->getPPKeywordID(); + }; + + auto MainFID = SM.getMainFileID(); + if (BuiltinFileInStack) { + // Directives included through builtin file can be mapped back to main + // file via line directives. + auto PreLoc = SM.getPresumedLoc(HashLoc); + if (auto FE = SM.getFileManager().getFile(PreLoc.getFilename())) { + if (SM.getFileEntryForID(MainFID) == *FE) { + HashLoc = SM.translateLineCol(MainFID, PreLoc.getLine(), + PreLoc.getColumn()); + AddMainFileInc(HashLoc); + } + } + } else if (isInsideMainFile(HashLoc, SM)) { + AddMainFileInc(HashLoc); } + if (File) { auto *IncludingFileEntry = SM.getFileEntryForID(SM.getFileID(HashLoc)); if (!IncludingFileEntry) { assert(SM.getBufferName(HashLoc).startswith("<") && "Expected #include location to be a file or "); // Treat as if included from the main file. - IncludingFileEntry = SM.getFileEntryForID(SM.getMainFileID()); + IncludingFileEntry = SM.getFileEntryForID(MainFID); } Out->recordInclude(IncludingFileEntry->getName(), File->getName(), File->tryGetRealPathName()); } } + void FileChanged(SourceLocation Loc, FileChangeReason Reason, + SrcMgr::CharacteristicKind FileType, + FileID PrevFID) override { + switch (Reason) { + case PPCallbacks::EnterFile: + if (BuiltinFile.isInvalid() && SM.isWrittenInBuiltinFile(Loc)) { + BuiltinFile = SM.getFileID(Loc); + BuiltinFileInStack = true; + } + break; + case PPCallbacks::ExitFile: + if (PrevFID == BuiltinFile) + BuiltinFileInStack = false; + break; + case PPCallbacks::RenameFile: + case PPCallbacks::SystemHeaderPragma: + break; + } + } + private: const SourceManager &SM; + // Set after entering the file. + FileID BuiltinFile; + // Indicates whether file is part of include stack. + bool BuiltinFileInStack = false; + IncludeStructure *Out; }; diff --git a/clang-tools-extra/clangd/unittests/HeadersTests.cpp b/clang-tools-extra/clangd/unittests/HeadersTests.cpp --- a/clang-tools-extra/clangd/unittests/HeadersTests.cpp +++ b/clang-tools-extra/clangd/unittests/HeadersTests.cpp @@ -16,6 +16,7 @@ #include "clang/Frontend/CompilerInvocation.h" #include "clang/Frontend/FrontendActions.h" #include "clang/Lex/PreprocessorOptions.h" +#include "llvm/Support/FormatVariadic.h" #include "llvm/Support/Path.h" #include "gmock/gmock.h" #include "gtest/gtest.h" @@ -25,7 +26,9 @@ namespace { using ::testing::AllOf; +using ::testing::Contains; using ::testing::ElementsAre; +using ::testing::Not; using ::testing::UnorderedElementsAre; class HeadersTest : public ::testing::Test { @@ -302,6 +305,26 @@ llvm::None); } +TEST_F(HeadersTest, PresumedLocations) { + std::string HeaderFile = testPath("implicit_include.h"); + + // Line map inclusion back to main file. + std::string HeaderContents = llvm::formatv("#line 0 \"{0}\"", MainFile); + HeaderContents += R"cpp( +#line 1 +#include )cpp"; + FS.Files[HeaderFile] = HeaderContents; + + // Including through non-builtin file has no effects. + FS.Files[MainFile] = "#include \"implicit_include.h\""; + EXPECT_THAT(collectIncludes().MainFileIncludes, + Not(Contains(Written("")))); + + // Now include through built-in file. + CDB.ExtraClangFlags = {"-include" + HeaderFile}; + EXPECT_THAT(collectIncludes().MainFileIncludes, + Contains(AllOf(IncludeLine(0), Written("")))); +} } // namespace } // namespace clangd } // namespace clang