Skip to content

Commit afe2d35

Browse files
committed
Mitigate misuse of pointer conversion in FilePath inits
1 parent 25380a1 commit afe2d35

File tree

1 file changed

+166
-2
lines changed

1 file changed

+166
-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(_: String) to create a path from a String.")
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(_: String).")
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(_: String).")
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>(

0 commit comments

Comments
 (0)