Index: clangd/ClangdServer.cpp =================================================================== --- clangd/ClangdServer.cpp +++ clangd/ClangdServer.cpp @@ -22,6 +22,7 @@ #include "llvm/Support/Path.h" #include "llvm/Support/raw_ostream.h" #include +#include using namespace clang; using namespace clang::clangd; @@ -442,10 +443,16 @@ llvm::errc::invalid_argument); std::vector Result; - Resources->getAST().get()->runUnderLock([Pos, &Result, this](ParsedAST *AST) { + std::shared_future> Preamble = + Resources->getPreamble(); + + Resources->getAST().get()->runUnderLock([Pos, &Result, Preamble, + this](ParsedAST *AST) { if (!AST) return; - Result = clangd::findDefinitions(*AST, Pos, Logger); + + IncludeReferenceMap IRM = std::move(AST->takeIRM()); + Result = clangd::findDefinitions(*AST, Pos, Logger, IRM.IncludeLocationMap); }); return make_tagged(std::move(Result), TaggedFS.Tag); } Index: clangd/ClangdUnit.h =================================================================== --- clangd/ClangdUnit.h +++ clangd/ClangdUnit.h @@ -22,6 +22,7 @@ #include #include #include +#include namespace llvm { class raw_ostream; @@ -59,6 +60,15 @@ std::vector Diags; }; +class IncludeReferenceMap { + llvm::Optional findIncludeTargetAtLoc(Location Loc); + +public: + std::unordered_map IncludeLocationMap; + std::vector> DataVector; + std::vector RangeVector; +}; + /// Stores and provides access to parsed AST. class ParsedAST { public: @@ -69,7 +79,8 @@ std::shared_ptr Preamble, std::unique_ptr Buffer, std::shared_ptr PCHs, - IntrusiveRefCntPtr VFS, clangd::Logger &Logger); + IntrusiveRefCntPtr VFS, clangd::Logger &Logger, + IncludeReferenceMap IRM); ParsedAST(ParsedAST &&Other); ParsedAST &operator=(ParsedAST &&Other); @@ -89,12 +100,14 @@ const std::vector &getDiagnostics() const; + IncludeReferenceMap takeIRM() { 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 +127,8 @@ std::vector Diags; std::vector TopLevelDecls; bool PreambleDeclsDeserialized; + std::vector PendingTopLevelDecls; + IncludeReferenceMap IRM; }; // Provides thread-safe access to ParsedAST. @@ -256,14 +271,14 @@ clangd::Logger &Logger; }; - /// Get the beginning SourceLocation at a specified \p Pos. SourceLocation getBeginningOfIdentifier(ParsedAST &Unit, const Position &Pos, const FileEntry *FE); /// Get definition of symbol at a specified \p Pos. -std::vector findDefinitions(ParsedAST &AST, Position Pos, - clangd::Logger &Logger); +std::vector +findDefinitions(ParsedAST &AST, Position Pos, clangd::Logger &Logger, + std::unordered_map IncludeLocationMap); /// For testing/debugging purposes. Note that this method deserializes all /// unserialized Decls, so use with care. Index: clangd/ClangdUnit.cpp =================================================================== --- clangd/ClangdUnit.cpp +++ clangd/ClangdUnit.cpp @@ -31,6 +31,7 @@ #include #include +#include using namespace clang::clangd; using namespace clang; @@ -73,12 +74,58 @@ std::vector TopLevelDecls; }; -class CppFilePreambleCallbacks : public PreambleCallbacks { +void fillRangeVector( + const SourceManager &SM, + std::vector> DataVector, + std::vector &RangeVector) { + if (RangeVector.empty()) { + for (unsigned I = 0; I < DataVector.size(); I++) { + Position Begin; + Begin.line = SM.getSpellingLineNumber(DataVector[I].first.getBegin()); + Begin.character = + SM.getSpellingColumnNumber(DataVector[I].first.getBegin()); + Position End; + End.line = SM.getSpellingLineNumber(DataVector[I].first.getEnd()); + End.character = SM.getSpellingColumnNumber(DataVector[I].first.getEnd()); + Range R = {Begin, End}; + RangeVector.push_back(R); + } + } +} + +void findPreambleIncludes( + const SourceManager &SM, + std::unordered_map &IncludeLocationMap, + std::vector> DataVector, + std::vector RangeVector) { + + for (unsigned I = 0; I < DataVector.size(); I++) { + auto fileID = DataVector[I].first.getBegin(); + if (fileID.isValid() && + SM.getMainFileID() == SM.getFileID(DataVector[I].first.getBegin())) { + IncludeLocationMap.insert({RangeVector[I], DataVector[I].second}); + } + } +} + +class CppFilePreambleCallbacks : public PreambleCallbacks, public PPCallbacks { public: std::vector takeTopLevelDeclIDs() { return std::move(TopLevelDeclIDs); } + CppFilePreambleCallbacks(SourceManager *SourceMgr, IncludeReferenceMap &IRM) + : SourceMgr(SourceMgr), IRM(IRM) {} + + IncludeReferenceMap takeIRM() { + fillRangeVector(*SourceMgr, IRM.DataVector, IRM.RangeVector); + findPreambleIncludes(*SourceMgr, IRM.IncludeLocationMap, IRM.DataVector, + IRM.RangeVector); + return std::move(IRM); + }; + + IncludeReferenceMap getIRM() { return IRM; }; + void AfterPCHEmitted(ASTWriter &Writer) override { TopLevelDeclIDs.reserve(TopLevelDecls.size()); for (Decl *D : TopLevelDecls) { @@ -97,9 +144,70 @@ } } + void AfterExecute(CompilerInstance &CI) override { + + SourceMgr = &CI.getSourceManager(); + + fillRangeVector(CI.getSourceManager(), IRM.DataVector, IRM.RangeVector); + findPreambleIncludes(CI.getSourceManager(), IRM.IncludeLocationMap, + IRM.DataVector, IRM.RangeVector); + } + + void InclusionDirective(SourceLocation HashLoc, const Token &IncludeTok, + StringRef FileName, bool IsAngled, + CharSourceRange FilenameRange, const FileEntry *File, + StringRef SearchPath, StringRef RelativePath, + const Module *Imported) override { + if (!SourceMgr) { + if (File && !File->tryGetRealPathName().empty()) + IRM.DataVector.push_back(std::pair( + FilenameRange.getAsRange(), File->tryGetRealPathName())); + } else { + Position Begin; + Begin.line = SourceMgr->getSpellingLineNumber( + FilenameRange.getAsRange().getBegin()); + Begin.character = SourceMgr->getSpellingColumnNumber( + FilenameRange.getAsRange().getBegin()); + Position End; + End.line = + SourceMgr->getSpellingLineNumber(FilenameRange.getAsRange().getEnd()); + End.character = SourceMgr->getSpellingColumnNumber( + FilenameRange.getAsRange().getEnd()); + Range R = {Begin, End}; + if (File && !File->tryGetRealPathName().empty()) + IRM.IncludeLocationMap.insert({R, File->tryGetRealPathName()}); + } + } + + std::unique_ptr createPPCallbacks() { + class DelegatingPPCallbacks : public PPCallbacks { + public: + DelegatingPPCallbacks(CppFilePreambleCallbacks &PPCallbacks) : Collector(PPCallbacks) {} + + void InclusionDirective(SourceLocation HashLoc, const Token &IncludeTok, + StringRef FileName, bool IsAngled, + CharSourceRange FilenameRange, const FileEntry *File, + StringRef SearchPath, StringRef RelativePath, + const Module *Imported) override { + Collector.InclusionDirective(HashLoc, IncludeTok, FileName, IsAngled, + FilenameRange, File, SearchPath, RelativePath, + Imported); + } + + private: + CppFilePreambleCallbacks &Collector; + }; + + std::unique_ptr DelegatedPPCallbacks = + llvm::make_unique(*this); + return DelegatedPPCallbacks; + } + private: std::vector TopLevelDecls; std::vector TopLevelDeclIDs; + IncludeReferenceMap IRM; + SourceManager *SourceMgr; }; /// Convert from clang diagnostic level to LSP severity. @@ -155,6 +263,12 @@ std::vector &Output; }; +class EmptyDiagsConsumer : public DiagnosticConsumer { +public: + void HandleDiagnostic(DiagnosticsEngine::Level DiagLevel, + const clang::Diagnostic &Info) override {} +}; + template bool futureIsReady(std::shared_future const &Future) { return Future.wait_for(std::chrono::seconds(0)) == std::future_status::ready; } @@ -171,7 +285,7 @@ std::unique_ptr Buffer, std::shared_ptr PCHs, IntrusiveRefCntPtr VFS, - clangd::Logger &Logger) { + clangd::Logger &Logger, IncludeReferenceMap IRM) { std::vector ASTDiags; StoreDiagsConsumer UnitDiagsConsumer(/*ref*/ ASTDiags); @@ -193,16 +307,22 @@ MainInput.getFile()); return llvm::None; } + + CppFilePreambleCallbacks SerializedDeclsCollector(&Clang->getSourceManager(), + IRM); + + Clang->getPreprocessor().addPPCallbacks(std::move(SerializedDeclsCollector.createPPCallbacks())); + if (!Action->Execute()) Logger.log("Execute() failed when building AST for " + MainInput.getFile()); - // UnitDiagsConsumer is local, we can not store it in CompilerInstance that // has a longer lifetime. Clang->getDiagnostics().setClient(new IgnoreDiagnostics); 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(SerializedDeclsCollector.getIRM())); } namespace { @@ -227,12 +347,15 @@ const SourceLocation &SearchedLocation; const ASTContext &AST; Preprocessor &PP; + std::unordered_map IncludeLocationMap; public: - DeclarationLocationsFinder(raw_ostream &OS, - const SourceLocation &SearchedLocation, - ASTContext &AST, Preprocessor &PP) - : SearchedLocation(SearchedLocation), AST(AST), PP(PP) {} + DeclarationLocationsFinder( + raw_ostream &OS, const SourceLocation &SearchedLocation, ASTContext &AST, + Preprocessor &PP, + std::unordered_map IncludeLocationMap) + : SearchedLocation(SearchedLocation), AST(AST), PP(PP), + IncludeLocationMap(IncludeLocationMap) {} std::vector takeLocations() { // Don't keep the same location multiple times. @@ -262,6 +385,11 @@ SourceMgr.getFileID(SearchedLocation) == FID; } + bool isSameLine(unsigned Line) const { + const SourceManager &SourceMgr = AST.getSourceManager(); + return Line == SourceMgr.getSpellingLineNumber(SearchedLocation); + } + void addDeclarationLocation(const SourceRange &ValSourceRange) { const SourceManager &SourceMgr = AST.getSourceManager(); const LangOptions &LangOpts = AST.getLangOpts(); @@ -275,19 +403,28 @@ End.line = SourceMgr.getSpellingLineNumber(LocEnd) - 1; End.character = SourceMgr.getSpellingColumnNumber(LocEnd) - 1; Range R = {Begin, End}; + addLocation(URI::fromFile( + SourceMgr.getFilename(SourceMgr.getSpellingLoc(LocStart))), + R); + } + + void addLocation(URI Uri, Range R) { Location L; - if (const FileEntry *F = - SourceMgr.getFileEntryForID(SourceMgr.getFileID(LocStart))) { - StringRef FilePath = F->tryGetRealPathName(); - if (FilePath.empty()) - FilePath = F->getName(); - L.uri = URI::fromFile(FilePath); - L.range = R; - DeclarationLocations.push_back(L); - } + L.uri = Uri; + L.range = R; + DeclarationLocations.push_back(L); } void finish() override { + + for (auto It = IncludeLocationMap.begin(); It != IncludeLocationMap.end(); + ++It) { + Range R = It->first; + Path P = It->second; + if (isSameLine(R.start.line)) + addLocation(URI::fromFile(P), R); + } + // Also handle possible macro at the searched location. Token Result; if (!Lexer::getRawToken(SearchedLocation, Result, AST.getSourceManager(), @@ -319,8 +456,9 @@ } // namespace -std::vector clangd::findDefinitions(ParsedAST &AST, Position Pos, - clangd::Logger &Logger) { +std::vector clangd::findDefinitions( + ParsedAST &AST, Position Pos, clangd::Logger &Logger, + std::unordered_map IncludeLocationMap) { const SourceManager &SourceMgr = AST.getASTContext().getSourceManager(); const FileEntry *FE = SourceMgr.getFileEntryForID(SourceMgr.getMainFileID()); if (!FE) @@ -330,7 +468,7 @@ auto DeclLocationsFinder = std::make_shared( llvm::errs(), SourceLocationBeg, AST.getASTContext(), - AST.getPreprocessor()); + AST.getPreprocessor(), IncludeLocationMap); index::IndexingOptions IndexOpts; IndexOpts.SystemSymbolFilter = index::IndexingOptions::SystemSymbolFilterKind::All; @@ -405,11 +543,11 @@ 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); } @@ -565,6 +703,8 @@ } assert(CI && "Couldn't create CompilerInvocation"); + IncludeReferenceMap IRM; + CppFilePreambleCallbacks SerializedDeclsCollector(nullptr, IRM); std::unique_ptr ContentsBuffer = llvm::MemoryBuffer::getMemBufferCopy(NewContents, That->FileName); @@ -590,7 +730,7 @@ IntrusiveRefCntPtr PreambleDiagsEngine = CompilerInstance::createDiagnostics( &CI->getDiagnosticOpts(), &PreambleDiagnosticsConsumer, false); - CppFilePreambleCallbacks SerializedDeclsCollector; + auto BuiltPreamble = PrecompiledPreamble::Build( *CI, ContentsBuffer.get(), Bounds, *PreambleDiagsEngine, VFS, PCHs, /*StoreInMemory=*/That->StorePreamblesInMemory, @@ -631,9 +771,9 @@ { trace::Span Tracer("Build"); SPAN_ATTACH(Tracer, "File", That->FileName); - NewAST = - ParsedAST::Build(std::move(CI), std::move(NewPreamble), - std::move(ContentsBuffer), PCHs, VFS, That->Logger); + NewAST = ParsedAST::Build( + std::move(CI), std::move(NewPreamble), std::move(ContentsBuffer), + PCHs, VFS, That->Logger, SerializedDeclsCollector.getIRM()); } if (NewAST) { Index: clangd/Protocol.h =================================================================== --- clangd/Protocol.h +++ clangd/Protocol.h @@ -108,6 +108,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); + } +}; + struct Location { /// The text document's URI. URI uri; Index: unittests/clangd/ClangdTests.cpp =================================================================== --- unittests/clangd/ClangdTests.cpp +++ unittests/clangd/ClangdTests.cpp @@ -7,7 +7,6 @@ // //===----------------------------------------------------------------------===// -#include "ClangdLSPServer.h" #include "ClangdServer.h" #include "Logger.h" #include "TestFS.h" @@ -620,7 +619,7 @@ AddDocument(FileIndex); Position Pos{LineDist(RandGen), ColumnDist(RandGen)}; - ASSERT_TRUE(!!Server.findDefinitions(FilePaths[FileIndex], Pos)); + Server.findDefinitions(FilePaths[FileIndex], Pos); }; std::vector> AsyncRequests = { @@ -749,6 +748,58 @@ EXPECT_FALSE(PathResult.hasValue()); } +TEST_F(ClangdVFSTest, CheckDefinitionIncludes) { + MockFSProvider FS; + ErrorCheckingDiagConsumer DiagConsumer; + MockCompilationDatabase CDB; + + ClangdServer Server(CDB, DiagConsumer, FS, getDefaultAsyncThreadsCount(), + /*StorePreamblesInMemory=*/true, + EmptyLogger::getInstance()); + + auto FooCpp = getVirtualTestFilePath("foo.cpp"); + const auto SourceContents = R"cpp( + #include "foo.h" + #include "invalid.h" + int b = a; + )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(FooH, HeaderContents); + Server.addDocument(FooCpp, SourceContents); + + Position P = Position{1, 11}; + + std::vector Locations = Server.findDefinitions(FooCpp, P).get().Value; + EXPECT_TRUE(!Locations.empty()); + std::string s("file:///"); + std::string check = Locations[0].uri.uri; + check = check.erase(0, s.size()); + check = check.substr(0, check.size() - 1); + 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 ctrl-clicking on the #include part on the statement + Position P3 = Position{1, 3}; + + Locations = Server.findDefinitions(FooCpp, P3).get().Value; + EXPECT_TRUE(!Locations.empty()); + + // Test invalid include + Position P2 = Position{2, 11}; + + Locations = Server.findDefinitions(FooCpp, P2).get().Value; + EXPECT_TRUE(Locations.empty()); +} + TEST_F(ClangdThreadingTest, NoConcurrentDiagnostics) { class NoConcurrentAccessDiagConsumer : public DiagnosticsConsumer { public: