Skip to content

Commit cf3ee98

Browse files
authored
Merge pull request #14283 from mikeash/fix-urc-overflow-to-side-table-in-deinit
[Runtime] Fix unowned refcount overflow to side table during deinit.
2 parents 82790fa + ed4e3b9 commit cf3ee98

File tree

4 files changed

+58
-11
lines changed

4 files changed

+58
-11
lines changed

docs/RefcountingStates.graffle

12.1 KB
Binary file not shown.

stdlib/public/SwiftShims/RefCount.h

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1276,12 +1276,11 @@ class RefCounts {
12761276
// Return weak reference count.
12771277
// Note that this is not equal to the number of outstanding weak pointers.
12781278
uint32_t getWeakCount() const;
1279-
1280-
1279+
12811280
private:
12821281
HeapObject *getHeapObject();
12831282

1284-
HeapObjectSideTableEntry* allocateSideTable();
1283+
HeapObjectSideTableEntry* allocateSideTable(bool failIfDeiniting);
12851284
};
12861285

12871286
typedef RefCounts<InlineRefCountBits> InlineRefCounts;
@@ -1464,6 +1463,10 @@ class HeapObjectSideTableEntry {
14641463
uint32_t getWeakCount() const {
14651464
return refCounts.getWeakCount();
14661465
}
1466+
1467+
void *getSideTable() {
1468+
return refCounts.getSideTable();
1469+
}
14671470
};
14681471

14691472

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

@@ -230,8 +240,6 @@ static void unownedReleaseALot(TestObject *object, uint64_t count) {
230240
}
231241

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

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

0 commit comments

Comments
 (0)