Skip to content

Commit 4d4ac02

Browse files
authored
Merge pull request #38383 from beccadax/crisis-on-infinite-names-5.5
[5.5] Don’t crash from circular swift_name attributes
2 parents f2bbcd5 + 0d796a2 commit 4d4ac02

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
@@ -9160,7 +9160,7 @@ ClangImporter::Implementation::importMirroredDecl(const clang::NamedDecl *decl,
91609160
}
91619161

91629162
DeclContext *ClangImporter::Implementation::importDeclContextImpl(
9163-
const clang::DeclContext *dc) {
9163+
const clang::Decl *ImportingDecl, const clang::DeclContext *dc) {
91649164
// Every declaration should come from a module, so we should not see the
91659165
// TranslationUnit DeclContext here.
91669166
assert(!dc->isTranslationUnit());
@@ -9173,7 +9173,8 @@ DeclContext *ClangImporter::Implementation::importDeclContextImpl(
91739173
// leads to the first category of the given name. We'd like to keep these
91749174
// categories separated.
91759175
auto useCanonical = !isa<clang::ObjCCategoryDecl>(decl);
9176-
auto swiftDecl = importDecl(decl, CurrentVersion, useCanonical);
9176+
auto swiftDecl = importDeclForDeclContext(ImportingDecl, decl->getName(),
9177+
decl, CurrentVersion, useCanonical);
91779178
if (!swiftDecl)
91789179
return nullptr;
91799180

@@ -9226,6 +9227,71 @@ GenericSignature ClangImporter::Implementation::buildGenericSignature(
92269227
GenericSignature());
92279228
}
92289229

9230+
Decl *
9231+
ClangImporter::Implementation::importDeclForDeclContext(
9232+
const clang::Decl *importingDecl,
9233+
StringRef writtenName,
9234+
const clang::NamedDecl *contextDecl,
9235+
Version version,
9236+
bool useCanonicalDecl)
9237+
{
9238+
auto key = std::make_tuple(importingDecl, writtenName, contextDecl, version,
9239+
useCanonicalDecl);
9240+
auto iter = find(llvm::reverse(contextDeclsBeingImported), key);
9241+
9242+
// No cycle? Remember that we're importing this, then import normally.
9243+
if (iter == contextDeclsBeingImported.rend()) {
9244+
contextDeclsBeingImported.push_back(key);
9245+
auto imported = importDecl(contextDecl, version, useCanonicalDecl);
9246+
contextDeclsBeingImported.pop_back();
9247+
return imported;
9248+
}
9249+
9250+
// There's a cycle. Is the declaration imported enough to break the cycle
9251+
// gracefully? If so, we'll have it in the decl cache.
9252+
if (auto cached = importDeclCached(contextDecl, version, useCanonicalDecl))
9253+
return cached;
9254+
9255+
// Can't break it? Warn and return nullptr, which is at least better than
9256+
// stack overflow by recursion.
9257+
9258+
// Avoid emitting warnings repeatedly.
9259+
if (!contextDeclsWarnedAbout.insert(contextDecl).second)
9260+
return nullptr;
9261+
9262+
auto convertLoc = [&](clang::SourceLocation clangLoc) {
9263+
return getBufferImporterForDiagnostics()
9264+
.resolveSourceLocation(getClangASTContext().getSourceManager(),
9265+
clangLoc);
9266+
};
9267+
9268+
auto getDeclName = [](const clang::Decl *D) -> StringRef {
9269+
if (auto ND = dyn_cast<clang::NamedDecl>(D))
9270+
return ND->getName();
9271+
return "<anonymous>";
9272+
};
9273+
9274+
SourceLoc loc = convertLoc(importingDecl->getLocation());
9275+
diagnose(loc, diag::swift_name_circular_context_import,
9276+
writtenName, getDeclName(importingDecl));
9277+
9278+
// Diagnose other decls involved in the cycle.
9279+
for (auto entry : make_range(contextDeclsBeingImported.rbegin(), iter)) {
9280+
auto otherDecl = std::get<0>(entry);
9281+
auto otherWrittenName = std::get<1>(entry);
9282+
diagnose(convertLoc(otherDecl->getLocation()),
9283+
diag::swift_name_circular_context_import_other,
9284+
otherWrittenName, getDeclName(otherDecl));
9285+
}
9286+
9287+
if (auto *parentModule = contextDecl->getOwningModule()) {
9288+
diagnose(loc, diag::unresolvable_clang_decl_is_a_framework_bug,
9289+
parentModule->getFullModuleName());
9290+
}
9291+
9292+
return nullptr;
9293+
}
9294+
92299295
DeclContext *
92309296
ClangImporter::Implementation::importDeclContextOf(
92319297
const clang::Decl *decl,
@@ -9243,13 +9309,15 @@ ClangImporter::Implementation::importDeclContextOf(
92439309
}
92449310

92459311
// Import the DeclContext.
9246-
importedDC = importDeclContextImpl(dc);
9312+
importedDC = importDeclContextImpl(decl, dc);
92479313
break;
92489314
}
92499315

92509316
case EffectiveClangContext::TypedefContext: {
92519317
// Import the typedef-name as a declaration.
9252-
auto importedDecl = importDecl(context.getTypedefName(), CurrentVersion);
9318+
auto importedDecl = importDeclForDeclContext(
9319+
decl, context.getTypedefName()->getName(), context.getTypedefName(),
9320+
CurrentVersion);
92539321
if (!importedDecl) return nullptr;
92549322

92559323
// Dig out the imported DeclContext.
@@ -9267,17 +9335,19 @@ ClangImporter::Implementation::importDeclContextOf(
92679335
if (auto clangDecl
92689336
= lookupTable->resolveContext(context.getUnresolvedName())) {
92699337
// Import the Clang declaration.
9270-
auto decl = importDecl(clangDecl, CurrentVersion);
9271-
if (!decl) return nullptr;
9338+
auto swiftDecl = importDeclForDeclContext(decl,
9339+
context.getUnresolvedName(),
9340+
clangDecl, CurrentVersion);
9341+
if (!swiftDecl) return nullptr;
92729342

92739343
// Look through typealiases.
9274-
if (auto typealias = dyn_cast<TypeAliasDecl>(decl))
9344+
if (auto typealias = dyn_cast<TypeAliasDecl>(swiftDecl))
92759345
importedDC = typealias->getDeclaredInterfaceType()->getAnyNominal();
92769346
else // Map to a nominal type declaration.
9277-
importedDC = dyn_cast<NominalTypeDecl>(decl);
9278-
break;
9347+
importedDC = dyn_cast<NominalTypeDecl>(swiftDecl);
92799348
}
92809349
}
9350+
break;
92819351
}
92829352
}
92839353

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)