Skip to content

[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

Merged
merged 1 commit into from
May 1, 2025

Conversation

j-hui
Copy link
Contributor

@j-hui j-hui commented Apr 30, 2025

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

@j-hui
Copy link
Contributor Author

j-hui commented Apr 30, 2025

@swift-ci please test

@j-hui j-hui force-pushed the fix-recursive-inheritance branch from 81b6280 to 8ac798b Compare April 30, 2025 02:04
@j-hui
Copy link
Contributor Author

j-hui commented Apr 30, 2025

@swift-ci please test

@j-hui
Copy link
Contributor Author

j-hui commented Apr 30, 2025

nit to self: I could clarify the commit message a little bit, to something like:

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

But I'll let the CI run to completion for now and check in on this tomorrow.

Alas the spurious CI failure deities have given me an opportunity to nitpick my commit message

@j-hui j-hui changed the title [cxx-interop] Avoid infinite recursion for classes with symbolic circular inheritance [cxx-interop] Avoid unchecked recursion when importing C++ classes with circular inheritance Apr 30, 2025
@j-hui j-hui force-pushed the fix-recursive-inheritance branch from 8ac798b to a39be57 Compare April 30, 2025 06:36
@j-hui
Copy link
Contributor Author

j-hui commented Apr 30, 2025

@swift-ci please test

@@ -8774,3 +8781,18 @@ bool importer::isClangNamespace(const DeclContext *dc) {

return false;
}

bool importer::isSymbolicCircularBase(const clang::CXXRecordDecl *symbolicClass,
Copy link
Contributor

Choose a reason for hiding this comment

The 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?

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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 CxxRecordDecls that describe uninstantiated templates).

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.

Copy link
Contributor

Choose a reason for hiding this comment

The 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.

@egorzhdan egorzhdan added the c++ interop Feature: Interoperability with C++ label Apr 30, 2025
Copy link
Contributor

@egorzhdan egorzhdan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, thanks!

@@ -8774,3 +8781,18 @@ bool importer::isClangNamespace(const DeclContext *dc) {

return false;
}

bool importer::isSymbolicCircularBase(const clang::CXXRecordDecl *symbolicClass,
Copy link
Contributor

Choose a reason for hiding this comment

The 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.

@j-hui
Copy link
Contributor Author

j-hui commented Apr 30, 2025

@swift-ci please test macos platform

@j-hui j-hui enabled auto-merge (squash) April 30, 2025 16:21
@j-hui j-hui disabled auto-merge April 30, 2025 16:22
@j-hui j-hui enabled auto-merge April 30, 2025 16:22
@j-hui j-hui disabled auto-merge April 30, 2025 16:25
…th 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
@j-hui j-hui force-pushed the fix-recursive-inheritance branch from a39be57 to 1f2107f Compare April 30, 2025 20:33
@@ -6364,6 +6364,11 @@ TinyPtrVector<ValueDecl *> ClangRecordMemberLookup::evaluate(
continue;

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

if (isSymbolicCircularBase(cxxRecord, baseRecord))
Copy link
Contributor Author

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 of recordDecl, where:

template <typename T> class Circular;
template <> class Circular<void> { ... };
template <typename T> class Circular<T> : Circular<void> { ... };

typedef Circular<SomeType> RecordDecl;
class InheritingDecl : RecordDecl { ... };

Neither check is complete, but heuristically checking recordDecl is probably better (at least that's what matches the circular inheritance case in std::exception).

@j-hui
Copy link
Contributor Author

j-hui commented Apr 30, 2025

@swift-ci please test

@j-hui
Copy link
Contributor Author

j-hui commented May 1, 2025

@swift-ci please test

@j-hui
Copy link
Contributor Author

j-hui commented May 1, 2025

(re-running because I seem to have run into an error fixed by swiftlang/swift-package-manager@bdfd754)

@j-hui
Copy link
Contributor Author

j-hui commented May 1, 2025

@swift-ci please test macos platform

@j-hui j-hui merged commit c2f904c into swiftlang:main May 1, 2025
4 of 5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
c++ interop Feature: Interoperability with C++
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants