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; @@ -425,7 +426,8 @@ llvm::errc::invalid_argument); std::vector Result; - Resources->getAST().get()->runUnderLock([Pos, &Result, &Ctx](ParsedAST *AST) { + Resources->getAST().get()->runUnderLock([Pos, &Result, &Ctx, + this](ParsedAST *AST) { if (!AST) return; Result = clangd::findDefinitions(Ctx, *AST, Pos); Index: clangd/ClangdUnit.h =================================================================== --- clangd/ClangdUnit.h +++ clangd/ClangdUnit.h @@ -23,6 +23,7 @@ #include #include #include +#include namespace llvm { class raw_ostream; @@ -58,17 +59,22 @@ std::vector Diags; }; +using IncludeReferenceMap = std::unordered_map; + /// Stores and provides access to parsed AST. class ParsedAST { public: /// Attempts to run Clang and store parsed AST. If \p Preamble is non-null /// it is reused during parsing. static llvm::Optional - Build(const Context &Ctx, std::unique_ptr CI, - std::shared_ptr Preamble, - std::unique_ptr Buffer, - std::shared_ptr PCHs, - IntrusiveRefCntPtr VFS); + Build(const Context &Ctx, + std::unique_ptr CI, + std::shared_ptr Preamble, + std::unique_ptr Buffer, + std::shared_ptr PCHs, + IntrusiveRefCntPtr VFS, + IncludeReferenceMap IRM); + ParsedAST(ParsedAST &&Other); ParsedAST &operator=(ParsedAST &&Other); @@ -88,12 +94,14 @@ const std::vector &getDiagnostics() const; + IncludeReferenceMap &getIRM() { 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(); @@ -113,6 +121,7 @@ std::vector Diags; std::vector TopLevelDecls; bool PreambleDeclsDeserialized; + IncludeReferenceMap IRM; }; // Provides thread-safe access to ParsedAST. @@ -258,13 +267,14 @@ SourceLocation getBeginningOfIdentifier(ParsedAST &Unit, const Position &Pos, const FileEntry *FE); -/// Get definition of symbol at a specified \p Pos. -std::vector findDefinitions(const Context &Ctx, ParsedAST &AST, - Position Pos); - std::vector findDocumentHighlights(const Context &Ctx, ParsedAST &AST, Position Pos); + + +std::vector +findDefinitions(const Context &Ctx, ParsedAST &AST, Position Pos); + /// For testing/debugging purposes. Note that this method deserializes all /// unserialized Decls, so use with care. void dumpAST(ParsedAST &AST, llvm::raw_ostream &OS); Index: clangd/ClangdUnit.cpp =================================================================== --- clangd/ClangdUnit.cpp +++ clangd/ClangdUnit.cpp @@ -27,8 +27,6 @@ #include "llvm/ADT/SmallVector.h" #include "llvm/Support/CrashRecoveryContext.h" #include "llvm/Support/Format.h" -#include -#include using namespace clang::clangd; using namespace clang; @@ -71,12 +69,50 @@ std::vector TopLevelDecls; }; +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.getMainFileID() == SourceMgr.getFileID(FilenameRange.getAsRange().getBegin()) && 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({getRange(FilenameRange.getAsRange()), File->tryGetRealPathName()}); + } + } + + Range getRange(SourceRange SR) { + Position Begin; + Begin.line = SourceMgr.getSpellingLineNumber(SR.getBegin()); + Begin.character = SourceMgr.getSpellingColumnNumber(SR.getBegin()); + Position End; + End.line = SourceMgr.getSpellingLineNumber(SR.getEnd()); + End.character = SourceMgr.getSpellingColumnNumber(SR.getEnd()); + return {Begin, End}; + } + +private: + SourceManager &SourceMgr; + IncludeReferenceMap &IRM; +}; + class CppFilePreambleCallbacks : public PreambleCallbacks { public: std::vector takeTopLevelDeclIDs() { return std::move(TopLevelDeclIDs); } + CppFilePreambleCallbacks(IncludeReferenceMap &IRM) + : IRM(IRM) {} + void AfterPCHEmitted(ASTWriter &Writer) override { TopLevelDeclIDs.reserve(TopLevelDecls.size()); for (Decl *D : TopLevelDecls) { @@ -95,9 +131,20 @@ } } + void BeforeExecute(CompilerInstance &CI) override { + SourceMgr = &CI.getSourceManager(); + } + + std::unique_ptr createPPCallbacks() { + return llvm::make_unique(*SourceMgr, IRM); + } + private: std::vector TopLevelDecls; std::vector TopLevelDeclIDs; + SourceManager *SourceMgr; + IncludeReferenceMap &IRM; + std::vector> TempPreambleIncludeLocations; }; /// Convert from clang diagnostic level to LSP severity. @@ -230,7 +277,8 @@ std::shared_ptr Preamble, std::unique_ptr Buffer, std::shared_ptr PCHs, - IntrusiveRefCntPtr VFS) { + IntrusiveRefCntPtr VFS, + IncludeReferenceMap IRM) { std::vector ASTDiags; StoreDiagsConsumer UnitDiagsConsumer(/*ref*/ ASTDiags); @@ -249,19 +297,22 @@ const FrontendInputFile &MainInput = Clang->getFrontendOpts().Inputs[0]; if (!Action->BeginSourceFile(*Clang, MainInput)) { log(Ctx, "BeginSourceFile() failed when building AST for " + - MainInput.getFile()); + 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()); - // 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(IRM)); } namespace { @@ -284,23 +335,34 @@ class DeclarationAndMacrosFinder : public index::IndexDataConsumer { std::vector Decls; std::vector MacroInfos; + std::vector DeclarationLocations; const SourceLocation &SearchedLocation; const ASTContext &AST; Preprocessor &PP; + IncludeReferenceMap IncludeLocationMap; public: DeclarationAndMacrosFinder(raw_ostream &OS, const SourceLocation &SearchedLocation, - ASTContext &AST, Preprocessor &PP) - : SearchedLocation(SearchedLocation), AST(AST), PP(PP) {} + ASTContext &AST, Preprocessor &PP, const IncludeReferenceMap &IncludeLocationMap) + : SearchedLocation(SearchedLocation), AST(AST), PP(PP), IncludeLocationMap(IncludeLocationMap) {} std::vector takeDecls() { - // Don't keep the same declaration multiple times. + // Don't keep the same declaration multiple times. + // This can happen when nodes in the AST are visited twice. + std::sort(Decls.begin(), Decls.end()); + auto Last = std::unique(Decls.begin(), Decls.end()); + Decls.erase(Last, Decls.end()); + return std::move(Decls); + } + + std::vector takeLocations() { + // Don't keep the same location multiple times. // This can happen when nodes in the AST are visited twice. - std::sort(Decls.begin(), Decls.end()); - auto Last = std::unique(Decls.begin(), Decls.end()); - Decls.erase(Last, Decls.end()); - return std::move(Decls); + std::sort(DeclarationLocations.begin(), DeclarationLocations.end()); + auto Last = std::unique(DeclarationLocations.begin(), DeclarationLocations.end()); + DeclarationLocations.erase(Last, DeclarationLocations.end()); + return std::move(DeclarationLocations); } std::vector takeMacroInfos() { @@ -328,7 +390,43 @@ SourceMgr.getFileID(SearchedLocation) == FID; } + + bool isInRange(Range R) const { + const SourceManager &SourceMgr = AST.getSourceManager(); + unsigned CharNumber = SourceMgr.getSpellingColumnNumber(SearchedLocation); + return R.start.line == SourceMgr.getSpellingLineNumber(SearchedLocation) && + (R.start.character >= CharNumber && CharNumber <= R.end.character); + + } + + void addDeclarationLocation(const SourceRange &ValSourceRange) { + const SourceManager &SourceMgr = AST.getSourceManager(); + const LangOptions &LangOpts = AST.getLangOpts(); + SourceLocation LocStart = ValSourceRange.getBegin(); + 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; + Range R = {Begin, End}; + DeclarationLocations.push_back(Location{URI::fromFile( + SourceMgr.getFilename(SourceMgr.getSpellingLoc(LocStart))), R}); + } + + void finish() override { + for (auto &IncludeLoc : IncludeLocationMap) { + Range R = IncludeLoc.first; + + if (isInRange(R)) { + DeclarationLocations.push_back(Location{URI::fromFile(IncludeLoc.second), Range{Position{0,0}, Position{0,0}}}); + return; + } + } + // Also handle possible macro at the searched location. Token Result; if (!Lexer::getRawToken(SearchedLocation, Result, AST.getSourceManager(), @@ -466,7 +564,7 @@ auto DeclMacrosFinder = std::make_shared( llvm::errs(), SourceLocationBeg, AST.getASTContext(), - AST.getPreprocessor()); + AST.getPreprocessor(), AST.getIRM()); index::IndexingOptions IndexOpts; IndexOpts.SystemSymbolFilter = index::IndexingOptions::SystemSymbolFilterKind::All; @@ -493,22 +591,27 @@ Result.push_back(*L); } - return Result; + if (Result.empty()) + return DeclMacrosFinder->takeLocations(); + else + return Result; } std::vector clangd::findDocumentHighlights(const Context &Ctx, ParsedAST &AST, Position Pos) { + const SourceManager &SourceMgr = AST.getASTContext().getSourceManager(); const FileEntry *FE = SourceMgr.getFileEntryForID(SourceMgr.getMainFileID()); if (!FE) return {}; SourceLocation SourceLocationBeg = getBeginningOfIdentifier(AST, Pos, FE); + IncludeReferenceMap IRM; auto DeclMacrosFinder = std::make_shared( llvm::errs(), SourceLocationBeg, AST.getASTContext(), - AST.getPreprocessor()); + AST.getPreprocessor(), IRM); index::IndexingOptions IndexOpts; IndexOpts.SystemSymbolFilter = index::IndexingOptions::SystemSymbolFilterKind::All; @@ -592,11 +695,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); } @@ -750,6 +853,7 @@ } assert(CI && "Couldn't create CompilerInvocation"); + IncludeReferenceMap IRM; std::unique_ptr ContentsBuffer = llvm::MemoryBuffer::getMemBufferCopy(NewContents, That->FileName); @@ -775,7 +879,9 @@ IntrusiveRefCntPtr PreambleDiagsEngine = CompilerInstance::createDiagnostics( &CI->getDiagnosticOpts(), &PreambleDiagnosticsConsumer, false); - CppFilePreambleCallbacks SerializedDeclsCollector; + + CppFilePreambleCallbacks SerializedDeclsCollector(IRM); + auto BuiltPreamble = PrecompiledPreamble::Build( *CI, ContentsBuffer.get(), Bounds, *PreambleDiagsEngine, VFS, PCHs, /*StoreInMemory=*/That->StorePreamblesInMemory, @@ -816,10 +922,9 @@ { trace::Span Tracer(Ctx, "Build"); SPAN_ATTACH(Tracer, "File", That->FileName); - NewAST = ParsedAST::Build(Ctx, std::move(CI), std::move(NewPreamble), - std::move(ContentsBuffer), PCHs, VFS); + NewAST = ParsedAST::Build(Ctx, + std::move(CI), std::move(NewPreamble), std::move(ContentsBuffer), PCHs, VFS, IRM); } - if (NewAST) { Diagnostics.insert(Diagnostics.end(), NewAST->getDiagnostics().begin(), NewAST->getDiagnostics().end()); 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 "Context.h" #include "TestFS.h" @@ -751,6 +750,75 @@ 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; + // 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(FooH, HeaderContents); + Server.addDocument(FooCpp, SourceContents); + + Position P = Position{1, 11}; + + auto ExpectedLocations = Server.findDefinitions(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 ctrl-clicking on the #include part on the statement + Position P2 = Position{1, 3}; + + ExpectedLocations = Server.findDefinitions(FooCpp, P2); + ASSERT_TRUE(!!ExpectedLocations); + Locations = ExpectedLocations->Value; + EXPECT_TRUE(!Locations.empty()); + + // Test invalid include + Position P3 = Position{2, 11}; + + ExpectedLocations = Server.findDefinitions(FooCpp, P3); + ASSERT_TRUE(!!ExpectedLocations); + Locations = ExpectedLocations->Value; + EXPECT_TRUE(Locations.empty()); + + // Test include outside of Preamble + Position P4 = Position{6, 5}; + + ExpectedLocations = Server.findDefinitions(FooCpp, P4); + ASSERT_TRUE(!!ExpectedLocations); + Locations = ExpectedLocations->Value; + EXPECT_TRUE(!Locations.empty()); +} + TEST_F(ClangdThreadingTest, NoConcurrentDiagnostics) { class NoConcurrentAccessDiagConsumer : public DiagnosticsConsumer { public: