-
Notifications
You must be signed in to change notification settings - Fork 10.5k
[cxx-interop] Avoid unchecked recursion when importing C++ classes with circular inheritance #81188
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6364,6 +6364,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. | ||
|
@@ -8774,3 +8779,18 @@ bool importer::isClangNamespace(const DeclContext *dc) { | |
|
||
return false; | ||
} | ||
|
||
bool importer::isSymbolicCircularBase(const clang::CXXRecordDecl *symbolicClass, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should we only do this when we are importing something in symbolic mode? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah that's a good point. I'm not entirely sure whether we should, because the symbolic mode is only the trigger for this issue rather than the actual problem itself (which is that we're not handling In any case, I can add the check just for this to be an extra conservative patch, since I presume we'll need to cherry-pick this. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we should be consistent here in terms of symbolic vs non-symbolic mode, i.e. bailing in both modes, like this patch does currently. |
||
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(); | ||
} |
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 |
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() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My last force push updated the test case to cover one more case (a class that inherits from a circularly inheriting class), and fixed this code to check for the right base. It used to check the C++ class of
inheritingDecl
, but now it checks the C++ class ofrecordDecl
, where:Neither check is complete, but heuristically checking
recordDecl
is probably better (at least that's what matches the circular inheritance case instd::exception
).