kadircet updated this revision to Diff 191323. kadircet marked 2 inline comments as done. kadircet added a comment.
- Show include stack in diagnostic message - Point to exact include location Repository: rCTE Clang Tools Extra CHANGES SINCE LAST ACTION https://reviews.llvm.org/D59302/new/ https://reviews.llvm.org/D59302 Files: clangd/ClangdServer.cpp clangd/ClangdServer.h clangd/ClangdUnit.cpp clangd/Compiler.h clangd/Diagnostics.cpp clangd/Diagnostics.h clangd/tool/ClangdMain.cpp unittests/clangd/DiagnosticsTests.cpp
Index: unittests/clangd/DiagnosticsTests.cpp =================================================================== --- unittests/clangd/DiagnosticsTests.cpp +++ unittests/clangd/DiagnosticsTests.cpp @@ -9,6 +9,7 @@ #include "Annotations.h" #include "ClangdUnit.h" #include "SourceCode.h" +#include "TestFS.h" #include "TestIndex.h" #include "TestTU.h" #include "index/MemIndex.h" @@ -73,7 +74,6 @@ return true; } - // Helper function to make tests shorter. Position pos(int line, int character) { Position Res; @@ -603,7 +603,174 @@ "Add include \"x.h\" for symbol a::X"))))); } +ParsedAST +build(const std::string &MainFile, const llvm::StringMap<std::string> &Files, + llvm::Optional<int64_t> LimitDiagsOutsideMainFile = llvm::None) { + std::vector<const char *> Cmd = {"clang", MainFile.c_str()}; + ParseInputs Inputs; + Inputs.CompileCommand.Filename = MainFile; + Inputs.CompileCommand.CommandLine = {Cmd.begin(), Cmd.end()}; + Inputs.CompileCommand.Directory = testRoot(); + Inputs.Contents = Files.lookup(MainFile); + Inputs.FS = buildTestFS(Files); + Inputs.Opts = ParseOptions(); + Inputs.Opts.LimitDiagsOutsideMainFile = LimitDiagsOutsideMainFile; + auto PCHs = std::make_shared<PCHContainerOperations>(); + auto CI = buildCompilerInvocation(Inputs); + assert(CI && "Failed to build compilation invocation."); + auto Preamble = + buildPreamble(MainFile, *CI, + /*OldPreamble=*/nullptr, + /*OldCompileCommand=*/Inputs.CompileCommand, Inputs, PCHs, + /*StoreInMemory=*/true, /*PreambleCallback=*/nullptr); + auto AST = buildAST(MainFile, createInvocationFromCommandLine(Cmd), Inputs, + Preamble, PCHs); + if (!AST.hasValue()) { + ADD_FAILURE() << "Failed to build code:\n" << Files.lookup(MainFile); + llvm_unreachable("Failed to build TestTU!"); + } + return std::move(*AST); +} + +TEST(DiagsInHeaders, DiagInsideHeader) { + Annotations Main(R"cpp( + #include [["a.h"]] + void foo() {})cpp"); + std::string MainFile = testPath("main.cc"); + llvm::StringMap<std::string> Files = {{MainFile, Main.code()}, + {testPath("a.h"), "no_type_spec;"}}; + auto FS = buildTestFS(Files); + + EXPECT_THAT(build(MainFile, Files).getDiagnostics(), + UnorderedElementsAre( + Diag(Main.range(), "/clangd-test/a.h:1:1: C++ requires a " + "type specifier for all declarations"))); +} + +TEST(DiagsInHeaders, DiagInTransitiveInclude) { + Annotations Main(R"cpp( + #include [["a.h"]] + void foo() {})cpp"); + std::string MainFile = testPath("main.cc"); + llvm::StringMap<std::string> Files = {{MainFile, Main.code()}, + {testPath("a.h"), "#include \"b.h\""}, + {testPath("b.h"), "no_type_spec;"}}; + auto FS = buildTestFS(Files); + + EXPECT_THAT(build(MainFile, Files).getDiagnostics(), + UnorderedElementsAre( + Diag(Main.range(), + "In file included from: " + "/clangd-test/a.h:1:10:\n/clangd-test/b.h:1:1: C++ " + "requires a type specifier for all declarations"))); +} + +TEST(DiagsInHeaders, DiagInMultipleHeaders) { + Annotations Main(R"cpp( + #include $a[["a.h"]] + #include $b[["b.h"]] + void foo() {})cpp"); + std::string MainFile = testPath("main.cc"); + llvm::StringMap<std::string> Files = {{MainFile, Main.code()}, + {testPath("a.h"), "no_type_spec;"}, + {testPath("b.h"), "no_type_spec;"}}; + auto FS = buildTestFS(Files); + + EXPECT_THAT( + build(MainFile, Files).getDiagnostics(), + UnorderedElementsAre( + Diag(Main.range("a"), "/clangd-test/a.h:1:1: C++ requires a type " + "specifier for all declarations"), + Diag(Main.range("b"), "/clangd-test/b.h:1:1: C++ requires a type " + "specifier for all declarations"))); +} + +TEST(DiagsInHeaders, PreferExpansionLocation) { + Annotations Main(R"cpp( + #include [["a.h"]] + #include "b.h" + void foo() {})cpp"); + std::string MainFile = testPath("main.cc"); + llvm::StringMap<std::string> Files = { + {MainFile, Main.code()}, + {testPath("a.h"), "#include \"b.h\"\n"}, + {testPath("b.h"), "#ifndef X\n#define X\nno_type_spec;\n#endif"}}; + auto FS = buildTestFS(Files); + + EXPECT_THAT(build(MainFile, Files).getDiagnostics(), + UnorderedElementsAre( + Diag(Main.range(), + "In file included from: " + "/clangd-test/a.h:1:10:\n/clangd-test/b.h:3:1: C++ " + "requires a type specifier for all declarations"))); +} + +TEST(DiagsInHeaders, PreferExpansionLocationMacros) { + Annotations Main(R"cpp( + #define X + #include "a.h" + #undef X + #include [["b.h"]] + void foo() {})cpp"); + std::string MainFile = testPath("main.cc"); + llvm::StringMap<std::string> Files = { + {MainFile, Main.code()}, + {testPath("a.h"), "#include \"c.h\"\n"}, + {testPath("b.h"), "#include \"c.h\"\n"}, + {testPath("c.h"), "#ifndef X\n#define X\nno_type_spec;\n#endif"}}; + auto FS = buildTestFS(Files); + + EXPECT_THAT(build(MainFile, Files).getDiagnostics(), + UnorderedElementsAre( + Diag(Main.range(), + "In file included from: " + "/clangd-test/b.h:1:10:\n/clangd-test/c.h:3:1: C++ " + "requires a type specifier for all declarations"))); +} + +TEST(DiagsInHeaders, LimitDiagsOutsideMainFile) { + Annotations Main(R"cpp( + #include [["a.h"]] + #include "b.h" + void foo() {})cpp"); + std::string MainFile = testPath("main.cc"); + llvm::StringMap<std::string> Files = { + {MainFile, Main.code()}, + {testPath("a.h"), "#include \"c.h\"\n"}, + {testPath("b.h"), "#include \"c.h\"\n"}, + {testPath("c.h"), "#ifndef X\n#define X\nno_type_spec;\n#endif"}}; + auto FS = buildTestFS(Files); + + EXPECT_THAT(build(MainFile, Files).getDiagnostics(), + UnorderedElementsAre( + Diag(Main.range(), + "In file included from: " + "/clangd-test/a.h:1:10:\n/clangd-test/c.h:3:1: C++ " + "requires a type specifier for all declarations"))); + + EXPECT_THAT(build(MainFile, Files, 0).getDiagnostics(), + UnorderedElementsAre()); +} + +TEST(DiagsInHeaders, OnlyErrorOrFatal) { + Annotations Main(R"cpp( + #include [["a.h"]] + void foo() {})cpp"); + std::string MainFile = testPath("main.cc"); + llvm::StringMap<std::string> Files = {{MainFile, Main.code()}, + {testPath("a.h"), + R"cpp( + no_type_spec; + int x = 5/0; + )cpp"}}; + auto FS = buildTestFS(Files); + + EXPECT_THAT(build(MainFile, Files).getDiagnostics(), + UnorderedElementsAre( + Diag(Main.range(), "/clangd-test/a.h:2:44: C++ requires a " + "type specifier for all declarations"))); +} + } // namespace } // namespace clangd } // namespace clang - Index: clangd/tool/ClangdMain.cpp =================================================================== --- clangd/tool/ClangdMain.cpp +++ clangd/tool/ClangdMain.cpp @@ -219,6 +219,12 @@ "includes using index."), llvm::cl::init(true)); +static llvm::cl::opt<int> LimitDiagsOutsideMainFile( + "limit-diags-outside-mainfile", + llvm::cl::desc("Attempts to fix diagnostic errors caused by missing " + "includes using index."), + llvm::cl::init(100), llvm::cl::Hidden); + namespace { /// \brief Supports a test URI scheme with relaxed constraints for lit tests. @@ -458,6 +464,7 @@ } Opts.ClangTidyOptProvider = ClangTidyOptProvider.get(); Opts.SuggestMissingIncludes = SuggestMissingIncludes; + Opts.LimitDiagsOutsideMainFile = LimitDiagsOutsideMainFile; ClangdLSPServer LSPServer( *TransportLayer, FSProvider, CCOpts, CompileCommandsDirPath, /*UseDirBasedCDB=*/CompileArgsFrom == FilesystemCompileArgs, Opts); Index: clangd/Diagnostics.h =================================================================== --- clangd/Diagnostics.h +++ clangd/Diagnostics.h @@ -14,6 +14,8 @@ #include "clang/Basic/Diagnostic.h" #include "clang/Basic/LangOptions.h" #include "llvm/ADT/ArrayRef.h" +#include "llvm/ADT/None.h" +#include "llvm/ADT/Optional.h" #include "llvm/ADT/STLExtras.h" #include "llvm/ADT/StringSet.h" #include <cassert> @@ -80,6 +82,9 @@ std::vector<Note> Notes; /// *Alternative* fixes for this diagnostic, one should be chosen. std::vector<Fix> Fixes; + /// If diagnostics is coming outside main file, points to relevant include + /// directive, otherwise None. + llvm::Optional<Position> IncludeDirective = llvm::None; }; llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const Diag &D); Index: clangd/Diagnostics.cpp =================================================================== --- clangd/Diagnostics.cpp +++ clangd/Diagnostics.cpp @@ -10,30 +10,19 @@ #include "Compiler.h" #include "Logger.h" #include "SourceCode.h" +#include "clang/Basic/Diagnostic.h" +#include "clang/Basic/SourceLocation.h" #include "clang/Basic/SourceManager.h" #include "clang/Lex/Lexer.h" #include "llvm/Support/Capacity.h" #include "llvm/Support/Path.h" +#include "llvm/Support/Signals.h" #include <algorithm> namespace clang { namespace clangd { namespace { - -bool mentionsMainFile(const Diag &D) { - if (D.InsideMainFile) - return true; - // Fixes are always in the main file. - if (!D.Fixes.empty()) - return true; - for (auto &N : D.Notes) { - if (N.InsideMainFile) - return true; - } - return false; -} - // Checks whether a location is within a half-open range. // Note that clang also uses closed source ranges, which this can't handle! bool locationInRange(SourceLocation L, CharSourceRange R, @@ -379,6 +368,38 @@ LastDiag = Diag(); LastDiag->ID = Info.getID(); FillDiagBase(*LastDiag); + auto IncludeLocation = Info.getSourceManager() + .getPresumedLoc(Info.getLocation(), + /*UseLineDirectives=*/false) + .getIncludeLoc(); + std::vector<std::string> IncludeStack; + while (IncludeLocation.isValid()) { + LastDiag->IncludeDirective = + sourceLocToPosition(Info.getSourceManager(), IncludeLocation); + IncludeStack.push_back( + IncludeLocation.printToString(Info.getSourceManager())); + IncludeLocation = Info.getSourceManager() + .getPresumedLoc(IncludeLocation, + /*UseLineDirectives=*/false) + .getIncludeLoc(); + } + if (LastDiag->IncludeDirective) { + std::string Message; + { + llvm::raw_string_ostream OS(Message); + // We don't show the main file as part of include stack, which is the + // last element. + if (IncludeStack.size() > 1) { + IncludeStack.pop_back(); + std::reverse(IncludeStack.begin(), IncludeStack.end()); + for (llvm::StringRef Inc : IncludeStack) + OS << "In file included from: " << Inc << ":\n"; + } + OS << Info.getLocation().printToString(Info.getSourceManager()) << ": " + << LastDiag->Message; + } + LastDiag->Message = std::move(Message); + } if (!Info.getFixItHints().empty()) AddFix(true /* try to invent a message instead of repeating the diag */); @@ -413,11 +434,7 @@ void StoreDiags::flushLastDiag() { if (!LastDiag) return; - if (mentionsMainFile(*LastDiag)) - Output.push_back(std::move(*LastDiag)); - else - vlog("Dropped diagnostic outside main file: {0}: {1}", LastDiag->File, - LastDiag->Message); + Output.push_back(std::move(*LastDiag)); LastDiag.reset(); } Index: clangd/Compiler.h =================================================================== --- clangd/Compiler.h +++ clangd/Compiler.h @@ -21,6 +21,7 @@ #include "clang/Frontend/CompilerInvocation.h" #include "clang/Frontend/PrecompiledPreamble.h" #include "clang/Tooling/CompilationDatabase.h" +#include "llvm/ADT/Optional.h" namespace clang { namespace clangd { @@ -38,6 +39,7 @@ struct ParseOptions { tidy::ClangTidyOptions ClangTidyOpts; bool SuggestMissingIncludes = false; + llvm::Optional<int64_t> LimitDiagsOutsideMainFile; }; /// Information required to run clang, e.g. to parse AST or do code completion. Index: clangd/ClangdUnit.cpp =================================================================== --- clangd/ClangdUnit.cpp +++ clangd/ClangdUnit.cpp @@ -14,11 +14,13 @@ #include "Headers.h" #include "IncludeFixer.h" #include "Logger.h" +#include "Protocol.h" #include "SourceCode.h" #include "Trace.h" #include "index/CanonicalIncludes.h" #include "index/Index.h" #include "clang/AST/ASTContext.h" +#include "clang/Basic/Diagnostic.h" #include "clang/Basic/LangOptions.h" #include "clang/Frontend/CompilerInstance.h" #include "clang/Frontend/CompilerInvocation.h" @@ -34,9 +36,11 @@ #include "clang/Serialization/ASTWriter.h" #include "clang/Tooling/CompilationDatabase.h" #include "llvm/ADT/ArrayRef.h" +#include "llvm/ADT/DenseMap.h" #include "llvm/ADT/STLExtras.h" #include "llvm/ADT/SmallString.h" #include "llvm/ADT/SmallVector.h" +#include "llvm/Support/ScopedPrinter.h" #include "llvm/Support/raw_ostream.h" #include <algorithm> #include <memory> @@ -45,6 +49,19 @@ namespace clangd { namespace { +bool mentionsMainFile(const Diag &D) { + if (D.InsideMainFile) + return true; + // Fixes are always in the main file. + if (!D.Fixes.empty()) + return true; + for (auto &N : D.Notes) { + if (N.InsideMainFile) + return true; + } + return false; +} + bool compileCommandsAreEqual(const tooling::CompileCommand &LHS, const tooling::CompileCommand &RHS) { // We don't check for Output, it should not matter to clangd. @@ -236,6 +253,15 @@ const LangOptions &LangOpts; }; +// Generates a mapping from start of an include directive to its range. +std::map<Position, Range> +positionToRangeMap(const std::vector<Inclusion> &MainFileIncludes) { + std::map<Position, Range> RangeMap; + for (const Inclusion &Inc : MainFileIncludes) + RangeMap[Inc.R.start] = Inc.R; + return RangeMap; +} + } // namespace void dumpAST(ParsedAST &AST, llvm::raw_ostream &OS) { @@ -371,14 +397,45 @@ // So just inform the preprocessor of EOF, while keeping everything alive. Clang->getPreprocessor().EndSourceFile(); - std::vector<Diag> Diags = ASTDiags.take(); + std::vector<Diag> Diags; + int64_t DiagsOutsideMainFile = 0; + for (const Diag &D : ASTDiags.take()) { + if (!mentionsMainFile(D) && Opts.LimitDiagsOutsideMainFile && + ++DiagsOutsideMainFile > *Opts.LimitDiagsOutsideMainFile) + continue; + Diags.push_back(D); + } // Populate diagnostic source. for (auto &D : Diags) D.S = !CTContext->getCheckName(D.ID).empty() ? Diag::ClangTidy : Diag::Clang; // Add diagnostics from the preamble, if any. - if (Preamble) - Diags.insert(Diags.begin(), Preamble->Diags.begin(), Preamble->Diags.end()); + if (Preamble) { + for (const Diag &D : Preamble->Diags) { + if (!mentionsMainFile(D)) { + if (D.Severity != DiagnosticsEngine::Level::Error && + D.Severity != DiagnosticsEngine::Level::Fatal) + continue; + if (Opts.LimitDiagsOutsideMainFile && + DiagsOutsideMainFile >= *Opts.LimitDiagsOutsideMainFile) + continue; + DiagsOutsideMainFile++; + } + Diags.push_back(D); + } + } + + // Remap diagnostics from headers into their include location inside main + // file. + const auto RangeMap = positionToRangeMap(Includes.MainFileIncludes); + for (auto &D : Diags) { + if (!mentionsMainFile(D)) { + assert(D.IncludeDirective && + "Diagnostic is not mentioned in mainfile but also not imported?"); + D.Range = RangeMap.at(*D.IncludeDirective); + D.File = MainInput.getFile(); + } + } return ParsedAST(std::move(Preamble), std::move(Clang), std::move(Action), std::move(ParsedDecls), std::move(Diags), std::move(Includes), std::move(CanonIncludes)); Index: clangd/ClangdServer.h =================================================================== --- clangd/ClangdServer.h +++ clangd/ClangdServer.h @@ -119,6 +119,8 @@ std::chrono::milliseconds(500); bool SuggestMissingIncludes = false; + + llvm::Optional<int64_t> LimitDiagsOutsideMainFile = llvm::None; }; // Sensible default options for use in tests. // Features like indexing must be enabled if desired. @@ -294,6 +296,10 @@ // can be caused by missing includes (e.g. member access in incomplete type). bool SuggestMissingIncludes = false; + // When set only that many number of diagnostics from headers will be surfaced + // in main file. + llvm::Optional<int64_t> LimitDiagsOutsideMainFile = llvm::None; + // GUARDED_BY(CachedCompletionFuzzyFindRequestMutex) llvm::StringMap<llvm::Optional<FuzzyFindRequest>> CachedCompletionFuzzyFindRequestByFile; Index: clangd/ClangdServer.cpp =================================================================== --- clangd/ClangdServer.cpp +++ clangd/ClangdServer.cpp @@ -113,6 +113,7 @@ : nullptr), ClangTidyOptProvider(Opts.ClangTidyOptProvider), SuggestMissingIncludes(Opts.SuggestMissingIncludes), + LimitDiagsOutsideMainFile(Opts.LimitDiagsOutsideMainFile), WorkspaceRoot(Opts.WorkspaceRoot), PCHs(std::make_shared<PCHContainerOperations>()), // Pass a callback into `WorkScheduler` to extract symbols from a newly @@ -153,6 +154,7 @@ if (ClangTidyOptProvider) Opts.ClangTidyOpts = ClangTidyOptProvider->getOptions(File); Opts.SuggestMissingIncludes = SuggestMissingIncludes; + Opts.LimitDiagsOutsideMainFile = LimitDiagsOutsideMainFile; // FIXME: some build systems like Bazel will take time to preparing // environment to build the file, it would be nice if we could emit a // "PreparingBuild" status to inform users, it is non-trivial given the
_______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits