Skip to content

Commit df2e63d

Browse files
authored
Diagnose modules with circular dependencies (swiftlang#16075)
This can't arise from a clean build, but it can happen if you have products lingering in a search path and then either rebuild one of the modules in the cycle, or change the search paths. The way this is implemented is for each module to track whether its imports have all been resolved. If, when loading a module, one of its dependencies hasn't resolved all of its imports yet, then we know there's a cycle. This doesn't produce the best diagnostics, but it's hard to get into this state in the first place, so that's probably okay. https://bugs.swift.org/browse/SR-7483
1 parent 9986178 commit df2e63d

File tree

17 files changed

+98
-124
lines changed

17 files changed

+98
-124
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -585,6 +585,9 @@ ERROR(serialization_missing_single_dependency,Fatal,
585585
"missing required module '%0'", (StringRef))
586586
ERROR(serialization_missing_dependencies,Fatal,
587587
"missing required modules: %0", (StringRef))
588+
ERROR(serialization_circular_dependency,Fatal,
589+
"circular dependency between modules '%0' and %1",
590+
(StringRef, Identifier))
588591
ERROR(serialization_missing_shadowed_module,Fatal,
589592
"cannot load underlying module for %0", (Identifier))
590593
ERROR(serialization_name_mismatch,Fatal,

include/swift/AST/Module.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,7 @@ class ModuleDecl : public DeclContext, public TypeDecl {
206206
unsigned TestingEnabled : 1;
207207
unsigned FailedToLoad : 1;
208208
unsigned ResilienceStrategy : 1;
209+
unsigned HasResolvedImports : 1;
209210
} Flags;
210211

211212
ModuleDecl(Identifier name, ASTContext &ctx);
@@ -257,6 +258,13 @@ class ModuleDecl : public DeclContext, public TypeDecl {
257258
Flags.FailedToLoad = failed;
258259
}
259260

261+
bool hasResolvedImports() const {
262+
return Flags.HasResolvedImports;
263+
}
264+
void setHasResolvedImports() {
265+
Flags.HasResolvedImports = true;
266+
}
267+
260268
ResilienceStrategy getResilienceStrategy() const {
261269
return ResilienceStrategy(Flags.ResilienceStrategy);
262270
}

include/swift/Serialization/Validation.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,10 @@ enum class Status {
4343
/// The module file is an overlay for a Clang module, which can't be found.
4444
MissingShadowedModule,
4545

46+
/// The module file depends on a module that is still being loaded, i.e.
47+
/// there is a circular dependency.
48+
CircularDependency,
49+
4650
/// The module file depends on a bridging header that can't be loaded.
4751
FailedToLoadBridgingHeader,
4852

lib/AST/ASTContext.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -428,6 +428,7 @@ ConstraintCheckerArenaRAII::~ConstraintCheckerArenaRAII() {
428428
static ModuleDecl *createBuiltinModule(ASTContext &ctx) {
429429
auto M = ModuleDecl::create(ctx.getIdentifier(BUILTIN_NAME), ctx);
430430
M->addFile(*new (ctx) BuiltinUnit(*M));
431+
M->setHasResolvedImports();
431432
return M;
432433
}
433434

lib/AST/Module.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -354,7 +354,7 @@ void SourceLookupCache::invalidate() {
354354
ModuleDecl::ModuleDecl(Identifier name, ASTContext &ctx)
355355
: DeclContext(DeclContextKind::Module, nullptr),
356356
TypeDecl(DeclKind::Module, &ctx, name, SourceLoc(), { }),
357-
Flags({0, 0, 0}) {
357+
Flags() {
358358
ctx.addDestructorCleanup(*this);
359359
setImplicit();
360360
setInterfaceType(ModuleType::get(this));

lib/ClangImporter/ClangImporter.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1086,6 +1086,7 @@ ClangImporter::create(ASTContext &ctx,
10861086
importer->Impl.ImportedHeaderUnit =
10871087
new (ctx) ClangModuleUnit(*importedHeaderModule, importer->Impl, nullptr);
10881088
importedHeaderModule->addFile(*importer->Impl.ImportedHeaderUnit);
1089+
importedHeaderModule->setHasResolvedImports();
10891090

10901091
importer->Impl.IsReadingBridgingPCH = false;
10911092

@@ -1591,6 +1592,7 @@ ModuleDecl *ClangImporter::Implementation::finishLoadingClangModule(
15911592
result = ModuleDecl::create(name, SwiftContext);
15921593
// Silence error messages about testably importing a Clang module.
15931594
result->setTestingEnabled();
1595+
result->setHasResolvedImports();
15941596

15951597
wrapperUnit =
15961598
new (SwiftContext) ClangModuleUnit(*result, *this, clangModule);
@@ -1735,6 +1737,7 @@ ClangModuleUnit *ClangImporter::Implementation::getWrapperForModule(
17351737
auto wrapper = ModuleDecl::create(name, SwiftContext);
17361738
// Silence error messages about testably importing a Clang module.
17371739
wrapper->setTestingEnabled();
1740+
wrapper->setHasResolvedImports();
17381741

17391742
auto file = new (SwiftContext) ClangModuleUnit(*wrapper, *this,
17401743
underlying);

lib/Frontend/Frontend.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -614,6 +614,14 @@ void CompilerInstance::parseAndCheckTypes(
614614
TypeCheckOptions);
615615
}
616616

617+
assert(llvm::all_of(MainModule->getFiles(), [](const FileUnit *File) -> bool {
618+
auto *SF = dyn_cast<SourceFile>(File);
619+
if (!SF)
620+
return true;
621+
return SF->ASTStage >= SourceFile::NameBound;
622+
}) && "some files have not yet had their imports resolved");
623+
MainModule->setHasResolvedImports();
624+
617625
const auto &options = Invocation.getFrontendOptions();
618626
forEachFileToTypeCheck([&](SourceFile &SF) {
619627
performTypeChecking(SF, PersistentState.getTopLevelContext(),

lib/Sema/SourceLoader.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,7 @@ ModuleDecl *SourceLoader::loadModule(SourceLoc importLoc,
155155
else
156156
performTypeChecking(*importFile, persistentState.getTopLevelContext(),
157157
None);
158+
importMod->setHasResolvedImports();
158159
return importMod;
159160
}
160161

lib/Serialization/ModuleFile.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1439,6 +1439,13 @@ Status ModuleFile::associateWithFileContext(FileUnit *file,
14391439
dependency.Import = {ctx.AllocateCopy(llvm::makeArrayRef(accessPathElem)),
14401440
module};
14411441
}
1442+
1443+
if (!module->hasResolvedImports()) {
1444+
// Notice that we check this condition /after/ recording the module that
1445+
// caused the problem. Clients need to be able to track down what the
1446+
// cycle was.
1447+
return error(Status::CircularDependency);
1448+
}
14421449
}
14431450

14441451
if (missingDependency) {

lib/Serialization/SerializedModuleLoader.cpp

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
#include "swift/Strings.h"
1616
#include "swift/AST/ASTContext.h"
1717
#include "swift/AST/DiagnosticsSema.h"
18+
#include "swift/Basic/Defer.h"
1819
#include "swift/Basic/STLExtras.h"
1920
#include "swift/Basic/SourceManager.h"
2021
#include "swift/Basic/Version.h"
@@ -316,6 +317,25 @@ FileUnit *SerializedModuleLoader::loadAST(
316317
break;
317318
}
318319

320+
case serialization::Status::CircularDependency: {
321+
auto circularDependencyIter =
322+
llvm::find_if(loadedModuleFile->getDependencies(),
323+
[](const ModuleFile::Dependency &next) {
324+
return !next.Import.second->hasResolvedImports();
325+
});
326+
assert(circularDependencyIter != loadedModuleFile->getDependencies().end()
327+
&& "circular dependency reported, but no module with unresolved "
328+
"imports found");
329+
330+
// FIXME: We should include the path of the circularity as well, but that's
331+
// hard because we're discovering this /while/ resolving imports, which
332+
// means the problematic modules haven't been recorded yet.
333+
Ctx.Diags.diagnose(*diagLoc, diag::serialization_circular_dependency,
334+
circularDependencyIter->getPrettyPrintedPath(),
335+
M.getName());
336+
break;
337+
}
338+
319339
case serialization::Status::MissingShadowedModule: {
320340
Ctx.Diags.diagnose(*diagLoc, diag::serialization_missing_shadowed_module,
321341
M.getName());
@@ -434,6 +454,7 @@ ModuleDecl *SerializedModuleLoader::loadModule(SourceLoc importLoc,
434454

435455
auto M = ModuleDecl::create(moduleID.first, Ctx);
436456
Ctx.LoadedModules[moduleID.first] = M;
457+
SWIFT_DEFER { M->setHasResolvedImports(); };
437458

438459
if (!loadAST(*M, moduleID.second, std::move(moduleInputBuffer),
439460
std::move(moduleDocInputBuffer), isFramework)) {
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// Circularities involving the module currently being type-checked.
2+
3+
// RUN: %empty-directory(%t)
4+
// RUN: %target-swift-frontend -emit-module %s -module-name A -o %t
5+
// RUN: %target-swift-frontend -emit-module %s -module-name B -D IMPORT_A -I %t -o %t
6+
// RUN: not %target-swift-frontend -typecheck %s -module-name A -D IMPORT_B -I %t 2>&1 | %FileCheck -check-prefix CHECK-ABA %s
7+
8+
// RUN: %target-swift-frontend -emit-module %s -module-name C -D IMPORT_B -I %t -o %t
9+
// RUN: not %target-swift-frontend -typecheck %s -module-name A -D IMPORT_C -I %t 2>&1 | %FileCheck -check-prefix CHECK-ABCA %s
10+
11+
// Circularities not involving the module currently being type-checked.
12+
// RUN: %empty-directory(%t/plain)
13+
// RUN: %target-swift-frontend -emit-module %s -module-name A -o %t/plain
14+
// RUN: %target-swift-frontend -emit-module %s -module-name B -o %t/plain
15+
// RUN: %empty-directory(%t/cycle)
16+
// RUN: %target-swift-frontend -emit-module %s -module-name A -D IMPORT_B -o %t/cycle -I %t/plain
17+
// RUN: %target-swift-frontend -emit-module %s -module-name B -D IMPORT_A -o %t/cycle -I %t/plain
18+
// RUN: not %target-swift-frontend -typecheck %s -module-name C -D IMPORT_A -I %t/cycle 2>&1 | %FileCheck -check-prefix CHECK-ABA-BUILT %s
19+
20+
21+
#if IMPORT_A
22+
import A
23+
// CHECK-ABA-BUILT: <unknown>:0: error: circular dependency between modules 'A' and 'B'
24+
#endif
25+
26+
#if IMPORT_B
27+
import B
28+
// CHECK-ABA: :[[@LINE-1]]:8: error: circular dependency between modules 'A' and 'B'
29+
#endif
30+
31+
#if IMPORT_C
32+
import C
33+
// CHECK-ABCA: <unknown>:0: error: circular dependency between modules 'A' and 'B'
34+
#endif

test/SourceKit/Indexing/Inputs/cycle-depend/A.response

Lines changed: 0 additions & 105 deletions
This file was deleted.

test/SourceKit/Indexing/Inputs/cycle-depend/A.swift

Lines changed: 0 additions & 5 deletions
This file was deleted.

test/SourceKit/Indexing/Inputs/cycle-depend/B.swift

Lines changed: 0 additions & 6 deletions
This file was deleted.

test/SourceKit/Indexing/index_module_cycle.swift

Lines changed: 0 additions & 6 deletions
This file was deleted.
Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
11
// RUN: %empty-directory(%t)
2-
// RUN: %swift -emit-module -o %t %S/Inputs/cycle-depend/A.swift -I %S/Inputs/cycle-depend -enable-source-import
2+
// RUN: %target-swift-frontend -emit-module -o %t %S/../../Inputs/empty.swift
3+
// RUN: %target-swift-frontend -emit-module -o %t %s -I %t -module-name A
4+
// RUN: rm %t/empty.swiftmodule
35

46
// RUN: not %sourcekitd-test -req=index %t/A.swiftmodule -- %t/A.swiftmodule 2>&1 | %FileCheck %s
57

8+
import empty
9+
610
// FIXME: Report the reason we couldn't load a module.
711
// CHECK-DISABLED: error response (Request Failed): missing module dependency
812
// CHECK: error response (Request Failed): failed to load module

tools/SourceKit/lib/SwiftLang/SwiftIndexing.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,8 @@ static void indexModule(llvm::MemoryBuffer *Input,
184184
IdxConsumer.failed("failed to load module");
185185
return;
186186
}
187+
188+
Mod->setHasResolvedImports();
187189
}
188190

189191
// Setup a typechecker for protocol conformance resolving.

0 commit comments

Comments
 (0)