Skip to content

Commit 528764c

Browse files
authored
Merge pull request #37940 from beccadax/crisis-on-infinite-names
Don’t crash from circular swift_name attributes
2 parents dc11047 + 05b9aec commit 528764c

File tree

5 files changed

+148
-12
lines changed

5 files changed

+148
-12
lines changed

include/swift/AST/DiagnosticsClangImporter.def

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,13 @@ WARNING(inconsistent_swift_name,none,
6767
(bool, StringRef, StringRef, DeclName, StringRef, DeclName,
6868
StringRef))
6969

70+
WARNING(swift_name_circular_context_import,none,
71+
"cycle detected while resolving '%0' in swift_name attribute for '%1'",
72+
(StringRef, StringRef))
73+
NOTE(swift_name_circular_context_import_other,none,
74+
"while resolving '%0' in swift_name attribute for '%1'",
75+
(StringRef, StringRef))
76+
7077
WARNING(unresolvable_clang_decl,none,
7178
"imported declaration '%0' could not be mapped to '%1'",
7279
(StringRef, StringRef))

lib/ClangImporter/ImportDecl.cpp

Lines changed: 79 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9204,7 +9204,7 @@ ClangImporter::Implementation::importMirroredDecl(const clang::NamedDecl *decl,
92049204
}
92059205

92069206
DeclContext *ClangImporter::Implementation::importDeclContextImpl(
9207-
const clang::DeclContext *dc) {
9207+
const clang::Decl *ImportingDecl, const clang::DeclContext *dc) {
92089208
// Every declaration should come from a module, so we should not see the
92099209
// TranslationUnit DeclContext here.
92109210
assert(!dc->isTranslationUnit());
@@ -9217,7 +9217,8 @@ DeclContext *ClangImporter::Implementation::importDeclContextImpl(
92179217
// leads to the first category of the given name. We'd like to keep these
92189218
// categories separated.
92199219
auto useCanonical = !isa<clang::ObjCCategoryDecl>(decl);
9220-
auto swiftDecl = importDecl(decl, CurrentVersion, useCanonical);
9220+
auto swiftDecl = importDeclForDeclContext(ImportingDecl, decl->getName(),
9221+
decl, CurrentVersion, useCanonical);
92219222
if (!swiftDecl)
92229223
return nullptr;
92239224

@@ -9270,6 +9271,71 @@ GenericSignature ClangImporter::Implementation::buildGenericSignature(
92709271
GenericSignature());
92719272
}
92729273

9274+
Decl *
9275+
ClangImporter::Implementation::importDeclForDeclContext(
9276+
const clang::Decl *importingDecl,
9277+
StringRef writtenName,
9278+
const clang::NamedDecl *contextDecl,
9279+
Version version,
9280+
bool useCanonicalDecl)
9281+
{
9282+
auto key = std::make_tuple(importingDecl, writtenName, contextDecl, version,
9283+
useCanonicalDecl);
9284+
auto iter = find(llvm::reverse(contextDeclsBeingImported), key);
9285+
9286+
// No cycle? Remember that we're importing this, then import normally.
9287+
if (iter == contextDeclsBeingImported.rend()) {
9288+
contextDeclsBeingImported.push_back(key);
9289+
auto imported = importDecl(contextDecl, version, useCanonicalDecl);
9290+
contextDeclsBeingImported.pop_back();
9291+
return imported;
9292+
}
9293+
9294+
// There's a cycle. Is the declaration imported enough to break the cycle
9295+
// gracefully? If so, we'll have it in the decl cache.
9296+
if (auto cached = importDeclCached(contextDecl, version, useCanonicalDecl))
9297+
return cached;
9298+
9299+
// Can't break it? Warn and return nullptr, which is at least better than
9300+
// stack overflow by recursion.
9301+
9302+
// Avoid emitting warnings repeatedly.
9303+
if (!contextDeclsWarnedAbout.insert(contextDecl).second)
9304+
return nullptr;
9305+
9306+
auto convertLoc = [&](clang::SourceLocation clangLoc) {
9307+
return getBufferImporterForDiagnostics()
9308+
.resolveSourceLocation(getClangASTContext().getSourceManager(),
9309+
clangLoc);
9310+
};
9311+
9312+
auto getDeclName = [](const clang::Decl *D) -> StringRef {
9313+
if (auto ND = dyn_cast<clang::NamedDecl>(D))
9314+
return ND->getName();
9315+
return "<anonymous>";
9316+
};
9317+
9318+
SourceLoc loc = convertLoc(importingDecl->getLocation());
9319+
diagnose(loc, diag::swift_name_circular_context_import,
9320+
writtenName, getDeclName(importingDecl));
9321+
9322+
// Diagnose other decls involved in the cycle.
9323+
for (auto entry : make_range(contextDeclsBeingImported.rbegin(), iter)) {
9324+
auto otherDecl = std::get<0>(entry);
9325+
auto otherWrittenName = std::get<1>(entry);
9326+
diagnose(convertLoc(otherDecl->getLocation()),
9327+
diag::swift_name_circular_context_import_other,
9328+
otherWrittenName, getDeclName(otherDecl));
9329+
}
9330+
9331+
if (auto *parentModule = contextDecl->getOwningModule()) {
9332+
diagnose(loc, diag::unresolvable_clang_decl_is_a_framework_bug,
9333+
parentModule->getFullModuleName());
9334+
}
9335+
9336+
return nullptr;
9337+
}
9338+
92739339
DeclContext *
92749340
ClangImporter::Implementation::importDeclContextOf(
92759341
const clang::Decl *decl,
@@ -9287,13 +9353,15 @@ ClangImporter::Implementation::importDeclContextOf(
92879353
}
92889354

92899355
// Import the DeclContext.
9290-
importedDC = importDeclContextImpl(dc);
9356+
importedDC = importDeclContextImpl(decl, dc);
92919357
break;
92929358
}
92939359

92949360
case EffectiveClangContext::TypedefContext: {
92959361
// Import the typedef-name as a declaration.
9296-
auto importedDecl = importDecl(context.getTypedefName(), CurrentVersion);
9362+
auto importedDecl = importDeclForDeclContext(
9363+
decl, context.getTypedefName()->getName(), context.getTypedefName(),
9364+
CurrentVersion);
92979365
if (!importedDecl) return nullptr;
92989366

92999367
// Dig out the imported DeclContext.
@@ -9311,17 +9379,19 @@ ClangImporter::Implementation::importDeclContextOf(
93119379
if (auto clangDecl
93129380
= lookupTable->resolveContext(context.getUnresolvedName())) {
93139381
// Import the Clang declaration.
9314-
auto decl = importDecl(clangDecl, CurrentVersion);
9315-
if (!decl) return nullptr;
9382+
auto swiftDecl = importDeclForDeclContext(decl,
9383+
context.getUnresolvedName(),
9384+
clangDecl, CurrentVersion);
9385+
if (!swiftDecl) return nullptr;
93169386

93179387
// Look through typealiases.
9318-
if (auto typealias = dyn_cast<TypeAliasDecl>(decl))
9388+
if (auto typealias = dyn_cast<TypeAliasDecl>(swiftDecl))
93199389
importedDC = typealias->getDeclaredInterfaceType()->getAnyNominal();
93209390
else // Map to a nominal type declaration.
9321-
importedDC = dyn_cast<NominalTypeDecl>(decl);
9322-
break;
9391+
importedDC = dyn_cast<NominalTypeDecl>(swiftDecl);
93239392
}
93249393
}
9394+
break;
93259395
}
93269396
}
93279397

