Skip to content

Commit 72ad6c5

Browse files
authored
Merge pull request #70945 from Azoy/fix-unmanaged-opaque
[stdlib] Avoid materializing strong references in potential dangling unmanaged opaque functions
2 parents cf44001 + f0a74ec commit 72ad6c5

File tree

2 files changed

+38
-2
lines changed

2 files changed

+38
-2
lines changed

stdlib/public/core/Unmanaged.swift

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,15 @@ 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: `value` is allowed to represent a dangling reference, so
38+
// this function must not ever try to dereference it. For
39+
// example, this function must NOT use the init(_private:) initializer
40+
// because doing so requires materializing a strong reference to 'Instance'.
41+
// This materialization would be enough to convince the compiler to add
42+
// retain/releases which must be avoided for the opaque pointer functions.
43+
// 'Unmanaged<Instance>' is layout compatible with 'UnsafeRawPointer' and
44+
// casting to that will not attempt to retain the reference held at 'value'.
45+
unsafeBitCast(value, to: Unmanaged<Instance>.self)
3846
}
3947

4048
/// Unsafely converts an unmanaged class reference to a pointer.
@@ -48,7 +56,13 @@ public struct Unmanaged<Instance: AnyObject> {
4856
/// - Returns: An opaque pointer to the value of this unmanaged reference.
4957
@_transparent
5058
public func toOpaque() -> UnsafeMutableRawPointer {
51-
return unsafeBitCast(_value, to: UnsafeMutableRawPointer.self)
59+
// NOTE: `self` is allowed to be a dangling reference.
60+
// Therefore, this function must not unsafeBitCast '_value' because
61+
// that will get a strong reference temporary value that the compiler will
62+
// try to retain/release. Use 'self' to avoid this. 'Unmanaged<Instance>' is
63+
// layout compatible with 'UnsafeRawPointer' and casting from that will not
64+
// attempt to retain the reference held at '_value'.
65+
unsafeBitCast(self, to: UnsafeMutableRawPointer.self)
5266
}
5367

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

test/stdlib/Unmanaged.swift

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,4 +71,26 @@ 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+
// Create a dangling pointer, usually to unmapped memory.
84+
let ref = UnsafeRawPointer(bitPattern: 1)!
85+
// Turn it into a dangling unmanaged reference.
86+
// We expect this not to crash, as this operation isn't
87+
// supposed to dereference the pointer in any way.
88+
let unmanaged = Unmanaged<Foobar>.fromOpaque(ref)
89+
// Similarly, converting the unmanaged reference back to a
90+
// pointer should not ever try to dereference it either.
91+
let ref2 = unmanaged.toOpaque()
92+
// ...and we must get back the same pointer.
93+
expectEqual(ref, ref2)
94+
}
95+
7496
runAllTests()

0 commit comments

Comments
 (0)