Skip to content

Commit 1fe1b86

Browse files
authored
Add a safe API for NSValue and migrate NSValue value fetching to the size variants for validation (#22265)
1 parent 6aad52a commit 1fe1b86

File tree

3 files changed

+70
-3
lines changed

3 files changed

+70
-3
lines changed

stdlib/public/Darwin/Foundation/NSValue.swift.gyb

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,3 +23,31 @@ ${ ObjectiveCBridgeableImplementationForNSValue("CGVector") }
2323
${ ObjectiveCBridgeableImplementationForNSValue("CGSize") }
2424
${ ObjectiveCBridgeableImplementationForNSValue("CGAffineTransform") }
2525

26+
extension NSValue {
27+
public func value<StoredType>(of type: StoredType.Type) -> StoredType? {
28+
if StoredType.self is AnyObject.Type {
29+
let encoding = String(cString: objCType)
30+
// some subclasses of NSValue can return @ and the default initialized variant returns ^v for encoding
31+
guard encoding == "^v" || encoding == "@" else {
32+
return nil
33+
}
34+
return nonretainedObjectValue as? StoredType
35+
} else if _isPOD(StoredType.self) {
36+
var storedSize = 0
37+
var storedAlignment = 0
38+
NSGetSizeAndAlignment(objCType, &storedSize, &storedAlignment)
39+
guard MemoryLayout<StoredType>.size == storedSize && MemoryLayout<StoredType>.alignment == storedAlignment else {
40+
return nil
41+
}
42+
let allocated = UnsafeMutablePointer<StoredType>.allocate(capacity: 1)
43+
defer { allocated.deallocate() }
44+
if #available(OSX 10.13, iOS 11.0, tvOS 11.0, watchOS 4.0, *) {
45+
getValue(allocated, size: storedSize)
46+
} else {
47+
getValue(allocated)
48+
}
49+
return allocated.pointee
50+
}
51+
return nil
52+
}
53+
}

test/stdlib/NSValueBridging.swift.gyb

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,4 +91,31 @@ nsValueBridging.test("NSValue can only be cast back to its original type") {
9191
_ = rangeObject as! CGRect
9292
}
9393

94+
nsValueBridging.test("NSValue fetching method should be able to convert constructed values safely") {
95+
let range = NSRange(location: 17, length: 38)
96+
let value = NSValue(range: range)
97+
expectEqual(value.value(of: NSRange.self)?.location, range.location)
98+
expectEqual(value.value(of: NSRange.self)?.length, range.length)
99+
expectEqual(value.value(of: CGRect.self), .none)
100+
expectEqual(value.value(of: String.self), .none)
101+
expectTrue(value.value(of: AnyObject.self) == nil)
102+
103+
104+
class GenericThingNotRootedInObjC<T> {
105+
init() { }
106+
}
107+
108+
let obj = GenericThingNotRootedInObjC<String>()
109+
// extend the lifetime to ensure that the non retained pointer is valid for the test duration
110+
withExtendedLifetime(obj) { () -> Void in
111+
let value = NSValue(nonretainedObject: obj)
112+
let resString = value.value(of: GenericThingNotRootedInObjC<String>.self)
113+
expectEqual(resString === obj, true) // ensure the value is exactly the same
114+
115+
let resInt = value.value(of: GenericThingNotRootedInObjC<Int>.self)
116+
expectTrue(resInt == nil)
117+
}
118+
119+
}
120+
94121
runAllTests()

utils/gyb_foundation_support.py

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,11 @@ def ObjectiveCBridgeableImplementationForNSValue(Type):
1212
_getObjCTypeEncoding({Type}.self)) == 0,
1313
"NSValue does not contain the right type to bridge to {Type}")
1414
result = {Type}()
15-
source.getValue(&result!)
15+
if #available(OSX 10.13, iOS 11.0, tvOS 11.0, watchOS 4.0, *) {{
16+
source.getValue(&result!, size: MemoryLayout<{Type}>.size)
17+
}} else {{
18+
source.getValue(&result!)
19+
}}
1620
}}
1721
1822
public static func _conditionallyBridgeFromObjectiveC(_ source: NSValue,
@@ -23,7 +27,11 @@ def ObjectiveCBridgeableImplementationForNSValue(Type):
2327
return false
2428
}}
2529
result = {Type}()
26-
source.getValue(&result!)
30+
if #available(OSX 10.13, iOS 11.0, tvOS 11.0, watchOS 4.0, *) {{
31+
source.getValue(&result!, size: MemoryLayout<{Type}>.size)
32+
}} else {{
33+
source.getValue(&result!)
34+
}}
2735
return true
2836
}}
2937
@@ -34,7 +42,11 @@ def ObjectiveCBridgeableImplementationForNSValue(Type):
3442
_getObjCTypeEncoding({Type}.self)) == 0,
3543
"NSValue does not contain the right type to bridge to {Type}")
3644
var result = {Type}()
37-
unwrappedSource.getValue(&result)
45+
if #available(OSX 10.13, iOS 11.0, tvOS 11.0, watchOS 4.0, *) {{
46+
unwrappedSource.getValue(&result, size: MemoryLayout<{Type}>.size)
47+
}} else {{
48+
unwrappedSource.getValue(&result)
49+
}}
3850
return result
3951
}}
4052
}}

0 commit comments

Comments
 (0)