lib/ClangImporter/ImporterImpl.h

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -913,8 +913,34 @@ class LLVM_LIBRARY_VISIBILITY ClangImporter::Implementation
913913
///
914914
/// \returns The imported declaration context, or null if it could not
915915
/// be converted.
916-
DeclContext *importDeclContextImpl(const clang::DeclContext *dc);
916+
DeclContext *importDeclContextImpl(const clang::Decl *ImportingDecl,
917+
const clang::DeclContext *dc);
917918

919+
private:
920+
/// Declarations currently being imported by \c importDeclForDeclContext().
921+
/// Used to break cycles when a swift_name attribute is circular in a way that
922+
/// can't be resolved, or there is some other cycle through
923+
/// \c importDeclContextOf().
924+
llvm::SmallVector<std::tuple<const clang::Decl *, StringRef,
925+
const clang::NamedDecl *, Version, bool>, 8>
926+
contextDeclsBeingImported;
927+
928+
/// Records which contexts \c importDeclForDeclContext() has already warned
929+
/// were unimportable.
930+
llvm::SmallPtrSet<const clang::NamedDecl *, 4> contextDeclsWarnedAbout;
931+
932+
/// Exactly equivalent to \c importDecl(), except with additional
933+
/// cycle-breaking code.
934+
///
935+
/// \param writtenName The name that should be used for the declaration
936+
/// in cycle diagnostics.
937+
Decl *importDeclForDeclContext(const clang::Decl *ImportingDecl,
938+
StringRef writtenName,
939+
const clang::NamedDecl *ClangDecl,
940+
Version version,
941+
bool UseCanonicalDecl = true);
942+
943+
public:
918944
/// Import the declaration context of a given Clang declaration into
919945
/// Swift.
920946
///

