SystemString.swift 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331
  1. /*
  2. This source file is part of the Swift System open source project
  3. Copyright (c) 2020 - 2024 Apple Inc. and the Swift System project authors
  4. Licensed under Apache License v2.0 with Runtime Library Exception
  5. See https://swift.org/LICENSE.txt for license information
  6. */
  7. // A platform-native character representation, currently used for file paths
  8. internal struct SystemChar:
  9. RawRepresentable, Sendable, Comparable, Hashable, Codable {
  10. internal typealias RawValue = CInterop.PlatformChar
  11. internal var rawValue: RawValue
  12. internal init(rawValue: RawValue) { self.rawValue = rawValue }
  13. internal init(_ rawValue: RawValue) { self.init(rawValue: rawValue) }
  14. static func < (lhs: SystemChar, rhs: SystemChar) -> Bool {
  15. lhs.rawValue < rhs.rawValue
  16. }
  17. }
  18. extension SystemChar {
  19. internal init(ascii: Unicode.Scalar) {
  20. self.init(rawValue: numericCast(UInt8(ascii: ascii)))
  21. }
  22. internal init(codeUnit: CInterop.PlatformUnicodeEncoding.CodeUnit) {
  23. self.init(rawValue: codeUnit._platformChar)
  24. }
  25. internal static var null: SystemChar { SystemChar(0x0) }
  26. internal static var slash: SystemChar { SystemChar(ascii: "/") }
  27. internal static var backslash: SystemChar { SystemChar(ascii: #"\"#) }
  28. internal static var dot: SystemChar { SystemChar(ascii: ".") }
  29. internal static var colon: SystemChar { SystemChar(ascii: ":") }
  30. internal static var question: SystemChar { SystemChar(ascii: "?") }
  31. internal var codeUnit: CInterop.PlatformUnicodeEncoding.CodeUnit {
  32. rawValue._platformCodeUnit
  33. }
  34. internal var asciiScalar: Unicode.Scalar? {
  35. guard isASCII else { return nil }
  36. return Unicode.Scalar(UInt8(truncatingIfNeeded: rawValue))
  37. }
  38. internal var isASCII: Bool {
  39. (0...0x7F).contains(rawValue)
  40. }
  41. internal var isLetter: Bool {
  42. guard isASCII else { return false }
  43. let asciiRaw: UInt8 = numericCast(rawValue)
  44. return (UInt8(ascii: "a") ... UInt8(ascii: "z")).contains(asciiRaw) ||
  45. (UInt8(ascii: "A") ... UInt8(ascii: "Z")).contains(asciiRaw)
  46. }
  47. }
  48. // A platform-native string representation, currently for file paths
  49. //
  50. // Always null-terminated.
  51. internal struct SystemString: Sendable {
  52. internal typealias Storage = [SystemChar]
  53. internal var nullTerminatedStorage: Storage
  54. }
  55. extension SystemString {
  56. internal init() {
  57. self.nullTerminatedStorage = [.null]
  58. _invariantCheck()
  59. }
  60. internal var length: Int {
  61. let len = nullTerminatedStorage.count - 1
  62. assert(len == self.count)
  63. return len
  64. }
  65. // Common funnel point. Ensure all non-empty inits go here.
  66. internal init(nullTerminated storage: Storage) {
  67. self.nullTerminatedStorage = storage
  68. _invariantCheck()
  69. }
  70. // Ensures that result is null-terminated
  71. internal init<C: Collection>(_ chars: C) where C.Element == SystemChar {
  72. var rawChars = Storage(chars)
  73. if rawChars.last != .null {
  74. rawChars.append(.null)
  75. }
  76. self.init(nullTerminated: rawChars)
  77. }
  78. }
  79. extension SystemString {
  80. fileprivate func _invariantsSatisfied() -> Bool {
  81. guard !nullTerminatedStorage.isEmpty else { return false }
  82. guard nullTerminatedStorage.last! == .null else { return false }
  83. guard nullTerminatedStorage.firstIndex(of: .null) == length else {
  84. return false
  85. }
  86. return true
  87. }
  88. fileprivate func _invariantCheck() {
  89. #if DEBUG
  90. precondition(_invariantsSatisfied())
  91. #endif // DEBUG
  92. }
  93. }
  94. extension SystemString: RandomAccessCollection, MutableCollection {
  95. internal typealias Element = SystemChar
  96. internal typealias Index = Storage.Index
  97. internal typealias Indices = Range<Index>
  98. internal var startIndex: Index {
  99. nullTerminatedStorage.startIndex
  100. }
  101. internal var endIndex: Index {
  102. nullTerminatedStorage.index(before: nullTerminatedStorage.endIndex)
  103. }
  104. internal subscript(position: Index) -> SystemChar {
  105. _read {
  106. precondition(position >= startIndex && position <= endIndex)
  107. yield nullTerminatedStorage[position]
  108. }
  109. set(newValue) {
  110. precondition(position >= startIndex && position <= endIndex)
  111. nullTerminatedStorage[position] = newValue
  112. _invariantCheck()
  113. }
  114. }
  115. }
  116. extension SystemString: RangeReplaceableCollection {
  117. internal mutating func replaceSubrange<C: Collection>(
  118. _ subrange: Range<Index>, with newElements: C
  119. ) where C.Element == SystemChar {
  120. defer { _invariantCheck() }
  121. nullTerminatedStorage.replaceSubrange(subrange, with: newElements)
  122. }
  123. internal mutating func reserveCapacity(_ n: Int) {
  124. defer { _invariantCheck() }
  125. nullTerminatedStorage.reserveCapacity(1 + n)
  126. }
  127. internal func withContiguousStorageIfAvailable<R>(
  128. _ body: (UnsafeBufferPointer<SystemChar>) throws -> R
  129. ) rethrows -> R? {
  130. // Do not include the null terminator, it is outside the Collection
  131. try nullTerminatedStorage.withContiguousStorageIfAvailable {
  132. try body(.init(start: $0.baseAddress, count: $0.count-1))
  133. }
  134. }
  135. internal mutating func withContiguousMutableStorageIfAvailable<R>(
  136. _ body: (inout UnsafeMutableBufferPointer<SystemChar>) throws -> R
  137. ) rethrows -> R? {
  138. defer { _invariantCheck() }
  139. // Do not include the null terminator, it is outside the Collection
  140. return try nullTerminatedStorage.withContiguousMutableStorageIfAvailable {
  141. var buffer = UnsafeMutableBufferPointer<SystemChar>(
  142. start: $0.baseAddress, count: $0.count-1
  143. )
  144. return try body(&buffer)
  145. }
  146. }
  147. }
  148. extension SystemString: Hashable, Codable {
  149. // Encoder is synthesized; it probably should have been explicit and used
  150. // a single-value container, but making that change now is somewhat risky.
  151. // Decoder is written explicitly to ensure that we validate invariants on
  152. // untrusted input.
  153. public init(from decoder: any Decoder) throws {
  154. let container = try decoder.container(keyedBy: CodingKeys.self)
  155. self.nullTerminatedStorage = try container.decode(
  156. Storage.self, forKey: .nullTerminatedStorage
  157. )
  158. guard _invariantsSatisfied() else {
  159. throw DecodingError.dataCorruptedError(
  160. forKey: .nullTerminatedStorage,
  161. in: container,
  162. debugDescription:
  163. "Encoding does not satisfy the invariants of SystemString"
  164. )
  165. }
  166. }
  167. }
  168. extension SystemString {
  169. internal func withNullTerminatedSystemChars<T>(
  170. _ f: (UnsafeBufferPointer<SystemChar>) throws -> T
  171. ) rethrows -> T {
  172. try nullTerminatedStorage.withUnsafeBufferPointer(f)
  173. }
  174. // withCodeUnits does not include the null terminator
  175. internal func withCodeUnits<T>(
  176. _ f: (UnsafeBufferPointer<CInterop.PlatformUnicodeEncoding.CodeUnit>) throws -> T
  177. ) rethrows -> T {
  178. try withNullTerminatedSystemChars {
  179. try $0.withMemoryRebound(to: CInterop.PlatformUnicodeEncoding.CodeUnit.self) {
  180. assert($0.last == .zero)
  181. return try f(.init(start: $0.baseAddress, count: $0.count&-1))
  182. }
  183. }
  184. }
  185. }
  186. extension Slice where Base == SystemString {
  187. internal func withCodeUnits<T>(
  188. _ f: (UnsafeBufferPointer<CInterop.PlatformUnicodeEncoding.CodeUnit>) throws -> T
  189. ) rethrows -> T {
  190. try base.withCodeUnits {
  191. try f(UnsafeBufferPointer(rebasing: $0[indices]))
  192. }
  193. }
  194. internal var string: String {
  195. withCodeUnits {
  196. String(decoding: $0, as: CInterop.PlatformUnicodeEncoding.self)
  197. }
  198. }
  199. internal func withPlatformString<T>(
  200. _ f: (UnsafePointer<CInterop.PlatformChar>) throws -> T
  201. ) rethrows -> T {
  202. // FIXME: avoid allocation if we're at the end
  203. return try SystemString(self).withPlatformString(f)
  204. }
  205. }
  206. extension String {
  207. internal init(decoding str: SystemString) {
  208. // TODO: Can avoid extra strlen
  209. self = str.withPlatformString {
  210. String(platformString: $0)
  211. }
  212. }
  213. internal init?(validating str: SystemString) {
  214. // TODO: Can avoid extra strlen
  215. guard let str = str.withPlatformString(String.init(validatingPlatformString:))
  216. else { return nil }
  217. self = str
  218. }
  219. }
  220. extension SystemString: ExpressibleByStringLiteral {
  221. internal init(stringLiteral: String) {
  222. self.init(stringLiteral)
  223. }
  224. internal init(_ string: String) {
  225. // TODO: can avoid extra strlen
  226. self = string.withPlatformString {
  227. SystemString(platformString: $0)
  228. }
  229. }
  230. }
  231. extension SystemString: CustomStringConvertible, CustomDebugStringConvertible {
  232. internal var string: String {
  233. self.withCodeUnits {
  234. String(decoding: $0, as: CInterop.PlatformUnicodeEncoding.self)
  235. }
  236. }
  237. internal var description: String { string }
  238. internal var debugDescription: String { description.debugDescription }
  239. }
  240. extension SystemString {
  241. /// Creates a system string by copying bytes from a null-terminated platform string.
  242. ///
  243. /// - Parameter platformString: A pointer to a null-terminated platform string.
  244. internal init(platformString: UnsafePointer<CInterop.PlatformChar>) {
  245. let count = 1 + system_platform_strlen(platformString)
  246. // TODO: Is this the right way?
  247. let chars: Array<SystemChar> = platformString.withMemoryRebound(
  248. to: SystemChar.self, capacity: count
  249. ) {
  250. let bufPtr = UnsafeBufferPointer(start: $0, count: count)
  251. return Array(bufPtr)
  252. }
  253. self.init(nullTerminated: chars)
  254. }
  255. /// Calls the given closure with a pointer to the contents of the sytem string,
  256. /// represented as a null-terminated platform string.
  257. ///
  258. /// - Parameter body: A closure with a pointer parameter
  259. /// that points to a null-terminated platform string.
  260. /// If `body` has a return value,
  261. /// that value is also used as the return value for this method.
  262. /// - Returns: The return value, if any, of the `body` closure parameter.
  263. ///
  264. /// The pointer passed as an argument to `body` is valid
  265. /// only during the execution of this method.
  266. /// Don't try to store the pointer for later use.
  267. internal func withPlatformString<T>(
  268. _ f: (UnsafePointer<CInterop.PlatformChar>) throws -> T
  269. ) rethrows -> T {
  270. try withNullTerminatedSystemChars { chars in
  271. let length = chars.count * MemoryLayout<SystemChar>.stride
  272. return try chars.baseAddress!.withMemoryRebound(
  273. to: CInterop.PlatformChar.self,
  274. capacity: length / MemoryLayout<CInterop.PlatformChar>.stride
  275. ) { pointer in
  276. assert(pointer[self.count] == 0)
  277. return try f(pointer)
  278. }
  279. }
  280. }
  281. }
  282. // TODO: SystemString should use a COW-interchangable storage form rather
  283. // than array, so you could "borrow" the storage from a non-bridged String
  284. // or Data or whatever