Skip to content

Commit 8ac798b

Browse files
committed
[cxx-interop] Avoid infinite recursion for classes with symbolic circular inheritance
It is possible for a C++ class template to inherit from a specialization of itself. Normally, these are imported to Swift as separate (unrelated) types, but when symbolic import is enabled, unspecialized templates are imported in place of their specializations, leading to circularly inheriting classes to seemingly inherit from themselves. This patch adds a check to guard against the most common case of circular inheritance, when a class template directly inherits from itself. This pattern appears in a recent version of libc++, necessitating this patch. However, the solution here is imperfect as it does not handle more complex/contrived circular inheritance patterns. This patch also adds a test case exercising this pattern. The -index-store-path flag causes swift-frontend to index the C++ module with symbolic import enabled, without the fix in this patch, that test triggers an assertion failure due to the circular reference (and can infinitely recurse in the StorageVisitor when assertions are disabled). rdar://148026461
1 parent f7bf701 commit 8ac798b

File tree

6 files changed

+98
-3
lines changed

6 files changed

+98
-3
lines changed

include/swift/ClangImporter/ClangImporter.h

+16
Original file line numberDiff line numberDiff line change
@@ -786,6 +786,22 @@ bool declIsCxxOnly(const Decl *decl);
786786

787787
/// Is this DeclContext an `enum` that represents a C++ namespace?
788788
bool isClangNamespace(const DeclContext *dc);
789+
790+
/// For some \a templatedClass that inherits from \a base, whether they are
791+
/// derived from the same class template.
792+
///
793+
/// This kind of circular inheritance can happen when a templated class inherits
794+
/// from a specialization of itself, e.g.:
795+
///
796+
/// template <typename T> class C;
797+
/// template <> class C<void> { /* ... */ };
798+
/// template <typename T> class C<T> : C<void> { /* ... */ };
799+
///
800+
/// Checking for this kind of scenario is necessary for avoiding infinite
801+
/// recursion during symbolic imports (importSymbolicCXXDecls), where
802+
/// specialized class templates are instead imported as unspecialized.
803+
bool isSymbolicCircularBase(const clang::CXXRecordDecl *templatedClass,
804+
const clang::RecordDecl *base);
789805
} // namespace importer
790806

