Index: clangd/ClangdUnit.h =================================================================== --- clangd/ClangdUnit.h +++ clangd/ClangdUnit.h @@ -23,6 +23,7 @@ #include #include #include +#include namespace llvm { class raw_ostream; @@ -47,15 +48,18 @@ llvm::SmallVector FixIts; }; +using IncludeReferenceMap = std::unordered_map; + // Stores Preamble and associated data. struct PreambleData { PreambleData(PrecompiledPreamble Preamble, std::vector TopLevelDeclIDs, - std::vector Diags); + std::vector Diags, IncludeReferenceMap IRM); PrecompiledPreamble Preamble; std::vector TopLevelDeclIDs; std::vector Diags; + IncludeReferenceMap IRM; }; /// Stores and provides access to parsed AST. @@ -89,12 +93,14 @@ const std::vector &getDiagnostics() const; + const IncludeReferenceMap &getIRM() const { return IRM; }; + private: ParsedAST(std::shared_ptr Preamble, std::unique_ptr Clang, std::unique_ptr Action, std::vector TopLevelDecls, - std::vector Diags); + std::vector Diags, IncludeReferenceMap IRM); private: void ensurePreambleDeclsDeserialized(); @@ -114,6 +120,7 @@ std::vector Diags; std::vector TopLevelDecls; bool PreambleDeclsDeserialized; + IncludeReferenceMap IRM; }; // Provides thread-safe access to ParsedAST. Index: clangd/ClangdUnit.cpp =================================================================== --- clangd/ClangdUnit.cpp +++ clangd/ClangdUnit.cpp @@ -71,12 +71,53 @@ std::vector TopLevelDecls; }; +// Converts a half-open clang source range to an LSP range. +// Note that clang also uses closed source ranges, which this can't handle! +Range toRange(CharSourceRange R, const SourceManager &M) { + // Clang is 1-based, LSP uses 0-based indexes. + return {{static_cast(M.getSpellingLineNumber(R.getBegin())) - 1, + static_cast(M.getSpellingColumnNumber(R.getBegin())) - 1}, + {static_cast(M.getSpellingLineNumber(R.getEnd())) - 1, + static_cast(M.getSpellingColumnNumber(R.getEnd())) - 1}}; +} + +class IncludeRefsCollector : public PPCallbacks { +public: + IncludeRefsCollector(SourceManager &SourceMgr, IncludeReferenceMap &IRM) + : SourceMgr(SourceMgr), IRM(IRM) {} + + void InclusionDirective(SourceLocation HashLoc, const Token &IncludeTok, + StringRef FileName, bool IsAngled, + CharSourceRange FilenameRange, const FileEntry *File, + StringRef SearchPath, StringRef RelativePath, + const Module *Imported) override { + auto SR = FilenameRange.getAsRange(); + if (SR.isInvalid() || !File || File->tryGetRealPathName().empty()) + return; + + if (SourceMgr.isInMainFile(FilenameRange.getAsRange().getBegin())) { + // Only inclusion directives in the main file make sense. The user cannot + // select directives not in the main file. + IRM.insert( + {toRange(FilenameRange, SourceMgr), File->tryGetRealPathName()}); + } + } + +private: + SourceManager &SourceMgr; + IncludeReferenceMap &IRM; +}; + class CppFilePreambleCallbacks : public PreambleCallbacks { public: std::vector takeTopLevelDeclIDs() { return std::move(TopLevelDeclIDs); } + IncludeReferenceMap takeIncludeReferenceMap() { return std::move(IRM); } + + CppFilePreambleCallbacks() : SourceMgr(nullptr) {} + void AfterPCHEmitted(ASTWriter &Writer) override { TopLevelDeclIDs.reserve(TopLevelDecls.size()); for (Decl *D : TopLevelDecls) { @@ -95,9 +136,20 @@ } } + void BeforeExecute(CompilerInstance &CI) override { + SourceMgr = &CI.getSourceManager(); + } + + std::unique_ptr createPPCallbacks() override { + assert(SourceMgr && "SourceMgr must be set at this point"); + return llvm::make_unique(*SourceMgr, IRM); + } + private: std::vector TopLevelDecls; std::vector TopLevelDeclIDs; + SourceManager *SourceMgr; + IncludeReferenceMap IRM; }; /// Convert from clang diagnostic level to LSP severity. @@ -129,16 +181,6 @@ return L != R.getEnd() && M.isPointWithin(L, R.getBegin(), R.getEnd()); } -// Converts a half-open clang source range to an LSP range. -// Note that clang also uses closed source ranges, which this can't handle! -Range toRange(CharSourceRange R, const SourceManager &M) { - // Clang is 1-based, LSP uses 0-based indexes. - return {{static_cast(M.getSpellingLineNumber(R.getBegin())) - 1, - static_cast(M.getSpellingColumnNumber(R.getBegin())) - 1}, - {static_cast(M.getSpellingLineNumber(R.getEnd())) - 1, - static_cast(M.getSpellingColumnNumber(R.getEnd())) - 1}}; -} - // Clang diags have a location (shown as ^) and 0 or more ranges (~~~~). // LSP needs a single range. Range diagnosticRange(const clang::Diagnostic &D, const LangOptions &L) { @@ -234,6 +276,11 @@ std::vector ASTDiags; StoreDiagsConsumer UnitDiagsConsumer(/*ref*/ ASTDiags); + IncludeReferenceMap IRM; + // Copy over the includes from the preamble, then combine with the + // non-preamble includes below. + if (Preamble) + IRM = Preamble->IRM; const PrecompiledPreamble *PreamblePCH = Preamble ? &Preamble->Preamble : nullptr; @@ -252,6 +299,10 @@ MainInput.getFile()); return llvm::None; } + + Clang->getPreprocessor().addPPCallbacks( + llvm::make_unique(Clang->getSourceManager(), IRM)); + if (!Action->Execute()) log(Ctx, "Execute() failed when building AST for " + MainInput.getFile()); @@ -261,7 +312,7 @@ std::vector ParsedDecls = Action->takeTopLevelDecls(); return ParsedAST(std::move(Preamble), std::move(Clang), std::move(Action), - std::move(ParsedDecls), std::move(ASTDiags)); + std::move(ParsedDecls), std::move(ASTDiags), std::move(IRM)); } namespace { @@ -335,19 +386,21 @@ PreambleData::PreambleData(PrecompiledPreamble Preamble, std::vector TopLevelDeclIDs, - std::vector Diags) + std::vector Diags, + IncludeReferenceMap IRM) : Preamble(std::move(Preamble)), - TopLevelDeclIDs(std::move(TopLevelDeclIDs)), Diags(std::move(Diags)) {} + TopLevelDeclIDs(std::move(TopLevelDeclIDs)), Diags(std::move(Diags)), + IRM(std::move(IRM)) {} ParsedAST::ParsedAST(std::shared_ptr Preamble, std::unique_ptr Clang, std::unique_ptr Action, std::vector TopLevelDecls, - std::vector Diags) + std::vector Diags, IncludeReferenceMap IRM) : Preamble(std::move(Preamble)), Clang(std::move(Clang)), Action(std::move(Action)), Diags(std::move(Diags)), - TopLevelDecls(std::move(TopLevelDecls)), - PreambleDeclsDeserialized(false) { + TopLevelDecls(std::move(TopLevelDecls)), PreambleDeclsDeserialized(false), + IRM(std::move(IRM)) { assert(this->Clang); assert(this->Action); } @@ -556,7 +609,8 @@ return std::make_shared( std::move(*BuiltPreamble), SerializedDeclsCollector.takeTopLevelDeclIDs(), - std::move(PreambleDiags)); + std::move(PreambleDiags), + SerializedDeclsCollector.takeIncludeReferenceMap()); } else { log(Ctx, "Could not build a preamble for file " + Twine(That->FileName)); Index: clangd/Protocol.h =================================================================== --- clangd/Protocol.h +++ clangd/Protocol.h @@ -112,6 +112,14 @@ }; bool fromJSON(const json::Expr &, Range &); json::Expr toJSON(const Range &); + +class RangeHash { +public: + std::size_t operator()(const Range &R) const { + return ((R.start.line & 0x18) << 3) | ((R.start.character & 0x18) << 1) | + ((R.end.line & 0x18) >> 1) | ((R.end.character & 0x18) >> 3); + } +}; llvm::raw_ostream &operator<<(llvm::raw_ostream &, const Range &); struct Location { Index: clangd/SourceCode.h =================================================================== --- clangd/SourceCode.h +++ clangd/SourceCode.h @@ -14,8 +14,11 @@ #ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_SOURCECODE_H #define LLVM_CLANG_TOOLS_EXTRA_CLANGD_SOURCECODE_H #include "Protocol.h" +#include "clang/Basic/SourceLocation.h" namespace clang { +class SourceManager; + namespace clangd { /// Turn a [line, column] pair into an offset in Code. @@ -24,6 +27,9 @@ /// Turn an offset in Code into a [line, column] pair. Position offsetToPosition(llvm::StringRef Code, size_t Offset); +/// Turn a SourceLocation into a [line, column] pair. +Position sourceLocToPosition(const SourceManager &SM, SourceLocation Loc); + } // namespace clangd } // namespace clang #endif Index: clangd/SourceCode.cpp =================================================================== --- clangd/SourceCode.cpp +++ clangd/SourceCode.cpp @@ -8,6 +8,8 @@ //===----------------------------------------------------------------------===// #include "SourceCode.h" +#include "clang/Basic/SourceManager.h" + namespace clang { namespace clangd { using namespace llvm; @@ -36,6 +38,11 @@ return {Lines, static_cast(Offset - StartOfLine)}; } +Position sourceLocToPosition(const SourceManager &SM, SourceLocation Loc) { + return {static_cast(SM.getSpellingLineNumber(Loc)) - 1, + static_cast(SM.getSpellingColumnNumber(Loc)) - 1}; +} + } // namespace clangd } // namespace clang Index: clangd/XRefs.cpp =================================================================== --- clangd/XRefs.cpp +++ clangd/XRefs.cpp @@ -7,6 +7,7 @@ // //===---------------------------------------------------------------------===// #include "XRefs.h" +#include "SourceCode.h" #include "clang/Index/IndexDataConsumer.h" #include "clang/Index/IndexingAction.h" namespace clang { @@ -103,12 +104,8 @@ return llvm::None; SourceLocation LocEnd = Lexer::getLocForEndOfToken(ValSourceRange.getEnd(), 0, SourceMgr, LangOpts); - Position Begin; - Begin.line = SourceMgr.getSpellingLineNumber(LocStart) - 1; - Begin.character = SourceMgr.getSpellingColumnNumber(LocStart) - 1; - Position End; - End.line = SourceMgr.getSpellingLineNumber(LocEnd) - 1; - End.character = SourceMgr.getSpellingColumnNumber(LocEnd) - 1; + Position Begin = sourceLocToPosition(SourceMgr, LocStart); + Position End = sourceLocToPosition(SourceMgr, LocEnd); Range R = {Begin, End}; Location L; @@ -142,6 +139,21 @@ indexTopLevelDecls(AST.getASTContext(), AST.getTopLevelDecls(), DeclMacrosFinder, IndexOpts); + /// Process targets for paths inside #include directive. + std::vector IncludeTargets; + for (auto &IncludeLoc : AST.getIRM()) { + Range R = IncludeLoc.first; + const SourceManager &SourceMgr = AST.getASTContext().getSourceManager(); + Position Pos = sourceLocToPosition(SourceMgr, SourceLocationBeg); + + if (R.start.line == Pos.line && R.start.character <= Pos.character && + Pos.character <= R.end.character) { + IncludeTargets.push_back(Location{URI::fromFile(IncludeLoc.second), + Range{Position{0, 0}, Position{0, 0}}}); + return IncludeTargets; + } + } + std::vector Decls = DeclMacrosFinder->takeDecls(); std::vector MacroInfos = DeclMacrosFinder->takeMacroInfos(); @@ -217,13 +229,8 @@ DocumentHighlight getDocumentHighlight(SourceRange SR, DocumentHighlightKind Kind) { const SourceManager &SourceMgr = AST.getSourceManager(); - SourceLocation LocStart = SR.getBegin(); - Position Begin; - Begin.line = SourceMgr.getSpellingLineNumber(LocStart) - 1; - Begin.character = SourceMgr.getSpellingColumnNumber(LocStart) - 1; - Position End; - End.line = SourceMgr.getSpellingLineNumber(SR.getEnd()) - 1; - End.character = SourceMgr.getSpellingColumnNumber(SR.getEnd()) - 1; + Position Begin = sourceLocToPosition(SourceMgr, SR.getBegin()); + Position End = sourceLocToPosition(SourceMgr, SR.getEnd()); Range R = {Begin, End}; DocumentHighlight DH; DH.range = R; Index: unittests/clangd/ClangdTests.cpp =================================================================== --- unittests/clangd/ClangdTests.cpp +++ unittests/clangd/ClangdTests.cpp @@ -751,6 +751,74 @@ EXPECT_FALSE(PathResult.hasValue()); } +TEST_F(ClangdVFSTest, CheckDefinitionIncludes) { + MockFSProvider FS; + ErrorCheckingDiagConsumer DiagConsumer; + MockCompilationDatabase CDB; + + ClangdServer Server(CDB, DiagConsumer, FS, getDefaultAsyncThreadsCount(), + /*StorePreamblesInMemory=*/true); + + auto FooCpp = getVirtualTestFilePath("foo.cpp"); + const auto SourceContents = R"cpp( + #include "foo.h" + #include "invalid.h" + int b = a; + // test + int foo; + #include "foo.h" + )cpp"; + FS.Files[FooCpp] = SourceContents; + auto FooH = getVirtualTestFilePath("foo.h"); + const auto HeaderContents = "int a;"; + + FS.Files[FooCpp] = SourceContents; + FS.Files[FooH] = HeaderContents; + + Server.addDocument(Context::empty(), FooH, HeaderContents); + Server.addDocument(Context::empty(), FooCpp, SourceContents); + + Position P = Position{1, 11}; + + auto ExpectedLocations = Server.findDefinitions(Context::empty(), FooCpp, P); + ASSERT_TRUE(!!ExpectedLocations); + std::vector Locations = ExpectedLocations->Value; + EXPECT_TRUE(!Locations.empty()); + std::string s("file:///"); + std::string check = Locations[0].uri.uri; + check = check.erase(0, s.size() - 1); + check = check.substr(0, check.size()); + ASSERT_EQ(check, FooH); + ASSERT_EQ(Locations[0].range.start.line, 0); + ASSERT_EQ(Locations[0].range.start.character, 0); + ASSERT_EQ(Locations[0].range.end.line, 0); + ASSERT_EQ(Locations[0].range.end.character, 0); + + // Test include in preamble + Position P2 = Position{1, 11}; + + ExpectedLocations = Server.findDefinitions(Context::empty(), FooCpp, P2); + ASSERT_TRUE(!!ExpectedLocations); + Locations = ExpectedLocations->Value; + EXPECT_TRUE(!Locations.empty()); + + // Test invalid include + Position P3 = Position{2, 11}; + + ExpectedLocations = Server.findDefinitions(Context::empty(), FooCpp, P3); + ASSERT_TRUE(!!ExpectedLocations); + Locations = ExpectedLocations->Value; + EXPECT_TRUE(Locations.empty()); + + // Test include outside of Preamble + Position P4 = Position{6, 11}; + + ExpectedLocations = Server.findDefinitions(Context::empty(), FooCpp, P4); + ASSERT_TRUE(!!ExpectedLocations); + Locations = ExpectedLocations->Value; + EXPECT_TRUE(!Locations.empty()); +} + TEST_F(ClangdThreadingTest, NoConcurrentDiagnostics) { class NoConcurrentAccessDiagConsumer : public DiagnosticsConsumer { public: