Skip to content

Commit 0fa722f

Browse files
committed
[6.2][Dependency Scanning] Give each scanner worker a unique Diagnostic Engine
Otherwise, when multiple workers encounter a diagnostic simultaneously we can encounter races which lead to corrupted diagnostic data or crashes Resolves rdar://159598539
1 parent d4c0b15 commit 0fa722f

File tree

5 files changed

+105
-10
lines changed

5 files changed

+105
-10
lines changed

include/swift/DependencyScan/DependencyScanningTool.h

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,21 +31,33 @@ struct ScanQueryInstance {
3131
std::shared_ptr<DependencyScanDiagnosticCollector> ScanDiagnostics;
3232
};
3333

34+
/// Pure virtual Diagnostic consumer intended for collecting
35+
/// emitted diagnostics in a thread-safe fashion
36+
class ThreadSafeDiagnosticCollector : public DiagnosticConsumer {
37+
private:
38+
llvm::sys::SmartMutex<true> DiagnosticConsumerStateLock;
39+
void handleDiagnostic(SourceManager &SM, const DiagnosticInfo &Info) final;
40+
41+
protected:
42+
virtual void addDiagnostic(SourceManager &SM, const DiagnosticInfo &Info) = 0;
43+
44+
public:
45+
ThreadSafeDiagnosticCollector() {}
46+
bool finishProcessing() final { return false; }
47+
};
48+
3449
/// Diagnostic consumer that simply collects the diagnostics emitted so-far
35-
class DependencyScanDiagnosticCollector : public DiagnosticConsumer {
50+
class DependencyScanDiagnosticCollector : public ThreadSafeDiagnosticCollector {
3651
private:
3752
struct ScannerDiagnosticInfo {
3853
std::string Message;
3954
llvm::SourceMgr::DiagKind Severity;
4055
std::optional<ScannerImportStatementInfo::ImportDiagnosticLocationInfo> ImportLocation;
4156
};
4257
std::vector<ScannerDiagnosticInfo> Diagnostics;
43-
llvm::sys::SmartMutex<true> ScanningDiagnosticConsumerStateLock;
44-
45-
void handleDiagnostic(SourceManager &SM, const DiagnosticInfo &Info) override;
4658

4759
protected:
48-
virtual void addDiagnostic(SourceManager &SM, const DiagnosticInfo &Info);
60+
void addDiagnostic(SourceManager &SM, const DiagnosticInfo &Info) override;
4961

5062
public:
5163
friend DependencyScanningTool;

include/swift/DependencyScan/ModuleDependencyScanner.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,8 @@ class ModuleDependencyScanningWorker {
8383

8484
// Worker-specific instance of CompilerInvocation
8585
std::unique_ptr<CompilerInvocation> workerCompilerInvocation;
86+
// Worker-specific diagnostic engine
87+
std::unique_ptr<DiagnosticEngine> workerDiagnosticEngine;
8688
// Worker-specific instance of ASTContext
8789
std::unique_ptr<ASTContext> workerASTContext;
8890
// An AST delegate for interface scanning.

lib/DependencyScan/DependencyScanningTool.cpp

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -64,8 +64,9 @@ llvm::ErrorOr<swiftscan_string_ref_t> getTargetInfo(ArrayRef<const char *> Comma
6464
return c_string_utils::create_clone(ResultStr.c_str());
6565
}
6666

67-
void DependencyScanDiagnosticCollector::handleDiagnostic(SourceManager &SM,
67+
void ThreadSafeDiagnosticCollector::handleDiagnostic(SourceManager &SM,
6868
const DiagnosticInfo &Info) {
69+
llvm::sys::SmartScopedLock<true> Lock(DiagnosticConsumerStateLock);
6970
addDiagnostic(SM, Info);
7071
for (auto ChildInfo : Info.ChildDiagnosticInfo) {
7172
addDiagnostic(SM, *ChildInfo);
@@ -74,8 +75,6 @@ void DependencyScanDiagnosticCollector::handleDiagnostic(SourceManager &SM,
7475

7576
void DependencyScanDiagnosticCollector::addDiagnostic(
7677
SourceManager &SM, const DiagnosticInfo &Info) {
77-
llvm::sys::SmartScopedLock<true> Lock(ScanningDiagnosticConsumerStateLock);
78-
7978
// Determine what kind of diagnostic we're emitting.
8079
llvm::SourceMgr::DiagKind SMKind;
8180
switch (Info.Kind) {

lib/DependencyScan/ModuleDependencyScanner.cpp

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,13 @@ ModuleDependencyScanningWorker::ModuleDependencyScanningWorker(
200200
// Create a scanner-specific Invocation and ASTContext.
201201
workerCompilerInvocation =
202202
std::make_unique<CompilerInvocation>(ScanCompilerInvocation);
203+
204+
// Instantiate a worker-specific diagnostic engine and copy over
205+
// the scanner's diagnostic consumers (expected to be thread-safe).
206+
workerDiagnosticEngine = std::make_unique<DiagnosticEngine>(ScanASTContext.SourceMgr);
207+
for (auto &scannerDiagConsumer : Diagnostics.getConsumers())
208+
workerDiagnosticEngine->addConsumer(*scannerDiagConsumer);
209+
203210
workerASTContext = std::unique_ptr<ASTContext>(
204211
ASTContext::get(workerCompilerInvocation->getLangOptions(),
205212
workerCompilerInvocation->getTypeCheckerOptions(),
@@ -209,7 +216,8 @@ ModuleDependencyScanningWorker::ModuleDependencyScanningWorker(
209216
workerCompilerInvocation->getSymbolGraphOptions(),
210217
workerCompilerInvocation->getCASOptions(),
211218
workerCompilerInvocation->getSerializationOptions(),
212-
ScanASTContext.SourceMgr, Diagnostics));
219+
ScanASTContext.SourceMgr, *workerDiagnosticEngine));
220+
213221
auto loader = std::make_unique<PluginLoader>(
214222
*workerASTContext, /*DepTracker=*/nullptr,
215223
workerCompilerInvocation->getFrontendOptions().CacheReplayPrefixMap,

unittests/DependencyScan/ModuleDeps.cpp

Lines changed: 75 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -307,7 +307,6 @@ public func funcB() { }\n"));
307307
for (auto &command : CommandStr)
308308
Command.push_back(command.c_str());
309309

310-
auto ScanDiagnosticConsumer = std::make_shared<DependencyScanDiagnosticCollector>();
311310

312311
auto DependenciesOrErr = ScannerTool.getDependencies(Command, {}, {});
313312

@@ -329,3 +328,78 @@ public func funcB() { }\n"));
329328
ASSERT_TRUE(Dependencies->dependencies->modules[0]->link_libraries->count == 0);
330329
swiftscan_dependency_graph_dispose(Dependencies);
331330
}
331+
332+
TEST_F(ScanTest, TestStressConcurrentDiagnostics) {
333+
SmallString<256> tempDir;
334+
ASSERT_FALSE(llvm::sys::fs::createUniqueDirectory("ScanTest.TestStressConcurrentDiagnostics", tempDir));
335+
SWIFT_DEFER { llvm::sys::fs::remove_directories(tempDir); };
336+
337+
// Create includes
338+
std::string IncludeDirPath = createFilename(tempDir, "include");
339+
ASSERT_FALSE(llvm::sys::fs::create_directory(IncludeDirPath));
340+
std::string CHeadersDirPath = createFilename(IncludeDirPath, "CHeaders");
341+
ASSERT_FALSE(llvm::sys::fs::create_directory(CHeadersDirPath));
342+
343+
// Create test input file
344+
std::string TestPathStr = createFilename(tempDir, "foo.swift");
345+
346+
// Create imported module C modulemap/headers
347+
std::string modulemapContent = "";
348+
std::string testFileContent = "";
349+
for (int i = 0; i < 100; ++i) {
350+
std::string headerName = "A_" + std::to_string(i) + ".h";
351+
std::string headerContent = "void funcA_" + std::to_string(i) + "(void);";
352+
ASSERT_FALSE(
353+
emitFileWithContents(CHeadersDirPath, headerName, headerContent));
354+
355+
std::string moduleMapEntry = "module A_" + std::to_string(i) + "{\n";
356+
moduleMapEntry.append("header \"A_" + std::to_string(i) + ".h\"\n");
357+
moduleMapEntry.append("export *\n");
358+
moduleMapEntry.append("}\n");
359+
modulemapContent.append(moduleMapEntry);
360+
testFileContent.append("import A_" + std::to_string(i) + "\n");
361+
}
362+
363+
ASSERT_FALSE(emitFileWithContents(tempDir, "foo.swift", testFileContent));
364+
ASSERT_FALSE(
365+
emitFileWithContents(CHeadersDirPath, "module.modulemap", modulemapContent));
366+
367+
// Paths to shims and stdlib
368+
llvm::SmallString<128> ShimsLibDir = StdLibDir;
369+
llvm::sys::path::append(ShimsLibDir, "shims");
370+
auto Target = llvm::Triple(llvm::sys::getDefaultTargetTriple());
371+
llvm::sys::path::append(StdLibDir, getPlatformNameForTriple(Target));
372+
373+
std::vector<std::string> BaseCommandStrArr = {
374+
TestPathStr,
375+
std::string("-I ") + CHeadersDirPath,
376+
std::string("-I ") + StdLibDir.str().str(),
377+
std::string("-I ") + ShimsLibDir.str().str(),
378+
// Pass in a flag which will cause every instantiation of
379+
// the clang scanner to fail with "unknown argument"
380+
"-Xcc",
381+
"-foobar"
382+
};
383+
384+
std::vector<std::string> CommandStr = BaseCommandStrArr;
385+
CommandStr.push_back("-module-name");
386+
CommandStr.push_back("testConcurrentDiags");
387+
388+
// On Windows we need to add an extra escape for path separator characters because otherwise
389+
// the command line tokenizer will treat them as escape characters.
390+
for (size_t i = 0; i < CommandStr.size(); ++i) {
391+
std::replace(CommandStr[i].begin(), CommandStr[i].end(), '\\', '/');
392+
}
393+
std::vector<const char*> Command;
394+
for (auto &command : CommandStr)
395+
Command.push_back(command.c_str());
396+
397+
auto DependenciesOrErr = ScannerTool.getDependencies(Command, {}, {});
398+
399+
// Ensure a hollow output with diagnostic info is produced
400+
ASSERT_FALSE(DependenciesOrErr.getError());
401+
auto Dependencies = DependenciesOrErr.get();
402+
auto Diagnostics = Dependencies->diagnostics;
403+
ASSERT_TRUE(Diagnostics->count > 100);
404+
swiftscan_dependency_graph_dispose(Dependencies);
405+
}

0 commit comments

Comments
 (0)