Skip to content

Commit 146833c

Browse files
committed
[Runtime] Fix unowned refcount overflow to side table during deinit.
32-bit has a 7-bit inline unowned refcount, then 31 bits in the side table. Overflowing the inline count in deinit on an object that didn't already have a side table would crash, because the code assumed that creating a side table in deinit was not allowed. (64-bit has 31 bits inline and in the side table. Overflowing the inline count immediately overflows the side table as well, so there's no change in behavior there.) rdar://problem/33765960
1 parent 9c37cb8 commit 146833c

File tree

4 files changed

+62
-10
lines changed

4 files changed

+62
-10
lines changed

docs/RefcountingStates.graffle

12.1 KB
Binary file not shown.

stdlib/public/SwiftShims/RefCount.h

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1265,12 +1265,16 @@ class RefCounts {
12651265
// Return weak reference count.
12661266
// Note that this is not equal to the number of outstanding weak pointers.
12671267
uint32_t getWeakCount() const;
1268-
1268+
1269+
HeapObjectSideTableEntry *getSideTable() const {
1270+
auto bits = refCounts.load(SWIFT_MEMORY_ORDER_CONSUME);
1271+
return bits.getSideTable();
1272+
}
12691273

12701274
private:
12711275
HeapObject *getHeapObject();
12721276

1273-
HeapObjectSideTableEntry* allocateSideTable();
1277+
HeapObjectSideTableEntry* allocateSideTable(bool failIfDeiniting);
12741278
};
12751279

12761280
typedef RefCounts<InlineRefCountBits> InlineRefCounts;
@@ -1453,6 +1457,10 @@ class HeapObjectSideTableEntry {
14531457
uint32_t getWeakCount() const {
14541458
return refCounts.getWeakCount();
14551459
}
1460+
1461+
HeapObjectSideTableEntry *getSideTable() {
1462+
return refCounts.getSideTable();
1463+
}
14561464
};
14571465

14581466

