Skip to content

Commit d0c1a45

Browse files
committed
DependenciesScanner: teach the scanner to diagnose dependency cycles
1 parent d3f132b commit d0c1a45

File tree

6 files changed

+86
-0
lines changed

6 files changed

+86
-0
lines changed

include/swift/AST/DiagnosticsCommon.def

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,5 +178,12 @@ WARNING(cross_imported_by_both_modules, none,
178178
"please report this bug to the maintainers of these modules",
179179
(Identifier, Identifier, Identifier))
180180

181+
//------------------------------------------------------------------------------
182+
// MARK: dependencies scanner diagnostics
183+
//------------------------------------------------------------------------------
184+
185+
ERROR(scanner_find_cycle, none,
186+
"dependency scanner detected dependency cycle: '%0'", (StringRef))
187+
181188
#define UNDEFINE_DIAGNOSTIC_MACROS
182189
#include "DefineDiagnosticMacros.h"

lib/FrontendTool/ScanDependencies.cpp

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -470,6 +470,57 @@ static void writeJSON(llvm::raw_ostream &out,
470470
}
471471
}
472472

473+
static bool diagnoseCycle(CompilerInstance &instance,
474+
ModuleDependenciesCache &cache,
475+
ModuleDependencyID mainId,
476+
InterfaceSubContextDelegate &astDelegate) {
477+
llvm::SetVector<ModuleDependencyID, std::vector<ModuleDependencyID>,
478+
std::set<ModuleDependencyID>> openSet;
479+
llvm::SetVector<ModuleDependencyID, std::vector<ModuleDependencyID>,
480+
std::set<ModuleDependencyID>> closeSet;
481+
// Start from the main module.
482+
openSet.insert(mainId);
483+
while(!openSet.empty()) {
484+
auto &lastOpen = openSet.back();
485+
auto beforeSize = openSet.size();
486+
for (auto dep: resolveDirectDependencies(instance, lastOpen, cache,
487+
astDelegate)) {
488+
if (closeSet.count(dep))
489+
continue;
490+
if (openSet.insert(dep)) {
491+
break;
492+
} else {
493+
// Find a cycle, diagnose.
494+
auto startIt = std::find(openSet.begin(), openSet.end(), dep);
495+
assert(startIt != openSet.end());
496+
llvm::SmallString<64> buffer;
497+
for (auto it = startIt; it != openSet.end(); ++ it) {
498+
buffer.append(it->first);
499+
buffer.append(it->second == ModuleDependenciesKind::Swift?
500+
".swiftmodule": ".pcm");
501+
buffer.append(" -> ");
502+
}
503+
buffer.append(startIt->first);
504+
buffer.append(startIt->second == ModuleDependenciesKind::Swift?
505+
".swiftmodule": ".pcm");
506+
instance.getASTContext().Diags.diagnose(SourceLoc(),
507+
diag::scanner_find_cycle,
508+
buffer.str());
509+
return true;
510+
}
511+
}
512+
// No new node added. We can close this node
513+
if (openSet.size() == beforeSize) {
514+
closeSet.insert(openSet.back());
515+
openSet.pop_back();
516+
} else {
517+
assert(openSet.size() == beforeSize + 1);
518+
}
519+
}
520+
assert(openSet.empty());
521+
return false;
522+
}
523+
473524
bool swift::scanDependencies(CompilerInstance &instance) {
474525
ASTContext &Context = instance.getASTContext();
475526
ModuleDecl *mainModule = instance.getMainModule();
@@ -592,6 +643,10 @@ bool swift::scanDependencies(CompilerInstance &instance) {
592643
allModules.insert(id);
593644
});
594645

646+
// Dignose cycle in dependency graph.
647+
if (diagnoseCycle(instance, cache, /*MainModule*/allModules.front(), ASTDelegate))
648+
return true;
649+
595650
// Write out the JSON description.
596651
writeJSON(out, instance, cache, ASTDelegate, allModules.getArrayRef());
597652

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
// swift-interface-format-version: 1.0
2+
// swift-module-flags: -module-name CycleOne
3+
import Swift
4+
import CycleTwo
5+
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
// swift-interface-format-version: 1.0
2+
// swift-module-flags: -module-name CycleThree
3+
import Swift
4+
import CycleOne
5+
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
// swift-interface-format-version: 1.0
2+
// swift-module-flags: -module-name CycleTwo
3+
import Swift
4+
import CycleThree
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// RUN: %empty-directory(%t)
2+
// RUN: mkdir -p %t/clang-module-cache
3+
4+
// RUN: not %target-swift-frontend -scan-dependencies -module-cache-path %t/clang-module-cache %s -o %t/deps.json -I %S/Inputs/CHeaders -I %S/Inputs/Swift -emit-dependencies -emit-dependencies-path %t/deps.d -import-objc-header %S/Inputs/CHeaders/Bridging.h -swift-version 4 -disable-implicit-swift-modules -Xcc -Xclang -Xcc -fno-implicit-modules &> %t/out.txt
5+
6+
// RUN: %FileCheck %s < %t/out.txt
7+
8+
// CHECK: dependency scanner detected dependency cycle: 'CycleOne.swiftmodule -> CycleTwo.swiftmodule -> CycleThree.swiftmodule -> CycleOne.swiftmodule'
9+
10+
import CycleOne

0 commit comments

Comments
 (0)