test/ClangImporter/Inputs/custom-modules/ObjCIRExtras.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,4 +72,11 @@ int global_int SWIFT_NAME(GlobalInt);
7272
@compatibility_alias SwiftGenericNameAlias SwiftGenericNameTest;
7373
@compatibility_alias SwiftConstrGenericNameAlias SwiftConstrGenericNameTest;
7474

75+
SWIFT_NAME(CircularName.Inner) @interface CircularName : NSObject @end
76+
77+
SWIFT_NAME(MutuallyCircularNameB.Inner) @interface MutuallyCircularNameA : NSObject @end
78+
SWIFT_NAME(MutuallyCircularNameA.Inner) @interface MutuallyCircularNameB : NSObject @end
79+
80+
void circularFriends(CircularName*, MutuallyCircularNameA*);
81+
7582
#pragma clang assume_nonnull end

test/ClangImporter/attr-swift_name.swift

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// RUN: %empty-directory(%t.mcp)
2-
// RUN: %target-swift-frontend(mock-sdk: %clang-importer-sdk) -I %S/Inputs/custom-modules -Xcc -w -typecheck %s -module-cache-path %t.mcp -disable-named-lazy-member-loading 2>&1 | %FileCheck %s
2+
// RUN: not %target-swift-frontend(mock-sdk: %clang-importer-sdk) -I %S/Inputs/custom-modules -Xcc -w -typecheck %s -module-cache-path %t.mcp -disable-named-lazy-member-loading 2>&1 | %FileCheck %s
33

44
// REQUIRES: objc_interop
55

@@ -15,7 +15,9 @@ func test(_ i: Int) {
1515
_ = t.renamedSomeProp
1616
_ = type(of: t).renamedClassProp
1717

18-
// We only see these two warnings because Clang can catch the other invalid
18+
_ = circularFriends(_:_:)
19+
20+
// We only see these five warnings because Clang can catch the other invalid
1921
// cases, and marks the attribute as invalid ahead of time.
2022

2123
// CHECK: warning: too few parameters in swift_name attribute (expected 2; got 1)
@@ -29,4 +31,28 @@ func test(_ i: Int) {
2931
// CHECK-NOT: warning:
3032
// CHECK: note: please report this issue to the owners of 'ObjCIRExtras'
3133
// CHECK-NOT: warning:
34+
35+
// CHECK: warning: cycle detected while resolving 'CircularName' in swift_name attribute for 'CircularName'
36+
// CHECK: SWIFT_NAME(CircularName.Inner) @interface CircularName : NSObject @end
37+
// CHECK-NOT: {{warning|note}}:
38+
// CHECK: note: please report this issue to the owners of 'ObjCIRExtras'
39+
// CHECK-NOT: warning:
40+
41+
// CHECK: warning: cycle detected while resolving 'MutuallyCircularNameB' in swift_name attribute for 'MutuallyCircularNameA'
42+
// CHECK: SWIFT_NAME(MutuallyCircularNameB.Inner) @interface MutuallyCircularNameA : NSObject @end
43+
// CHECK-NOT: {{warning|note}}:
44+
// CHECK: note: while resolving 'MutuallyCircularNameA' in swift_name attribute for 'MutuallyCircularNameB'
45+
// CHECK: SWIFT_NAME(MutuallyCircularNameA.Inner) @interface MutuallyCircularNameB : NSObject @end
46+
// CHECK-NOT: {{warning|note}}:
47+
// CHECK: note: please report this issue to the owners of 'ObjCIRExtras'
48+
// CHECK-NOT: warning:
49+
50+
// CHECK: warning: cycle detected while resolving 'MutuallyCircularNameA' in swift_name attribute for 'MutuallyCircularNameB'
51+
// CHECK: SWIFT_NAME(MutuallyCircularNameA.Inner) @interface MutuallyCircularNameB : NSObject @end
52+
// CHECK-NOT: {{warning|note}}:
53+
// CHECK: note: while resolving 'MutuallyCircularNameB' in swift_name attribute for 'MutuallyCircularNameA'
54+
// CHECK: SWIFT_NAME(MutuallyCircularNameB.Inner) @interface MutuallyCircularNameA : NSObject @end
55+
// CHECK-NOT: {{warning|note}}:
56+
// CHECK: note: please report this issue to the owners of 'ObjCIRExtras'
57+
// CHECK-NOT: warning:
3258
}

0 commit comments

Comments
 (0)