Skip to content

🍒 [6.2] [cxx-interop] Avoid unchecked recursion when importing C++ classes with circular inheritance #81212

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions include/swift/ClangImporter/ClangImporter.h
Original file line number Diff line number Diff line change
Expand Up @@ -786,6 +786,22 @@ bool declIsCxxOnly(const Decl *decl);

/// Is this DeclContext an `enum` that represents a C++ namespace?
bool isClangNamespace(const DeclContext *dc);

/// For some \a templatedClass that inherits from \a base, whether they are
/// derived from the same class template.
///
/// This kind of circular inheritance can happen when a templated class inherits
/// from a specialization of itself, e.g.:
///
/// template <typename T> class C;
/// template <> class C<void> { /* ... */ };
/// template <typename T> class C<T> : C<void> { /* ... */ };
///
/// Checking for this kind of scenario is necessary for avoiding infinite
/// recursion during symbolic imports (importSymbolicCXXDecls), where
/// specialized class templates are instead imported as unspecialized.
bool isSymbolicCircularBase(const clang::CXXRecordDecl *templatedClass,
const clang::RecordDecl *base);
} // namespace importer

struct ClangInvocationFileMapping {
Expand Down
20 changes: 20 additions & 0 deletions lib/ClangImporter/ClangImporter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6388,6 +6388,11 @@ TinyPtrVector<ValueDecl *> ClangRecordMemberLookup::evaluate(
continue;

auto *baseRecord = baseType->getAs<clang::RecordType>()->getDecl();

if (isSymbolicCircularBase(cxxRecord, baseRecord))
// Skip circular bases to avoid unbounded recursion
continue;

if (auto import = clangModuleLoader->importDeclDirectly(baseRecord)) {
// If we are looking up the base class, go no further. We will have
// already found it during the other lookup.
Expand Down Expand Up @@ -8798,3 +8803,18 @@ bool importer::isClangNamespace(const DeclContext *dc) {

return false;
}

bool importer::isSymbolicCircularBase(const clang::CXXRecordDecl *symbolicClass,
const clang::RecordDecl *base) {
auto *classTemplate = symbolicClass->getDescribedClassTemplate();
if (!classTemplate)
return false;

auto *specializedBase =
dyn_cast<clang::ClassTemplateSpecializationDecl>(base);
if (!specializedBase)
return false;

return classTemplate->getCanonicalDecl() ==
specializedBase->getSpecializedTemplate()->getCanonicalDecl();
}
6 changes: 5 additions & 1 deletion lib/Sema/TypeCheckInvertible.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,12 @@
//===----------------------------------------------------------------------===//

#include "TypeCheckInvertible.h"
#include "TypeChecker.h"
#include "swift/AST/ASTContext.h"
#include "swift/AST/ClangModuleLoader.h"
#include "swift/AST/GenericEnvironment.h"
#include "swift/Basic/Assertions.h"
#include "TypeChecker.h"
#include "swift/ClangImporter/ClangImporter.h"

using namespace swift;

Expand Down Expand Up @@ -316,6 +317,9 @@ bool StorageVisitor::visit(NominalTypeDecl *nominal, DeclContext *dc) {
dyn_cast_or_null<clang::CXXRecordDecl>(nominal->getClangDecl())) {
for (auto cxxBase : cxxRecordDecl->bases()) {
if (auto cxxBaseDecl = cxxBase.getType()->getAsCXXRecordDecl()) {
if (importer::isSymbolicCircularBase(cxxRecordDecl, cxxBaseDecl))
// Skip circular bases to avoid unbounded recursion
continue;
auto clangModuleLoader = dc->getASTContext().getClangModuleLoader();
auto importedDecl =
clangModuleLoader->importDeclDirectly(cxxBaseDecl);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
#ifndef CIRCULAR_INHERITANCE_H
#define CIRCULAR_INHERITANCE_H

struct DinoEgg {
void dinoEgg(void) const {}
};

template <typename Chicken>
struct Egg;

template <>
struct Egg<void> : DinoEgg {
Egg() {}
void voidEgg(void) const {}
};

template <typename Chicken>
struct Egg : Egg<void> {
Egg(Chicken _chicken) {}
Chicken chickenEgg(Chicken c) const { return c; }
};

typedef Egg<void> VoidEgg;
typedef Egg<bool> BoolEgg;
typedef Egg<Egg<void>> EggEgg;

struct NewEgg : Egg<int> {
NewEgg() : Egg<int>(555) {}
void newEgg() const {}
};

#endif // !CIRCULAR_INHERITANCE_H
5 changes: 5 additions & 0 deletions test/Interop/Cxx/class/inheritance/Inputs/module.modulemap
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,8 @@ module InheritedLookup {
header "inherited-lookup.h"
requires cplusplus
}

module CircularInheritance {
header "circular-inheritance.h"
requires cplusplus
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// RUN: %empty-directory(%t/index)
// RUN: %target-typecheck-verify-swift -verify-ignore-unknown -I %S/Inputs -cxx-interoperability-mode=default -index-store-path %t/index
//
// Note that we specify an -index-store-path to ensure we also test importing symbolic C++ decls,
// to exercise code that handles importing unspecialized class templates.

import CircularInheritance

let voidEgg = VoidEgg()
voidEgg.dinoEgg()
voidEgg.voidEgg()

let boolEgg = BoolEgg(false)
boolEgg.dinoEgg()
boolEgg.voidEgg()
boolEgg.chickenEgg(true)

let eggEgg = EggEgg(VoidEgg())
eggEgg.dinoEgg()
eggEgg.voidEgg()
eggEgg.chickenEgg(VoidEgg())

let newEgg = NewEgg()
newEgg.dinoEgg()
newEgg.voidEgg()
newEgg.chickenEgg(555)
newEgg.newEgg()