Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to prevent a custom init method from revealing the absolute source path in Xcode?

To a new Xcode project, I have added a new class with a custom init method in this way:

import Cocoa

class NewClass: NSControl {

    override init(frame: CGRect) {
        super.init(frame: frame)
    }

    required init(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

However, if I build and archive the project, the absolute path to the source file is revealed in the binary:

/Users/my_username/my_path_to_xcode_projects/new_project/NewClass.swift

I plan to distribute my final project over the internet. How can I prevent Xcode to disclose my directory layout to others? I have learned from answers to similar questions that in the "Build Settings" of Xcode one should set "Deployment Prostprocessing" to Yes in order to strip symbols from the binary but this does not seem to work in my case.

like image 1000
klopps Avatar asked Jul 22 '16 22:07

klopps


1 Answers

If you look closely at the signature of fatalError:

@noreturn public func fatalError(
    @autoclosure message: () -> String = default, 
    file: StaticString = #file, line: UInt = #line)

You can see the #file and #line default arguments. These are evaluated at compile time and get resolved to the current file and line.

To not use the default arguments, you can provide them yourself:

fatalError("Something went wrong", file: "", line: 0)

When this is compiled on the release configuration, the path isn't included in the binary.

If you search for #file in the standard library, you can find 4 other instances, two of which are assert and assertionFailure which won't be included in a release build. The remaining 2 are precondition and preconditionFailure.

Workaround

If we add the DEBUG/RELEASE symbols: enter image description here

we can have conditional compilation of mocked fatalError, etc. functions that don't use the #file preprocessor statements on release builds:

#if RELEASE
@inline(__always) @noreturn func fatalError(@autoclosure message: () -> String = "") {
    Swift.fatalError(message, file: "", line: 0)
}
    
@inline(__always) func preconditionAlt(@autoclosure condition: () -> Bool, @autoclosure _ message: () -> String = "") {
    Swift.precondition(condition, message, file: "", line: 0)
}

@inline(__always) @noreturn func preconditionFailureAlt(@autoclosure message: () -> String = "") {
    Swift.preconditionFailure(message, file: "", line: 0)
}
#else
@inline(__always) func preconditionAlt(@autoclosure condition: () -> Bool, @autoclosure _ message: () -> String = "", file: StaticString = #file, line: UInt = #line) {
    Swift.precondition(condition, message, file: file, line: line)
}

@inline(__always) @noreturn func preconditionFailureAlt(@autoclosure message: () -> String = "") {
    Swift.preconditionFailure(message, file: file, line: line)
}
#endif

All instances where you called any of these functions now uses the custom implementation. This way you can still get file and line information when debugging but prevent it from appearing in the product. You'll have to change every instance of precondition[Failure] to precondition[Failure]Alt though, Swift cannot unambiguously infer the function otherwise. You can press Shift-Alt-Cmd-F to search and replace in the whole project

Note

If a third party framework uses these functions and you compile it on your machine, the path will obviously be included in the framework product. To prevent this you either have to use a precompiled version or compile it yourself with the mocking.

If any other self declared or third party function uses #file, you obviously would have to change that as well, it's not very common though.

There may be other possibilities for the file structure to be leaked of which I don't know.

like image 97
Silvan Mosberger Avatar answered Nov 03 '22 01:11

Silvan Mosberger