Skip to content

[cxx-interop] Forbid C++ function template instantiation with Swift types #77430

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 17 commits into from
Jan 16, 2025

Conversation

j-hui
Copy link
Contributor

@j-hui j-hui commented Nov 6, 2024

Instantiating C++ function templates with Swift types is not currently supported, but the Swift compiler attempts to do so. Sometimes this leads to a compiler crash; other times it leads to a runtime crash. This patch turns those crashes into compiler errors.

At the call site of a C++ function template, the Swift compiler must convert Swift types deduced by the Swift type checker back into Clang types, which are then used to instantiate the C++ function template. Prior to this patch, this conversion was performed by ClangTypeConverter::convert(), which converts unsupported Swift types. This patch factors out some reusable parts of convert() and uses them in a new ClangTypeConverter::convertTemplateArgument() method that only converts supported template argument types. In the future, this method can be elaborated to support instantiating C++ templates with more types.

rdar://112692940

@j-hui j-hui requested a review from egorzhdan November 6, 2024 21:29
@j-hui j-hui marked this pull request as draft November 6, 2024 21:30
@j-hui
Copy link
Contributor Author

j-hui commented Nov 6, 2024

@swift-ci please test

@fahadnayyar fahadnayyar self-requested a review November 12, 2024 03:21
@j-hui j-hui force-pushed the templates-reject-swift-classes branch from 3c36efb to 59be37b Compare December 13, 2024 00:28
@j-hui j-hui changed the title Forbid C++ function template instantiation with Swift types [cxx-interop] Forbid C++ function template instantiation with Swift types Dec 13, 2024
@j-hui
Copy link
Contributor Author

j-hui commented Jan 11, 2025

@swift-ci please test

@j-hui
Copy link
Contributor Author

j-hui commented Jan 11, 2025

@swift-ci Please test

@j-hui j-hui marked this pull request as ready for review January 11, 2025 03:29
@j-hui
Copy link
Contributor Author

j-hui commented Jan 11, 2025

This patch does not attempt to fix an existing issue, where the Swift compiler crashes when trying to instantiate a C++ function template with an optional pointer type. Notably, doing this still causes the compiler to crash:

// C++
template <typename T> void T takes(T t) { }    // Some C++ template function

int *ptr;                                      // Some C++ ptr
// Swift
takes(ptr)    // crashes, because the type of ptr is UnsafeMutablePointer<Int32>?

A workaround is to remove the optionality from the pointer:

let sptr = ptr!
takes(sptr)   // OK

@j-hui
Copy link
Contributor Author

j-hui commented Jan 11, 2025

C function pointers are also not supported as a result of this patch. I cannot seem reliably obtain the original Clang type for imported function types.

@Xazax-hun
Copy link
Contributor

Could you open some tickets for these issues that are not covered by this PR?

@Xazax-hun
Copy link
Contributor

I wonder if it would be possible to split this up to 2 PRs. One that is purely a refactoring, and one that actually introduces the functional changes. It would make the review much easier. But in case that is too much work it is fine as is.

@j-hui
Copy link
Contributor Author

j-hui commented Jan 13, 2025

@swift-ci please test

1 similar comment
@j-hui
Copy link
Contributor Author

j-hui commented Jan 13, 2025

@swift-ci please test

@j-hui
Copy link
Contributor Author

j-hui commented Jan 14, 2025

To track the issues with instantiating C++ function templates with pointer types, I've opened rdar://142850017

Copy link
Contributor

@Xazax-hun Xazax-hun left a comment

Choose a reason for hiding this comment

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

Looks good to me, some minor nits/questions inline.


auto result = lookup();
if (!result.isNull())
Cache.insert({type, result});
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 it is possible to replace the double lookup in Cache case of a cache miss using try_emplace. Something like:

auto [it, inserted] = Cache.try_emplace(type, clang::QualType{});
if (!inserted)
  it->second = lookup();
return it->second;

Also, if lookup() returns null, do we want to repeat it the next call? Is there any reason to expect a different result next time?

