Skip to content

Commit bdb13a5

Browse files
authored
Merge pull request #80 from apple/glessard/rdar91436410
Mitigate misuse of Swift's pointer conversion feature
2 parents 74eb6f4 + 3920df7 commit bdb13a5

File tree

3 files changed

+434
-2
lines changed

3 files changed

+434
-2
lines changed

Sources/System/FilePath/FilePathString.swift

Lines changed: 166 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,49 @@ extension FilePath {
2020
self.init(_platformString: platformString)
2121
}
2222

23+
/// Creates a file path by copying bytes from a null-terminated platform
24+
/// string.
25+
///
26+
/// - Note It is a precondition that `platformString` must be null-terminated.
27+
/// The absence of a null byte will trigger a runtime error.
28+
///
29+
/// - Parameter platformString: A null-terminated platform string.
30+
@inlinable
31+
@_alwaysEmitIntoClient
32+
public init(platformString: [CInterop.PlatformChar]) {
33+
guard let _ = platformString.firstIndex(of: 0) else {
34+
fatalError(
35+
"input of FilePath.init(platformString:) must be null-terminated"
36+
)
37+
}
38+
self = platformString.withUnsafeBufferPointer {
39+
FilePath(platformString: $0.baseAddress!)
40+
}
41+
}
42+
43+
@inlinable
44+
@_alwaysEmitIntoClient
45+
@available(*, deprecated, message: "Use FilePath.init(_ scalar: Unicode.Scalar)")
46+
public init(platformString: inout CInterop.PlatformChar) {
47+
guard platformString == 0 else {
48+
fatalError(
49+
"input of FilePath.init(platformString:) must be null-terminated"
50+
)
51+
}
52+
self = FilePath()
53+
}
54+
55+
@inlinable
56+
@_alwaysEmitIntoClient
57+
@available(*, deprecated, message: "Use FilePath(_: String) to create a path from a String")
58+
public init(platformString: String) {
59+
if let nullLoc = platformString.firstIndex(of: "\0") {
60+
self = FilePath(String(platformString[..<nullLoc]))
61+
} else {
62+
self = FilePath(platformString)
63+
}
64+
}
65+
2366
/// Calls the given closure with a pointer to the contents of the file path,
2467
/// represented as a null-terminated platform string.
2568
///
@@ -60,6 +103,59 @@ extension FilePath.Component {
60103
self.init(_platformString: platformString)
61104
}
62105

106+
/// Creates a file path component by copying bytes from a null-terminated
107+
/// platform string. It is a precondition that a null byte indicates the end of
108+
/// the string. The absence of a null byte will trigger a runtime error.
109+
///
110+
/// Returns `nil` if `platformString` is empty, is a root, or has more than
111+
/// one component in it.
112+
///
113+
/// - Note It is a precondition that `platformString` must be null-terminated.
114+
/// The absence of a null byte will trigger a runtime error.
115+
///
116+
/// - Parameter platformString: A null-terminated platform string.
117+
@inlinable
118+
@_alwaysEmitIntoClient
119+
public init?(platformString: [CInterop.PlatformChar]) {
120+
guard let _ = platformString.firstIndex(of: 0) else {
121+
fatalError(
122+
"input of FilePath.Component.init?(platformString:) must be null-terminated"
123+
)
124+
}
125+
guard let component = platformString.withUnsafeBufferPointer({
126+
FilePath.Component(platformString: $0.baseAddress!)
127+
}) else {
128+
return nil
129+
}
130+
self = component
131+
}
132+
133+
@inlinable
134+
@_alwaysEmitIntoClient
135+
@available(*, deprecated, message: "Use FilePath.Component.init(_ scalar: Unicode.Scalar)")
136+
public init?(platformString: inout CInterop.PlatformChar) {
137+
guard platformString == 0 else {
138+
fatalError(
139+
"input of FilePath.Component.init?(platformString:) must be null-terminated"
140+
)
141+
}
142+
return nil
143+
}
144+
145+
@inlinable
146+
@_alwaysEmitIntoClient
147+
@available(*, deprecated, message: "Use FilePath.Component.init(_: String)")
148+
public init?(platformString: String) {
149+
let string: String
150+
if let nullLoc = platformString.firstIndex(of: "\0") {
151+
string = String(platformString[..<nullLoc])
152+
} else {
153+
string = platformString
154+
}
155+
guard let component = FilePath.Component(string) else { return nil }
156+
self = component
157+
}
158+
63159
/// Calls the given closure with a pointer to the contents of the file path
64160
/// component, represented as a null-terminated platform string.
65161
///
@@ -96,6 +192,58 @@ extension FilePath.Root {
96192
self.init(_platformString: platformString)
97193
}
98194

