Skip to content

Commit

Permalink
Merge pull request #80 from apple/glessard/rdar91436410
Browse files Browse the repository at this point in the history
Mitigate misuse of Swift's pointer conversion feature
  • Loading branch information
glessard committed Jun 3, 2022
2 parents 74eb6f4 + 3920df7 commit bdb13a5
Show file tree
Hide file tree
Showing 3 changed files with 434 additions and 2 deletions.
168 changes: 166 additions & 2 deletions Sources/System/FilePath/FilePathString.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,49 @@ extension FilePath {
self.init(_platformString: platformString)
}

/// Creates a file path by copying bytes from a null-terminated platform
/// string.
///
/// - Note It is a precondition that `platformString` must be null-terminated.
/// The absence of a null byte will trigger a runtime error.
///
/// - Parameter platformString: A null-terminated platform string.
@inlinable
@_alwaysEmitIntoClient
public init(platformString: [CInterop.PlatformChar]) {
guard let _ = platformString.firstIndex(of: 0) else {
fatalError(
"input of FilePath.init(platformString:) must be null-terminated"
)
}
self = platformString.withUnsafeBufferPointer {
FilePath(platformString: $0.baseAddress!)
}
}

@inlinable
@_alwaysEmitIntoClient
@available(*, deprecated, message: "Use FilePath.init(_ scalar: Unicode.Scalar)")
public init(platformString: inout CInterop.PlatformChar) {
guard platformString == 0 else {
fatalError(
"input of FilePath.init(platformString:) must be null-terminated"
)
}
self = FilePath()
}

@inlinable
@_alwaysEmitIntoClient
@available(*, deprecated, message: "Use FilePath(_: String) to create a path from a String")
public init(platformString: String) {
if let nullLoc = platformString.firstIndex(of: "\0") {
self = FilePath(String(platformString[..<nullLoc]))
} else {
self = FilePath(platformString)
}
}

/// Calls the given closure with a pointer to the contents of the file path,
/// represented as a null-terminated platform string.
///
Expand Down Expand Up @@ -60,6 +103,59 @@ extension FilePath.Component {
self.init(_platformString: platformString)
}

/// Creates a file path component by copying bytes from a null-terminated
/// platform string. It is a precondition that a null byte indicates the end of
/// the string. The absence of a null byte will trigger a runtime error.
///
/// Returns `nil` if `platformString` is empty, is a root, or has more than
/// one component in it.
///
/// - Note It is a precondition that `platformString` must be null-terminated.
/// The absence of a null byte will trigger a runtime error.
///
/// - Parameter platformString: A null-terminated platform string.
@inlinable
@_alwaysEmitIntoClient
public init?(platformString: [CInterop.PlatformChar]) {
guard let _ = platformString.firstIndex(of: 0) else {
fatalError(
"input of FilePath.Component.init?(platformString:) must be null-terminated"
)
}
guard let component = platformString.withUnsafeBufferPointer({
FilePath.Component(platformString: $0.baseAddress!)
}) else {
return nil
}
self = component
}

@inlinable
@_alwaysEmitIntoClient
@available(*, deprecated, message: "Use FilePath.Component.init(_ scalar: Unicode.Scalar)")
public init?(platformString: inout CInterop.PlatformChar) {
guard platformString == 0 else {
fatalError(
"input of FilePath.Component.init?(platformString:) must be null-terminated"
)
}
return nil
}

@inlinable
@_alwaysEmitIntoClient
@available(*, deprecated, message: "Use FilePath.Component.init(_: String)")
public init?(platformString: String) {
let string: String
if let nullLoc = platformString.firstIndex(of: "\0") {
string = String(platformString[..<nullLoc])
} else {
string = platformString
}
guard let component = FilePath.Component(string) else { return nil }
self = component
}

/// Calls the given closure with a pointer to the contents of the file path
/// component, represented as a null-terminated platform string.
///
Expand Down Expand Up @@ -96,6 +192,58 @@ extension FilePath.Root {
self.init(_platformString: platformString)
}

/// Creates a file path root by copying bytes from a null-terminated platform
/// string. It is a precondition that a null byte indicates the end of
/// the string. The absence of a null byte will trigger a runtime error.
///
/// Returns `nil` if `platformString` is empty or is not a root.
///
/// - Note It is a precondition that `platformString` must be null-terminated.
/// The absence of a null byte will trigger a runtime error.
///
/// - Parameter platformString: A null-terminated platform string.
@inlinable
@_alwaysEmitIntoClient
public init?(platformString: [CInterop.PlatformChar]) {
guard let _ = platformString.firstIndex(of: 0) else {
fatalError(
"input of FilePath.Root.init?(platformString:) must be null-terminated"
)
}
guard let component = platformString.withUnsafeBufferPointer({
FilePath.Root(platformString: $0.baseAddress!)
}) else {
return nil
}
self = component
}

@inlinable
@_alwaysEmitIntoClient
@available(*, deprecated, message: "Use FilePath.Root.init(_ scalar: Unicode.Scalar)")
public init?(platformString: inout CInterop.PlatformChar) {
guard platformString == 0 else {
fatalError(
"input of FilePath.Root.init?(platformString:) must be null-terminated"
)
}
return nil
}

@inlinable
@_alwaysEmitIntoClient
@available(*, deprecated, message: "Use FilePath.Root.init(_: String)")
public init?(platformString: String) {
let string: String
if let nullLoc = platformString.firstIndex(of: "\0") {
string = String(platformString[..<nullLoc])
} else {
string = platformString
}
guard let root = FilePath.Root(string) else { return nil }
self = root
}

