Skip to content

Commit c90d822

Browse files
committed
Fix crash when back-deploying concurrency to iOS 15.0/15.1-era OSs
The introduction of the library providing back-deployment fixes for Swift 5.6 concurrency libraries (and older) introduced a concurrency crash in the following OS version ranges - macOS [12.0, 12.1) - iOS [15.0, 15.1) - tvOS [15.0, 15.1) - watchOS [8.0, 8.2) Neither older nor newer versions of the listed OSs were affected. The actual bug involved a miscommunication between the code in the library that back-deploys fixes (`libswiftCompatibility56.a`) and the concurrency library in the OS itself. Specifically, the OS versions at the end of the ranges above introduced voucher support into the concurrency runtime in the OS (an important feature for performance). The code in `libswiftCompatibility56.a` that back-ports concurrency fixes also included the voucher support, which provides a consistent state for those OS versions and anything newer. OS versions that predate the introduction of concurrency in the OS are similarly unaffected, because the embedded `libswift_Concurrency.dylib` matches that of Swift 5.6, which includes voucher support. The OS versions in the affected range include a concurrency library in the OS that does not manage vouchers. The `libswiftCompatibility56.a` back-deployed fixes library has code that works on the same data structures but does manage vouchers, leading to crashes when (say) a job allocated by the OS version didn't set the "voucher" field, but the `libswiftCompatibility56.a` tried to free it, essentially a form of overrelease. The fix is to teach the voucher-handling code in `libswiftCompatibility56.a` to first check what version of the OS it is executing on. If it's in the affected range, all handling of vouchers is disables so it acts like the concurrency library in the OS. For both earlier OS versions and later OS versions the voucher-handler code executes unchanged. This entirely library is disabled when running on OS versions containing the Swift 5.7 concurrency library (or newer), so those don't need to pay for the extra check when dealing with vouchers. Fixes rdar://108837762, rdar://108864311, rdar://108958765.
1 parent e87acf4 commit c90d822

File tree

2 files changed

+33
-0
lines changed

2 files changed

+33
-0
lines changed

stdlib/toolchain/Compatibility56/Concurrency/Actor.cpp

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,3 +146,21 @@ void swift::adoptTaskVoucher(AsyncTask *task) {
146146
void swift::restoreTaskVoucher(AsyncTask *task) {
147147
ExecutorTrackingInfo::current()->restoreVoucher(task);
148148
}
149+
150+
static swift_once_t voucherDisableCheckOnce;
151+
static bool vouchersDisabled;
152+
153+
static void _initializeVouchersDisabled(void *ctxt) {
154+
if (__builtin_available(macOS 12.1, iOS 15.2, tvOS 15.2, watchOS 8.3, *)) {
155+
vouchersDisabled = false;
156+
} else {
157+
vouchersDisabled = true;
158+
}
159+
}
160+
161+
bool VoucherManager::vouchersAreDisabled() {
162+
swift_once(&voucherDisableCheckOnce,
163+
&_initializeVouchersDisabled,
164+
nullptr);
165+
return vouchersDisabled;
166+
}

stdlib/toolchain/Compatibility56/include/Concurrency/VoucherSupport.h

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,12 @@ class VoucherManager {
3131
/// async work.
3232
llvm::Optional<voucher_t> OriginalVoucher;
3333

34+
/// Determine whether vouchers are disabled entirely. This evaluates
35+
/// true on platforms whose concurrency library does not support the
36+
/// propagation of vouchers, in which case all of the operations of
37+
/// this class must be no-ops.
38+
static bool vouchersAreDisabled();
39+
3440
public:
3541
VoucherManager() {
3642
SWIFT_TASK_DEBUG_LOG("[%p] Constructing VoucherManager", this);
@@ -41,6 +47,9 @@ class VoucherManager {
4147
/// VoucherManager object is destroyed. It may also be called in other
4248
/// places to restore the original voucher and reset the VoucherManager.
4349
void leave() {
50+
if (vouchersAreDisabled())
51+
return;
52+
4453
if (OriginalVoucher) {
4554
SWIFT_TASK_DEBUG_LOG("[%p] Restoring original voucher %p", this,
4655
*OriginalVoucher);
@@ -62,6 +71,9 @@ class VoucherManager {
6271
/// this is permanent. For Tasks, the voucher must be restored using
6372
/// restoreVoucher if the task suspends.
6473
void swapToJob(Job *job) {
74+
if (vouchersAreDisabled())
75+
return;
76+
6577
SWIFT_TASK_DEBUG_LOG("[%p] Swapping jobs to %p", this, job);
6678
assert(job);
6779
assert(job->Voucher != SWIFT_DEAD_VOUCHER);
@@ -99,6 +111,9 @@ class VoucherManager {
99111
// Take the current thread's adopted voucher and place it back into the task
100112
// that previously owned it, re-adopting the thread's original voucher.
101113
void restoreVoucher(AsyncTask *task) {
114+
if (vouchersAreDisabled())
115+
return;
116+
102117
SWIFT_TASK_DEBUG_LOG("[%p] Restoring %svoucher on task %p", this,
103118
OriginalVoucher ? "" : "missing ", task);
104119
assert(OriginalVoucher);

0 commit comments

Comments
 (0)