-
Notifications
You must be signed in to change notification settings - Fork 15.2k
Description
Exceptions from std::async cannot be caught on macOS ARM with Clang 18+
Summary
On macOS ARM with Clang 18+, exceptions thrown from std::async contexts cannot be caught properly. The exception escapes all catch blocks and causes program termination with libc++abi: terminating due to uncaught exception.
Important Finding: This bug is NOT module-specific. It affects both C++ modules and regular C++ code when using std::async.
Environment
- Platform: macOS ARM (Apple Silicon)
- Compiler: Clang 18+ (tested with Clang 21.1.5)
- Standard Library: libc++ (Homebrew LLVM)
- Optimization: All optimization levels (
-O0,-O1,-O2,-O3) - bug occurs regardless
Minimal Reproduction
#include <iostream>
#include <stdexcept>
#include <future>
struct custom_exception : public std::runtime_error {
custom_exception() : std::runtime_error("custom_exception") {}
};
// Key: The std::async call must be in a separate function to trigger the bug
void throw_from_async() {
auto future = std::async(std::launch::async, []() {
throw custom_exception{};
});
future.get();
}
int main() {
try {
throw_from_async();
}
catch (const custom_exception& e) {
std::cout << "SUCCESS: Exception caught: " << e.what() << "\n";
return 0;
}
catch (...) {
std::cout << "FAILED: Exception not caught\n";
return 1;
}
}Compile:
clang++ -std=c++20 -stdlib=libc++ -O3 \
-L/opt/homebrew/opt/llvm/lib/c++ -L/opt/homebrew/opt/llvm/lib \
-Wl,-rpath,/opt/homebrew/opt/llvm/lib/c++ -Wl,-rpath,/opt/homebrew/opt/llvm/lib \
-lc++ \
minimal_repro.cpp -o minimal_reproOr use the provided build script: minimal_repro.sh
Expected: Exception is caught, prints "SUCCESS: Exception caught"
Actual: Program terminates with:
libc++abi: terminating due to uncaught exception of type custom_exception: custom_exception
Root Cause Analysis
Based on investigation, this appears to be related to:
- LLVM built-in unwinder: The bug is specific to macOS ARM where LLVM's built-in unwinder is enabled by default in Homebrew Clang 18+
- Exception propagation across threads: The issue manifests specifically with
std::async, suggesting problems with exception unwinding across thread boundaries - Function boundaries: The bug requires the
std::asynccall to be in a separate function (not inlined intomain())
Additional Context
- The bug does not occur on Linux ARM (e.g., Asahi Linux) with the same Clang version
- The bug does not occur with Clang < 18
- The bug affects both module and non-module code
- The bug persists even with explicit RTTI flags (
-frtti -fexceptions -fcxx-exceptions) - The bug occurs at all optimization levels (
-O0,-O1,-O2,-O3) - optimization level does not affect the bug
Workaround
No known workaround: The bug occurs at all optimization levels (-O0 through -O3).
The only potential workaround is to avoid using std::async with exceptions on macOS ARM, or to catch exceptions within the async lambda itself before they propagate across thread boundaries.
Related Issues
This may be related to or a duplicate of:
- llvm/llvm-project#92121 - Exception handling issues on macOS ARM
Test Cases
A comprehensive test suite demonstrating the bug is available. The minimal reproduction code is included above, and test files are available as GitHub Gists:
- Minimal reproduction: minimal_repro.cpp (44 lines, also included above)
- Build script: minimal_repro.sh - Automated build and test script
- Full test suite with 11 test cases covering various exception patterns
- Non-module test confirming bug is not module-specific
Steps to Reproduce
- On macOS ARM, install Homebrew LLVM:
brew install llvm - Compile the minimal reproduction code above with
-O3 - Run the executable
- Observe program termination instead of exception being caught
Impact
This bug affects any code using std::async with exception handling on macOS ARM, making it impossible to properly handle exceptions from asynchronous operations. This is a critical issue for production code relying on exception safety in concurrent contexts.