195+
/// Creates a file path root by copying bytes from a null-terminated platform
196+
/// string. It is a precondition that a null byte indicates the end of
197+
/// the string. The absence of a null byte will trigger a runtime error.
198+
///
199+
/// Returns `nil` if `platformString` is empty or is not a root.
200+
///
201+
/// - Note It is a precondition that `platformString` must be null-terminated.
202+
/// The absence of a null byte will trigger a runtime error.
203+
///
204+
/// - Parameter platformString: A null-terminated platform string.
205+
@inlinable
206+
@_alwaysEmitIntoClient
207+
public init?(platformString: [CInterop.PlatformChar]) {
208+
guard let _ = platformString.firstIndex(of: 0) else {
209+
fatalError(
210+
"input of FilePath.Root.init?(platformString:) must be null-terminated"
211+
)
212+
}
213+
guard let component = platformString.withUnsafeBufferPointer({
214+
FilePath.Root(platformString: $0.baseAddress!)
215+
}) else {
216+
return nil
217+
}
218+
self = component
219+
}
220+
221+
@inlinable
222+
@_alwaysEmitIntoClient
223+
@available(*, deprecated, message: "Use FilePath.Root.init(_ scalar: Unicode.Scalar)")
224+
public init?(platformString: inout CInterop.PlatformChar) {
225+
guard platformString == 0 else {
226+
fatalError(
227+
"input of FilePath.Root.init?(platformString:) must be null-terminated"
228+
)
229+
}
230+
return nil
231+
}
232+
233+
@inlinable
234+
@_alwaysEmitIntoClient
235+
@available(*, deprecated, message: "Use FilePath.Root.init(_: String)")
236+
public init?(platformString: String) {
237+
let string: String
238+
if let nullLoc = platformString.firstIndex(of: "\0") {
239+
string = String(platformString[..<nullLoc])
240+
} else {
241+
string = platformString
242+
}
243+
guard let root = FilePath.Root(string) else { return nil }
244+
self = root
245+
}
246+
99247
/// Calls the given closure with a pointer to the contents of the file path
100248
/// root, represented as a null-terminated platform string.
101249
///
@@ -148,7 +296,7 @@ extension FilePath.Component: ExpressibleByStringLiteral {
148296
public init(stringLiteral: String) {
149297
guard let s = FilePath.Component(stringLiteral) else {
150298
// TODO: static assert
151-
preconditionFailure("""
299+
fatalError("""
152300
FilePath.Component must be created from exactly one non-root component
153301
""")
154302
}
@@ -172,7 +320,7 @@ extension FilePath.Root: ExpressibleByStringLiteral {
172320
public init(stringLiteral: String) {
173321
guard let s = FilePath.Root(stringLiteral) else {
174322
// TODO: static assert
175-
preconditionFailure("""
323+
fatalError("""
176324
FilePath.Root must be created from a root
177325
""")
178326
}
@@ -405,10 +553,26 @@ extension String {
405553
extension FilePath {
406554
/// For backwards compatibility only. This initializer is equivalent to
407555
/// the preferred `FilePath(platformString:)`.
556+
@available(*, deprecated, renamed: "init(platformString:)")
408557
public init(cString: UnsafePointer<CChar>) {
409558
self.init(platformString: cString)
410559
}
411560

561+
@available(*, deprecated, renamed: "init(platformString:)")
562+
public init(cString: [CChar]) {
563+
self.init(platformString: cString)
564+
}
565+
566+
@available(*, deprecated, renamed: "init(platformString:)")
567+
public init(cString: inout CChar) {
568+
self.init(platformString: &cString)
569+
}
570+
571+
@available(*, deprecated, renamed: "init(platformString:)")
572+
public init(cString: String) {
573+
self.init(platformString: cString)
574+
}
575+
412576
/// For backwards compatibility only. This function is equivalent to
413577
/// the preferred `withPlatformString`.
414578
public func withCString<Result>(

Sources/System/PlatformString.swift

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,56 @@ extension String {
2424
self.init(_errorCorrectingPlatformString: platformString)
2525
}
2626

27+
/// Creates a string by interpreting the null-terminated platform string as
28+
/// UTF-8 on Unix and UTF-16 on Windows.
29+
///
30+
/// - Parameter platformString: The null-terminated platform string to be
31+
/// interpreted as `CInterop.PlatformUnicodeEncoding`.
32+
///
33+
/// - Note It is a precondition that `platformString` must be null-terminated.
34+
/// The absence of a null byte will trigger a runtime error.
35+
///
36+
/// If the content of the platform string isn't well-formed Unicode,
37+
/// this initializer replaces invalid bytes with U+FFFD.
38+
/// This means that, depending on the semantics of the specific platform,
39+
/// conversion to a string and back might result in a value that's different
40+
/// from the original platform string.
41+
@inlinable
42+
@_alwaysEmitIntoClient
43+
public init(platformString: [CInterop.PlatformChar]) {
44+
guard let _ = platformString.firstIndex(of: 0) else {
45+
fatalError(
46+
"input of String.init(platformString:) must be null-terminated"
47+
)
48+
}
49+
self = platformString.withUnsafeBufferPointer {
50+
String(platformString: $0.baseAddress!)
51+
}
52+
}
53+
54+
@inlinable
55+
@_alwaysEmitIntoClient
56+
@available(*, deprecated, message: "Use String.init(_ scalar: Unicode.Scalar)")
57+
public init(platformString: inout CInterop.PlatformChar) {
58+
guard platformString == 0 else {
59+
fatalError(
60+
"input of String.init(platformString:) must be null-terminated"
61+
)
62+
}
63+
self = ""
64+
}
65+
66+
@inlinable
67+
@_alwaysEmitIntoClient
68+
@available(*, deprecated, message: "Use a copy of the String argument")
69+
public init(platformString: String) {
70+
if let nullLoc = platformString.firstIndex(of: "\0") {
71+
self = String(platformString[..<nullLoc])
72+
} else {
73+
self = platformString
74+
}
75+
}
76+
2777
/// Creates a string by interpreting the null-terminated platform string as
2878
/// UTF-8 on Unix and UTF-16 on Windows.
2979
///
@@ -38,6 +88,62 @@ extension String {
3888
self.init(_platformString: platformString)
3989
}
4090

91+
/// Creates a string by interpreting the null-terminated platform string as
92+
/// UTF-8 on Unix and UTF-16 on Windows.
93+
///
94+
/// - Parameter platformString: The null-terminated platform string to be
95+
/// interpreted as `CInterop.PlatformUnicodeEncoding`.
96+
///
97+
/// - Note It is a precondition that `platformString` must be null-terminated.
98+
/// The absence of a null byte will trigger a runtime error.
99+
///
100+
/// If the contents of the platform string isn't well-formed Unicode,
101+
/// this initializer returns `nil`.
102+
@inlinable
103+
@_alwaysEmitIntoClient
104+
public init?(
105+
validatingPlatformString platformString: [CInterop.PlatformChar]
106+
) {
107+
guard let _ = platformString.firstIndex(of: 0) else {
108+
fatalError(
109+
"input of String.init(validatingPlatformString:) must be null-terminated"
110+
)
111+
}
112+
guard let string = platformString.withUnsafeBufferPointer({
113+
String(validatingPlatformString: $0.baseAddress!)
114+
}) else {
115+
return nil
116+
}
117+
self = string
118+
}
119+
120+
@inlinable
121+
@_alwaysEmitIntoClient
122+
@available(*, deprecated, message: "Use String(_ scalar: Unicode.Scalar)")
123+
public init?(
124+
validatingPlatformString platformString: inout CInterop.PlatformChar
125+
) {
126+
guard platformString == 0 else {
127+
fatalError(
128+
"input of String.init(validatingPlatformString:) must be null-terminated"
129+
)
130+
}
131+
self = ""
132+
}
133+
134+
@inlinable
135+
@_alwaysEmitIntoClient
136+
@available(*, deprecated, message: "Use a copy of the String argument")
137+
public init?(
138+
validatingPlatformString platformString: String
139+
) {
140+
if let nullLoc = platformString.firstIndex(of: "\0") {
141+
self = String(platformString[..<nullLoc])
142+
} else {
143+
self = platformString
144+
}
145+
}
146+
41147
/// Calls the given closure with a pointer to the contents of the string,
42148
/// represented as a null-terminated platform string.
43149
///

0 commit comments

Comments
 (0)