791807
struct ClangInvocationFileMapping {

lib/ClangImporter/ClangImporter.cpp

+24-2
Original file line numberDiff line numberDiff line change
@@ -6258,11 +6258,12 @@ TinyPtrVector<ValueDecl *> ClangRecordMemberLookup::evaluate(
62586258
// to import all non-public members by default; if that is disabled, we only
62596259
// import non-public members annotated with SWIFT_PRIVATE_FILEID (since those
62606260
// are the only classes that need non-public members.)
6261-
auto *cxxRecordDecl =
6261+
auto *inheritingCxxRec =
62626262
dyn_cast<clang::CXXRecordDecl>(inheritingDecl->getClangDecl());
62636263
auto skipIfNonPublic =
62646264
!ctx.LangOpts.hasFeature(Feature::ImportNonPublicCxxMembers) &&
6265-
cxxRecordDecl && importer::getPrivateFileIDAttrs(cxxRecordDecl).empty();
6265+
inheritingCxxRec &&
6266+
importer::getPrivateFileIDAttrs(inheritingCxxRec).empty();
62666267

62676268
auto directResults = evaluateOrDefault(
62686269
ctx.evaluator,
@@ -6364,6 +6365,12 @@ TinyPtrVector<ValueDecl *> ClangRecordMemberLookup::evaluate(
63646365
continue;
63656366

63666367
auto *baseRecord = baseType->getAs<clang::RecordType>()->getDecl();
6368+
6369+
if (inheritingCxxRec &&
6370+
isSymbolicCircularBase(inheritingCxxRec, baseRecord))
6371+
// Skip circular bases to avoid unbounded recursion
6372+
continue;
6373+
63676374
if (auto import = clangModuleLoader->importDeclDirectly(baseRecord)) {
63686375
// If we are looking up the base class, go no further. We will have
63696376
// already found it during the other lookup.
@@ -8774,3 +8781,18 @@ bool importer::isClangNamespace(const DeclContext *dc) {
87748781

87758782
return false;
87768783
}
8784+
8785+
bool importer::isSymbolicCircularBase(const clang::CXXRecordDecl *symbolicClass,
8786+
const clang::RecordDecl *base) {
8787+
auto *classTemplate = symbolicClass->getDescribedClassTemplate();
8788+
if (!classTemplate)
8789+
return false;
8790+
8791+
auto *specializedBase =
8792+
dyn_cast<clang::ClassTemplateSpecializationDecl>(base);
8793+
if (!specializedBase)
8794+
return false;
8795+
8796+
return classTemplate->getCanonicalDecl() ==
8797+
specializedBase->getSpecializedTemplate()->getCanonicalDecl();
8798+
}

lib/Sema/TypeCheckInvertible.cpp

+5-1
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,12 @@
1717
//===----------------------------------------------------------------------===//
1818

1919
#include "TypeCheckInvertible.h"
20+
#include "TypeChecker.h"
2021
#include "swift/AST/ASTContext.h"
2122
#include "swift/AST/ClangModuleLoader.h"
2223
#include "swift/AST/GenericEnvironment.h"
2324
#include "swift/Basic/Assertions.h"
24-
#include "TypeChecker.h"
25+
#include "swift/ClangImporter/ClangImporter.h"
2526

2627
using namespace swift;
2728

@@ -316,6 +317,9 @@ bool StorageVisitor::visit(NominalTypeDecl *nominal, DeclContext *dc) {
316317
dyn_cast_or_null<clang::CXXRecordDecl>(nominal->getClangDecl())) {
317318
for (auto cxxBase : cxxRecordDecl->bases()) {
318319
if (auto cxxBaseDecl = cxxBase.getType()->getAsCXXRecordDecl()) {
320+
if (importer::isSymbolicCircularBase(cxxRecordDecl, cxxBaseDecl))
321+
// Skip circular bases to avoid unbounded recursion
322+
continue;
319323
auto clangModuleLoader = dc->getASTContext().getClangModuleLoader();
320324
auto importedDecl =
321325
clangModuleLoader->importDeclDirectly(cxxBaseDecl);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
#ifndef CIRCULAR_INHERITANCE_H
2+
#define CIRCULAR_INHERITANCE_H
3+
4+
struct DinoEgg {
5+
bool dinoEgg(void) const { return true; }
6+
};
7+
8+
template <typename Chicken>
9+
struct Egg;
10+
11+
template <>
12+
struct Egg<void> : DinoEgg {
13+
Egg() {}
14+
bool voidEgg(void) const { return false; }
15+
};
16+
17+
template <typename Chicken>
18+
struct Egg : Egg<void> {
19+
Egg(Chicken _chicken) {}
20+
Chicken chickenEgg(Chicken c) const { return c; }
21+
};
22+
23+
typedef Egg<void> VoidEgg;
24+
typedef Egg<bool> BoolEgg;
25+
typedef Egg<Egg<void>> EggEgg;
26+
27+
#endif // !CIRCULAR_INHERITANCE_H

test/Interop/Cxx/class/inheritance/Inputs/module.modulemap

+5
Original file line numberDiff line numberDiff line change
@@ -48,3 +48,8 @@ module InheritedLookup {
4848
header "inherited-lookup.h"
4949
requires cplusplus
5050
}
51+
52+
module CircularInheritance {
53+
header "circular-inheritance.h"
54+
requires cplusplus
55+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// RUN: %empty-directory(%t/index)
2+
// RUN: %target-typecheck-verify-swift -verify-ignore-unknown -I %S/Inputs -cxx-interoperability-mode=default -index-store-path %t/index
3+
//
4+
// Note that we specify an -index-store-path to ensure we also test importing symbolic C++ decls,
5+
// to exercise code that handles importing unspecialized class templates.
6+
7+
import CircularInheritance
8+
9+
let voidEgg = VoidEgg()
10+
let _: Bool = voidEgg.dinoEgg()
11+
let _: Bool = voidEgg.voidEgg()
12+
13+
let boolEgg = BoolEgg(false)
14+
let _: Bool = boolEgg.dinoEgg()
15+
let _: Bool = boolEgg.voidEgg()
16+
let _: Bool = boolEgg.chickenEgg(true)
17+
18+
let eggEgg = EggEgg(VoidEgg())
19+
let _: Bool = eggEgg.dinoEgg()
20+
let _: Bool = eggEgg.voidEgg()
21+
let _: VoidEgg = eggEgg.chickenEgg(VoidEgg())

0 commit comments

Comments
 (0)