Skip to content

Commit 4179845

Browse files
benlangmuircyndyishida
authored andcommitted
[clang][deps] Ensure DiagnosticConsumer::finish is always called (llvm#127110)
When using the clang dependency scanner with an arbitrary DiagnosticConsumer, it is important that we always call finish(). Previously, if there was an error preventing us from reaching the scanning action, or if the command line contained no scannable actions we would fail to finish(), which would break some consumers (e.g. serialized diag consumer). (cherry picked from commit e3cab30)
1 parent 3e23309 commit 4179845

File tree

2 files changed

+94
-6
lines changed

2 files changed

+94
-6
lines changed

clang/lib/Tooling/DependencyScanning/DependencyScanningWorker.cpp

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -451,16 +451,13 @@ class DependencyScanningAction : public tooling::ToolAction {
451451
// Create the compiler's actual diagnostics engine.
452452
if (!DiagGenerationAsCompilation)
453453
sanitizeDiagOpts(ScanInstance.getDiagnosticOpts());
454+
assert(!DiagConsumerFinished && "attempt to reuse finished consumer");
454455
ScanInstance.createDiagnostics(DiagConsumer, /*ShouldOwnClient=*/false);
455456
if (!ScanInstance.hasDiagnostics())
456457
return false;
457458
if (VerboseOS)
458459
ScanInstance.setVerboseOutputStream(*VerboseOS);
459460

460-
// Some DiagnosticConsumers require that finish() is called.
461-
auto DiagConsumerFinisher =
462-
llvm::make_scope_exit([DiagConsumer]() { DiagConsumer->finish(); });
463-
464461
ScanInstance.getPreprocessorOpts().AllowPCHWithDifferentModulesCachePath =
465462
true;
466463

@@ -615,8 +612,9 @@ class DependencyScanningAction : public tooling::ToolAction {
615612
if (ScanInstance.getDiagnostics().hasErrorOccurred())
616613
return false;
617614

618-
// Each action is responsible for calling finish.
619-
DiagConsumerFinisher.release();
615+
// ExecuteAction is responsible for calling finish.
616+
DiagConsumerFinished = true;
617+
620618
if (!ScanInstance.ExecuteAction(*Action))
621619
return false;
622620

@@ -650,6 +648,7 @@ class DependencyScanningAction : public tooling::ToolAction {
650648
}
651649

652650
bool hasScanned() const { return Scanned; }
651+
bool hasDiagConsumerFinished() const { return DiagConsumerFinished; }
653652

654653
/// Take the cc1 arguments corresponding to the most recent invocation used
655654
/// with this action. Any modifications implied by the discovered dependencies
@@ -692,6 +691,7 @@ class DependencyScanningAction : public tooling::ToolAction {
692691
std::optional<std::string> TUCacheKey;
693692
bool Scanned = false;
694693
raw_ostream *VerboseOS;
694+
bool DiagConsumerFinished = false;
695695
};
696696

697697
} // end anonymous namespace
@@ -909,6 +909,11 @@ bool DependencyScanningWorker::scanDependencies(
909909
if (Success && !Action.hasScanned())
910910
Diags->Report(diag::err_fe_expected_compiler_job)
911911
<< llvm::join(CommandLine, " ");
912+
913+
// Ensure finish() is called even if we never reached ExecuteAction().
914+
if (!Action.hasDiagConsumerFinished())
915+
DC.finish();
916+
912917
return Success && Action.hasScanned();
913918
}
914919

clang/unittests/Tooling/DependencyScanning/DependencyScannerTest.cpp

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
#include "clang/Frontend/FrontendActions.h"
1616
#include "clang/Tooling/CompilationDatabase.h"
1717
#include "clang/Tooling/DependencyScanning/DependencyScanningTool.h"
18+
#include "clang/Tooling/DependencyScanning/DependencyScanningWorker.h"
1819
#include "clang/Tooling/Tooling.h"
1920
#include "llvm/ADT/STLExtras.h"
2021
#include "llvm/CAS/CASProvidingFileSystem.h"
@@ -357,3 +358,85 @@ TEST(DependencyScanner, ScanDepsWithModuleLookup) {
357358
InterceptFS->StatPaths.end());
358359
EXPECT_EQ(InterceptFS->ReadFiles, std::vector<std::string>{"test.m"});
359360
}
361+
362+
TEST(DependencyScanner, ScanDepsWithDiagConsumer) {
363+
StringRef CWD = "/root";
364+
365+
auto VFS = new llvm::vfs::InMemoryFileSystem();
366+
VFS->setCurrentWorkingDirectory(CWD);
367+
auto Sept = llvm::sys::path::get_separator();
368+
std::string HeaderPath =
369+
std::string(llvm::formatv("{0}root{0}header.h", Sept));
370+
std::string TestPath = std::string(llvm::formatv("{0}root{0}test.cpp", Sept));
371+
std::string AsmPath = std::string(llvm::formatv("{0}root{0}test.s", Sept));
372+
373+
VFS->addFile(HeaderPath, 0, llvm::MemoryBuffer::getMemBuffer("\n"));
374+
VFS->addFile(TestPath, 0,
375+
llvm::MemoryBuffer::getMemBuffer("#include \"header.h\"\n"));
376+
VFS->addFile(AsmPath, 0, llvm::MemoryBuffer::getMemBuffer(""));
377+
378+
DependencyScanningService Service(ScanningMode::DependencyDirectivesScan,
379+
ScanningOutputFormat::Make, CASOptions(),
380+
nullptr, nullptr, nullptr);
381+
DependencyScanningWorker Worker(Service, VFS);
382+
383+
llvm::DenseSet<ModuleID> AlreadySeen;
384+
FullDependencyConsumer DC(AlreadySeen);
385+
CallbackActionController AC(nullptr);
386+
387+
struct EnsureFinishedConsumer : public DiagnosticConsumer {
388+
bool Finished = false;
389+
void finish() override { Finished = true; }
390+
};
391+
392+
{
393+
// Check that a successful scan calls DiagConsumer.finish().
394+
std::vector<std::string> Args = {"clang",
395+
"-target",
396+
"x86_64-apple-macosx10.7",
397+
"-c",
398+
"test.cpp",
399+
"-o"
400+
"test.cpp.o"};
401+
402+
EnsureFinishedConsumer DiagConsumer;
403+
bool Success = Worker.computeDependencies(CWD, Args, DC, AC, DiagConsumer);
404+
405+
EXPECT_TRUE(Success);
406+
EXPECT_EQ(DiagConsumer.getNumErrors(), 0u);
407+
EXPECT_TRUE(DiagConsumer.Finished);
408+
}
409+
410+
{
411+
// Check that an invalid command-line, which never enters the scanning
412+
// action calls DiagConsumer.finish().
413+
std::vector<std::string> Args = {"clang", "-invalid-arg"};
414+
EnsureFinishedConsumer DiagConsumer;
415+
bool Success = Worker.computeDependencies(CWD, Args, DC, AC, DiagConsumer);
416+
417+
EXPECT_FALSE(Success);
418+
EXPECT_GE(DiagConsumer.getNumErrors(), 1u);
419+
EXPECT_TRUE(DiagConsumer.Finished);
420+
}
421+
422+
{
423+
// Check that a valid command line that produces no scanning jobs calls
424+
// DiagConsumer.finish().
425+
std::vector<std::string> Args = {"clang",
426+
"-target",
427+
"x86_64-apple-macosx10.7",
428+
"-c",
429+
"-x",
430+
"assembler",
431+
"test.s",
432+
"-o"
433+
"test.cpp.o"};
434+
435+
EnsureFinishedConsumer DiagConsumer;
436+
bool Success = Worker.computeDependencies(CWD, Args, DC, AC, DiagConsumer);
437+
438+
EXPECT_FALSE(Success);
439+
EXPECT_EQ(DiagConsumer.getNumErrors(), 1u);
440+
EXPECT_TRUE(DiagConsumer.Finished);
441+
}
442+
}

0 commit comments

Comments
 (0)