Copy link
Contributor

Choose a reason for hiding this comment

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

I see there are other parts of this file that are doing double lookups in case of cache misses, up to you if you want to fix those as well.

Copy link
Contributor Author

@j-hui j-hui Jan 14, 2025

Choose a reason for hiding this comment

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

Also, if lookup() returns null, do we want to repeat it the next call? Is there any reason to expect a different result next time?

Probably not, but I'm not fully confident of that. Do you reckon it's worth inserting an #ifndef NDEBUG to catch such circumstances? I think I'll do that in a subsequent patch, if at all. But it could be a good invariant to check for during debug builds.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I see there are other parts of this file that are doing double lookups in case of cache misses, up to you if you want to fix those as well.

I think it might be better to fix those in a follow-up patch, since this one is starting to get large. I'll change my use of Cache.find() though.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Er, sanity check: you meant if (inserted), right?

auto [it, inserted] = Cache.try_emplace(type, clang::QualType{});
if (inserted)
  it->second = lookup();
return it->second;

inserted == true would imply there was previously no entry, which is when we would need to perform a lookup(). If inserted == false then there was already a cached entry.

Copy link
Contributor

Choose a reason for hiding this comment

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

Sorry, my bad! You are right!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

As it turns out, DenseMap::try_emplace() does not work because the returned iterator may get invalidated by insertions during nested conversions, i.e.:

auto [it, inserted] = Cache.try_emplace(type, clang::QualType{});
if (inserted)
  it->second = lookup(); // lookup() may cause insertions that invalidate it
return it->second;

So I'm going to revert that change.

