diff --git a/clang-tools-extra/clangd/ParsedAST.cpp b/clang-tools-extra/clangd/ParsedAST.cpp --- a/clang-tools-extra/clangd/ParsedAST.cpp +++ b/clang-tools-extra/clangd/ParsedAST.cpp @@ -674,9 +674,10 @@ if (PreserveDiags) { Diags = CompilerInvocationDiags; // Add diagnostics from the preamble, if any. - if (Preamble) - Diags->insert(Diags->end(), Preamble->Diags.begin(), - Preamble->Diags.end()); + if (Preamble) { + auto PDiags = Patch->patchedDiags(); + Diags->insert(Diags->end(), PDiags.begin(), PDiags.end()); + } // Finally, add diagnostics coming from the AST. { std::vector D = ASTDiags.take(&*CTContext); diff --git a/clang-tools-extra/clangd/Preamble.h b/clang-tools-extra/clangd/Preamble.h --- a/clang-tools-extra/clangd/Preamble.h +++ b/clang-tools-extra/clangd/Preamble.h @@ -34,6 +34,7 @@ #include "clang/Frontend/PrecompiledPreamble.h" #include "clang/Lex/Lexer.h" #include "clang/Tooling/CompilationDatabase.h" +#include "llvm/ADT/ArrayRef.h" #include "llvm/ADT/StringRef.h" #include @@ -157,6 +158,9 @@ /// Whether diagnostics generated using this patch are trustable. bool preserveDiagnostics() const; + /// Returns diag locations for Modified contents, only contains diags attached + /// to an #include or #define directive. + llvm::ArrayRef patchedDiags() const { return PatchedDiags; } static constexpr llvm::StringLiteral HeaderName = "__preamble_patch__.h"; private: @@ -168,9 +172,11 @@ PreamblePatch() = default; std::string PatchContents; std::string PatchFileName; - /// Includes that are present in both \p Baseline and \p Modified. Used for - /// patching includes of baseline preamble. + // Includes that are present in both \p Baseline and \p Modified. Used for + // patching includes of baseline preamble. std::vector PreambleIncludes; + // Diags that were attached to an #include or a #define directive. + std::vector PatchedDiags; PreambleBounds ModifiedBounds = {0, false}; }; diff --git a/clang-tools-extra/clangd/Preamble.cpp b/clang-tools-extra/clangd/Preamble.cpp --- a/clang-tools-extra/clangd/Preamble.cpp +++ b/clang-tools-extra/clangd/Preamble.cpp @@ -9,6 +9,7 @@ #include "Preamble.h" #include "Compiler.h" #include "Config.h" +#include "Diagnostics.h" #include "Headers.h" #include "SourceCode.h" #include "clang-include-cleaner/Record.h" @@ -35,6 +36,8 @@ #include "llvm/ADT/IntrusiveRefCntPtr.h" #include "llvm/ADT/STLExtras.h" #include "llvm/ADT/SmallString.h" +#include "llvm/ADT/SmallVector.h" +#include "llvm/ADT/StringMap.h" #include "llvm/ADT/StringRef.h" #include "llvm/Support/Error.h" #include "llvm/Support/ErrorHandling.h" @@ -43,7 +46,9 @@ #include "llvm/Support/Path.h" #include "llvm/Support/VirtualFileSystem.h" #include "llvm/Support/raw_ostream.h" -#include +#include +#include +#include #include #include #include @@ -306,6 +311,8 @@ struct ScannedPreamble { std::vector Includes; std::vector TextualDirectives; + // Literal lines of the preamble contents. + std::vector Lines; PreambleBounds Bounds = {0, false}; }; @@ -332,7 +339,7 @@ if (!CI) return error("failed to create compiler invocation"); CI->getDiagnosticOpts().IgnoreWarnings = true; - auto ContentsBuffer = llvm::MemoryBuffer::getMemBuffer(Contents); + auto ContentsBuffer = llvm::MemoryBuffer::getMemBuffer(PI.Contents); // This means we're scanning (though not preprocessing) the preamble section // twice. However, it's important to precisely follow the preamble bounds used // elsewhere. @@ -363,6 +370,10 @@ return std::move(Err); Action.EndSourceFile(); SP.Includes = std::move(Includes.MainFileIncludes); + for (auto Pos = Contents.find('\n'); Pos != Contents.npos; + Contents = Contents.substr(Pos + 1), Pos = Contents.find('\n')) { + SP.Lines.push_back(Contents.substr(0, Pos)); + } return SP; } @@ -465,6 +476,19 @@ WallTimer Timer; }; +// Represents an include as spelled in the source code, without taking resolved +// path or location into account. +struct IncludeKey { + tok::PPKeywordKind Directive; + llvm::StringRef Written; + + bool operator<(const IncludeKey &RHS) const { + return std::tie(Directive, Written) < std::tie(RHS.Directive, RHS.Written); + } + bool operator==(const IncludeKey &RHS) const { + return std::tie(Directive, Written) == std::tie(RHS.Directive, RHS.Written); + } +}; } // namespace std::shared_ptr @@ -558,8 +582,8 @@ if (BuiltPreamble) { log("Built preamble of size {0} for file {1} version {2} in {3} seconds", - BuiltPreamble->getSize(), FileName, Inputs.Version, - PreambleTimer.getTime()); + BuiltPreamble->getSize(), FileName, Inputs.Version, + PreambleTimer.getTime()); std::vector Diags = PreambleDiagnostics.take(); auto Result = std::make_shared(std::move(*BuiltPreamble)); Result->Version = Inputs.Version; @@ -612,6 +636,105 @@ } } +// Checks if all pointers in \p D are for the same line of the main file. +static bool diagReferencesMultipleLines(const Diag &D) { + int Line = D.Range.start.line; + if (D.Range.end.line != Line) + return true; + bool NotePointsToOutside = llvm::any_of(D.Notes, [&](const Note &N) { + return N.File == D.File && + (N.Range.start.line != Line || N.Range.end.line != Line); + }); + if (NotePointsToOutside) + return true; + bool FixPointsToOutside = llvm::any_of(D.Fixes, [&](const Fix &F) { + for (auto &E : F.Edits) + if (E.range.start.line != Line || E.range.end.line != Line) + return true; + return false; + }); + if (FixPointsToOutside) + return true; + return false; +} + +// Move all references in \p D to \p NewLine. This assumes none of the +// references to the main file are multi-line. +static Diag translateDiag(const Diag &D, int NewLine) { + assert(!diagReferencesMultipleLines(D)); + Diag Translated = D; + Translated.Range.start.line = Translated.Range.end.line = NewLine; + for (auto &N : Translated.Notes) { + if (N.File != D.File) + continue; + N.Range.start.line = N.Range.end.line = NewLine; + } + for (auto &F : Translated.Fixes) { + for (auto &E : F.Edits) + E.range.start.line = E.range.end.line = NewLine; + } + return Translated; +} + +static llvm::DenseMap> +mapDiagsToLines(llvm::ArrayRef Diags) { + llvm::DenseMap> LineToDiags; + for (auto &D : Diags) { + if (diagReferencesMultipleLines(D)) + continue; + LineToDiags[D.Range.start.line].emplace_back(&D); + } + return LineToDiags; +} + +static std::map +getExistingIncludes(const ScannedPreamble &BaselineScan, + llvm::ArrayRef MFI) { + std::map ExistingIncludes; + // There might be includes coming from disabled regions, record these for + // exclusion. note that we don't have resolved paths for those. + for (const auto &Inc : BaselineScan.Includes) + ExistingIncludes[{Inc.Directive, Inc.Written}] = nullptr; + for (const auto &Inc : MFI) + ExistingIncludes[{Inc.Directive, Inc.Written}] = &Inc; + return ExistingIncludes; +} + +// Translate diagnostics from baseline into modified for the lines that have the +// same spelling. +static std::vector patchDiags(llvm::ArrayRef BaselineDiags, + const ScannedPreamble &BaselineScan, + const ScannedPreamble &ModifiedScan) { + std::vector PatchedDiags; + // List of diagnostics associated to a particular line in the baseline. + auto LineToDiags = mapDiagsToLines(BaselineDiags); + auto MoveDiagsBetweenLines = [&LineToDiags, &PatchedDiags](int OldLine, + int NewLine) { + auto DiagsForInclude = LineToDiags.find(OldLine); + if (DiagsForInclude == LineToDiags.end()) + return; + for (auto *D : DiagsForInclude->second) + PatchedDiags.emplace_back(translateDiag(*D, NewLine)); + }; + llvm::StringMap> BaselineContentsToLine; + for (int Line = 0, E = BaselineScan.Lines.size(); Line != E; ++Line) { + llvm::StringRef Contents = BaselineScan.Lines[Line]; + BaselineContentsToLine[Contents].push_back(Line); + } + for (int Line = 0, E = ModifiedScan.Lines.size(); Line != E; ++Line) { + auto PreviousLine = BaselineContentsToLine.find(ModifiedScan.Lines[Line]); + if (PreviousLine == BaselineContentsToLine.end()) + continue; + int Closest = PreviousLine->second.front(); + for (auto AlternateLine : PreviousLine->second) { + if (abs(AlternateLine - Line) < abs(Closest - Line)) + Closest = AlternateLine; + } + MoveDiagsBetweenLines(Closest, Line); + } + return PatchedDiags; +} + PreamblePatch PreamblePatch::create(llvm::StringRef FileName, const ParseInputs &Modified, const PreambleData &Baseline, @@ -629,7 +752,7 @@ // there's nothing to do but generate an empty patch. auto BaselineScan = scanPreamble( // Contents needs to be null-terminated. - Baseline.Preamble.getContents().str(), Modified.CompileCommand); + Baseline.Preamble.getContents(), Modified.CompileCommand); if (!BaselineScan) { elog("Failed to scan baseline of {0}: {1}", FileName, BaselineScan.takeError()); @@ -665,18 +788,12 @@ escapeBackslashAndQuotes(FileName, Patch); Patch << "\"\n"; + // Map from an include to its line in the Modified contents. if (IncludesChanged && PatchType == PatchType::All) { // We are only interested in newly added includes, record the ones in // Baseline for exclusion. - llvm::DenseMap, - const Inclusion *> - ExistingIncludes; - for (const auto &Inc : Baseline.Includes.MainFileIncludes) - ExistingIncludes[{Inc.Directive, Inc.Written}] = &Inc; - // There might be includes coming from disabled regions, record these for - // exclusion too. note that we don't have resolved paths for those. - for (const auto &Inc : BaselineScan->Includes) - ExistingIncludes.try_emplace({Inc.Directive, Inc.Written}); + auto ExistingIncludes = + getExistingIncludes(*BaselineScan, Baseline.Includes.MainFileIncludes); // Calculate extra includes that needs to be inserted. for (auto &Inc : ModifiedScan->Includes) { auto It = ExistingIncludes.find({Inc.Directive, Inc.Written}); @@ -728,6 +845,8 @@ Patch << TD.Text << '\n'; } } + + PP.PatchedDiags = patchDiags(Baseline.Diags, *BaselineScan, *ModifiedScan); dlog("Created preamble patch: {0}", Patch.str()); Patch.flush(); return PP; @@ -769,6 +888,7 @@ PreamblePatch PP; PP.PreambleIncludes = Preamble.Includes.MainFileIncludes; PP.ModifiedBounds = Preamble.Preamble.getBounds(); + PP.PatchedDiags = Preamble.Diags; return PP; } diff --git a/clang-tools-extra/clangd/unittests/PreambleTests.cpp b/clang-tools-extra/clangd/unittests/PreambleTests.cpp --- a/clang-tools-extra/clangd/unittests/PreambleTests.cpp +++ b/clang-tools-extra/clangd/unittests/PreambleTests.cpp @@ -696,24 +696,21 @@ Annotations Code(BaselinePreamble); Annotations NewCode(("#define BAR\n" + BaselinePreamble).str()); auto AST = createPatchedAST(Code.code(), NewCode.code()); - // FIXME: We should point at the NewCode. - EXPECT_THAT(mainFileDiagRanges(*AST), ElementsAre(Code.range())); + EXPECT_THAT(mainFileDiagRanges(*AST), ElementsAreArray(NewCode.ranges())); } { // Check with removals from preamble. Annotations Code(("#define BAR\n" + BaselinePreamble).str()); Annotations NewCode(BaselinePreamble); auto AST = createPatchedAST(Code.code(), NewCode.code()); - // FIXME: We should point at the NewCode. - EXPECT_THAT(mainFileDiagRanges(*AST), ElementsAre(Code.range())); + EXPECT_THAT(mainFileDiagRanges(*AST), ElementsAreArray(NewCode.ranges())); } { // Drop line with diags. Annotations Code(BaselinePreamble); Annotations NewCode("#define BAR\n#define BAZ\n"); auto AST = createPatchedAST(Code.code(), NewCode.code()); - // FIXME: No diagnostics. - EXPECT_THAT(mainFileDiagRanges(*AST), ElementsAre(Code.range())); + EXPECT_THAT(mainFileDiagRanges(*AST), IsEmpty()); } { // Picks closest line in case of multiple alternatives. @@ -724,16 +721,14 @@ #define BAR #include )"); auto AST = createPatchedAST(Code.code(), NewCode.code()); - // FIXME: Should point at ranges in NewCode. - EXPECT_THAT(mainFileDiagRanges(*AST), ElementsAre(Code.range())); + EXPECT_THAT(mainFileDiagRanges(*AST), ElementsAre(NewCode.range())); } { // Drop diag if line spelling has changed. Annotations Code(BaselinePreamble); Annotations NewCode(" # include "); auto AST = createPatchedAST(Code.code(), NewCode.code()); - // FIXME: No diags. - EXPECT_THAT(mainFileDiagRanges(*AST), ElementsAre(Code.range())); + EXPECT_THAT(mainFileDiagRanges(*AST), IsEmpty()); } } } // namespace