sammccall created this revision. sammccall added a reviewer: hokein. Herald added subscribers: cfe-commits, usaxena95, kadircet, arphaman, jkorous, MaskRay, ilya-biryukov. Herald added a reviewer: aaron.ballman. Herald added a project: clang.
Parsing std::make_unique is an exception to the usual non-parsing of function bodies in the preamble. (A hook is added to PreambleCallbacks to allow this). This allows us to diagnose make_unique<Foo>(wrong arg list), and opens the door to providing signature help (by detecting where the arg list is forwarded to). This function is trivial (checked libc++ and libstdc++) and doesn't result in any extra templates being instantiated, so this should be cheap. This uncovered a second issue (already visible with class templates)... Errors produced by template instantiation have primary locations within the template, with instantiation stack reported as notes. For templates defined in headers, these end up reported at the #include directive, which isn't terribly helpful as the header itself is probably fine. This patch reports them at the instantiation site (the first location in the instantiation stack that's in the main file). This in turn required a bit of refactoring in Diagnostics so we can delay relocating the diagnostic until all notes are available. Repository: rG LLVM Github Monorepo https://reviews.llvm.org/D81351 Files: clang-tools-extra/clangd/Diagnostics.cpp clang-tools-extra/clangd/Diagnostics.h clang-tools-extra/clangd/Preamble.cpp clang-tools-extra/clangd/unittests/DiagnosticsTests.cpp clang/include/clang/Frontend/PrecompiledPreamble.h clang/lib/Frontend/PrecompiledPreamble.cpp
Index: clang/lib/Frontend/PrecompiledPreamble.cpp =================================================================== --- clang/lib/Frontend/PrecompiledPreamble.cpp +++ clang/lib/Frontend/PrecompiledPreamble.cpp @@ -189,6 +189,10 @@ Action.setEmittedPreamblePCH(getWriter()); } + bool shouldSkipFunctionBody(Decl *D) override { + return Action.Callbacks.shouldSkipFunctionBody(D); + } + private: PrecompilePreambleAction &Action; std::unique_ptr<raw_ostream> Out; @@ -756,6 +760,7 @@ return nullptr; } CommentHandler *PreambleCallbacks::getCommentHandler() { return nullptr; } +bool PreambleCallbacks::shouldSkipFunctionBody(Decl *D) { return true; } static llvm::ManagedStatic<BuildPreambleErrorCategory> BuildPreambleErrCategory; Index: clang/include/clang/Frontend/PrecompiledPreamble.h =================================================================== --- clang/include/clang/Frontend/PrecompiledPreamble.h +++ clang/include/clang/Frontend/PrecompiledPreamble.h @@ -34,6 +34,7 @@ namespace clang { class CompilerInstance; class CompilerInvocation; +class Decl; class DeclGroupRef; class PCHContainerOperations; @@ -283,6 +284,8 @@ virtual std::unique_ptr<PPCallbacks> createPPCallbacks(); /// The returned CommentHandler will be added to the preprocessor if not null. virtual CommentHandler *getCommentHandler(); + + virtual bool shouldSkipFunctionBody(Decl *D); }; enum class BuildPreambleError { Index: clang-tools-extra/clangd/unittests/DiagnosticsTests.cpp =================================================================== --- clang-tools-extra/clangd/unittests/DiagnosticsTests.cpp +++ clang-tools-extra/clangd/unittests/DiagnosticsTests.cpp @@ -45,9 +45,9 @@ return Field(&Diag::Fixes, UnorderedElementsAre(FixMatcher1, FixMatcher2)); } -::testing::Matcher<const Diag &> -WithNote(::testing::Matcher<Note> NoteMatcher) { - return Field(&Diag::Notes, ElementsAre(NoteMatcher)); +template <typename... NoteMatcherTypes> +::testing::Matcher<const Diag &> WithNote(NoteMatcherTypes... NoteMatcher) { + return Field(&Diag::Notes, ElementsAre(NoteMatcher...)); } MATCHER_P2(Diag, Range, Message, @@ -272,6 +272,51 @@ "use a trailing return type for this function"))))); } +TEST(DiagnosticTest, TemplatesInHeaders) { + // Diagnostics from templates defined in headers are placed at the expansion. + Annotations Main(R"cpp( + Derived<int> [[y]]; // error-ok + )cpp"); + Annotations Header(R"cpp( + template <typename T> + struct Derived : [[T]] {};" + )cpp"); + TestTU TU = TestTU::withCode(Main.code()); + TU.HeaderCode = Header.code().str(); + EXPECT_THAT( + TU.build().getDiagnostics(), + ElementsAre(AllOf( + Diag(Main.range(), "in template: base specifier must name a class"), + WithNote(Diag(Header.range(), "error occurred here"), + Diag(Main.range(), "in instantiation of template class " + "'Derived<int>' requested here"))))); +} + +TEST(DiagnosticTest, MakeUnique) { + // We usually miss diagnostics from header functions as we don't parse them. + // std::make_unique is an exception. + Annotations Main(R"cpp( + struct S { S(char*); }; + auto x = std::[[make_unique]]<S>(42); // error-ok + )cpp"); + TestTU TU = TestTU::withCode(Main.code()); + TU.HeaderCode = R"cpp( + namespace std { + // These mocks aren't quite right - we omit unique_ptr for simplicity. + // forward is included to show its body is not needed to get the diagnostic. + template <typename T> T&& forward(T& t) { return static_cast<T&&>(t); } + template <typename T, typename... A> T* make_unique(A&&... args) { + return new T(std::forward<A>(args)...); + } + } + )cpp"; + EXPECT_THAT(TU.build().getDiagnostics(), + UnorderedElementsAre( + Diag(Main.range(), + "in template: " + "no matching constructor for initialization of 'S'"))); +} + TEST(DiagnosticTest, NoMultipleDiagnosticInFlight) { Annotations Main(R"cpp( template <typename T> struct Foo { Index: clang-tools-extra/clangd/Preamble.cpp =================================================================== --- clang-tools-extra/clangd/Preamble.cpp +++ clang-tools-extra/clangd/Preamble.cpp @@ -12,6 +12,7 @@ #include "SourceCode.h" #include "support/Logger.h" #include "support/Trace.h" +#include "clang/AST/DeclTemplate.h" #include "clang/Basic/Diagnostic.h" #include "clang/Basic/LangOptions.h" #include "clang/Basic/SourceLocation.h" @@ -97,6 +98,19 @@ return IWYUHandler.get(); } + bool shouldSkipFunctionBody(Decl *D) override { + // Generally we skip function bodies in preambles for speed. + // We can make exceptions for functions that are cheap to parse and + // instantiate, widely used, and valuable (e.g. commonly produce errors). + if (const auto *FT = llvm::dyn_cast<clang::FunctionTemplateDecl>(D)) { + if (const auto *II = FT->getDeclName().getAsIdentifierInfo()) + // std::make_unique is trivial, and we diagnose bad constructor calls. + if (II->isStr("make_unique") && FT->isInStdNamespace()) + return false; + } + return true; + } + private: PathRef File; PreambleParsedCallback ParsedCallback; Index: clang-tools-extra/clangd/Diagnostics.h =================================================================== --- clang-tools-extra/clangd/Diagnostics.h +++ clang-tools-extra/clangd/Diagnostics.h @@ -13,6 +13,7 @@ #include "support/Path.h" #include "clang/Basic/Diagnostic.h" #include "clang/Basic/LangOptions.h" +#include "clang/Basic/SourceLocation.h" #include "llvm/ADT/ArrayRef.h" #include "llvm/ADT/DenseSet.h" #include "llvm/ADT/None.h" @@ -64,6 +65,7 @@ // Since File is only descriptive, we store a separate flag to distinguish // diags from the main file. bool InsideMainFile = false; + unsigned ID; // e.g. member of clang::diag, or clang-tidy assigned ID. }; llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const DiagBase &D); @@ -82,7 +84,6 @@ /// A top-level diagnostic that may have Notes and Fixes. struct Diag : DiagBase { - unsigned ID; // e.g. member of clang::diag, or clang-tidy assigned ID. std::string Name; // if ID was recognized. // The source of this diagnostic. enum { @@ -145,9 +146,9 @@ std::vector<Diag> Output; llvm::Optional<LangOptions> LangOpts; llvm::Optional<Diag> LastDiag; - /// Set iff adjustDiagFromHeader resulted in changes to LastDiag. - bool LastDiagWasAdjusted = false; - llvm::DenseSet<int> IncludeLinesWithErrors; + llvm::Optional<FullSourceLoc> LastDiagLoc; + bool LastDiagWasOriginalError = false; + llvm::DenseSet<std::pair<unsigned, unsigned>> IncludedErrorLocations; bool LastPrimaryDiagnosticWasSuppressed = false; }; Index: clang-tools-extra/clangd/Diagnostics.cpp =================================================================== --- clang-tools-extra/clangd/Diagnostics.cpp +++ clang-tools-extra/clangd/Diagnostics.cpp @@ -24,6 +24,7 @@ #include "llvm/ADT/DenseSet.h" #include "llvm/ADT/Optional.h" #include "llvm/ADT/STLExtras.h" +#include "llvm/ADT/ScopeExit.h" #include "llvm/ADT/SmallString.h" #include "llvm/ADT/StringRef.h" #include "llvm/ADT/Twine.h" @@ -120,49 +121,96 @@ return halfOpenToRange(M, R); } -// Returns whether the \p D is modified. -bool adjustDiagFromHeader(Diag &D, const clang::Diagnostic &Info, - const LangOptions &LangOpts) { - // We only report diagnostics with at least error severity from headers. - // Use default severity to avoid noise with -Werror. - if (!Info.getDiags()->getDiagnosticIDs()->isDefaultMappingAsError( - Info.getID())) - return false; - - const SourceManager &SM = Info.getSourceManager(); - const SourceLocation &DiagLoc = SM.getExpansionLoc(Info.getLocation()); - SourceLocation IncludeInMainFile; +// Try to find a location in the main-file to report the diagnostic D. +// Returns a description like "in included file", or nullptr on failure. +const char *getMainFileRange(const Diag &D, const SourceManager &SM, + SourceLocation DiagLoc, Range &R) { + // Look for a note in the main file indicating template instantiation. + for (const auto &N : D.Notes) { + if (N.InsideMainFile) { + switch (N.ID) { + case diag::note_template_class_instantiation_was_here: + case diag::note_template_class_explicit_specialization_was_here: + case diag::note_template_class_instantiation_here: + case diag::note_template_member_class_here: + case diag::note_template_member_function_here: + case diag::note_function_template_spec_here: + case diag::note_template_static_data_member_def_here: + case diag::note_template_variable_def_here: + case diag::note_template_enum_def_here: + case diag::note_template_nsdmi_here: + case diag::note_template_type_alias_instantiation_here: + case diag::note_template_exception_spec_instantiation_here: + case diag::note_template_requirement_instantiation_here: + case diag::note_evaluating_exception_spec_here: + case diag::note_default_arg_instantiation_here: + case diag::note_default_function_arg_instantiation_here: + case diag::note_explicit_template_arg_substitution_here: + case diag::note_function_template_deduction_instantiation_here: + case diag::note_deduced_template_arg_substitution_here: + case diag::note_prior_template_arg_substitution: + case diag::note_template_default_arg_checking: + case diag::note_concept_specialization_here: + case diag::note_nested_requirement_here: + case diag::note_checking_constraints_for_template_id_here: + case diag::note_checking_constraints_for_var_spec_id_here: + case diag::note_checking_constraints_for_class_spec_id_here: + case diag::note_checking_constraints_for_function_here: + case diag::note_constraint_substitution_here: + case diag::note_constraint_normalization_here: + case diag::note_parameter_mapping_substitution_here: + R = N.Range; + return "in template"; + default: + break; + } + } + } + // Look for where the file with the error was #included. auto GetIncludeLoc = [&SM](SourceLocation SLoc) { return SM.getIncludeLoc(SM.getFileID(SLoc)); }; - for (auto IncludeLocation = GetIncludeLoc(DiagLoc); IncludeLocation.isValid(); + for (auto IncludeLocation = GetIncludeLoc(SM.getExpansionLoc(DiagLoc)); + IncludeLocation.isValid(); IncludeLocation = GetIncludeLoc(IncludeLocation)) { if (clangd::isInsideMainFile(IncludeLocation, SM)) { - IncludeInMainFile = IncludeLocation; - break; + R.start = sourceLocToPosition(SM, IncludeLocation); + R.end = sourceLocToPosition( + SM, + Lexer::getLocForEndOfToken(IncludeLocation, 0, SM, LangOptions())); + return "in included file"; } } - if (IncludeInMainFile.isInvalid()) - return false; + return nullptr; +} - // Update diag to point at include inside main file. - D.File = SM.getFileEntryForID(SM.getMainFileID())->getName().str(); - D.Range.start = sourceLocToPosition(SM, IncludeInMainFile); - D.Range.end = sourceLocToPosition( - SM, Lexer::getLocForEndOfToken(IncludeInMainFile, 0, SM, LangOpts)); - D.InsideMainFile = true; +// Place the diagnostic the main file, rather than the header, if possible: +// - for errors in included files, use the #include location +// - for errors in template instantiation, use the instantation location +// In both cases, add the original header location as a note. +bool tryMoveToMainFile(Diag &D, FullSourceLoc DiagLoc) { + const SourceManager &SM = DiagLoc.getManager(); + DiagLoc = DiagLoc.getExpansionLoc(); + Range R; + const char *Prefix = getMainFileRange(D, SM, DiagLoc, R); + if (!Prefix) + return false; // Add a note that will point to real diagnostic. const auto *FE = SM.getFileEntryForID(SM.getFileID(DiagLoc)); - D.Notes.emplace_back(); - Note &N = D.Notes.back(); + D.Notes.emplace(D.Notes.begin()); + Note &N = D.Notes.front(); N.AbsFile = std::string(FE->tryGetRealPathName()); N.File = std::string(FE->getName()); N.Message = "error occurred here"; - N.Range = diagnosticRange(Info, LangOpts); + N.Range = D.Range; + // Update diag to point at include inside main file. + D.File = SM.getFileEntryForID(SM.getMainFileID())->getName().str(); + D.Range = std::move(R); + D.InsideMainFile = true; // Update message to mention original file. - D.Message = llvm::Twine("in included file: ", D.Message).str(); + D.Message = llvm::formatv("{0}: {1}", Prefix, D.Message); return true; } @@ -475,6 +523,7 @@ } void StoreDiags::EndSourceFile() { + flushLastDiag(); LangOpts = None; } @@ -525,6 +574,8 @@ flushLastDiag(); LastDiag = Diag(); + LastDiagLoc.reset(); + LastDiagWasOriginalError = false; LastDiag->ID = Info.getID(); fillNonLocationData(DiagLevel, Info, *LastDiag); LastDiag->InsideMainFile = true; @@ -550,6 +601,7 @@ D.File = std::string(SM.getFilename(Info.getLocation())); D.AbsFile = getCanonicalPath( SM.getFileEntryForID(SM.getFileID(Info.getLocation())), SM); + D.ID = Info.getID(); return D; }; @@ -634,10 +686,11 @@ LastPrimaryDiagnosticWasSuppressed = false; LastDiag = Diag(); - LastDiag->ID = Info.getID(); FillDiagBase(*LastDiag); - if (!InsideMainFile) - LastDiagWasAdjusted = adjustDiagFromHeader(*LastDiag, Info, *LangOpts); + LastDiagLoc.emplace(Info.getLocation(), Info.getSourceManager()); + LastDiagWasOriginalError = + Info.getDiags()->getDiagnosticIDs()->isDefaultMappingAsError( + Info.getID()); if (!Info.getFixItHints().empty()) AddFix(true /* try to invent a message instead of repeating the diag */); @@ -679,16 +732,28 @@ void StoreDiags::flushLastDiag() { if (!LastDiag) return; - if (!isBlacklisted(*LastDiag) && mentionsMainFile(*LastDiag) && - (!LastDiagWasAdjusted || - // Only report the first diagnostic coming from each particular header. - IncludeLinesWithErrors.insert(LastDiag->Range.start.line).second)) { - Output.push_back(std::move(*LastDiag)); - } else { - vlog("Dropped diagnostic: {0}: {1}", LastDiag->File, LastDiag->Message); + auto Finish = llvm::make_scope_exit([&, NDiags(Output.size())] { + if (Output.size() == NDiags) // No new diag emitted. + vlog("Dropped diagnostic: {0}: {1}", LastDiag->File, LastDiag->Message); + LastDiag.reset(); + }); + + if (isBlacklisted(*LastDiag)) + return; + // Move errors that occur from headers into main file. + if (!LastDiag->InsideMainFile && LastDiagLoc && LastDiagWasOriginalError) { + if (tryMoveToMainFile(*LastDiag, *LastDiagLoc)) { + // Suppress multiple errors from the same inclusion. + if (!IncludedErrorLocations + .insert({LastDiag->Range.start.line, + LastDiag->Range.start.character}) + .second) + return; + } } - LastDiag.reset(); - LastDiagWasAdjusted = false; + if (!mentionsMainFile(*LastDiag)) + return; + Output.push_back(std::move(*LastDiag)); } } // namespace clangd
_______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits