Skip to content

Commit c2f904c

Browse files
authored
Merge pull request #81188 from j-hui/fix-recursive-inheritance
[cxx-interop] Avoid unchecked recursion when importing C++ classes with circular inheritance
2 parents 2a6a621 + 1f2107f commit c2f904c

File tree

6 files changed

+105
-1
lines changed

6 files changed

+105
-1
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

+20
Original file line numberDiff line numberDiff line change
@@ -6364,6 +6364,11 @@ TinyPtrVector<ValueDecl *> ClangRecordMemberLookup::evaluate(
63646364
continue;
63656365

63666366
auto *baseRecord = baseType->getAs<clang::RecordType>()->getDecl();
6367+
6368+
if (isSymbolicCircularBase(cxxRecord, baseRecord))
6369+
// Skip circular bases to avoid unbounded recursion
6370+
continue;
6371+
63676372
if (auto import = clangModuleLoader->importDeclDirectly(baseRecord)) {
63686373
// If we are looking up the base class, go no further. We will have
63696374
// already found it during the other lookup.
@@ -8774,3 +8779,18 @@ bool importer::isClangNamespace(const DeclContext *dc) {
87748779

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

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,32 @@
1+
#ifndef CIRCULAR_INHERITANCE_H
2+
#define CIRCULAR_INHERITANCE_H
3+
4+
struct DinoEgg {
5+
void dinoEgg(void) const {}
6+
};
7+
8+
template <typename Chicken>
9+
struct Egg;
10+
11+
template <>
12+
struct Egg<void> : DinoEgg {
13+
Egg() {}
14+
void voidEgg(void) const {}
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+
struct NewEgg : Egg<int> {
28+
NewEgg() : Egg<int>(555) {}
29+
void newEgg() const {}
30+
};
31+
32+
#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,27 @@
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+
voidEgg.dinoEgg()
11+
voidEgg.voidEgg()
12+
13+
let boolEgg = BoolEgg(false)
14+
boolEgg.dinoEgg()
15+
boolEgg.voidEgg()
16+
boolEgg.chickenEgg(true)
17+
18+
let eggEgg = EggEgg(VoidEgg())
19+
eggEgg.dinoEgg()
20+
eggEgg.voidEgg()
21+
eggEgg.chickenEgg(VoidEgg())
22+
23+
let newEgg = NewEgg()
24+
newEgg.dinoEgg()
25+
newEgg.voidEgg()
26+
newEgg.chickenEgg(555)
27+
newEgg.newEgg()

0 commit comments

Comments
 (0)