1
0

Util+StringArray.swift 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117
  1. /*
  2. This source file is part of the Swift System open source project
  3. Copyright (c) 2021 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. extension Array where Element == String {
  8. internal typealias CStr = UnsafePointer<CChar>?
  9. /// Call `body` with a buffer of `UnsafePointer<CChar>?` values,
  10. /// suitable for passing to a C function that expects a C string array.
  11. /// The buffer is guaranteed to be followed by an extra storage slot
  12. /// containing a null pointer. (For C functions that expect an array
  13. /// terminated with a null pointer.)
  14. ///
  15. /// This function is careful not to heap allocate memory unless there are
  16. /// too many strings, or if it needs to copy too much character data.
  17. internal func _withCStringArray<R>(
  18. _ body: (UnsafeBufferPointer<UnsafePointer<CChar>?>) throws -> R
  19. ) rethrows -> R {
  20. if self.count == 0 {
  21. // Fast path: empty array.
  22. let p: CStr = nil
  23. return try Swift.withUnsafePointer(to: p) { array in
  24. try body(UnsafeBufferPointer(start: array, count: 0))
  25. }
  26. }
  27. #if SYSTEM_OS_BUILD // String._guts isn't accessible from SwiftPM or CMake
  28. if self.count == 1, self[0]._guts._isLargeZeroTerminatedContiguousUTF8 {
  29. // Fast path: Single fast string.
  30. let start = self[0]._guts._largeContiguousUTF8CodeUnits.baseAddress!
  31. var p: (CStr, CStr) = (
  32. UnsafeRawPointer(start).assumingMemoryBound(to: CChar.self),
  33. nil
  34. )
  35. return try Swift.withUnsafeBytes(of: &p) { buffer in
  36. let start = buffer.baseAddress!.assumingMemoryBound(to: CStr.self)
  37. return try body(UnsafeBufferPointer(start: start, count: 1))
  38. }
  39. }
  40. #endif
  41. // We need to create a buffer for the C array.
  42. return try _withStackBuffer(
  43. capacity: (self.count + 1) * MemoryLayout<CStr>.stride
  44. ) { array in
  45. let array = array.bindMemory(to: CStr.self)
  46. // Calculate number of bytes we need for character storage
  47. let bytes = self.reduce(into: 0) { count, string in
  48. #if SYSTEM_OS_BUILD
  49. if string._guts._isLargeZeroTerminatedContiguousUTF8 { return }
  50. #endif
  51. count += string.utf8.count + 1 // Plus one for terminating NUL
  52. }
  53. #if SYSTEM_OS_BUILD
  54. if bytes == 0 {
  55. // Fast path: we only contain strings with stable null-terminated storage
  56. for i in self.indices {
  57. let string = self[i]
  58. precondition(string._guts._isLargeZeroTerminatedContiguousUTF8)
  59. let address = string._guts._largeContiguousUTF8CodeUnits.baseAddress!
  60. array[i] = UnsafeRawPointer(address).assumingMemoryBound(to: CChar.self)
  61. }
  62. array[self.count] = nil
  63. return try body(UnsafeBufferPointer(rebasing: array.dropLast()))
  64. }
  65. #endif
  66. return try _withStackBuffer(capacity: bytes) { chars in
  67. var chars = chars
  68. for i in self.indices {
  69. let (cstr, scratchUsed) = self[i]._getCStr(with: chars)
  70. array[i] = cstr.assumingMemoryBound(to: CChar.self)
  71. chars = .init(rebasing: chars[scratchUsed...])
  72. }
  73. array[self.count] = nil
  74. return try body(UnsafeBufferPointer(rebasing: array.dropLast()))
  75. }
  76. }
  77. }
  78. }
  79. extension String {
  80. fileprivate func _getCStr(
  81. with scratch: UnsafeMutableRawBufferPointer
  82. ) -> (cstr: UnsafeRawPointer, scratchUsed: Int) {
  83. #if SYSTEM_OS_BUILD
  84. if _guts._isLargeZeroTerminatedContiguousUTF8 {
  85. // This is a wonderful string, we can just use its storage address.
  86. let address = _guts._largeContiguousUTF8CodeUnits.baseAddress!
  87. return (UnsafeRawPointer(address), 0)
  88. }
  89. #endif
  90. let r: (UnsafeRawPointer, Int)? = self.utf8.withContiguousStorageIfAvailable { source in
  91. // This is a somewhat okay string -- we need to use memcpy.
  92. precondition(source.count <= scratch.count)
  93. let start = scratch.baseAddress!
  94. start.copyMemory(from: source.baseAddress!, byteCount: source.count)
  95. start.storeBytes(of: 0, toByteOffset: source.count, as: UInt8.self)
  96. return (UnsafeRawPointer(start), source.count + 1)
  97. }
  98. if let r = r { return r }
  99. // What a horrible string; we need to copy individual bytes.
  100. precondition(self.utf8.count <= scratch.count)
  101. var c = 0
  102. for byte in self.utf8 {
  103. scratch[c] = byte
  104. c += 1
  105. }
  106. scratch[c] = 0
  107. c += 1
  108. return (UnsafeRawPointer(scratch.baseAddress!), c)
  109. }
  110. }