Skip to content

Commit 94a9c51

Browse files
committed
[runtime] thread safety for weak references
It has been fairly easy to cause the runtime to crash on multithreaded read-read access to weak references (e.g. https://bugs.swift.org/browse/SR-192). Although weak references are value types, they can get elevated to the heap in multiple ways, such as when captured by a closure or when used as a property in a class object instance. In such cases, race conditions involving weak references could cause the runtime to perform to multiple decrement operations of the unowned reference count for a single increment; this eventually causes early deallocation, leading to use-after-free, modify-after-free and double-free errors. This commit changes the weak reference operations to use a spinlock rather than assuming thread-exclusive access, when appropriate. With this change, the crasher discussed in SR-192 no longer encounters crashes due to modify-after-free or double-free errors.
1 parent 56052cf commit 94a9c51

File tree

3 files changed

+136
-52
lines changed

3 files changed

+136
-52
lines changed

include/swift/Runtime/HeapObject.h

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -634,9 +634,15 @@ static inline void swift_unownedTakeAssign(UnownedReference *dest,
634634

635635
/// A weak reference value object. This is ABI.
636636
struct WeakReference {
637-
HeapObject *Value;
637+
uintptr_t Value;
638638
};
639639

640+
/// Return true if this is a native weak reference
641+
///
642+
/// \param ref - never null
643+
/// \return true if ref is a native weak reference
644+
bool isNativeSwiftWeakReference(WeakReference *ref);
645+
640646
/// Initialize a weak reference.
641647
///
642648
/// \param ref - never null

stdlib/public/runtime/HeapObject.cpp

Lines changed: 91 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -779,73 +779,142 @@ void swift::swift_deallocObject(HeapObject *object,
779779
}
780780
}
781781

782+
enum: uintptr_t {
783+
WR_NATIVE = 1<<(swift::heap_object_abi::ObjCReservedLowBits),
784+
WR_READING = 1<<(swift::heap_object_abi::ObjCReservedLowBits+1),
785+
786+
WR_NATIVEMASK = WR_NATIVE | swift::heap_object_abi::ObjCReservedBitsMask,
787+
};
788+
789+
static_assert(WR_READING < alignof(void*),
790+
"weakref lock bit mustn't interfere with real pointer bits");
791+
792+
enum: short {
793+
WR_SPINLIMIT = 64,
794+
};
795+
796+
bool swift::isNativeSwiftWeakReference(WeakReference *ref) {
797+
return (ref->Value & WR_NATIVEMASK) == WR_NATIVE;
798+
}
799+
782800
void swift::swift_weakInit(WeakReference *ref, HeapObject *value) {
783-
ref->Value = value;
801+
ref->Value = (uintptr_t)value | WR_NATIVE;
784802
SWIFT_RT_ENTRY_CALL(swift_unownedRetain)(value);
785803
}
786804

787805
void swift::swift_weakAssign(WeakReference *ref, HeapObject *newValue) {
788806
SWIFT_RT_ENTRY_CALL(swift_unownedRetain)(newValue);
789-
auto oldValue = ref->Value;
790-
ref->Value = newValue;
807+
auto oldValue = (HeapObject*) (ref->Value & ~WR_NATIVE);
808+
ref->Value = (uintptr_t)newValue | WR_NATIVE;
791809
SWIFT_RT_ENTRY_CALL(swift_unownedRelease)(oldValue);
792810
}
793811

794812
HeapObject *swift::swift_weakLoadStrong(WeakReference *ref) {
795-
auto object = ref->Value;
796-
if (object == nullptr) return nullptr;
813+
if (ref->Value == (uintptr_t)nullptr) {
814+
return nullptr;
815+
}
816+
817+
// ref might be visible to other threads
818+
auto ptr = __atomic_fetch_or(&ref->Value, WR_READING, __ATOMIC_RELAXED);
819+
while (ptr & WR_READING) {
820+
short c = 0;
821+
while (__atomic_load_n(&ref->Value, __ATOMIC_RELAXED) & WR_READING) {
822+
if (++c == WR_SPINLIMIT) {
823+
sched_yield();
824+
c -= 1;
825+
}
826+
}
827+
ptr = __atomic_fetch_or(&ref->Value, WR_READING, __ATOMIC_RELAXED);
828+
}
829+
830+
auto object = (HeapObject*)(ptr & ~WR_NATIVE);
831+
if (object == nullptr) {
832+
__atomic_store_n(&ref->Value, (uintptr_t)nullptr, __ATOMIC_RELAXED);
833+
return nullptr;
834+
}
797835
if (object->refCount.isDeallocating()) {
836+
__atomic_store_n(&ref->Value, (uintptr_t)nullptr, __ATOMIC_RELAXED);
798837
SWIFT_RT_ENTRY_CALL(swift_unownedRelease)(object);
799-
ref->Value = nullptr;
800838
return nullptr;
801839
}
802-
return swift_tryRetain(object);
840+
auto result = swift_tryRetain(object);
841+
__atomic_store_n(&ref->Value, ptr, __ATOMIC_RELAXED);
842+
return result;
803843
}
804844

805845
HeapObject *swift::swift_weakTakeStrong(WeakReference *ref) {
806-
auto result = swift_weakLoadStrong(ref);
807-
swift_weakDestroy(ref);
846+
auto object = (HeapObject*) (ref->Value & ~WR_NATIVE);
847+
if (object == nullptr) return nullptr;
848+
auto result = swift_tryRetain(object);
849+
ref->Value = (uintptr_t)nullptr;
850+
swift_unownedRelease(object);
808851
return result;
809852
}
810853

811854
void swift::swift_weakDestroy(WeakReference *ref) {
812-
auto tmp = ref->Value;
813-
ref->Value = nullptr;
855+
auto tmp = (HeapObject*) (ref->Value & ~WR_NATIVE);
856+
ref->Value = (uintptr_t)nullptr;
814857
SWIFT_RT_ENTRY_CALL(swift_unownedRelease)(tmp);
815858
}
816859

817860
void swift::swift_weakCopyInit(WeakReference *dest, WeakReference *src) {
818-
auto object = src->Value;
861+
if (src->Value == (uintptr_t)nullptr) {
862+
dest->Value = (uintptr_t)nullptr;
863+
return;
864+
}
865+
866+
// src might be visible to other threads
867+
auto ptr = __atomic_fetch_or(&src->Value, WR_READING, __ATOMIC_RELAXED);
868+
while (ptr & WR_READING) {
869+
short c = 0;
870+
while (__atomic_load_n(&src->Value, __ATOMIC_RELAXED) & WR_READING) {
871+
if (++c == WR_SPINLIMIT) {
872+
sched_yield();
873+
c -= 1;
874+
}
875+
}
876+
ptr = __atomic_fetch_or(&src->Value, WR_READING, __ATOMIC_RELAXED);
877+
}
878+
879+
auto object = (HeapObject*)(ptr & ~WR_NATIVE);
819880
if (object == nullptr) {
820-
dest->Value = nullptr;
881+
__atomic_store_n(&src->Value, (uintptr_t)nullptr, __ATOMIC_RELAXED);
882+
dest->Value = (uintptr_t)nullptr;
821883
} else if (object->refCount.isDeallocating()) {
822-
src->Value = nullptr;
823-
dest->Value = nullptr;
884+
__atomic_store_n(&src->Value, (uintptr_t)nullptr, __ATOMIC_RELAXED);
824885
SWIFT_RT_ENTRY_CALL(swift_unownedRelease)(object);
886+
dest->Value = (uintptr_t)nullptr;
825887
} else {
826-
dest->Value = object;
827888
SWIFT_RT_ENTRY_CALL(swift_unownedRetain)(object);
889+
__atomic_store_n(&src->Value, ptr, __ATOMIC_RELAXED);
890+
dest->Value = (uintptr_t)object | WR_NATIVE;
828891
}
829892
}
830893

831894
void swift::swift_weakTakeInit(WeakReference *dest, WeakReference *src) {
832-
auto object = src->Value;
833-
dest->Value = object;
834-
if (object != nullptr && object->refCount.isDeallocating()) {
835-
dest->Value = nullptr;
895+
auto object = (HeapObject*) (src->Value & ~WR_NATIVE);
896+
if (object == nullptr) {
897+
dest->Value = (uintptr_t)nullptr;
898+
} else if (object->refCount.isDeallocating()) {
899+
dest->Value = (uintptr_t)nullptr;
836900
SWIFT_RT_ENTRY_CALL(swift_unownedRelease)(object);
901+
} else {
902+
dest->Value = (uintptr_t)object | WR_NATIVE;
837903
}
904+
src->Value = (uintptr_t)nullptr;
838905
}
839906

840907
void swift::swift_weakCopyAssign(WeakReference *dest, WeakReference *src) {
841-
if (auto object = dest->Value) {
908+
if (dest->Value) {
909+
auto object = (HeapObject*) (dest->Value & ~WR_NATIVE);
842910
SWIFT_RT_ENTRY_CALL(swift_unownedRelease)(object);
843911
}
844912
swift_weakCopyInit(dest, src);
845913
}
846914

847915
void swift::swift_weakTakeAssign(WeakReference *dest, WeakReference *src) {
848-
if (auto object = dest->Value) {
916+
if (dest->Value) {
917+
auto object = (HeapObject*) (dest->Value & ~WR_NATIVE);
849918
SWIFT_RT_ENTRY_CALL(swift_unownedRelease)(object);
850919
}
851920
swift_weakTakeInit(dest, src);

stdlib/public/runtime/SwiftObject.mm

Lines changed: 38 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1022,7 +1022,7 @@ static void doWeakDestroy(WeakReference *addr, bool valueIsNative) {
10221022

10231023
void swift::swift_unknownWeakInit(WeakReference *addr, void *value) {
10241024
if (isObjCTaggedPointerOrNull(value)) {
1025-
addr->Value = (HeapObject*) value;
1025+
addr->Value = (uintptr_t) value;
10261026
return;
10271027
}
10281028
doWeakInit(addr, value, usesNativeSwiftReferenceCounting_allocated(value));
@@ -1033,18 +1033,18 @@ static void doWeakDestroy(WeakReference *addr, bool valueIsNative) {
10331033
// and re-initialize.
10341034
if (isObjCTaggedPointerOrNull(newValue)) {
10351035
swift_unknownWeakDestroy(addr);
1036-
addr->Value = (HeapObject*) newValue;
1036+
addr->Value = (uintptr_t) newValue;
10371037
return;
10381038
}
10391039

10401040
bool newIsNative = usesNativeSwiftReferenceCounting_allocated(newValue);
10411041

10421042
// If the existing value is not allocated, this is just an initialize.
1043-
void *oldValue = addr->Value;
1043+
void *oldValue = (void*) addr->Value;
10441044
if (isObjCTaggedPointerOrNull(oldValue))
10451045
return doWeakInit(addr, newValue, newIsNative);
10461046

1047-
bool oldIsNative = usesNativeSwiftReferenceCounting_allocated(oldValue);
1047+
bool oldIsNative = isNativeSwiftWeakReference(addr);
10481048

10491049
// If they're both native, we can use the native function.
10501050
if (oldIsNative && newIsNative)
@@ -1061,53 +1061,62 @@ static void doWeakDestroy(WeakReference *addr, bool valueIsNative) {
10611061
}
10621062

10631063
void *swift::swift_unknownWeakLoadStrong(WeakReference *addr) {
1064-
void *value = addr->Value;
1065-
if (isObjCTaggedPointerOrNull(value)) return value;
1066-
1067-
if (usesNativeSwiftReferenceCounting_allocated(value)) {
1064+
if (isNativeSwiftWeakReference(addr)) {
10681065
return swift_weakLoadStrong(addr);
1069-
} else {
1070-
return (void*) objc_loadWeakRetained((id*) &addr->Value);
10711066
}
1072-
}
10731067

1074-
void *swift::swift_unknownWeakTakeStrong(WeakReference *addr) {
1075-
void *value = addr->Value;
1068+
void *value = (void*) addr->Value;
10761069
if (isObjCTaggedPointerOrNull(value)) return value;
10771070

1078-
if (usesNativeSwiftReferenceCounting_allocated(value)) {
1071+
return (void*) objc_loadWeakRetained((id*) &addr->Value);
1072+
}
1073+
1074+
void *swift::swift_unknownWeakTakeStrong(WeakReference *addr) {
1075+
if (isNativeSwiftWeakReference(addr)) {
10791076
return swift_weakTakeStrong(addr);
1080-
} else {
1081-
void *result = (void*) objc_loadWeakRetained((id*) &addr->Value);
1082-
objc_destroyWeak((id*) &addr->Value);
1083-
return result;
10841077
}
1078+
1079+
void *value = (void*) addr->Value;
1080+
if (isObjCTaggedPointerOrNull(value)) return value;
1081+
1082+
void *result = (void*) objc_loadWeakRetained((id*) &addr->Value);
1083+
objc_destroyWeak((id*) &addr->Value);
1084+
return result;
10851085
}
10861086

10871087
void swift::swift_unknownWeakDestroy(WeakReference *addr) {
1088+
if (isNativeSwiftWeakReference(addr)) {
1089+
return swift_weakDestroy(addr);
1090+
}
1091+
10881092
id object = (id) addr->Value;
10891093
if (isObjCTaggedPointerOrNull(object)) return;
1090-
doWeakDestroy(addr, usesNativeSwiftReferenceCounting_allocated(object));
1094+
objc_destroyWeak((id*) &addr->Value);
10911095
}
1096+
10921097
void swift::swift_unknownWeakCopyInit(WeakReference *dest, WeakReference *src) {
1098+
if (isNativeSwiftWeakReference(src)) {
1099+
return swift_weakCopyInit(dest, src);
1100+
}
1101+
10931102
id object = (id) src->Value;
10941103
if (isObjCTaggedPointerOrNull(object)) {
1095-
dest->Value = (HeapObject*) object;
1096-
return;
1104+
dest->Value = (uintptr_t) object;
1105+
} else {
1106+
objc_copyWeak((id*) &dest->Value, (id*) src);
10971107
}
1098-
if (usesNativeSwiftReferenceCounting_allocated(object))
1099-
return swift_weakCopyInit(dest, src);
1100-
objc_copyWeak((id*) &dest->Value, (id*) src);
11011108
}
11021109
void swift::swift_unknownWeakTakeInit(WeakReference *dest, WeakReference *src) {
1110+
if (isNativeSwiftWeakReference(src)) {
1111+
return swift_weakTakeInit(dest, src);
1112+
}
1113+
11031114
id object = (id) src->Value;
11041115
if (isObjCTaggedPointerOrNull(object)) {
1105-
dest->Value = (HeapObject*) object;
1106-
return;
1116+
dest->Value = (uintptr_t) object;
1117+
} else {
1118+
objc_moveWeak((id*) &dest->Value, (id*) &src->Value);
11071119
}
1108-
if (usesNativeSwiftReferenceCounting_allocated(object))
1109-
return swift_weakTakeInit(dest, src);
1110-
objc_moveWeak((id*) &dest->Value, (id*) &src->Value);
11111120
}
11121121
void swift::swift_unknownWeakCopyAssign(WeakReference *dest, WeakReference *src) {
11131122
if (dest == src) return;

0 commit comments

Comments
 (0)