Ethenyl
Ethenyl

Reputation: 684

App crashes when using NSStringFromClass() in release target

Hello fellow developers,

I'm currently facing a strange issue with some Swift 3 code.

I have a custom logging class (using XCGLogger) :

struct Log {
    #if DEBUG
        fileprivate static let Logger: XCGLogger = {
            let logger = XCGLogger(identifier: "API")

            if let standardConsoleDestination = logger.destination(withIdentifier: XCGLogger.Constants.baseConsoleDestinationIdentifier) as? ConsoleDestination {
                standardConsoleDestination.showLogIdentifier = true
                standardConsoleDestination.showFunctionName = false
                standardConsoleDestination.showThreadName = false
                standardConsoleDestination.showLevel = true
                standardConsoleDestination.showFileName = false
                standardConsoleDestination.showLineNumber = false
                standardConsoleDestination.showDate = true
                standardConsoleDestination.outputLevel = .debug
            }

            return logger
        }()
    #endif

    fileprivate static func log(_ level: XCGLogger.Level, message: String) {
        #if DEBUG
            Logger.logln("[💎] \(message)", level: level, userInfo: [:])
        #endif
    }

    static func warning(_ message: String) {
        log(.warning, message: message)
    }

    static func parseFailure(in aClass: AnyClass, json: [String: Any]) {
        let className = NSStringFromClass(aClass)
        Log.warning("Failed to create \(className) object from JSON: \(json)")
    }
}

The DEBUG pre-processor directive is activated for Debug mode.

Then I'm using this method to instantiate an object from a JSON payload :

required init?(json: [String: Any]) {
    guard
        let value1 = json["value1"] as? String,
        let value2 = json["value2"] as? String
        else {
            Log.parseFailure(in: type(of: self), json: json)
            return nil
    }

    self.value1 = value1
    self.value2 = value2
}

This is pretty basic but it's just to illustrate the case I've encountered.

I'm getting the following crash :

Exception Type:  EXC_BAD_ACCESS (SIGSEGV)
Exception Subtype: KERN_INVALID_ADDRESS at 0x0000000e115ebec8

1   MyProject                   0x00000001015cb92c static Log.parseFailure -> () (in MyProject) (Log.swift:0)
2   MyProject                   0x000000010151c9a0 Object.init(json : [String : Any]) -> Object? (Object.swift:0)
...

I'm sure it's something dumb... Maybe the class being deallocated before the Log happens, resulting in the self typeof to be nil.

Any insight is welcome on the topic so thanks for your contribution / comment.

EDIT

I've been able to replicate the issue by enabling testing on release mode, it seems that disabling optimisation to check the content of the parameters solved the issue.

Maybe one of these is adding some sort of security... I'll dig deeper while trying to find a workaround.

Upvotes: 2

Views: 633

Answers (1)

Ethenyl
Ethenyl

Reputation: 684

Okay, I found the culprit :

let className = NSStringFromClass(aClass)
Log.warning("Failed to create \(className) object from JSON: \(json)")

The NSStringFromClass() function is breaking when Swift optimization is enabled (SWIFT_OPTIMIZATION_LEVEL).

I replaced it with the following :

let className = String(describing: aClass)
Log.warning("Failed to create \(className) object from JSON: \(json)")

And now everything is working but it was a pain to test...

I'll update the post name to reflect that this bug is different than an issue with a dictionary.

Upvotes: 1

Related Questions