Skip to content

Commit 78c693b

Browse files
committed
Avoid mishandling retain_n of immortal objects where n >= 3
1 parent 0f0a908 commit 78c693b

File tree

2 files changed

+97
-4
lines changed

2 files changed

+97
-4
lines changed

stdlib/public/SwiftShims/RefCount.h

Lines changed: 45 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -805,6 +805,13 @@ class RefCounts {
805805
// Increment the reference count.
806806
void increment(uint32_t inc = 1) {
807807
auto oldbits = refCounts.load(SWIFT_MEMORY_ORDER_CONSUME);
808+
809+
// constant propagation will remove this in swift_retain, it should only
810+
// be present in swift_retain_n
811+
if (inc != 1 && oldbits.isImmortal(true)) {
812+
return;
813+
}
814+
808815
RefCountBits newbits;
809816
do {
810817
newbits = oldbits;
@@ -820,6 +827,13 @@ class RefCounts {
820827

821828
void incrementNonAtomic(uint32_t inc = 1) {
822829
auto oldbits = refCounts.load(SWIFT_MEMORY_ORDER_CONSUME);
830+
831+
// constant propagation will remove this in swift_retain, it should only
832+
// be present in swift_retain_n
833+
if (inc != 1 && oldbits.isImmortal(true)) {
834+
return;
835+
}
836+
823837
auto newbits = oldbits;
824838
bool fast = newbits.incrementStrongExtraRefCount(inc);
825839
if (SWIFT_UNLIKELY(!fast)) {
@@ -982,6 +996,12 @@ class RefCounts {
982996
bool doDecrementSlow(RefCountBits oldbits, uint32_t dec) {
983997
RefCountBits newbits;
984998

999+
// constant propagation will remove this in swift_release, it should only
1000+
// be present in swift_release_n
1001+
if (dec != 1 && oldbits.isImmortal(true)) {
1002+
return false;
1003+
}
1004+
9851005
bool deinitNow;
9861006
do {
9871007
newbits = oldbits;
@@ -1024,6 +1044,12 @@ class RefCounts {
10241044
bool doDecrementNonAtomicSlow(RefCountBits oldbits, uint32_t dec) {
10251045
bool deinitNow;
10261046
auto newbits = oldbits;
1047+
1048+
// constant propagation will remove this in swift_release, it should only
1049+
// be present in swift_release_n
1050+
if (dec != 1 && oldbits.isImmortal(true)) {
1051+
return false;
1052+
}
10271053

10281054
bool fast =
10291055
newbits.decrementStrongExtraRefCount(dec);
@@ -1066,6 +1092,12 @@ class RefCounts {
10661092
auto oldbits = refCounts.load(SWIFT_MEMORY_ORDER_CONSUME);
10671093
RefCountBits newbits;
10681094

1095+
// constant propagation will remove this in swift_release, it should only
1096+
// be present in swift_release_n
1097+
if (dec != 1 && oldbits.isImmortal(true)) {
1098+
return false;
1099+
}
1100+
10691101
do {
10701102
newbits = oldbits;
10711103
bool fast =
@@ -1248,7 +1280,15 @@ class RefCounts {
12481280
// Return weak reference count.
12491281
// Note that this is not equal to the number of outstanding weak pointers.
12501282
uint32_t getWeakCount() const;
1251-
1283+
1284+
// DO NOT TOUCH.
1285+
// This exists for the benefits of the Refcounting.cpp tests. Do not use it
1286+
// elsewhere.
1287+
auto getBitsValue()
1288+
-> decltype(auto) {
1289+
return refCounts.load(std::memory_order_relaxed).getBitsValue();
1290+
}
1291+
12521292
private:
12531293
HeapObject *getHeapObject();
12541294

@@ -1449,13 +1489,14 @@ inline bool RefCounts<InlineRefCountBits>::doDecrementNonAtomic(uint32_t dec) {
14491489
// Use slow path if we can't guarantee atomicity.
14501490
if (oldbits.hasSideTable() || oldbits.getUnownedRefCount() != 1)
14511491
return doDecrementNonAtomicSlow<performDeinit>(oldbits, dec);
1492+
1493+
if (oldbits.isImmortal(true)) {
1494+
return false;
1495+
}
14521496

14531497
auto newbits = oldbits;
14541498
bool fast = newbits.decrementStrongExtraRefCount(dec);
14551499
if (!fast) {
1456-
if (oldbits.isImmortal(false)) {
1457-
return false;
1458-
}
14591500
return doDecrementNonAtomicSlow<performDeinit>(oldbits, dec);
14601501
}
14611502

unittests/runtime/Refcounting.cpp

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@
1717
using namespace swift;
1818

1919
struct TestObject : HeapObject {
20+
constexpr TestObject(HeapMetadata const *newMetadata)
21+
: HeapObject(newMetadata, InlineRefCounts::Immortal)
22+
, Addr(NULL), Value(0) {}
23+
2024
size_t *Addr;
2125
size_t Value;
2226
};
@@ -34,6 +38,8 @@ static const FullMetadata<ClassMetadata> TestClassObjectMetadata = {
3438
{ { nullptr }, ClassFlags::UsesSwiftRefcounting, 0, 0, 0, 0, 0, 0 }
3539
};
3640

41+
static TestObject ImmortalTestObject{&TestClassObjectMetadata};
42+
3743
/// Create an object that, when deallocated, stores the given value to
3844
/// the given pointer.
3945
static TestObject *allocTestObject(size_t *addr, size_t value) {
@@ -220,3 +226,49 @@ TEST(RefcountingTest, nonatomic_unknown_retain_release_n) {
220226
EXPECT_EQ(0u, value);
221227
EXPECT_EQ(1u, swift_retainCount(object));
222228
}
229+
230+
// Verify that refcounting operations on immortal objects never changes the
231+
// refcount field.
232+
TEST(RefcountingTest, immortal_retain_release) {
233+
auto initialBitsValue = ImmortalTestObject.refCounts.getBitsValue();
234+
235+
swift_retain(&ImmortalTestObject);
236+
EXPECT_EQ(initialBitsValue, ImmortalTestObject.refCounts.getBitsValue());
237+
swift_release(&ImmortalTestObject);
238+
EXPECT_EQ(initialBitsValue, ImmortalTestObject.refCounts.getBitsValue());
239+
swift_nonatomic_retain(&ImmortalTestObject);
240+
EXPECT_EQ(initialBitsValue, ImmortalTestObject.refCounts.getBitsValue());
241+
swift_nonatomic_release(&ImmortalTestObject);
242+
EXPECT_EQ(initialBitsValue, ImmortalTestObject.refCounts.getBitsValue());
243+
244+
swift_unownedRetain(&ImmortalTestObject);
245+
EXPECT_EQ(initialBitsValue, ImmortalTestObject.refCounts.getBitsValue());
246+
swift_unownedRelease(&ImmortalTestObject);
247+
EXPECT_EQ(initialBitsValue, ImmortalTestObject.refCounts.getBitsValue());
248+
swift_nonatomic_unownedRetain(&ImmortalTestObject);
249+
EXPECT_EQ(initialBitsValue, ImmortalTestObject.refCounts.getBitsValue());
250+
swift_nonatomic_unownedRelease(&ImmortalTestObject);
251+
EXPECT_EQ(initialBitsValue, ImmortalTestObject.refCounts.getBitsValue());
252+
253+
for (unsigned i = 0; i < 32; i++) {
254+
uint32_t amount = 1U << i;
255+
256+
swift_retain_n(&ImmortalTestObject, amount);
257+
EXPECT_EQ(initialBitsValue, ImmortalTestObject.refCounts.getBitsValue());
258+
swift_release_n(&ImmortalTestObject, amount);
259+
EXPECT_EQ(initialBitsValue, ImmortalTestObject.refCounts.getBitsValue());
260+
swift_nonatomic_retain_n(&ImmortalTestObject, amount);
261+
EXPECT_EQ(initialBitsValue, ImmortalTestObject.refCounts.getBitsValue());
262+
swift_nonatomic_release_n(&ImmortalTestObject, amount);
263+
EXPECT_EQ(initialBitsValue, ImmortalTestObject.refCounts.getBitsValue());
264+
265+
swift_unownedRetain_n(&ImmortalTestObject, amount);
266+
EXPECT_EQ(initialBitsValue, ImmortalTestObject.refCounts.getBitsValue());
267+
swift_unownedRelease_n(&ImmortalTestObject, amount);
268+
EXPECT_EQ(initialBitsValue, ImmortalTestObject.refCounts.getBitsValue());
269+
swift_nonatomic_unownedRetain_n(&ImmortalTestObject, amount);
270+
EXPECT_EQ(initialBitsValue, ImmortalTestObject.refCounts.getBitsValue());
271+
swift_nonatomic_unownedRelease_n(&ImmortalTestObject, amount);
272+
EXPECT_EQ(initialBitsValue, ImmortalTestObject.refCounts.getBitsValue());
273+
}
274+
}

0 commit comments

Comments
 (0)