Skip to content

Instantly share code, notes, and snippets.

@nathan-fiscaletti
Created December 27, 2020 18:34
Show Gist options
  • Save nathan-fiscaletti/892e074dc14e6707603414cd2d80c287 to your computer and use it in GitHub Desktop.
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.
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