Skip to content

Use a regular downcast instead of an unsafe downcast for Error to NSError conversion #5222

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

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

parkera
Copy link
Contributor

@parkera parkera commented Jun 2, 2025

Try suggestion in the bug and resolve #5221 .

@parkera
Copy link
Contributor Author

parkera commented Jun 2, 2025

@swift-ci test

@@ -891,8 +891,8 @@ func _convertNSErrorToError(_ error: NSError?) -> Error {

public // COMPILER_INTRINSIC
func _convertErrorToNSError(_ error: Error) -> NSError {
if let object = _extractDynamicValue(error as Any) {
return unsafeDowncast(object, to: NSError.self)
if let object = _extractDynamicValue(error as Any), let asNS = object as? NSError {
Copy link
Contributor

@jmschonfeld jmschonfeld Jun 2, 2025

Choose a reason for hiding this comment

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

I suppose CI will tell us, but I don't expect this will work here. I don't know the full details, but I believe that _convertErrorToNSError is effectively the implementation of as NSError, so I suspect this is likely to cause infinite recursion. To check that, I compiled

public func foo(_ e: any Error) -> NSError {
    e as NSError
}

in GodBolt on Linux and saw that it indeed compiles down to a call to this function:

output.foo(Swift.Error) -> Foundation.NSError:
        b       ($s10Foundation22_convertErrorToNSErroryAA0E0Cs0C0_pF)

That said, I don't know the answer to what this should be instead. I'd be interested to know what the type of object is here in the failing case

Copy link
Contributor Author

@parkera parkera Jun 2, 2025

Choose a reason for hiding this comment

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

I did run unit tests on this locally, but it's possible we for some reason do not hit this path.

Copy link
Contributor

Choose a reason for hiding this comment

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

@jmschonfeld

I'd be interested to know what the type of object is here in the failing case

The original bug report has a very short reproducer:

import Foundation

public class BotError: Error {}

print(BotError().localizedDescription)

Copy link
Contributor

Choose a reason for hiding this comment

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

I suppose CI will tell us, but I don't expect this will work here. I don't know the full details, but I believe that _convertErrorToNSError is effectively the implementation of as NSError, so I suspect this is likely to cause infinite recursion.

If you compare

public func foo(_ e: any Error) -> NSError? {
    e as? NSError
}

public func bar(_ e: any Error) -> NSError? {
    e as NSError
}

you'll see that as? NSError causes a call to swift_dynamicCast rather than a call to _convertErrorToNSError.

Copy link
Contributor

Choose a reason for hiding this comment

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

Looks like with the reproducer, both error and object are the BotError instance:

(lldb) frame select 2
frame #2: 0x0000aaaae793437c TestCLI`_convertErrorToNSError(error=0x0000aaab15256780) at NSError.swift:895:16
   892 	public // COMPILER_INTRINSIC
   893 	func _convertErrorToNSError(_ error: Error) -> NSError {
   894 	    if let object = _extractDynamicValue(error as Any) {
-> 895 	        return unsafeDowncast(object, to: NSError.self)
    	               ^
   896 	    } else {
   897 	        let domain: String
   898 	        let code: Int
(lldb) frame variable
(TestCLI.BotError) error = 0x0000aaab15256780 {}
(TestCLI.BotError) object = 0x0000aaab15256780 {}

Copy link
Contributor

Choose a reason for hiding this comment

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

And when the error type is a struct or enum instead of a class, _extractDynamicValue returns nil

Copy link
Contributor

@jmschonfeld jmschonfeld left a comment

Choose a reason for hiding this comment

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

Based on @al45tair's comments and taking a look at the existing _extractDynamicValue and _bridgeErrorToNSError implementations, this seems good to me! It makes sense that we'd want classes that aren't NSErrors to take the else path rather than just assuming classes that conform to Error are NSErrors

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Runtime crash in very basic program causing SIGBUS: illegal alignment on Linux
3 participants