Expand Up
@@ -9,7 +9,9 @@
#include " Preamble.h"
#include " Compiler.h"
#include " Config.h"
#include " Diagnostics.h"
#include " Headers.h"
#include " Protocol.h"
#include " SourceCode.h"
#include " clang-include-cleaner/Record.h"
#include " support/Logger.h"
Expand All
@@ -35,6 +37,9 @@
#include " llvm/ADT/IntrusiveRefCntPtr.h"
#include " llvm/ADT/STLExtras.h"
#include " llvm/ADT/SmallString.h"
#include " llvm/ADT/SmallVector.h"
#include " llvm/ADT/StringExtras.h"
#include " llvm/ADT/StringMap.h"
#include " llvm/ADT/StringRef.h"
#include " llvm/Support/Error.h"
#include " llvm/Support/ErrorHandling.h"
Expand All
@@ -43,8 +48,9 @@
#include " llvm/Support/Path.h"
#include " llvm/Support/VirtualFileSystem.h"
#include " llvm/Support/raw_ostream.h"
#include < iterator >
#include < cstddef >
#include < memory>
#include < optional>
#include < string>
#include < system_error>
#include < utility>
Expand All
@@ -53,7 +59,6 @@
namespace clang {
namespace clangd {
namespace {
constexpr llvm::StringLiteral PreamblePatchHeaderName = " __preamble_patch__.h" ;
bool compileCommandsAreEqual (const tooling::CompileCommand &LHS,
const tooling::CompileCommand &RHS) {
Expand Down
Expand Up
@@ -213,6 +218,9 @@ struct TextualPPDirective {
// Full text that's representing the directive, including the `#`.
std::string Text;
unsigned Offset;
tok::PPKeywordKind Directive = tok::PPKeywordKind::pp_not_keyword;
// Name of the macro being defined in the case of a #define directive.
std::string MacroName;
bool operator ==(const TextualPPDirective &RHS) const {
return std::tie (DirectiveLine, Offset, Text) ==
Expand Down
Expand Up
@@ -283,6 +291,8 @@ struct DirectiveCollector : public PPCallbacks {
return ;
TextualDirectives.emplace_back ();
TextualPPDirective &TD = TextualDirectives.back ();
TD.Directive = tok::pp_define;
TD.MacroName = MacroNameTok.getIdentifierInfo ()->getName ().str ();
const auto *MI = MD->getMacroInfo ();
TD.Text =
Expand All
@@ -302,6 +312,8 @@ struct DirectiveCollector : public PPCallbacks {
struct ScannedPreamble {
std::vector<Inclusion> Includes;
std::vector<TextualPPDirective> TextualDirectives;
// Literal lines of the preamble contents.
std::vector<llvm::StringRef> Lines;
PreambleBounds Bounds = {0 , false };
};
Expand All
@@ -328,7 +340,7 @@ scanPreamble(llvm::StringRef Contents, const tooling::CompileCommand &Cmd) {
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.
Expand Down
Expand Up
@@ -359,6 +371,7 @@ scanPreamble(llvm::StringRef Contents, const tooling::CompileCommand &Cmd) {
return std::move (Err);
Action.EndSourceFile ();
SP.Includes = std::move (Includes.MainFileIncludes );
llvm::append_range (SP.Lines , llvm::split (Contents, " \n " ));
return SP;
}
Expand All
@@ -376,12 +389,6 @@ const char *spellingForIncDirective(tok::PPKeywordKind IncludeDirective) {
llvm_unreachable (" not an include directive" );
}
// Checks whether \p FileName is a valid spelling of main file.
bool isMainFile (llvm::StringRef FileName, const SourceManager &SM) {
auto FE = SM.getFileManager ().getFile (FileName);
return FE && *FE == SM.getFileEntryForID (SM.getMainFileID ());
}
// Accumulating wall time timer. Similar to llvm::Timer, but much cheaper,
// it only tracks wall time.
// Since this is a generic timer, We may want to move this to support/ if we
Expand Down
Expand Up
@@ -467,6 +474,93 @@ class TimerFS : public llvm::vfs::ProxyFileSystem {
WallTimer Timer;
};
// Helpers for patching diagnostics between two versions of file contents.
class DiagPatcher {
llvm::ArrayRef<llvm::StringRef> OldLines;
llvm::ArrayRef<llvm::StringRef> CurrentLines;
llvm::StringMap<llvm::SmallVector<int >> CurrentContentsToLine;
// Translates a range from old lines to current lines.
// Finds the consecutive set of lines that corresponds to the same contents in
// old and current, and applies the same translation to the range.
// Returns true if translation succeeded.
bool translateRange (Range &R) {
int OldStart = R.start .line ;
int OldEnd = R.end .line ;
assert (OldStart <= OldEnd);
size_t RangeLen = OldEnd - OldStart + 1 ;
auto RangeContents = OldLines.slice (OldStart).take_front (RangeLen);
// Make sure the whole range is covered in old contents.
if (RangeContents.size () < RangeLen)
return false ;
std::optional<int > Closest;
for (int AlternateLine : CurrentContentsToLine.lookup (RangeContents[0 ])) {
// Check if AlternateLine matches all lines in the range.
if (RangeContents !=
CurrentLines.slice (AlternateLine).take_front (RangeLen))
continue ;
int Delta = AlternateLine - OldStart;
if (!Closest.has_value () || abs (Delta) < abs (*Closest))
Closest = Delta;
}
// Couldn't find any viable matches in the current contents.
if (!Closest.has_value ())
return false ;
R.start .line += *Closest;
R.end .line += *Closest;
return true ;
}
// Translates a Note by patching its range when inside main file. Returns true
// on success.
bool translateNote (Note &N) {
if (!N.InsideMainFile )
return true ;
if (translateRange (N.Range ))
return true ;
return false ;
}
// Tries to translate all the edit ranges inside the fix. Returns true on
// success. On failure fixes might be in an invalid state.
bool translateFix (Fix &F) {
return llvm::all_of (
F.Edits , [this ](TextEdit &E) { return translateRange (E.range ); });
}
public:
DiagPatcher (llvm::ArrayRef<llvm::StringRef> OldLines,
llvm::ArrayRef<llvm::StringRef> CurrentLines) {
this ->OldLines = OldLines;
this ->CurrentLines = CurrentLines;
for (int Line = 0 , E = CurrentLines.size (); Line != E; ++Line) {
llvm::StringRef Contents = CurrentLines[Line];
CurrentContentsToLine[Contents].push_back (Line);
}
}
// Translate diagnostic by moving its main range to new location (if inside
// the main file). Preserve all the notes and fixes that can be translated to
// new contents.
// Drops the whole diagnostic if main range can't be patched.
std::optional<Diag> translateDiag (const Diag &D) {
Range NewRange = D.Range ;
// Patch range if it's inside main file.
if (D.InsideMainFile && !translateRange (NewRange)) {
// Drop the diagnostic if we couldn't patch the range.
return std::nullopt;
}
Diag NewD = D;
NewD.Range = NewRange;
// Translate ranges inside notes and fixes too, dropping the ones that are
// no longer relevant.
llvm::erase_if (NewD.Notes , [this ](Note &N) { return !translateNote (N); });
llvm::erase_if (NewD.Fixes , [this ](Fix &F) { return !translateFix (F); });
return NewD;
}
};
} // namespace
std::shared_ptr<const PreambleData>
Expand Down
Expand Up
@@ -560,8 +654,8 @@ buildPreamble(PathRef FileName, CompilerInvocation CI,
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<Diag> Diags = PreambleDiagnostics.take ();
auto Result = std::make_shared<PreambleData>(std::move (*BuiltPreamble));
Result->Version = Inputs.Version ;
Expand Down
Expand Up
@@ -614,6 +708,22 @@ void escapeBackslashAndQuotes(llvm::StringRef Text, llvm::raw_ostream &OS) {
}
}
// Translate diagnostics from baseline into modified for the lines that have the
// same spelling.
static std::vector<Diag> patchDiags (llvm::ArrayRef<Diag> BaselineDiags,
const ScannedPreamble &BaselineScan,
const ScannedPreamble &ModifiedScan) {
std::vector<Diag> PatchedDiags;
if (BaselineDiags.empty ())
return PatchedDiags;
DiagPatcher Patcher (BaselineScan.Lines , ModifiedScan.Lines );
for (auto &D : BaselineDiags) {
if (auto NewD = Patcher.translateDiag (D))
PatchedDiags.emplace_back (std::move (*NewD));
}
return PatchedDiags;
}
PreamblePatch PreamblePatch::create (llvm::StringRef FileName,
const ParseInputs &Modified,
const PreambleData &Baseline,
Expand All
@@ -631,7 +741,7 @@ PreamblePatch PreamblePatch::create(llvm::StringRef FileName,
// 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 ());
Expand All
@@ -655,7 +765,7 @@ PreamblePatch PreamblePatch::create(llvm::StringRef FileName,
// This shouldn't coincide with any real file name.
llvm::SmallString<128 > PatchName;
llvm::sys::path::append (PatchName, llvm::sys::path::parent_path (FileName),
PreamblePatchHeaderName );
PreamblePatch::HeaderName );
PP.PatchFileName = PatchName.str ().str ();
PP.ModifiedBounds = ModifiedScan->Bounds ;
Expand Down
Expand Up
@@ -724,10 +834,16 @@ PreamblePatch PreamblePatch::create(llvm::StringRef FileName,
// reduce complexity. The former might cause problems because scanning is
// imprecise and might pick directives from disabled regions.
for (const auto &TD : ModifiedScan->TextualDirectives ) {
// Introduce an #undef directive before #defines to suppress any
// re-definition warnings.
if (TD.Directive == tok::pp_define)
Patch << " #undef " << TD.MacroName << ' \n ' ;
Patch << " #line " << TD.DirectiveLine << ' \n ' ;
Patch << TD.Text << ' \n ' ;
}
}
PP.PatchedDiags = patchDiags (Baseline.Diags , *BaselineScan, *ModifiedScan);
dlog (" Created preamble patch: {0}" , Patch.str ());
Patch.flush ();
return PP;
Expand Down
Expand Up
@@ -769,28 +885,13 @@ PreamblePatch PreamblePatch::unmodified(const PreambleData &Preamble) {
PreamblePatch PP;
PP.PreambleIncludes = Preamble.Includes .MainFileIncludes ;
PP.ModifiedBounds = Preamble.Preamble .getBounds ();
PP.PatchedDiags = Preamble.Diags ;
return PP;
}
SourceLocation translatePreamblePatchLocation (SourceLocation Loc,
const SourceManager &SM) {
auto DefFile = SM.getFileID (Loc);
if (auto FE = SM.getFileEntryRefForID (DefFile)) {
auto IncludeLoc = SM.getIncludeLoc (DefFile);
// Preamble patch is included inside the builtin file.
if (IncludeLoc.isValid () && SM.isWrittenInBuiltinFile (IncludeLoc) &&
FE->getName ().endswith (PreamblePatchHeaderName)) {
auto Presumed = SM.getPresumedLoc (Loc);
// Check that line directive is pointing at main file.
if (Presumed.isValid () && Presumed.getFileID ().isInvalid () &&
isMainFile (Presumed.getFilename (), SM)) {
Loc = SM.translateLineCol (SM.getMainFileID (), Presumed.getLine (),
Presumed.getColumn ());
}
}
}
return Loc;
bool PreamblePatch::preserveDiagnostics () const {
return PatchContents.empty () ||
Config::current ().Diagnostics .AllowStalePreamble ;
}
} // namespace clangd
} // namespace clang