/// Calls the given closure with a pointer to the contents of the file path
/// root, represented as a null-terminated platform string.
///
Expand Down Expand Up @@ -148,7 +296,7 @@ extension FilePath.Component: ExpressibleByStringLiteral {
public init(stringLiteral: String) {
guard let s = FilePath.Component(stringLiteral) else {
// TODO: static assert
preconditionFailure("""
fatalError("""
FilePath.Component must be created from exactly one non-root component
""")
}
Expand All @@ -172,7 +320,7 @@ extension FilePath.Root: ExpressibleByStringLiteral {
public init(stringLiteral: String) {
guard let s = FilePath.Root(stringLiteral) else {
// TODO: static assert
preconditionFailure("""
fatalError("""
FilePath.Root must be created from a root
""")
}
Expand Down Expand Up @@ -405,10 +553,26 @@ extension String {
extension FilePath {
/// For backwards compatibility only. This initializer is equivalent to
/// the preferred `FilePath(platformString:)`.
@available(*, deprecated, renamed: "init(platformString:)")
public init(cString: UnsafePointer<CChar>) {
self.init(platformString: cString)
}

@available(*, deprecated, renamed: "init(platformString:)")
public init(cString: [CChar]) {
self.init(platformString: cString)
}

@available(*, deprecated, renamed: "init(platformString:)")
public init(cString: inout CChar) {
self.init(platformString: &cString)
}

@available(*, deprecated, renamed: "init(platformString:)")
public init(cString: String) {
self.init(platformString: cString)
}

/// For backwards compatibility only. This function is equivalent to
/// the preferred `withPlatformString`.
public func withCString<Result>(
Expand Down
106 changes: 106 additions & 0 deletions Sources/System/PlatformString.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,56 @@ extension String {
self.init(_errorCorrectingPlatformString: platformString)
}

/// Creates a string by interpreting the null-terminated platform string as
/// UTF-8 on Unix and UTF-16 on Windows.
///
/// - Parameter platformString: The null-terminated platform string to be
/// interpreted as `CInterop.PlatformUnicodeEncoding`.
///
/// - Note It is a precondition that `platformString` must be null-terminated.
/// The absence of a null byte will trigger a runtime error.
///
/// If the content of the platform string isn't well-formed Unicode,
/// this initializer replaces invalid bytes with U+FFFD.
/// This means that, depending on the semantics of the specific platform,
/// conversion to a string and back might result in a value that's different
/// from the original platform string.
@inlinable
@_alwaysEmitIntoClient
public init(platformString: [CInterop.PlatformChar]) {
guard let _ = platformString.firstIndex(of: 0) else {
fatalError(
"input of String.init(platformString:) must be null-terminated"
)
}
self = platformString.withUnsafeBufferPointer {
String(platformString: $0.baseAddress!)
}
}

@inlinable
@_alwaysEmitIntoClient
@available(*, deprecated, message: "Use String.init(_ scalar: Unicode.Scalar)")
public init(platformString: inout CInterop.PlatformChar) {
guard platformString == 0 else {
fatalError(
"input of String.init(platformString:) must be null-terminated"
)
}
self = ""
}

@inlinable
@_alwaysEmitIntoClient
@available(*, deprecated, message: "Use a copy of the String argument")
public init(platformString: String) {
if let nullLoc = platformString.firstIndex(of: "\0") {
self = String(platformString[..<nullLoc])
} else {
self = platformString
}
}

/// Creates a string by interpreting the null-terminated platform string as
/// UTF-8 on Unix and UTF-16 on Windows.
///
Expand All @@ -38,6 +88,62 @@ extension String {
self.init(_platformString: platformString)
}

/// Creates a string by interpreting the null-terminated platform string as
/// UTF-8 on Unix and UTF-16 on Windows.
///
/// - Parameter platformString: The null-terminated platform string to be
/// interpreted as `CInterop.PlatformUnicodeEncoding`.
///
/// - Note It is a precondition that `platformString` must be null-terminated.
/// The absence of a null byte will trigger a runtime error.
///
/// If the contents of the platform string isn't well-formed Unicode,
/// this initializer returns `nil`.
@inlinable
@_alwaysEmitIntoClient
public init?(
validatingPlatformString platformString: [CInterop.PlatformChar]
) {
guard let _ = platformString.firstIndex(of: 0) else {
fatalError(
"input of String.init(validatingPlatformString:) must be null-terminated"
)
}
guard let string = platformString.withUnsafeBufferPointer({
String(validatingPlatformString: $0.baseAddress!)
}) else {
return nil
}
self = string
}

@inlinable
@_alwaysEmitIntoClient
@available(*, deprecated, message: "Use String(_ scalar: Unicode.Scalar)")
public init?(
validatingPlatformString platformString: inout CInterop.PlatformChar
) {
guard platformString == 0 else {
fatalError(
"input of String.init(validatingPlatformString:) must be null-terminated"
)
}
self = ""
}

@inlinable
@_alwaysEmitIntoClient
@available(*, deprecated, message: "Use a copy of the String argument")
public init?(
validatingPlatformString platformString: String
) {
if let nullLoc = platformString.firstIndex(of: "\0") {
self = String(platformString[..<nullLoc])
} else {
self = platformString
}
}

/// Calls the given closure with a pointer to the contents of the string,
/// represented as a null-terminated platform string.
///
Expand Down
Loading

0 comments on commit bdb13a5

Please sign in to comment.