});
}
+ void onFailedAST(PathRef Path, std::vector<Diag> Diags,
+ PublishFn Publish) override {
+ Publish([&]() { DiagConsumer.onDiagnosticsReady(Path, Diags); });
+ }
+
void onFileUpdated(PathRef File, const TUStatus &Status) override {
DiagConsumer.onFileUpdated(File, Status);
}
}
llvm::Optional<ParsedAST>
-ParsedAST::build(std::unique_ptr<CompilerInvocation> CI,
+ParsedAST::build(std::unique_ptr<clang::CompilerInvocation> CI,
+ llvm::ArrayRef<Diag> CompilerInvocationDiags,
std::shared_ptr<const PreambleData> Preamble,
std::unique_ptr<llvm::MemoryBuffer> Buffer,
llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> VFS,
// So just inform the preprocessor of EOF, while keeping everything alive.
Clang->getPreprocessor().EndSourceFile();
- std::vector<Diag> Diags = ASTDiags.take(CTContext.getPointer());
+ std::vector<Diag> Diags = CompilerInvocationDiags;
// Add diagnostics from the preamble, if any.
if (Preamble)
- Diags.insert(Diags.begin(), Preamble->Diags.begin(), Preamble->Diags.end());
+ Diags.insert(Diags.end(), Preamble->Diags.begin(), Preamble->Diags.end());
+ // Finally, add diagnostics coming from the AST.
+ {
+ std::vector<Diag> D = ASTDiags.take(CTContext.getPointer());
+ Diags.insert(Diags.end(), D.begin(), D.end());
+ }
return ParsedAST(std::move(Preamble), std::move(Clang), std::move(Action),
std::move(Tokens), std::move(ParsedDecls), std::move(Diags),
std::move(Includes), std::move(CanonIncludes));
llvm::Optional<ParsedAST>
buildAST(PathRef FileName, std::unique_ptr<CompilerInvocation> Invocation,
+ llvm::ArrayRef<Diag> CompilerInvocationDiags,
const ParseInputs &Inputs,
std::shared_ptr<const PreambleData> Preamble) {
trace::Span Tracer("BuildAST");
}
return ParsedAST::build(
- std::make_unique<CompilerInvocation>(*Invocation), Preamble,
+ std::make_unique<CompilerInvocation>(*Invocation),
+ CompilerInvocationDiags, Preamble,
llvm::MemoryBuffer::getMemBufferCopy(Inputs.Contents, FileName),
std::move(VFS), Inputs.Index, Inputs.Opts);
}
#include "clang/Tooling/CompilationDatabase.h"
#include "clang/Tooling/Core/Replacement.h"
#include "clang/Tooling/Syntax/Tokens.h"
+#include "llvm/ADT/ArrayRef.h"
#include <memory>
#include <string>
#include <vector>
/// it is reused during parsing.
static llvm::Optional<ParsedAST>
build(std::unique_ptr<clang::CompilerInvocation> CI,
+ llvm::ArrayRef<Diag> CompilerInvocationDiags,
std::shared_ptr<const PreambleData> Preamble,
std::unique_ptr<llvm::MemoryBuffer> Buffer,
- IntrusiveRefCntPtr<llvm::vfs::FileSystem> VFS, const SymbolIndex *Index,
- const ParseOptions &Opts);
+ llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> VFS,
+ const SymbolIndex *Index, const ParseOptions &Opts);
ParsedAST(ParsedAST &&Other);
ParsedAST &operator=(ParsedAST &&Other);
/// result of calling buildPreamble.
llvm::Optional<ParsedAST>
buildAST(PathRef FileName, std::unique_ptr<CompilerInvocation> Invocation,
+ llvm::ArrayRef<Diag> CompilerInvocationDiags,
const ParseInputs &Inputs,
std::shared_ptr<const PreambleData> Preamble);
ParseInput.FS = VFS;
ParseInput.Contents = Input.Contents;
ParseInput.Opts = ParseOptions();
- auto CI = buildCompilerInvocation(ParseInput);
+
+ IgnoreDiagnostics IgnoreDiags;
+ auto CI = buildCompilerInvocation(ParseInput, IgnoreDiags);
if (!CI) {
elog("Couldn't create CompilerInvocation");
return false;
bool CompletingInPreamble = PreambleRegion.Size > Input.Offset;
// NOTE: we must call BeginSourceFile after prepareCompilerInstance. Otherwise
// the remapped buffers do not get freed.
- IgnoreDiagnostics DummyDiagsConsumer;
auto Clang = prepareCompilerInstance(
std::move(CI),
(Input.Preamble && !CompletingInPreamble) ? &Input.Preamble->Preamble
: nullptr,
- std::move(ContentsBuffer), std::move(VFS), DummyDiagsConsumer);
+ std::move(ContentsBuffer), std::move(VFS), IgnoreDiags);
Clang->getPreprocessorOpts().SingleFileParseMode = CompletingInPreamble;
Clang->setCodeCompletionConsumer(Consumer.release());
}
std::unique_ptr<CompilerInvocation>
-buildCompilerInvocation(const ParseInputs &Inputs) {
+buildCompilerInvocation(const ParseInputs &Inputs,
+ clang::DiagnosticConsumer &D) {
std::vector<const char *> ArgStrs;
for (const auto &S : Inputs.CompileCommand.CommandLine)
ArgStrs.push_back(S.c_str());
// dirs.
}
- // FIXME(ibiryukov): store diagnostics from CommandLine when we start
- // reporting them.
- IgnoreDiagnostics IgnoreDiagnostics;
llvm::IntrusiveRefCntPtr<DiagnosticsEngine> CommandLineDiagsEngine =
- CompilerInstance::createDiagnostics(new DiagnosticOptions,
- &IgnoreDiagnostics, false);
+ CompilerInstance::createDiagnostics(new DiagnosticOptions, &D, false);
std::unique_ptr<CompilerInvocation> CI = createInvocationFromCommandLine(
ArgStrs, CommandLineDiagsEngine, Inputs.FS,
/*ShouldRecoverOnErrors=*/true);
/// Builds compiler invocation that could be used to build AST or preamble.
std::unique_ptr<CompilerInvocation>
-buildCompilerInvocation(const ParseInputs &Inputs);
+buildCompilerInvocation(const ParseInputs &Inputs,
+ clang::DiagnosticConsumer &D);
/// Creates a compiler instance, configured so that:
/// - Contents of the parsed file are remapped to \p MainFile.
#include "clang/Basic/Diagnostic.h"
#include "clang/Basic/DiagnosticIDs.h"
#include "clang/Basic/FileManager.h"
+#include "clang/Basic/SourceLocation.h"
#include "clang/Basic/SourceManager.h"
#include "clang/Lex/Lexer.h"
#include "clang/Lex/Token.h"
#include "llvm/ADT/ArrayRef.h"
#include "llvm/ADT/DenseSet.h"
+#include "llvm/ADT/SmallString.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/ADT/Twine.h"
#include "llvm/Support/Capacity.h"
}
std::vector<Diag> StoreDiags::take(const clang::tidy::ClangTidyContext *Tidy) {
+ // Do not forget to emit a pending diagnostic if there is one.
+ flushLastDiag();
+
// Fill in name/source now that we have all the context needed to map them.
for (auto &Diag : Output) {
if (const char *ClangDiag = getDiagnosticCode(Diag.ID)) {
}
void StoreDiags::EndSourceFile() {
- flushLastDiag();
LangOpts = None;
}
OS << "…";
}
+/// Fills \p D with all information, except the location-related bits.
+/// Also note that ID and Name are not part of clangd::DiagBase and should be
+/// set elsewhere.
+static void fillNonLocationData(DiagnosticsEngine::Level DiagLevel,
+ const clang::Diagnostic &Info,
+ clangd::DiagBase &D) {
+ llvm::SmallString<64> Message;
+ Info.FormatDiagnostic(Message);
+
+ D.Message = Message.str();
+ D.Severity = DiagLevel;
+ D.Category = DiagnosticIDs::getCategoryNameFromID(
+ DiagnosticIDs::getCategoryNumberForDiag(Info.getID()))
+ .str();
+}
+
void StoreDiags::HandleDiagnostic(DiagnosticsEngine::Level DiagLevel,
const clang::Diagnostic &Info) {
DiagnosticConsumer::HandleDiagnostic(DiagLevel, Info);
+ if (Info.getLocation().isInvalid()) {
+ // Handle diagnostics coming from command-line arguments. The source manager
+ // is *not* available at this point, so we cannot use it.
+ if (DiagLevel < DiagnosticsEngine::Level::Error) {
+ IgnoreDiagnostics::log(DiagLevel, Info);
+ return; // non-errors add too much noise, do not show them.
+ }
+
+ flushLastDiag();
+
+ LastDiag = Diag();
+ LastDiag->ID = Info.getID();
+ fillNonLocationData(DiagLevel, Info, *LastDiag);
+ LastDiag->InsideMainFile = true;
+ // Put it at the start of the main file, for a lack of a better place.
+ LastDiag->Range.start = Position{0, 0};
+ LastDiag->Range.end = Position{0, 0};
+ return;
+ }
+
if (!LangOpts || !Info.hasSourceManager()) {
IgnoreDiagnostics::log(DiagLevel, Info);
return;
SourceManager &SM = Info.getSourceManager();
auto FillDiagBase = [&](DiagBase &D) {
- D.Range = diagnosticRange(Info, *LangOpts);
- llvm::SmallString<64> Message;
- Info.FormatDiagnostic(Message);
- D.Message = Message.str();
+ fillNonLocationData(DiagLevel, Info, D);
+
D.InsideMainFile = InsideMainFile;
+ D.Range = diagnosticRange(Info, *LangOpts);
D.File = SM.getFilename(Info.getLocation());
D.AbsFile = getCanonicalPath(
SM.getFileEntryForID(SM.getFileID(Info.getLocation())), SM);
- D.Severity = DiagLevel;
- D.Category = DiagnosticIDs::getCategoryNameFromID(
- DiagnosticIDs::getCategoryNumberForDiag(Info.getID()))
- .str();
return D;
};
LastDiag = Diag();
LastDiag->ID = Info.getID();
FillDiagBase(*LastDiag);
- LastDiagWasAdjusted = false;
if (!InsideMainFile)
LastDiagWasAdjusted = adjustDiagFromHeader(*LastDiag, Info, *LangOpts);
vlog("Dropped diagnostic: {0}: {1}", LastDiag->File, LastDiag->Message);
}
LastDiag.reset();
+ LastDiagWasAdjusted = false;
}
} // namespace clangd
#include "TUScheduler.h"
#include "Cancellation.h"
#include "Compiler.h"
+#include "Diagnostics.h"
#include "GlobalCompilationDatabase.h"
#include "Logger.h"
#include "Trace.h"
void ASTWorker::update(ParseInputs Inputs, WantDiagnostics WantDiags) {
llvm::StringRef TaskName = "Update";
auto Task = [=]() mutable {
+ auto RunPublish = [&](llvm::function_ref<void()> Publish) {
+ // Ensure we only publish results from the worker if the file was not
+ // removed, making sure there are not race conditions.
+ std::lock_guard<std::mutex> Lock(PublishMu);
+ if (CanPublishResults)
+ Publish();
+ };
+
// Get the actual command as `Inputs` does not have a command.
// 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
Inputs.CompileCommand.Directory,
llvm::join(Inputs.CompileCommand.CommandLine, " "));
// Rebuild the preamble and the AST.
+ StoreDiags CompilerInvocationDiagConsumer;
std::unique_ptr<CompilerInvocation> Invocation =
- buildCompilerInvocation(Inputs);
+ buildCompilerInvocation(Inputs, CompilerInvocationDiagConsumer);
+ std::vector<Diag> CompilerInvocationDiags =
+ CompilerInvocationDiagConsumer.take();
if (!Invocation) {
elog("Could not build CompilerInvocation for file {0}", FileName);
// Remove the old AST if it's still in cache.
TUStatus::BuildDetails Details;
Details.BuildFailed = true;
emitTUStatus({TUAction::BuildingPreamble, TaskName}, &Details);
+ // Report the diagnostics we collected when parsing the command line.
+ Callbacks.onFailedAST(FileName, std::move(CompilerInvocationDiags),
+ RunPublish);
// Make sure anyone waiting for the preamble gets notified it could not
// be built.
PreambleWasBuilt.notify();
llvm::Optional<std::unique_ptr<ParsedAST>> AST = IdleASTs.take(this);
if (!AST) {
llvm::Optional<ParsedAST> NewAST =
- buildAST(FileName, std::move(Invocation), Inputs, NewPreamble);
+ buildAST(FileName, std::move(Invocation), CompilerInvocationDiags,
+ Inputs, NewPreamble);
AST = NewAST ? std::make_unique<ParsedAST>(std::move(*NewAST)) : nullptr;
if (!(*AST)) { // buildAST fails.
TUStatus::BuildDetails Details;
Details.ReuseAST = true;
emitTUStatus({TUAction::BuildingFile, TaskName}, &Details);
}
+
// We want to report the diagnostics even if this update was cancelled.
// It seems more useful than making the clients wait indefinitely if they
// spam us with updates.
// Note *AST can still be null if buildAST fails.
if (*AST) {
trace::Span Span("Running main AST callback");
- auto RunPublish = [&](llvm::function_ref<void()> Publish) {
- // Ensure we only publish results from the worker if the file was not
- // removed, making sure there are not race conditions.
- std::lock_guard<std::mutex> Lock(PublishMu);
- if (CanPublishResults)
- Publish();
- };
Callbacks.onMainAST(FileName, **AST, RunPublish);
RanASTCallback = true;
+ } else {
+ // Failed to build the AST, at least report diagnostics from the command
+ // line if there were any.
+ // FIXME: we might have got more errors while trying to build the AST,
+ // surface them too.
+ Callbacks.onFailedAST(FileName, CompilerInvocationDiags, RunPublish);
}
// Stash the AST in the cache for further use.
IdleASTs.put(this, std::move(*AST));
llvm::Optional<std::unique_ptr<ParsedAST>> AST = IdleASTs.take(this);
auto CurrentInputs = getCurrentFileInputs();
if (!AST) {
- std::unique_ptr<CompilerInvocation> Invocation =
- buildCompilerInvocation(*CurrentInputs);
+ StoreDiags CompilerInvocationDiagConsumer;
+ std::unique_ptr<CompilerInvocation> Invocation = buildCompilerInvocation(
+ *CurrentInputs, CompilerInvocationDiagConsumer);
// Try rebuilding the AST.
llvm::Optional<ParsedAST> NewAST =
Invocation
? buildAST(FileName,
std::make_unique<CompilerInvocation>(*Invocation),
- *CurrentInputs, getPossiblyStalePreamble())
+ CompilerInvocationDiagConsumer.take(), *CurrentInputs,
+ getPossiblyStalePreamble())
: None;
AST = NewAST ? std::make_unique<ParsedAST>(std::move(*NewAST)) : nullptr;
}
#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_TUSCHEDULER_H
#include "ClangdUnit.h"
+#include "Diagnostics.h"
#include "Function.h"
#include "GlobalCompilationDatabase.h"
+#include "Path.h"
#include "Threading.h"
#include "index/CanonicalIncludes.h"
#include "llvm/ADT/Optional.h"
/// Publish() may never run in this case).
virtual void onMainAST(PathRef Path, ParsedAST &AST, PublishFn Publish) {}
+ /// Called whenever the AST fails to build. \p Diags will have the diagnostics
+ /// that led to failure.
+ virtual void onFailedAST(PathRef Path, std::vector<Diag> Diags,
+ PublishFn Publish) {}
+
/// Called whenever the TU status is updated.
virtual void onFileUpdated(PathRef File, const TUStatus &Status) {}
};
Inputs.FS = std::move(FS);
Inputs.FS->setCurrentWorkingDirectory(Cmd.Directory);
Inputs.CompileCommand = std::move(Cmd);
- auto CI = buildCompilerInvocation(Inputs);
+ IgnoreDiagnostics IgnoreDiags;
+ auto CI = buildCompilerInvocation(Inputs, IgnoreDiags);
if (!CI)
return llvm::createStringError(llvm::inconvertibleErrorCode(),
"Couldn't build compiler invocation");
- IgnoreDiagnostics IgnoreDiags;
auto Clang = prepareCompilerInstance(std::move(CI), /*Preamble=*/nullptr,
std::move(*Buf), Inputs.FS, IgnoreDiags);
if (!Clang)
#include "Annotations.h"
#include "ClangdUnit.h"
#include "Compiler.h"
+#include "Diagnostics.h"
#include "SourceCode.h"
#include "TestFS.h"
#include "TestTU.h"
Inputs.FS = buildTestFS({{testPath("foo.cpp"), "void test() {}"}});
Inputs.CompileCommand.CommandLine = {"clang", "-fsome-unknown-flag",
testPath("foo.cpp")};
- EXPECT_NE(buildCompilerInvocation(Inputs), nullptr);
+ IgnoreDiagnostics IgnoreDiags;
+ EXPECT_NE(buildCompilerInvocation(Inputs, IgnoreDiags), nullptr);
// Unknown forwarded to -cc1 should not a failure either.
Inputs.CompileCommand.CommandLine = {
"clang", "-Xclang", "-fsome-unknown-flag", testPath("foo.cpp")};
- EXPECT_NE(buildCompilerInvocation(Inputs), nullptr);
+ EXPECT_NE(buildCompilerInvocation(Inputs, IgnoreDiags), nullptr);
}
} // namespace
#include "AST.h"
#include "Annotations.h"
#include "ClangdUnit.h"
+#include "Compiler.h"
#include "SyncAPI.h"
#include "TestFS.h"
#include "TestTU.h"
)cpp";
// Rebuild the file.
- auto CI = buildCompilerInvocation(PI);
+ IgnoreDiagnostics IgnoreDiags;
+ auto CI = buildCompilerInvocation(PI, IgnoreDiags);
FileIndex Index;
bool IndexUpdated = false;
ParseInputs PI;
PI.CompileCommand = *Cmd;
PI.FS = VFS;
- auto CI = buildCompilerInvocation(PI);
+ auto CI = buildCompilerInvocation(PI, IgnoreDiags);
EXPECT_TRUE(static_cast<bool>(CI));
// The diagnostic options must be set before creating a CompilerInstance.
CI->getDiagnosticOpts().IgnoreWarnings = true;
#include "Path.h"
#include "TUScheduler.h"
#include "TestFS.h"
+#include "Threading.h"
+#include "clang/Basic/DiagnosticDriver.h"
+#include "llvm/ADT/ArrayRef.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/ScopeExit.h"
#include "gmock/gmock.h"
using ::testing::AnyOf;
using ::testing::Each;
using ::testing::ElementsAre;
+using ::testing::Eq;
+using ::testing::Field;
+using ::testing::IsEmpty;
using ::testing::Pointee;
using ::testing::UnorderedElementsAre;
/// in updateWithDiags.
static std::unique_ptr<ParsingCallbacks> captureDiags() {
class CaptureDiags : public ParsingCallbacks {
+ public:
void onMainAST(PathRef File, ParsedAST &AST, PublishFn Publish) override {
- auto Diags = AST.getDiagnostics();
+ reportDiagnostics(File, AST.getDiagnostics(), Publish);
+ }
+
+ void onFailedAST(PathRef File, std::vector<Diag> Diags,
+ PublishFn Publish) override {
+ reportDiagnostics(File, Diags, Publish);
+ }
+
+ private:
+ void reportDiagnostics(PathRef File, llvm::ArrayRef<Diag> Diags,
+ PublishFn Publish) {
auto D = Context::current().get(DiagsCallbackKey);
if (!D)
return;
-
Publish([&]() {
const_cast<
llvm::unique_function<void(PathRef, std::vector<Diag>)> &> (*D)(
TUState(TUAction::Idle, /*No action*/ "")));
}
+TEST_F(TUSchedulerTests, CommandLineErrors) {
+ // We should see errors from command-line parsing inside the main file.
+ CDB.ExtraClangFlags = {"-fsome-unknown-flag"};
+
+ TUScheduler S(CDB, /*AsyncThreadsCount=*/getDefaultAsyncThreadsCount(),
+ /*StorePreambleInMemory=*/true, /*ASTCallbacks=*/captureDiags(),
+ /*UpdateDebounce=*/std::chrono::steady_clock::duration::zero(),
+ ASTRetentionPolicy());
+
+ Notification Ready;
+ std::vector<Diag> Diagnostics;
+ updateWithDiags(S, testPath("foo.cpp"), "void test() {}",
+ WantDiagnostics::Yes, [&](std::vector<Diag> D) {
+ Diagnostics = std::move(D);
+ Ready.notify();
+ });
+ Ready.wait();
+
+ EXPECT_THAT(
+ Diagnostics,
+ ElementsAre(AllOf(
+ Field(&Diag::ID, Eq(diag::err_drv_unknown_argument)),
+ Field(&Diag::Name, Eq("drv_unknown_argument")),
+ Field(&Diag::Message, "unknown argument: '-fsome-unknown-flag'"))));
+}
+
+TEST_F(TUSchedulerTests, CommandLineWarnings) {
+ // We should not see warnings from command-line parsing.
+ CDB.ExtraClangFlags = {"-Wsome-unknown-warning"};
+
+ TUScheduler S(CDB, /*AsyncThreadsCount=*/getDefaultAsyncThreadsCount(),
+ /*StorePreambleInMemory=*/true, /*ASTCallbacks=*/captureDiags(),
+ /*UpdateDebounce=*/std::chrono::steady_clock::duration::zero(),
+ ASTRetentionPolicy());
+
+ Notification Ready;
+ std::vector<Diag> Diagnostics;
+ updateWithDiags(S, testPath("foo.cpp"), "void test() {}",
+ WantDiagnostics::Yes, [&](std::vector<Diag> D) {
+ Diagnostics = std::move(D);
+ Ready.notify();
+ });
+ Ready.wait();
+
+ EXPECT_THAT(Diagnostics, IsEmpty());
+}
+
} // namespace
} // namespace clangd
} // namespace clang
//===----------------------------------------------------------------------===//
#include "TestTU.h"
+#include "Compiler.h"
+#include "Diagnostics.h"
#include "TestFS.h"
#include "index/FileIndex.h"
#include "index/MemIndex.h"
Inputs.Index = ExternalIndex;
if (Inputs.Index)
Inputs.Opts.SuggestMissingIncludes = true;
- auto CI = buildCompilerInvocation(Inputs);
+ StoreDiags Diags;
+ auto CI = buildCompilerInvocation(Inputs, Diags);
assert(CI && "Failed to build compilation invocation.");
auto Preamble =
buildPreamble(FullFilename, *CI,
/*OldPreamble=*/nullptr,
/*OldCompileCommand=*/Inputs.CompileCommand, Inputs,
/*StoreInMemory=*/true, /*PreambleCallback=*/nullptr);
- auto AST = buildAST(FullFilename, std::move(CI), Inputs, Preamble);
+ auto AST =
+ buildAST(FullFilename, std::move(CI), Diags.take(), Inputs, Preamble);
if (!AST.hasValue()) {
ADD_FAILURE() << "Failed to build code:\n" << Code;
llvm_unreachable("Failed to build TestTU!");