stdlib/public/runtime/RefCount.cpp

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ template bool RefCounts<SideTableRefCountBits>::tryIncrementAndPinNonAtomicSlow(
8989
// Returns null if the object is deiniting.
9090
// SideTableRefCountBits specialization intentionally does not exist.
9191
template <>
92-
HeapObjectSideTableEntry* RefCounts<InlineRefCountBits>::allocateSideTable()
92+
HeapObjectSideTableEntry* RefCounts<InlineRefCountBits>::allocateSideTable(bool failIfDeiniting)
9393
{
9494
auto oldbits = refCounts.load(SWIFT_MEMORY_ORDER_CONSUME);
9595

@@ -98,7 +98,7 @@ HeapObjectSideTableEntry* RefCounts<InlineRefCountBits>::allocateSideTable()
9898
// Already have a side table. Return it.
9999
return oldbits.getSideTable();
100100
}
101-
else if (oldbits.getIsDeiniting()) {
101+
else if (failIfDeiniting && oldbits.getIsDeiniting()) {
102102
// Already past the start of deinit. Do nothing.
103103
return nullptr;
104104
}
@@ -118,7 +118,7 @@ HeapObjectSideTableEntry* RefCounts<InlineRefCountBits>::allocateSideTable()
118118
delete side;
119119
return result;
120120
}
121-
else if (oldbits.getIsDeiniting()) {
121+
else if (failIfDeiniting && oldbits.getIsDeiniting()) {
122122
// Already past the start of deinit. Do nothing.
123123
return nullptr;
124124
}
@@ -136,7 +136,7 @@ HeapObjectSideTableEntry* RefCounts<InlineRefCountBits>::allocateSideTable()
136136
template <>
137137
HeapObjectSideTableEntry* RefCounts<InlineRefCountBits>::formWeakReference()
138138
{
139-
auto side = allocateSideTable();
139+
auto side = allocateSideTable(true);
140140
if (side)
141141
return side->incrementWeak();
142142
else
@@ -145,7 +145,7 @@ HeapObjectSideTableEntry* RefCounts<InlineRefCountBits>::formWeakReference()
145145

146146
template <typename RefCountBits>
147147
void RefCounts<RefCountBits>::incrementUnownedSlow(uint32_t n) {
148-
auto side = allocateSideTable();
148+
auto side = allocateSideTable(false);
149149
if (side)
150150
return side->incrementUnowned(n);
151151
// Overflow but side table allocation failed.

unittests/runtime/LongTests/LongRefcounting.cpp

Lines changed: 47 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
//
1111
//===----------------------------------------------------------------------===//
1212

13+
#include <functional>
14+
1315
#include "swift/Runtime/HeapObject.h"
1416
#include "swift/Runtime/Metadata.h"
1517
#include "swift/Demangling/ManglingMacros.h"
@@ -42,8 +44,11 @@ struct TestObject : HeapObject {
4244
// On exit from deinit: is destroyed
4345
WeakReference *WeakRef;
4446

47+
// Callback invoked during the object's deinit.
48+
std::function<void()> DeinitCallback;
49+
4550
TestObject(size_t *addr, size_t value)
46-
: Addr(addr), Value(value), CheckLifecycle(false), WeakRef(nullptr)
51+
: Addr(addr), Value(value), CheckLifecycle(false), WeakRef(nullptr), DeinitCallback(nullptr)
4752
{ }
4853
};
4954

@@ -91,8 +96,13 @@ static SWIFT_CC(swift) void deinitTestObject(SWIFT_CONTEXT HeapObject *_object)
9196
}
9297
}
9398

99+
if (object->DeinitCallback != nullptr) {
100+
object->DeinitCallback();
101+
}
102+
94103
*object->Addr = object->Value;
95104
object->Addr = nullptr;
105+
object->~TestObject();
96106
swift_deallocObject(object, sizeof(TestObject), alignof(TestObject) - 1);
97107
}
98108

@@ -231,8 +241,6 @@ static void unownedReleaseALot(TestObject *object, uint64_t count) {
231241
}
232242

233243
// Maximum legal unowned retain count. 31 bits with no implicit +1.
234-
// (FIXME 32-bit architecture has 7 bit inline count;
235-
// that bound does not yet have its own tests.)
236244
const uint64_t maxURC = (1ULL << (32 - 1)) - 1;
237245

238246
TEST(LongRefcountingTest, unowned_retain_max) {
@@ -1047,3 +1055,39 @@ TEST(LongRefcountingTest, lifecycle_live_deiniting_deinited_freed_with_side_Deat
10471055
EXPECT_UNALLOCATED(side);
10481056
EXPECT_EQ(0, weakValue);
10491057
}
1058+
1059+
TEST(LongRefcountingTest, lifecycle_live_deiniting_urc_overflow_to_side) {
1060+
::testing::FLAGS_gtest_death_test_style = "threadsafe";
1061+
1062+
uint64_t urcOverflowCount;
1063+
switch(sizeof(void *)) {
1064+
// 32-bit has a 7-bit inline refcount that overflows into the side table.
1065+
case 4: urcOverflowCount = 1 << 7; break;
1066+
1067+
// 64-bit can't store any extra count in the side table, so there's nothing to test.
1068+
case 8: return;
1069+
1070+
// We should never see any other bitness.
1071+
default: FAIL(); break;
1072+
}
1073+
1074+
size_t deinited = 0;
1075+
auto object = allocTestObject(&deinited, 1);
1076+
HeapObjectSideTableEntry *side = nullptr;
1077+
object->DeinitCallback = [&]() {
1078+
for (uint64_t i = 0; i < urcOverflowCount; i++) {
1079+
swift_unownedRetain(object);
1080+
}
1081+
1082+
side = object->refCounts.getSideTable();
1083+
EXPECT_ALLOCATED(side);
1084+
1085+
for (uint64_t i = 0; i < urcOverflowCount; i++) {
1086+
swift_unownedRelease(object);
1087+
}
1088+
};
1089+
1090+
swift_release(object);
1091+
EXPECT_UNALLOCATED(object);
1092+
EXPECT_UNALLOCATED(side);
1093+
}

0 commit comments

Comments
 (0)