Diagnosing memory, thread, and crash issues early
Identify runtime crashes and undefined behaviors in your app during testing using Xcode’s sanitizer tools.
Overview
Identifying potential issues during development saves testing time later and improves the stability of your code. Xcode provides several runtime tools to identify potential issues in your code:
Address Sanitizer—The ASan tool identifies potential memory-related corruption issues.
Thread Sanitizer—The TSan tool detects race conditions between threads.
Main Thread Checker—This tool verifies that system APIs that must run on the main thread actually do run on that thread.
Undefined Behavior Sanitizer—The UBSan tool detects divide-by-zero errors, attempts to access memory using a misaligned pointer, and other undefined behaviors.
These are LLVM-based tools that add specific checks to your code. You enable them at build time using the Xcode scheme editor. Select the appropriate scheme for your project and choose Product > Scheme > Edit Scheme to display the scheme editor. Select the Run or Test schemes, navigate to the Diagnostics section, and select the sanitizers you want to run.
Test your app with sanitizer tools enabled to catch otherwise difficult to catch bugs. Test your code using a comprehensive set of unit tests, and use additional integration and UI tests to exercise additional code at runtime. The Address Sanitizer, Thread Sanitizer, Undefined Behavior Sanitizer, and Main Thread Checker values of a test plan configuration enable these sanitizers during test runs, see Improving code assessment by organizing tests into test plans. For more information about testing your code, see Testing.
Locate memory corruption issues in your code
Accessing memory improperly can introduce unexpected issues into your code, and even pose a security threat. The Address Sanitizer tool detects memory-access attempts that don’t belong to an allocated block. To enable this tool, select Address Sanitizer from the Diagnostics section of the appropriate scheme.
[Image]
To enable ASan from the command line, use the following flags:
-fsanitize=address(clang)-sanitize=address(swiftc)-enableAddressSanitizer YES(xcodebuild)
The Address Sanitizer tool replaces the malloc(_:) and free(_:) functions with custom implementations. The custom malloc(_:) function surrounds a requested memory block with special off-limits regions, and reports attempts to access those regions. The free(_:) function places a deallocated block into a special quarantine queue, and reports attempts to access that quarantined memory.
For most use cases, the overhead that Address Sanitizer adds to your code should be acceptable for daily development. Running your code with Address Sanitizer increases memory usage by two to three times, and also adds 2x to 5x slowdown of your code. To improve your code’s memory usage, compile your code with the -O1 optimization.
Detect data races among your app’s threads
Race conditions occur when multiple threads access the same memory without proper synchronization. Race conditions are difficult to detect during regular testing because they don’t occur consistently. However, fixing them is important because they cause your code to behave unpredictably, and may even lead to memory corruption.
To detect race conditions and other thread-related issues, enable the Thread Sanitizer tool from the Diagnostics section of the appropriate build scheme.
[Image]
To enable TSan from the command line, use the following flags:
-fsanitize=thread(clang)-sanitize=thread(swiftc)-enableThreadSanitizer YES(xcodebuild)
The Thread Sanitizer tool inserts diagnostics into your code to record each memory read or write operation. These diagnostics generate a timestamp for each operation, as well as its location in memory. The tool then reports any operations that occur at the same location at approximately the same time. The tool also detects other thread-related bugs, such as uninitialized mutexes and thread leaks.
Because Thread Sanitizer inserts diagnostics into your code, it increases memory usage by five to ten times. Running your code with these diagnostics also introduces a 2x to 20x slowdown of your app. To improve your code’s memory usage, compile your code with the -O1 optimization.
Detect improper UI updates on background threads
Some system frameworks contain APIs that you must only call from your app’s main thread. This requirement applies to most of the AppKit and UIKit user interface APIs, and also applies to some other system APIs. Calling these APIs from the main thread prevents race conditions by serializing the execution of the associated tasks. Failure to perform these operations on the main thread may result in visual defects, data corruption, or crashes.
The Main Thread Checker tool ensures that all calls that must occur on the main thread do so. To enable this tool, select Main Thread Checker from the Diagnostics section of the appropriate scheme.
[Image]
The Main Thread Checker tool dynamically replaces system methods that must execute on the main thread with variants that check the current thread. The tool replaces only system APIs with well-known thread requirements, and doesn’t replace all system APIs. Because the replacements occur in system frameworks, Main Thread Checker doesn’t require you to recompile your app.
To fix problems identified by Main Thread Checker, dispatch calls to your app’s main thread. The most common place where main thread errors occur is completion handler blocks. The following code wraps the text label modification with an asynchronous dispatch call to the main thread.
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
if let data = data {
// Redirect to the main thread.
DispatchQueue.main.async {
self.label.text = "\(data.count) bytes downloaded"
}
}
}
task.resume()
The performance impact of Main Thread Checker is minimal. The tool adds 1–2% CPU overhead to your process, and increases process launch time by no more than 100 milliseconds. Because of this minimal impact, Xcode enables Main Thread Checker by default for your development-related schemes.
Detect operations with undefined semantics
Code that results in undefined behavior can lead to crashes or incorrect output. In some cases, the code may not result in any problems at all initially, making it even harder to diagnose the problem later when conditions are different. The Undefined Behavior Sanitizer tool checks C-based code for a variety of common runtime errors, including:
Attempts to divide by zero
Attempts to load memory from a misaligned pointer
Attempts to dereference a
NULLpointerMath operations that result in integer overflow
To enable this tool, select Undefined Behavior Sanitizer from the Diagnostics section of the appropriate scheme.
[Image]
To enable UBSan from the command line, add the -fsanitize=undefined option in clang or the enableUndefinedBehaviorSanitizer YES option in xcodebuild. To enable individual sanitizer checks, use the following options:
Compiler flag | UBSan check |
|---|---|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
The Undefined Behavior Sanitizer tool inserts diagnostics into your code at compile time. The nature of these checks differs according to the type of operation. For example, before performing a mathematical operation on an integer value, the tool adds a check to determine if the operation triggers an integer overflow.
The performance impact of Undefined Behavior Sanitizer is minimal. The tool adds an average of 20% CPU overhead to the debug version of your app.
Topics
Address Sanitizer
Use of deallocated memoryDeallocation of deallocated memoryDeallocation of nonallocated memoryUse of stack memory after function returnUse of out-of-scope stack memoryOverflow and underflow of buffersOverflow of C++ containers
Thread Sanitizer
Undefined Behavior Sanitizer
Misaligned pointerInvalid Boolean valueOut-of-bounds array accessInvalid enumeration valueReaching of unreachable pointDynamic type violationInvalid float castDivision by zeroNonnull argument violationNonnull return value violationNonnull variable assignment violationNull reference creation and null pointer dereferenceInvalid object sizeInvalid shiftInteger overflowInvalid variable-length array