Skip to content

Commit c384b1c

Browse files
committed
Avoid materializing strong references in potential dangling unmanaged opaque functions
Add test and comments
1 parent 8b514ec commit c384b1c

File tree

2 files changed

+31
-2
lines changed

2 files changed

+31
-2
lines changed

stdlib/public/core/Unmanaged.swift

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,13 @@ public struct Unmanaged<Instance: AnyObject> {
3434
public static func fromOpaque(
3535
@_nonEphemeral _ value: UnsafeRawPointer
3636
) -> Unmanaged {
37-
return Unmanaged(_private: unsafeBitCast(value, to: Instance.self))
37+
// NOTE: This function does NOT go through the init(_private:) initializer
38+
// because it requires us to materialize a strong reference to 'Instance'.
39+
// This materialization is enough to convince the compiler to add
40+
// retain/releases which we want to avoid for the opaque pointer functions.
41+
// 'Unmanaged<Instance>' is layout compatible with 'UnsafeRawPointer' and
42+
// casting to that will not attempt to retain the reference held at 'value'.
43+
unsafeBitCast(value, to: Unmanaged<Instance>.self)
3844
}
3945

4046
/// Unsafely converts an unmanaged class reference to a pointer.
@@ -48,7 +54,12 @@ public struct Unmanaged<Instance: AnyObject> {
4854
/// - Returns: An opaque pointer to the value of this unmanaged reference.
4955
@_transparent
5056
public func toOpaque() -> UnsafeMutableRawPointer {
51-
return unsafeBitCast(_value, to: UnsafeMutableRawPointer.self)
57+
// NOTE: This function does not attempt to unsafeBitCast '_value' because
58+
// that will get a strong reference temporary value who the compiler will
59+
// try to retain/release. Use 'self' to avoid this. 'Unmanaged<Instance>' is
60+
// layout compatible with 'UnsafeRawPointer' and casting from that will not
61+
// attempt to retain the reference held at '_value'.
62+
unsafeBitCast(self, to: UnsafeMutableRawPointer.self)
5263
}
5364

5465
/// Creates an unmanaged reference with an unbalanced retain.

test/stdlib/Unmanaged.swift

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,4 +71,22 @@ UnmanagedTests.test("Opaque") {
7171
_fixLifetime(ref)
7272
}
7373

74+
UnmanagedTests.test("Opaque avoid retain/release") {
75+
// The purpose of this test is to ensure that the fromOpaque/toOpaque calls do
76+
// NOT attempt to retain/release the class instance at the passed pointer.
77+
// Here we're simulating a dangling pointer referencing a class who has
78+
// already been released (the allocated pointer points at nothing) and
79+
// attempting to create an Unmanaged instance from it and get back the
80+
// pointer. This test's expectEqual is kind of bogus, we're just checking that
81+
// it doesn't crash.
82+
83+
let ref = UnsafeMutablePointer<Int>(capacity: 1)
84+
let unmanaged = Unmanaged.fromOpaque(UnsafeRawPointer(ref))
85+
let ref2 = unmanaged.toOpaque()
86+
87+
expectEqual(ref, ref2)
88+
89+
ref.deallocate()
90+
}
91+
7492
runAllTests()

0 commit comments

Comments
 (0)