Swift: assertionFailure sometimes execute in release build

In my opinion,

assertionFailure
will be ignored in release build. But when I run codes below in release mode,
assertionFailure
can run and stop the program. Why?


Codes:

enum MessageType: RawRepresentable {
    case news
    case unknownMessageType(value: String)


    init?(rawValue: String) {
        assertionFailure("Tihs assertionFailure will never stop execution")
        switch rawValue {
        case "A":
            self = .news
        case "B":
            self = .news
        case "C":
            self = .news
        case "D":
            self = .news
        case "E":
            self = .news
        case "F":
            self = .news
        case "G":
            self = .news
        case "H":
            self = .news
        case "I":
            self = .news
        case "J":
            self = .news
        case "K":
            self = .news
        case "L":
            self = .news
        case "M":
            self = .news
        case "N":
            self = .news
        case "O":
            self = .news
        case "P":
            self = .news
        case "Q":
            self = .news
        case "R":
            self = .news
        case "S":
            self = .news
        case "T":
            self = .news
        case "U":
            self = .news
        default:
            assertionFailure("This assertionFailure will cause a fatal error and stop execution")
            self = .unknownMessageType(value: rawValue)
        }
    }


    var rawValue: String {
        switch self {
        case .news:
            return "A"
        case .unknownMessageType(value: let value):
            return value
        }
    }
}


his statement will cause a crash

let message = MessageType(rawValue: "aaaa")
, caused by the second
assertionFailure
, not the first one.

nother weird thing is if I remove any two

case
(at least two
case
) in function
init?(rawValue: String)
, assertionFailure will be ignored, there will not be any crash. PS, I do not disable optimization for release build. All setting is default.
Answered by DTS Engineer in 341320022

I’m going to label this as a compiler bug. If you disassemble the code for

MessageType.init?(rawValue:)
you’ll see that the compiler generates no code for the first call to
assertionFailure
, which is exactly what you’d expect, but generates a bunch of code for the second call. And then if you remove the last two cases in the
switch
statement it stops generating that code and the Release build runs as expected.

I suspect that this is a failure in the compiler’s mandatory optimisation phases. Swift is unusual in that some optimisations have to run for the code to be correct, and in this case it seems that the optimisations being done by the Release build are causing this mandatory optimisation to not run.

Still, I’m hardly a compiler expert, so this is all just speculation.

I don’t think there’s anything else you can do here. It seems that the latest compiler is working as expected, so there’s no point even filing a bug.

But hey, thanks for creating a thread about this. It’s a very interesting problem.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

PS, I do not disable optimization for release build. All setting is default.

And yet I still think that’s the root cause of this problem. Consider this sequence:

  1. Create a new app from the Single View App template.

  2. Modify

    viewDidLoad
    to include an assertion failure.
    override func viewDidLoad() {
        assertionFailure("Hello Cruel World!")
        super.viewDidLoad()
    }

    .

  3. Run the app; it traps as expected.

  4. Stop the app.

  5. In Product > Scheme > Edit Scheme > Run > Info > Build Configuration, select Release instead of Debug.

  6. Now run the app again; this time it doesn’t trap.

I did this here in my office and it works as expected (Xcode 10.1 running on macOS 10.14.1 targeting iOS 12.1 simulator). I recommend that you repeat these steps yourself, just to confirm that the basics are working.

If the above works in your test app but fails in your main app then you need to look at the Optimization Level (

SWIFT_OPTIMIZATION_LEVEL
) build setting. One option here is to compare the build transcript for your Debug and Release builds. You should see Xcode pass
-Onone
(aka No Optimisation) in the Debug build configuration and
-O
(aka Optimize for Speed) in the Release build.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Hi eskimo,


Thank you for reply. I created a new app from the Single View App template and duplicate this issue.

1. I select Release instead of Debug in Scheme Run setting.

2. I add code as **** in AppDelegate

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
        // Override point for customization after application launch.
        let message = MessageType(rawValue: "aaaa")
        return true
    }

3. I new a swift file named Message.swift, and fill in the codes I had posted before.

4. Run app, it doesn't trap.

5. Add new line in AppDelegate

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
        // Override point for customization after application launch.
        assertionFailure("Will not terminate in AppDelegate")
        let message = MessageType(rawValue: "aaaa")
        return true
    }

6. Run app again, this time Xcode pause at the second assertionFailure in Message.swift

    init?(rawValue: String) {
        assertionFailure("Tihs assertionFailure will never stop execution")
        switch rawValue {
        case "A":
            self = .news
......
        default:
            assertionFailure("This assertionFailure will cause a fatal error and stop execution")
            self = .unknownMessageType(value: rawValue)
        }


There are three assertionFailure in my sample, one in AppDelegate, another in the first line of init funcion of Message.swift, the last one in the next line of default. Only third assertionFailure will be run in Release build. I have test this issue in a new single view app project with default setting, and I had check the Optimization setting for Release build, it's Optimize for Speed[-O].

6. Run app again, this time Xcode pause at the second

assertionFailure
in
Message.swift

Weird. I just tried these exact steps here in my office and didn’t see this behaviour. In my case all three instances of

assertionFailure
were silently ignored.

I’m testing with Xcode 10.1 on macOS 10.14.1 targeting the iOS 12.1 simulator and an iOS 12.1 device. What about you?

Can you post a copy of your test project somewhere online where I can download and try it? [Posting a link requires moderator approval, but I can take care of that.]

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Hi eskimo,


I had uploaded demo project here: https://github.com/bd-jhunter/TestAssertFailure

I'm testing with Xcode 9.4.1 and iPhone 7P(iOS 12.1) and iPhone Simulator(iOS 11), it will crash.

I did another test, run demo project in Xcode 10.0.1, all three instances of

assertionFailure
were silently ignored.

Anoher weired thing is: in Xcode 9, if I remove any two case statements in switch case, assertionFailure wil be ignored.

Thank you for your support, I'm full of curiosity why it happen in Xcode 9.

Accepted Answer

I’m going to label this as a compiler bug. If you disassemble the code for

MessageType.init?(rawValue:)
you’ll see that the compiler generates no code for the first call to
assertionFailure
, which is exactly what you’d expect, but generates a bunch of code for the second call. And then if you remove the last two cases in the
switch
statement it stops generating that code and the Release build runs as expected.

I suspect that this is a failure in the compiler’s mandatory optimisation phases. Swift is unusual in that some optimisations have to run for the code to be correct, and in this case it seems that the optimisations being done by the Release build are causing this mandatory optimisation to not run.

Still, I’m hardly a compiler expert, so this is all just speculation.

I don’t think there’s anything else you can do here. It seems that the latest compiler is working as expected, so there’s no point even filing a bug.

But hey, thanks for creating a thread about this. It’s a very interesting problem.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Hi eskimo,


Thank you for let me know the root cause. I have no idea about how to rewrite Swift codes into c language like rewrite Objective-c via "clang rewrite-objc", so I can not prove my guess.

Swift: assertionFailure sometimes execute in release build
 
 
Q