(Also I realize that lookup() is not the best choice of variable name; I've renamed it to conversion().)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Also, following up on this:

Also, if lookup() returns null, do we want to repeat it the next call? Is there any reason to expect a different result next time?

It turns out that it is necessary, even though the original convert() function did not appear to do this. I don't remember why I even added that check, or how the old convert() function was working before... I'll leave it as is for now but investigate that in a follow-up patch.


// This type was imported from Clang, so we can convert it back by retrieving
// ClangDecl stored in the imported type decl (without making a cache entry.)
if (auto nominal = type->getAs<NominalType>())
Copy link
Contributor

Choose a reason for hiding this comment

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

I am a bit on the edge whether we want to use a TypeVisitor here, but maybe it is OK as is for now.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I considered doing that but I figured we don't visit more types than we visit, and setting up the TypeVisitor pattern also involves a lot of additional boilerplate. My thinking was that we can refactor this in the future, according to our needs then.

@Xazax-hun
Copy link
Contributor

In case there is a concise way to describe what needs to be true for a Swift type to be supported as a C++ template argument, that would be a useful comment to add. In case it would just repeat the implementation details, feel free to ignore this.

@j-hui
Copy link
Contributor Author

j-hui commented Jan 14, 2025

I had written something describing what you are asking in a comment, which I moved to the declaration in the header file in afe1e61.

@j-hui
Copy link
Contributor Author

j-hui commented Jan 14, 2025

@swift-ci Please test

@j-hui j-hui enabled auto-merge (squash) January 14, 2025 18:35
@j-hui
Copy link
Contributor Author

j-hui commented Jan 14, 2025

@swift-ci Please test

@j-hui
Copy link
Contributor Author

j-hui commented Jan 15, 2025

Hm. I'm not sure why I'm now encountering errors while building this in CI.... I'm going to try this one more time.

@j-hui
Copy link
Contributor Author

j-hui commented Jan 15, 2025

@swift-ci please test

j-hui added 17 commits January 15, 2025 14:43
At the moment, instantiating C++ function templates with Swift types is
not supported. However, some Swift types---notably, Swift classes---have
been falling through the conversion logic and causing swiftc crashes.
This patch aims to turn those crashes into compiler errors.

These issues are due to relying on ClangTypeConverter::convert() to
look for and reject types that a function template should not be
instantiated with. However, convert() is also used in other contexts
with different conversion requirements, where those types should be
allowed. Rather than complicate the existing conversion logic, this
patch factors out some reusable parts of convert(), and uses them in
a new ClangTypeConverter method specifically intended for converting
C++ function template arguments during instantiation. In the future,
this method can be elaborated to add support for instantiating C++
templates with Swift types is added.

rdar://138947983
rdar://138946240
rdar://138944852
rdar://138946844
rdar://138946593
rdar://138945719
@j-hui j-hui force-pushed the templates-reject-swift-classes branch from 3a01220 to 427c824 Compare January 15, 2025 22:43
@j-hui
Copy link
Contributor Author

j-hui commented Jan 15, 2025

@swift-ci Please test

@j-hui j-hui merged commit 5a4ff29 into swiftlang:main Jan 16, 2025
5 checks passed
@j-hui j-hui deleted the templates-reject-swift-classes branch January 16, 2025 15:05
j-hui added a commit to j-hui/swift that referenced this pull request Apr 23, 2025
…ift closures

Swift started to explicitly forbid the instantiation of C++ function
templates with arbitrary types in swiftlang#77430, because many types cause the
Swift compiler to crash. However, those checks prevented them from being
instantiated with Swift closures (which were previously fine), causing
a regression.

This patch relaxes the convertTemplateArgument() function to also allow
converting Swift function types, and adds some tests to make sure doing
so is fine. This patch also does some cleanup of an existing test
checking the instantiation of various types.

rdar://148124104
j-hui added a commit to j-hui/swift that referenced this pull request Apr 24, 2025
…ift closures

Swift started to explicitly forbid the instantiation of C++ function
templates with arbitrary types in swiftlang#77430, because many types cause the
Swift compiler to crash. However, those checks prevented them from being
instantiated with Swift closures (which were previously fine), causing
a regression.

This patch relaxes the convertTemplateArgument() function to also allow
converting Swift function types, and adds some tests to make sure doing
so is fine.

This patch also does some cleanup of existing tests checking the
instantiation of various types, and adds testing for C function pointers
and Obj-C blocks.

rdar://148124104
j-hui added a commit to j-hui/swift that referenced this pull request Apr 24, 2025
…ift closures

Swift started to explicitly forbid the instantiation of C++ function
templates with arbitrary types in swiftlang#77430, because many types cause the
Swift compiler to crash. However, those checks prevented them from being
instantiated with Swift closures (which were previously fine), causing
a regression.

This patch relaxes the convertTemplateArgument() function to also allow
converting Swift function types, and adds some tests to make sure doing
so is fine.

This patch also does some cleanup of existing tests checking the
instantiation of various types, and adds testing for C function pointers
and Obj-C blocks.

rdar://148124104
j-hui added a commit to j-hui/swift that referenced this pull request Apr 30, 2025
…ift closures

Swift started to explicitly forbid the instantiation of C++ function
templates with arbitrary types in swiftlang#77430, because many types cause the
Swift compiler to crash. However, those checks prevented them from being
instantiated with Swift closures (which were previously fine), causing
a regression.

This patch relaxes the convertTemplateArgument() function to also allow
converting Swift function types, and adds some tests to make sure doing
so is fine.

This patch also does some cleanup of existing tests checking the
instantiation of various types, and adds testing for C function pointers
and Obj-C blocks.

rdar://148124104
j-hui added a commit to j-hui/swift that referenced this pull request Apr 30, 2025
…ift closures

Swift started to explicitly forbid the instantiation of C++ function
templates with arbitrary types in swiftlang#77430, because many types cause the
Swift compiler to crash. However, those checks prevented them from being
instantiated with Swift closures (which were previously fine), causing
a regression.

This patch relaxes the convertTemplateArgument() function to also allow
converting Swift function types, and adds some tests to make sure doing
so is fine.

This patch also does some cleanup of existing tests checking the
instantiation of various types, and adds testing for C function pointers
and Obj-C blocks.

rdar://148124104
(cherry picked from commit 284de98)
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