Created
December 27, 2020 18:34
-
-
Save nathan-fiscaletti/892e074dc14e6707603414cd2d80c287 to your computer and use it in GitHub Desktop.
An extension of Swifts String class that provides functions for generating several types of memory pointers. Includes a full test suite.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Foundation | |
import XCTest | |
// myString.stackPointer() -> UnsafePointer<Int8>?: | |
// Returns a pointer to the memory allocated on the stack for this string. | |
// | |
// myString.mutableStackPointer() -> UnsafeMutablePointer<Int8>? | |
// Returns a mutable pointer to the memory allocated on the stack for this string. | |
// | |
// myString.withUnsignedStackPointer(closure) | |
// Passes an unsigned pointer to the memory allocated on the stack for this string to the closure. | |
// | |
// myString.withUnsignedMutableStackPointer(closure) | |
// Passes an unsigned mutable pointer to the memory allocated on the stack for this string to the closure. | |
// | |
// myString.heapPointer() -> UnsafePointer<Int8>? | |
// Returns a new pointer to memory allocated on the heap for this string. You must deallocate this when finished. | |
// | |
// myString.mutableHeapPointer() -> UnsafeMutablePointer<Int8>? | |
// Returns a new mutable pointer to memory allocated on the heap for this string. You must deallocate this when finished. | |
// | |
// myString.unsignedHeapPointer() -> UnsafePointer<UInt8>? | |
// Returns a new unsigned pointer to memory allocated on the heap for this string. You must deallocate this when finished. | |
// | |
// myString.unsignedMutableHeapPointer() -> UnsafeMutablePointer<UInt8>? | |
// Returns a new unsigned mutable pointer to memory allocated on the heap for this string. You must deallocate this when finished. | |
extension String { | |
/** | |
* Returns the pointer to stack allocated memory containing this string of the type UnsafePointer<Int8> | |
*/ | |
func stackPointer() -> UnsafePointer<Int8>? { | |
return (self as NSString).utf8String | |
} | |
/** | |
* Returns the pointer to stack allocated memory containing this string of the type UnsafeMutablePointer<Int8> | |
*/ | |
func mutableStackPointer() -> UnsafeMutablePointer<Int8>? { | |
return UnsafeMutablePointer<Int8>(mutating: self.stackPointer()) | |
} | |
/** | |
* Calls handle with the pointer to stack allocated memory containing this string of the type UnsafePointer<UInt8> | |
*/ | |
func withUnsignedStackPointer(_ handle: (UnsafePointer<UInt8>?) -> Void) { | |
return Array(self.utf8).withUnsafeBytes { (p: UnsafeRawBufferPointer) -> Void in | |
handle(p.bindMemory(to: UInt8.self).baseAddress!) | |
} | |
} | |
/** | |
* Calls handle with the pointer to stack allocated memory containing this string of the type UnsafeMutablePointer<UInt8> | |
*/ | |
func withUnsignedMutableStackPointer(_ handle: (UnsafeMutablePointer<UInt8>?) -> Void) { | |
self.withUnsignedStackPointer { (unsigned: UnsafePointer<UInt8>?) in | |
handle(UnsafeMutablePointer<UInt8>(mutating: unsigned)) | |
} | |
} | |
/** | |
* Allocates memory on the heap containing this string of the type UnsafePointer<Int8> | |
* | |
* You must call .deallocate() on the result of this function when you are done using it. | |
*/ | |
func heapPointer() -> UnsafePointer<Int8>? { | |
guard let data = self.data(using: .utf8, allowLossyConversion: false) else { return nil } | |
let buffer = UnsafeMutablePointer<Int8>.allocate(capacity: data.count) | |
bzero(buffer, MemoryLayout<Int8>.size * data.count) | |
data.withUnsafeBytes { (p: UnsafeRawBufferPointer) -> Void in | |
buffer.initialize(from: p.bindMemory(to: Int8.self).baseAddress!, count: data.count) | |
} | |
return UnsafePointer<Int8>(buffer) | |
} | |
/** | |
* Allocates memory on the heap containing this string of the type UnsafeMutablePointer<Int8> | |
* | |
* You must call .deallocate() on the result of this function when you are done using it. | |
*/ | |
func mutableHeapPointer() -> UnsafeMutablePointer<Int8>? { | |
return UnsafeMutablePointer<Int8>(mutating: self.heapPointer()) | |
} | |
/** | |
* Allocates memory on the heap containing this string of the type UnsafePointer<UInt8> | |
* | |
* You must call .deallocate() on the result of this function when you are done using it. | |
*/ | |
func unsignedHeapPointer() -> UnsafePointer<UInt8>? { | |
guard let data = self.data(using: .utf8, allowLossyConversion: false) else { return nil } | |
let buffer = UnsafeMutablePointer<UInt8>.allocate(capacity: data.count) | |
bzero(buffer, MemoryLayout<UInt8>.size * data.count) | |
let stream = OutputStream(toBuffer: buffer, capacity: data.count) | |
stream.open() | |
data.withUnsafeBytes { (p: UnsafeRawBufferPointer) -> Void in | |
stream.write(p.bindMemory(to: UInt8.self).baseAddress!, maxLength: data.count) | |
} | |
stream.close() | |
return UnsafePointer<UInt8>(buffer) | |
} | |
/** | |
* Allocates memory on the heap containing this string of the type UnsafeMutablePointer<UInt8> | |
* | |
* You must call .deallocate() on the result of this function when you are done using it. | |
*/ | |
func unsignedMutableHeapPointer() -> UnsafeMutablePointer<UInt8>? { | |
return UnsafeMutablePointer<UInt8>(mutating: self.unsignedHeapPointer()) | |
} | |
} | |
class StringPointerTests: XCTestCase { | |
// TODO: Detect Stack | |
// | |
// I wish there was a way to test if a pointer | |
// resides on the stack, or a way to expect a | |
// SIGABRT in XCTest so that when i call | |
// ptr?.deallocate() on a stack allocated pointer | |
// and the SIGABRT is signaled, it would be caught. | |
// | |
// Since i can't think of anything right now, we | |
// don't do any tests surrounding pointee residence. | |
func testStackPointer() { | |
let str = "test" | |
let ptr = str.stackPointer() | |
// Make sure it's not nil | |
XCTAssertNotNil(ptr) | |
// Make sure it contains the same data | |
XCTAssertEqual(String(cString: ptr!), str) | |
} | |
func testMutableStackPointer() { | |
let str = "test" | |
let ptr = str.mutableStackPointer() | |
// Make sure it's not nil | |
XCTAssertNotNil(ptr) | |
// Make sure it contains the same data | |
XCTAssertEqual(String(cString: ptr!), str) | |
} | |
func testUnsignedStackPointer() { | |
let str = "test" | |
str.withUnsignedStackPointer { (ptr: UnsafePointer<UInt8>?) in | |
// Make sure it's not nil | |
XCTAssertNotNil(ptr) | |
// Since i can't directly convert this pointer back to a string, let's just verify the bytes | |
XCTAssertEqual(ptr?.pointee, UInt8(116)) // verify letter 't' | |
XCTAssertEqual(ptr?.advanced(by: 1).pointee, UInt8(101)) // verify letter 'e' | |
XCTAssertEqual(ptr?.advanced(by: 2).pointee, UInt8(115)) // verify letter 's' | |
XCTAssertEqual(ptr?.advanced(by: 3).pointee, UInt8(116)) // verify letter 't' | |
XCTAssertEqual(ptr?.advanced(by: 4).pointee, UInt8(0) ) // verify string terminator | |
} | |
} | |
func testUnsignedMutableStackPointer() { | |
let str = "test" | |
str.withUnsignedMutableStackPointer { (ptr: UnsafeMutablePointer<UInt8>?) in | |
// Make sure it's not nil | |
XCTAssertNotNil(ptr) | |
// Since i can't directly convert this pointer back to a string, let's just verify the bytes | |
XCTAssertEqual(ptr?.pointee, UInt8(116)) // verify letter 't' | |
XCTAssertEqual(ptr?.advanced(by: 1).pointee, UInt8(101)) // verify letter 'e' | |
XCTAssertEqual(ptr?.advanced(by: 2).pointee, UInt8(115)) // verify letter 's' | |
XCTAssertEqual(ptr?.advanced(by: 3).pointee, UInt8(116)) // verify letter 't' | |
XCTAssertEqual(ptr?.advanced(by: 4).pointee, UInt8(0) ) // verify string terminator | |
} | |
} | |
func testHeapPointer() { | |
let str = "test" | |
let ptr = str.heapPointer() | |
// Make sure it's not nil | |
XCTAssertNotNil(ptr) | |
// Make sure it contains the same data | |
XCTAssertEqual(String(cString: ptr!), str) | |
// Deallocate the pointer | |
ptr?.deallocate() | |
} | |
func testMutableHeapPointer() { | |
let str = "test" | |
let ptr = str.mutableHeapPointer() | |
// Make sure it's not nil | |
XCTAssertNotNil(ptr) | |
// Make sure it contains the same data | |
XCTAssertEqual(String(cString: ptr!), str) | |
// Deallocate the pointer | |
ptr?.deallocate() | |
} | |
func testUnsignedHeapPointer() { | |
let str = "test" | |
let ptr = str.unsignedHeapPointer() | |
// Make sure it's not nil | |
XCTAssertNotNil(ptr) | |
// Since i can't directly convert this pointer back to a string, let's just verify the bytes | |
XCTAssertEqual(ptr?.pointee, UInt8(116)) // verify letter 't' | |
XCTAssertEqual(ptr?.advanced(by: 1).pointee, UInt8(101)) // verify letter 'e' | |
XCTAssertEqual(ptr?.advanced(by: 2).pointee, UInt8(115)) // verify letter 's' | |
XCTAssertEqual(ptr?.advanced(by: 3).pointee, UInt8(116)) // verify letter 't' | |
XCTAssertEqual(ptr?.advanced(by: 4).pointee, UInt8(0) ) // verify string terminator | |
// Deallocate the pointer | |
ptr?.deallocate() | |
} | |
func testUnsignedMutableHeapPointer() { | |
let str = "test" | |
let ptr = str.unsignedMutableHeapPointer() | |
// Make sure it's not nil | |
XCTAssertNotNil(ptr) | |
// Since i can't directly convert this pointer back to a string, let's just verify the bytes | |
XCTAssertEqual(ptr?.pointee, UInt8(116)) // verify letter 't' | |
XCTAssertEqual(ptr?.advanced(by: 1).pointee, UInt8(101)) // verify letter 'e' | |
XCTAssertEqual(ptr?.advanced(by: 2).pointee, UInt8(115)) // verify letter 's' | |
XCTAssertEqual(ptr?.advanced(by: 3).pointee, UInt8(116)) // verify letter 't' | |
XCTAssertEqual(ptr?.advanced(by: 4).pointee, UInt8(0) ) // verify string terminator | |
// Deallocate the pointer | |
ptr?.deallocate() | |
} | |
} | |
// Run the test suite | |
StringPointerTests.defaultTestSuite.run() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment