Files
swift-mirror/test/Interpreter/refcount_overflow.swift
Mike Ash 13b58a0b5c [Runtime] Fix strong and unowned refcount overflow on 32-bit.
Fix overflow detection on unowned refcounts so that we create a side table when incrementing from 126. Implement strong refcount overflow to the side table.

The unowned refcount is never supposed to be 127, because that (sometimes) represents the immortal refcount. We attempt to detect that by checking newValue == Offsets::UnownedRefCountMask, but the mask is shifted so that condition is never true. We managed to hit the side table case when incrementing from 127, because it looks like the immortal case. But that broke when we fixed immortal side table initialization in b41079a8f54ae2d61c68cdda46c74232084af020. With that change, we now create an immortal side table when overflowing the unowned refcount, then try to increment the unowned refcount in that immortal side table, which traps.

rdar://123788910
2024-03-01 09:42:45 -05:00

227 lines
6.1 KiB
Swift

// RUN: %empty-directory(%t)
//
// RUN: %target-clang -x c %S/Inputs/retain_release_wrappers.c -c -o %t/retain_release_wrappers.o
// RUN: %target-build-swift -enable-experimental-feature Extern %t/retain_release_wrappers.o %s -o %t/refcount_overflow
// RUN: %target-codesign %t/refcount_overflow
// RUN: %target-run %t/refcount_overflow
// REQUIRES: executable_test
// UNSUPPORTED: use_os_stdlib
// UNSUPPORTED: back_deployment_runtime
import StdlibUnittest
// Declarations of runtime ABI refcounting functions.
@_extern(c)
func wrapper_swift_retain_n(_ obj: UnsafeMutableRawPointer, _ n: UInt32) -> UnsafeMutableRawPointer
@_extern(c)
func wrapper_swift_release_n(_ obj: UnsafeMutableRawPointer, _ n: UInt32)
@_extern(c)
func wrapper_swift_nonatomic_retain_n(_ obj: UnsafeMutableRawPointer, _ n: UInt32) -> UnsafeMutableRawPointer
@_extern(c)
func wrapper_swift_nonatomic_release_n(_ obj: UnsafeMutableRawPointer, _ n: UInt32)
@_extern(c)
func wrapper_swift_unownedRetain_n(_ obj: UnsafeMutableRawPointer, _ n: UInt32) -> UnsafeMutableRawPointer
@_extern(c)
func wrapper_swift_unownedRelease_n(_ obj: UnsafeMutableRawPointer, _ n: UInt32)
@_extern(c)
func wrapper_swift_nonatomic_unownedRetain_n(_ obj: UnsafeMutableRawPointer, _ n: UInt32) -> UnsafeMutableRawPointer
@_extern(c)
func wrapper_swift_nonatomic_unownedRelease_n(_ obj: UnsafeMutableRawPointer, _ n: UInt32)
// Maximum legal retain count.
// 30 bits of extra retain count, plus 1 for the implicit retain.
let maxRC = (1 as UInt32) << 30
// Maximum retain count that fits inline on 32-bit.
let maxInlineRC32 = (1 as UInt32) << 22
// Maximum unowned count that fits inline on 32-bit.
let maxInlineURC32 = (1 as UInt32) << 7
var didDeinit = false
class C {
deinit {
didDeinit = true
}
}
// Get a pointer to an object with a single strong retain count. This prevents
// ARC from doing different things to us depending on optimization level.
func getTestObject() -> UnsafeMutableRawPointer {
let obj = C()
let ptr = unsafeBitCast(obj, to: UnsafeMutableRawPointer.self)
_ = wrapper_swift_retain_n(ptr, 1)
return ptr
}
// Balance the retain from getTestObject.
func releaseTestObject(_ obj: UnsafeMutableRawPointer) {
wrapper_swift_release_n(obj, 1)
}
let RefcountOverflowTests = TestSuite("RefcountOverflow")
RefcountOverflowTests.test("retain") {
do {
let obj = getTestObject()
_ = wrapper_swift_retain_n(obj, maxRC - 1)
wrapper_swift_release_n(obj, maxRC - 1)
releaseTestObject(obj)
}
expectTrue(didDeinit)
didDeinit = false
}
RefcountOverflowTests.test("retain2") {
do {
let obj = getTestObject()
_ = wrapper_swift_retain_n(obj, maxRC - 1)
wrapper_swift_release_n(obj, maxRC - 1)
releaseTestObject(obj)
}
expectTrue(didDeinit)
didDeinit = false
}
RefcountOverflowTests.test("nonatomic_retain") {
do {
let obj = getTestObject()
_ = wrapper_swift_nonatomic_retain_n(obj, maxRC - 1)
wrapper_swift_nonatomic_release_n(obj, maxRC - 1)
releaseTestObject(obj)
}
expectTrue(didDeinit)
didDeinit = false
}
RefcountOverflowTests.test("retain one by one") {
do {
// Retain to near maxInlineRC32, then +1 over the limit.
let obj = getTestObject()
_ = wrapper_swift_retain_n(obj, maxInlineRC32 - 100)
for _ in 0..<200 {
_ = wrapper_swift_retain_n(obj, 1)
}
for _ in 0..<200 {
wrapper_swift_release_n(obj, 1)
}
wrapper_swift_release_n(obj, maxInlineRC32 - 100)
releaseTestObject(obj)
}
expectTrue(didDeinit)
didDeinit = false
}
RefcountOverflowTests.test("nonatomic_retain one by one") {
do {
// Retain to near maxInlineRC32, then +1 over the limit.
let obj = getTestObject()
_ = wrapper_swift_nonatomic_retain_n(obj, maxInlineRC32 - 100)
for _ in 0..<200 {
_ = wrapper_swift_nonatomic_retain_n(obj, 1)
}
for _ in 0..<200 {
wrapper_swift_nonatomic_release_n(obj, 1)
}
wrapper_swift_nonatomic_release_n(obj, maxInlineRC32 - 100)
releaseTestObject(obj)
}
expectTrue(didDeinit)
didDeinit = false
}
RefcountOverflowTests.test("unownedRetain") {
do {
let obj = getTestObject()
_ = wrapper_swift_unownedRetain_n(obj, maxRC - 1)
wrapper_swift_unownedRelease_n(obj, maxRC - 1)
releaseTestObject(obj)
}
expectTrue(didDeinit)
didDeinit = false
}
RefcountOverflowTests.test("nonatomic_unownedRetain") {
do {
let obj = getTestObject()
_ = wrapper_swift_nonatomic_unownedRetain_n(obj, maxRC - 1)
wrapper_swift_nonatomic_unownedRelease_n(obj, maxRC - 1)
releaseTestObject(obj)
}
expectTrue(didDeinit)
didDeinit = false
}
RefcountOverflowTests.test("unownedRetain one by one") {
do {
let obj = getTestObject()
let n = maxInlineURC32 * 2
for _ in 0..<n {
_ = wrapper_swift_unownedRetain_n(obj, 1)
}
for _ in 0..<n {
wrapper_swift_unownedRelease_n(obj, 1)
}
releaseTestObject(obj)
}
expectTrue(didDeinit)
didDeinit = false
}
RefcountOverflowTests.test("nonatomic_unownedRetain one by one") {
do {
let obj = getTestObject()
let n = maxInlineURC32 * 2
for _ in 0..<n {
_ = wrapper_swift_nonatomic_unownedRetain_n(obj, 1)
}
for _ in 0..<n {
wrapper_swift_nonatomic_unownedRelease_n(obj, 1)
}
releaseTestObject(obj)
}
expectTrue(didDeinit)
didDeinit = false
}
RefcountOverflowTests.test("unownedRetain moderate increment") {
do {
let obj = getTestObject()
let n = maxInlineURC32 * 3 / 4
_ = wrapper_swift_unownedRetain_n(obj, n)
_ = wrapper_swift_unownedRetain_n(obj, n)
wrapper_swift_unownedRelease_n(obj, n)
wrapper_swift_unownedRelease_n(obj, n)
releaseTestObject(obj)
}
expectTrue(didDeinit)
didDeinit = false
}
RefcountOverflowTests.test("nonatomic_unownedRetain moderate increment") {
do {
let obj = getTestObject()
let n = maxInlineURC32 * 3 / 4
_ = wrapper_swift_nonatomic_unownedRetain_n(obj, n)
_ = wrapper_swift_nonatomic_unownedRetain_n(obj, n)
wrapper_swift_nonatomic_unownedRelease_n(obj, n)
wrapper_swift_nonatomic_unownedRelease_n(obj, n)
releaseTestObject(obj)
}
expectTrue(didDeinit)
didDeinit = false
}
runAllTests()