Skip to content

Commit e3cab30

Browse files
authored
[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).
1 parent c9ba2b0 commit e3cab30

File tree

2 files changed

+93
-6
lines changed

2 files changed

+93
-6
lines changed

clang/lib/Tooling/DependencyScanning/DependencyScanningWorker.cpp

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -324,15 +324,12 @@ class DependencyScanningAction : public tooling::ToolAction {
324324

325325
// Create the compiler's actual diagnostics engine.
326326
sanitizeDiagOpts(ScanInstance.getDiagnosticOpts());
327+
assert(!DiagConsumerFinished && "attempt to reuse finished consumer");
327328
ScanInstance.createDiagnostics(DriverFileMgr->getVirtualFileSystem(),
328329
DiagConsumer, /*ShouldOwnClient=*/false);
329330
if (!ScanInstance.hasDiagnostics())
330331
return false;
331332

332-
// Some DiagnosticConsumers require that finish() is called.
333-
auto DiagConsumerFinisher =
334-
llvm::make_scope_exit([DiagConsumer]() { DiagConsumer->finish(); });
335-
336333
ScanInstance.getPreprocessorOpts().AllowPCHWithDifferentModulesCachePath =
337334
true;
338335

@@ -446,10 +443,11 @@ class DependencyScanningAction : public tooling::ToolAction {
446443
if (ScanInstance.getDiagnostics().hasErrorOccurred())
447444
return false;
448445

449-
// Each action is responsible for calling finish.
450-
DiagConsumerFinisher.release();
451446
const bool Result = ScanInstance.ExecuteAction(*Action);
452447

448+
// ExecuteAction is responsible for calling finish.
449+
DiagConsumerFinished = true;
450+
453451
if (Result)
454452
setLastCC1Arguments(std::move(OriginalInvocation));
455453

@@ -460,6 +458,7 @@ class DependencyScanningAction : public tooling::ToolAction {
460458
}
461459

462460
bool hasScanned() const { return Scanned; }
461+
bool hasDiagConsumerFinished() const { return DiagConsumerFinished; }
463462

464463
/// Take the cc1 arguments corresponding to the most recent invocation used
465464
/// with this action. Any modifications implied by the discovered dependencies
@@ -491,6 +490,7 @@ class DependencyScanningAction : public tooling::ToolAction {
491490
std::shared_ptr<ModuleDepCollector> MDC;
492491
std::vector<std::string> LastCC1Arguments;
493492
bool Scanned = false;
493+
bool DiagConsumerFinished = false;
494494
};
495495

496496
} // end anonymous namespace
@@ -693,6 +693,11 @@ bool DependencyScanningWorker::scanDependencies(
693693
if (Success && !Action.hasScanned())
694694
Diags->Report(diag::err_fe_expected_compiler_job)
695695
<< llvm::join(CommandLine, " ");
696+
697+
// Ensure finish() is called even if we never reached ExecuteAction().
698+
if (!Action.hasDiagConsumerFinished())
699+
DC.finish();
700+
696701
return Success && Action.hasScanned();
697702
}
698703

clang/unittests/Tooling/DependencyScanning/DependencyScannerTest.cpp

Lines changed: 82 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/MC/TargetRegistry.h"
@@ -302,3 +303,84 @@ TEST(DependencyScanner, ScanDepsWithModuleLookup) {
302303
InterceptFS->StatPaths.end());
303304
EXPECT_EQ(InterceptFS->ReadFiles, std::vector<std::string>{"test.m"});
304305
}
306+
307+
TEST(DependencyScanner, ScanDepsWithDiagConsumer) {
308+
StringRef CWD = "/root";
309+
310+
auto VFS = new llvm::vfs::InMemoryFileSystem();
311+
VFS->setCurrentWorkingDirectory(CWD);
312+
auto Sept = llvm::sys::path::get_separator();
313+
std::string HeaderPath =
314+
std::string(llvm::formatv("{0}root{0}header.h", Sept));
315+
std::string TestPath = std::string(llvm::formatv("{0}root{0}test.cpp", Sept));
316+
std::string AsmPath = std::string(llvm::formatv("{0}root{0}test.s", Sept));
317+
318+
VFS->addFile(HeaderPath, 0, llvm::MemoryBuffer::getMemBuffer("\n"));
319+
VFS->addFile(TestPath, 0,
320+
llvm::MemoryBuffer::getMemBuffer("#include \"header.h\"\n"));
321+
VFS->addFile(AsmPath, 0, llvm::MemoryBuffer::getMemBuffer(""));
322+
323+
DependencyScanningService Service(ScanningMode::DependencyDirectivesScan,
324+
ScanningOutputFormat::Make);
325+
DependencyScanningWorker Worker(Service, VFS);
326+
327+
llvm::DenseSet<ModuleID> AlreadySeen;
328+
FullDependencyConsumer DC(AlreadySeen);
329+
CallbackActionController AC(nullptr);
330+
331+
struct EnsureFinishedConsumer : public DiagnosticConsumer {
332+
bool Finished = false;
333+
void finish() override { Finished = true; }
334+
};
335+
336+
{
337+
// Check that a successful scan calls DiagConsumer.finish().
338+
std::vector<std::string> Args = {"clang",
339+
"-target",
340+
"x86_64-apple-macosx10.7",
341+
"-c",
342+
"test.cpp",
343+
"-o"
344+
"test.cpp.o"};
345+
346+
EnsureFinishedConsumer DiagConsumer;
347+
bool Success = Worker.computeDependencies(CWD, Args, DC, AC, DiagConsumer);
348+
349+
EXPECT_TRUE(Success);
350+
EXPECT_EQ(DiagConsumer.getNumErrors(), 0u);
351+
EXPECT_TRUE(DiagConsumer.Finished);
352+
}
353+
354+
{
355+
// Check that an invalid command-line, which never enters the scanning
356+
// action calls DiagConsumer.finish().
357+
std::vector<std::string> Args = {"clang", "-invalid-arg"};
358+
EnsureFinishedConsumer DiagConsumer;
359+
bool Success = Worker.computeDependencies(CWD, Args, DC, AC, DiagConsumer);
360+
361+
EXPECT_FALSE(Success);
362+
EXPECT_GE(DiagConsumer.getNumErrors(), 1u);
363+
EXPECT_TRUE(DiagConsumer.Finished);
364+
}
365+
366+
{
367+
// Check that a valid command line that produces no scanning jobs calls
368+
// DiagConsumer.finish().
369+
std::vector<std::string> Args = {"clang",
370+
"-target",
371+
"x86_64-apple-macosx10.7",
372+
"-c",
373+
"-x",
374+
"assembler",
375+
"test.s",
376+
"-o"
377+
"test.cpp.o"};
378+
379+
EnsureFinishedConsumer DiagConsumer;
380+
bool Success = Worker.computeDependencies(CWD, Args, DC, AC, DiagConsumer);
381+
382+
EXPECT_FALSE(Success);
383+
EXPECT_EQ(DiagConsumer.getNumErrors(), 1u);
384+
EXPECT_TRUE(DiagConsumer.Finished);
385+
}
386+
}

0 commit comments

Comments
 (0)