Skip to content

Commit 2cd33e6

Browse files
committed
[clangd] Track document versions, include them with diags, enhance logs
Summary: This ties to an LSP feature (diagnostic versioning) but really a lot of the value is in being able to log what's happening with file versions and queues more descriptively and clearly. As such it's fairly invasive, for a logging patch :-\ Key decisions: - at the LSP layer, we don't reqire the client to provide versions (LSP makes it mandatory but we never enforced it). If not provided, versions start at 0 and increment. DraftStore handles this. - don't propagate magically using contexts, but rather manually: addDocument -> ParseInputs -> (ParsedAST, Preamble, various callbacks) Context-propagation would hide the versions from ClangdServer, which would make producing good log messages hard - within ClangdServer, treat versions as opaque and unordered. std::string is a convenient type for this, and allows richer versions for embedders. They're "mandatory" but "null" is a reasonable default. Subscribers: ilya-biryukov, javed.absar, MaskRay, jkorous, arphaman, kadircet, usaxena95, cfe-commits Tags: #clang Differential Revision: https://reviews.llvm.org/D75582
1 parent e6d9b2c commit 2cd33e6

36 files changed

+321
-159
lines changed

clang-tools-extra/clangd/ClangdLSPServer.cpp

Lines changed: 34 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,21 @@
4141
namespace clang {
4242
namespace clangd {
4343
namespace {
44+
45+
// LSP defines file versions as numbers that increase.
46+
// ClangdServer treats them as opaque and therefore uses strings instead.
47+
std::string encodeVersion(int64_t LSPVersion) {
48+
return llvm::to_string(LSPVersion);
49+
}
50+
llvm::Optional<int64_t> decodeVersion(llvm::StringRef Encoded) {
51+
int64_t Result;
52+
if (llvm::to_integer(Encoded, Result, 10))
53+
return Result;
54+
else if (!Encoded.empty()) // Empty can be e.g. diagnostics on close.
55+
elog("unexpected non-numeric version {0}", Encoded);
56+
return llvm::None;
57+
}
58+
4459
/// Transforms a tweak into a code action that would apply it if executed.
4560
/// EXPECTS: T.prepare() was called and returned true.
4661
CodeAction toCodeAction(const ClangdServer::TweakRef &T, const URIForFile &File,
@@ -630,8 +645,9 @@ void ClangdLSPServer::onDocumentDidOpen(
630645

631646
const std::string &Contents = Params.textDocument.text;
632647

633-
DraftMgr.addDraft(File, Params.textDocument.version, Contents);
634-
Server->addDocument(File, Contents, WantDiagnostics::Yes);
648+
auto Version = DraftMgr.addDraft(File, Params.textDocument.version, Contents);
649+
Server->addDocument(File, Contents, encodeVersion(Version),
650+
WantDiagnostics::Yes);
635651
}
636652

637653
void ClangdLSPServer::onDocumentDidChange(
@@ -654,7 +670,8 @@ void ClangdLSPServer::onDocumentDidChange(
654670
return;
655671
}
656672

657-
Server->addDocument(File, Draft->Contents, WantDiags, Params.forceRebuild);
673+
Server->addDocument(File, Draft->Contents, encodeVersion(Draft->Version),
674+
WantDiags, Params.forceRebuild);
658675
}
659676

660677
void ClangdLSPServer::onFileEvent(const DidChangeWatchedFilesParams &Params) {
@@ -1347,7 +1364,8 @@ bool ClangdLSPServer::shouldRunCompletion(
13471364
}
13481365

13491366
void ClangdLSPServer::onHighlightingsReady(
1350-
PathRef File, std::vector<HighlightingToken> Highlightings) {
1367+
PathRef File, llvm::StringRef Version,
1368+
std::vector<HighlightingToken> Highlightings) {
13511369
std::vector<HighlightingToken> Old;
13521370
std::vector<HighlightingToken> HighlightingsCopy = Highlightings;
13531371
{
@@ -1358,14 +1376,18 @@ void ClangdLSPServer::onHighlightingsReady(
13581376
// LSP allows us to send incremental edits of highlightings. Also need to diff
13591377
// to remove highlightings from tokens that should no longer have them.
13601378
std::vector<LineHighlightings> Diffed = diffHighlightings(Highlightings, Old);
1361-
publishSemanticHighlighting(
1362-
{{URIForFile::canonicalize(File, /*TUPath=*/File)},
1363-
toSemanticHighlightingInformation(Diffed)});
1379+
SemanticHighlightingParams Notification;
1380+
Notification.TextDocument.uri =
1381+
URIForFile::canonicalize(File, /*TUPath=*/File);
1382+
Notification.TextDocument.version = decodeVersion(Version);
1383+
Notification.Lines = toSemanticHighlightingInformation(Diffed);
1384+
publishSemanticHighlighting(Notification);
13641385
}
13651386

1366-
void ClangdLSPServer::onDiagnosticsReady(PathRef File,
1387+
void ClangdLSPServer::onDiagnosticsReady(PathRef File, llvm::StringRef Version,
13671388
std::vector<Diag> Diagnostics) {
13681389
PublishDiagnosticsParams Notification;
1390+
Notification.version = decodeVersion(Version);
13691391
Notification.uri = URIForFile::canonicalize(File, /*TUPath=*/File);
13701392
DiagnosticToReplacementMap LocalFixIts; // Temporary storage
13711393
for (auto &Diag : Diagnostics) {
@@ -1475,8 +1497,10 @@ void ClangdLSPServer::reparseOpenedFiles(
14751497
// Reparse only opened files that were modified.
14761498
for (const Path &FilePath : DraftMgr.getActiveFiles())
14771499
if (ModifiedFiles.find(FilePath) != ModifiedFiles.end())
1478-
Server->addDocument(FilePath, DraftMgr.getDraft(FilePath)->Contents,
1479-
WantDiagnostics::Auto);
1500+
if (auto Draft = DraftMgr.getDraft(FilePath)) // else disappeared in race?
1501+
Server->addDocument(FilePath, std::move(Draft->Contents),
1502+
encodeVersion(Draft->Version),
1503+
WantDiagnostics::Auto);
14801504
}
14811505

14821506
} // namespace clangd

clang-tools-extra/clangd/ClangdLSPServer.h

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
#include "clang/Tooling/Core/Replacement.h"
2222
#include "llvm/ADT/Optional.h"
2323
#include "llvm/ADT/StringSet.h"
24+
#include "llvm/Support/JSON.h"
2425
#include <memory>
2526

2627
namespace clang {
@@ -57,10 +58,11 @@ class ClangdLSPServer : private ClangdServer::Callbacks {
5758

5859
private:
5960
// Implement ClangdServer::Callbacks.
60-
void onDiagnosticsReady(PathRef File, std::vector<Diag> Diagnostics) override;
61+
void onDiagnosticsReady(PathRef File, llvm::StringRef Version,
62+
std::vector<Diag> Diagnostics) override;
6163
void onFileUpdated(PathRef File, const TUStatus &Status) override;
6264
void
63-
onHighlightingsReady(PathRef File,
65+
onHighlightingsReady(PathRef File, llvm::StringRef Version,
6466
std::vector<HighlightingToken> Highlightings) override;
6567
void onBackgroundIndexProgress(const BackgroundQueue::Stats &Stats) override;
6668

clang-tools-extra/clangd/ClangdServer.cpp

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -62,11 +62,11 @@ struct UpdateIndexCallbacks : public ParsingCallbacks {
6262
: FIndex(FIndex), ServerCallbacks(ServerCallbacks),
6363
SemanticHighlighting(SemanticHighlighting) {}
6464

65-
void onPreambleAST(PathRef Path, ASTContext &Ctx,
65+
void onPreambleAST(PathRef Path, llvm::StringRef Version, ASTContext &Ctx,
6666
std::shared_ptr<clang::Preprocessor> PP,
6767
const CanonicalIncludes &CanonIncludes) override {
6868
if (FIndex)
69-
FIndex->updatePreamble(Path, Ctx, std::move(PP), CanonIncludes);
69+
FIndex->updatePreamble(Path, Version, Ctx, std::move(PP), CanonIncludes);
7070
}
7171

7272
void onMainAST(PathRef Path, ParsedAST &AST, PublishFn Publish) override {
@@ -80,16 +80,19 @@ struct UpdateIndexCallbacks : public ParsingCallbacks {
8080

8181
if (ServerCallbacks)
8282
Publish([&]() {
83-
ServerCallbacks->onDiagnosticsReady(Path, std::move(Diagnostics));
83+
ServerCallbacks->onDiagnosticsReady(Path, AST.version(),
84+
std::move(Diagnostics));
8485
if (SemanticHighlighting)
85-
ServerCallbacks->onHighlightingsReady(Path, std::move(Highlightings));
86+
ServerCallbacks->onHighlightingsReady(Path, AST.version(),
87+
std::move(Highlightings));
8688
});
8789
}
8890

89-
void onFailedAST(PathRef Path, std::vector<Diag> Diags,
90-
PublishFn Publish) override {
91+
void onFailedAST(PathRef Path, llvm::StringRef Version,
92+
std::vector<Diag> Diags, PublishFn Publish) override {
9193
if (ServerCallbacks)
92-
Publish([&]() { ServerCallbacks->onDiagnosticsReady(Path, Diags); });
94+
Publish(
95+
[&]() { ServerCallbacks->onDiagnosticsReady(Path, Version, Diags); });
9396
}
9497

9598
void onFileUpdated(PathRef File, const TUStatus &Status) override {
@@ -169,6 +172,7 @@ ClangdServer::ClangdServer(const GlobalCompilationDatabase &CDB,
169172
}
170173

171174
void ClangdServer::addDocument(PathRef File, llvm::StringRef Contents,
175+
llvm::StringRef Version,
172176
WantDiagnostics WantDiags, bool ForceRebuild) {
173177
auto FS = FSProvider.getFileSystem();
174178

@@ -183,6 +187,7 @@ void ClangdServer::addDocument(PathRef File, llvm::StringRef Contents,
183187
ParseInputs Inputs;
184188
Inputs.FS = FS;
185189
Inputs.Contents = std::string(Contents);
190+
Inputs.Version = Version.str();
186191
Inputs.ForceRebuild = ForceRebuild;
187192
Inputs.Opts = std::move(Opts);
188193
Inputs.Index = Index;

clang-tools-extra/clangd/ClangdServer.h

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ class ClangdServer {
6969

7070
/// Called by ClangdServer when \p Diagnostics for \p File are ready.
7171
/// May be called concurrently for separate files, not for a single file.
72-
virtual void onDiagnosticsReady(PathRef File,
72+
virtual void onDiagnosticsReady(PathRef File, llvm::StringRef Version,
7373
std::vector<Diag> Diagnostics) {}
7474
/// Called whenever the file status is updated.
7575
/// May be called concurrently for separate files, not for a single file.
@@ -78,7 +78,7 @@ class ClangdServer {
7878
/// Called by ClangdServer when some \p Highlightings for \p File are ready.
7979
/// May be called concurrently for separate files, not for a single file.
8080
virtual void
81-
onHighlightingsReady(PathRef File,
81+
onHighlightingsReady(PathRef File, llvm::StringRef Version,
8282
std::vector<HighlightingToken> Highlightings) {}
8383

8484
/// Called when background indexing tasks are enqueued/started/completed.
@@ -171,13 +171,17 @@ class ClangdServer {
171171
/// \p File is already tracked. Also schedules parsing of the AST for it on a
172172
/// separate thread. When the parsing is complete, DiagConsumer passed in
173173
/// constructor will receive onDiagnosticsReady callback.
174+
/// Version identifies this snapshot and is propagated to ASTs, preambles,
175+
/// diagnostics etc built from it.
174176
void addDocument(PathRef File, StringRef Contents,
177+
llvm::StringRef Version = "null",
175178
WantDiagnostics WD = WantDiagnostics::Auto,
176179
bool ForceRebuild = false);
177180

178181
/// Remove \p File from list of tracked files, schedule a request to free
179182
/// resources associated with it. Pending diagnostics for closed files may not
180183
/// be delivered, even if requested with WantDiags::Auto or WantDiags::Yes.
184+
/// An empty set of diagnostics will be delivered, with Version = "".
181185
void removeDocument(PathRef File);
182186

183187
/// Run code completion for \p File at \p Pos.

clang-tools-extra/clangd/Compiler.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ struct ParseInputs {
4545
tooling::CompileCommand CompileCommand;
4646
IntrusiveRefCntPtr<llvm::vfs::FileSystem> FS;
4747
std::string Contents;
48+
// Version identifier for Contents, provided by the client and opaque to us.
49+
std::string Version = "null";
4850
// Prevent reuse of the cached preamble/AST. Slow! Useful to workaround
4951
// clangd's assumption that missing header files will stay missing.
5052
bool ForceRebuild = false;

clang-tools-extra/clangd/ParsedAST.cpp

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -239,7 +239,8 @@ void dumpAST(ParsedAST &AST, llvm::raw_ostream &OS) {
239239
}
240240

241241
llvm::Optional<ParsedAST>
242-
ParsedAST::build(std::unique_ptr<clang::CompilerInvocation> CI,
242+
ParsedAST::build(llvm::StringRef Version,
243+
std::unique_ptr<clang::CompilerInvocation> CI,
243244
llvm::ArrayRef<Diag> CompilerInvocationDiags,
244245
std::shared_ptr<const PreambleData> Preamble,
245246
std::unique_ptr<llvm::MemoryBuffer> Buffer,
@@ -427,10 +428,10 @@ ParsedAST::build(std::unique_ptr<clang::CompilerInvocation> CI,
427428
std::vector<Diag> D = ASTDiags.take(CTContext.getPointer());
428429
Diags.insert(Diags.end(), D.begin(), D.end());
429430
}
430-
return ParsedAST(std::move(Preamble), std::move(Clang), std::move(Action),
431-
std::move(Tokens), std::move(Macros), std::move(ParsedDecls),
432-
std::move(Diags), std::move(Includes),
433-
std::move(CanonIncludes));
431+
return ParsedAST(Version, std::move(Preamble), std::move(Clang),
432+
std::move(Action), std::move(Tokens), std::move(Macros),
433+
std::move(ParsedDecls), std::move(Diags),
434+
std::move(Includes), std::move(CanonIncludes));
434435
}
435436

436437
ParsedAST::ParsedAST(ParsedAST &&Other) = default;
@@ -512,14 +513,15 @@ const CanonicalIncludes &ParsedAST::getCanonicalIncludes() const {
512513
return CanonIncludes;
513514
}
514515

515-
ParsedAST::ParsedAST(std::shared_ptr<const PreambleData> Preamble,
516+
ParsedAST::ParsedAST(llvm::StringRef Version,
517+
std::shared_ptr<const PreambleData> Preamble,
516518
std::unique_ptr<CompilerInstance> Clang,
517519
std::unique_ptr<FrontendAction> Action,
518520
syntax::TokenBuffer Tokens, MainFileMacros Macros,
519521
std::vector<Decl *> LocalTopLevelDecls,
520522
std::vector<Diag> Diags, IncludeStructure Includes,
521523
CanonicalIncludes CanonIncludes)
522-
: Preamble(std::move(Preamble)), Clang(std::move(Clang)),
524+
: Version(Version), Preamble(std::move(Preamble)), Clang(std::move(Clang)),
523525
Action(std::move(Action)), Tokens(std::move(Tokens)),
524526
Macros(std::move(Macros)), Diags(std::move(Diags)),
525527
LocalTopLevelDecls(std::move(LocalTopLevelDecls)),
@@ -546,7 +548,7 @@ buildAST(PathRef FileName, std::unique_ptr<CompilerInvocation> Invocation,
546548
}
547549

548550
return ParsedAST::build(
549-
std::make_unique<CompilerInvocation>(*Invocation),
551+
Inputs.Version, std::make_unique<CompilerInvocation>(*Invocation),
550552
CompilerInvocationDiags, Preamble,
551553
llvm::MemoryBuffer::getMemBufferCopy(Inputs.Contents, FileName),
552554
std::move(VFS), Inputs.Index, Inputs.Opts);

clang-tools-extra/clangd/ParsedAST.h

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ class ParsedAST {
4848
/// Attempts to run Clang and store parsed AST. If \p Preamble is non-null
4949
/// it is reused during parsing.
5050
static llvm::Optional<ParsedAST>
51-
build(std::unique_ptr<clang::CompilerInvocation> CI,
51+
build(llvm::StringRef Version, std::unique_ptr<clang::CompilerInvocation> CI,
5252
llvm::ArrayRef<Diag> CompilerInvocationDiags,
5353
std::shared_ptr<const PreambleData> Preamble,
5454
std::unique_ptr<llvm::MemoryBuffer> Buffer,
@@ -101,14 +101,19 @@ class ParsedAST {
101101
/// (!) does not have tokens from the preamble.
102102
const syntax::TokenBuffer &getTokens() const { return Tokens; }
103103

104+
/// Returns the version of the ParseInputs this AST was built from.
105+
llvm::StringRef version() const { return Version; }
106+
104107
private:
105-
ParsedAST(std::shared_ptr<const PreambleData> Preamble,
108+
ParsedAST(llvm::StringRef Version,
109+
std::shared_ptr<const PreambleData> Preamble,
106110
std::unique_ptr<CompilerInstance> Clang,
107111
std::unique_ptr<FrontendAction> Action, syntax::TokenBuffer Tokens,
108112
MainFileMacros Macros, std::vector<Decl *> LocalTopLevelDecls,
109113
std::vector<Diag> Diags, IncludeStructure Includes,
110114
CanonicalIncludes CanonIncludes);
111115

116+
std::string Version;
112117
// In-memory preambles must outlive the AST, it is important that this member
113118
// goes before Clang and Action.
114119
std::shared_ptr<const PreambleData> Preamble;

clang-tools-extra/clangd/Preamble.cpp

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -75,12 +75,13 @@ class CppFilePreambleCallbacks : public PreambleCallbacks {
7575

7676
} // namespace
7777

78-
PreambleData::PreambleData(PrecompiledPreamble Preamble,
78+
PreambleData::PreambleData(llvm::StringRef Version,
79+
PrecompiledPreamble Preamble,
7980
std::vector<Diag> Diags, IncludeStructure Includes,
8081
MainFileMacros Macros,
8182
std::unique_ptr<PreambleFileStatusCache> StatCache,
8283
CanonicalIncludes CanonIncludes)
83-
: Preamble(std::move(Preamble)), Diags(std::move(Diags)),
84+
: Version(Version), Preamble(std::move(Preamble)), Diags(std::move(Diags)),
8485
Includes(std::move(Includes)), Macros(std::move(Macros)),
8586
StatCache(std::move(StatCache)), CanonIncludes(std::move(CanonIncludes)) {
8687
}
@@ -102,12 +103,17 @@ buildPreamble(PathRef FileName, CompilerInvocation &CI,
102103
compileCommandsAreEqual(Inputs.CompileCommand, OldCompileCommand) &&
103104
OldPreamble->Preamble.CanReuse(CI, ContentsBuffer.get(), Bounds,
104105
Inputs.FS.get())) {
105-
vlog("Reusing preamble for {0}", FileName);
106+
vlog("Reusing preamble version {0} for version {1} of {2}",
107+
OldPreamble->Version, Inputs.Version, FileName);
106108
return OldPreamble;
107109
}
108-
vlog(OldPreamble ? "Rebuilding invalidated preamble for {0}"
109-
: "Building first preamble for {0}",
110-
FileName);
110+
if (OldPreamble)
111+
vlog("Rebuilding invalidated preamble for {0} version {1} "
112+
"(previous was version {2})",
113+
FileName, Inputs.Version, OldPreamble->Version);
114+
else
115+
vlog("Building first preamble for {0} verson {1}", FileName,
116+
Inputs.Version);
111117

112118
trace::Span Tracer("BuildPreamble");
113119
SPAN_ATTACH(Tracer, "File", FileName);
@@ -145,16 +151,17 @@ buildPreamble(PathRef FileName, CompilerInvocation &CI,
145151
CI.getFrontendOpts().SkipFunctionBodies = false;
146152

147153
if (BuiltPreamble) {
148-
vlog("Built preamble of size {0} for file {1}", BuiltPreamble->getSize(),
149-
FileName);
154+
vlog("Built preamble of size {0} for file {1} version {2}",
155+
BuiltPreamble->getSize(), FileName, Inputs.Version);
150156
std::vector<Diag> Diags = PreambleDiagnostics.take();
151157
return std::make_shared<PreambleData>(
152-
std::move(*BuiltPreamble), std::move(Diags),
158+
Inputs.Version, std::move(*BuiltPreamble), std::move(Diags),
153159
SerializedDeclsCollector.takeIncludes(),
154160
SerializedDeclsCollector.takeMacros(), std::move(StatCache),
155161
SerializedDeclsCollector.takeCanonicalIncludes());
156162
} else {
157-
elog("Could not build a preamble for file {0}", FileName);
163+
elog("Could not build a preamble for file {0} version {2}", FileName,
164+
Inputs.Version);
158165
return nullptr;
159166
}
160167
}

clang-tools-extra/clangd/Preamble.h

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,11 +43,14 @@ namespace clangd {
4343
/// As we must avoid re-parsing the preamble, any information that can only
4444
/// be obtained during parsing must be eagerly captured and stored here.
4545
struct PreambleData {
46-
PreambleData(PrecompiledPreamble Preamble, std::vector<Diag> Diags,
47-
IncludeStructure Includes, MainFileMacros Macros,
46+
PreambleData(llvm::StringRef Version, PrecompiledPreamble Preamble,
47+
std::vector<Diag> Diags, IncludeStructure Includes,
48+
MainFileMacros Macros,
4849
std::unique_ptr<PreambleFileStatusCache> StatCache,
4950
CanonicalIncludes CanonIncludes);
5051

52+
// Version of the ParseInputs this preamble was built from.
53+
std::string Version;
5154
tooling::CompileCommand CompileCommand;
5255
PrecompiledPreamble Preamble;
5356
std::vector<Diag> Diags;

clang-tools-extra/clangd/Protocol.cpp

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -92,8 +92,7 @@ bool fromJSON(const llvm::json::Value &Params, TextDocumentIdentifier &R) {
9292

9393
llvm::json::Value toJSON(const VersionedTextDocumentIdentifier &R) {
9494
auto Result = toJSON(static_cast<const TextDocumentIdentifier &>(R));
95-
if (R.version)
96-
Result.getAsObject()->try_emplace("version", R.version);
95+
Result.getAsObject()->try_emplace("version", R.version);
9796
return Result;
9897
}
9998

@@ -547,8 +546,9 @@ bool fromJSON(const llvm::json::Value &Params, Diagnostic &R) {
547546

548547
llvm::json::Value toJSON(const PublishDiagnosticsParams &PDP) {
549548
return llvm::json::Object{
550-
{"uri", PDP.uri},
551-
{"diagnostics", PDP.diagnostics},
549+
{"uri", PDP.uri},
550+
{"diagnostics", PDP.diagnostics},
551+
{"version", PDP.version},
552552
};
553553
}
554554

0 commit comments

Comments
 (0)