Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ let package = Package(
.package(
url: "https://github.com/apple/swift-argument-parser", from: "1.3.0"),
.package(
url: "https://github.com/swiftlang/swift-syntax.git", from: "602.0.0"),
url: "https://github.com/swiftlang/swift-syntax.git", from: "603.0.0"),
.package(
url: "https://github.com/pointfreeco/swift-macro-testing.git",
from: "0.6.4"),
Expand Down
57 changes: 55 additions & 2 deletions Sources/BinaryParsing/Parsers/String.swift
Original file line number Diff line number Diff line change
Expand Up @@ -60,10 +60,18 @@ extension String {
internal init(_uncheckedParsingUTF16 input: inout ParserSpan)
throws(ParsingError)
{
assert(input.count.isMultiple(of: 2))
let stringBytes = input.divide(at: input.endPosition)
self = unsafe stringBytes.withUnsafeBytes { buffer in
let utf16Buffer = unsafe buffer.assumingMemoryBound(to: UInt16.self)
return unsafe String(decoding: utf16Buffer, as: UTF16.self)
guard let base = buffer.baseAddress else { return "" }
if base._isAligned(for: UInt16.self) {
let utf16Buffer = unsafe buffer.assumingMemoryBound(to: UInt16.self)
return unsafe String(decoding: utf16Buffer, as: UTF16.self)
} else {
let utf16Buffer = unsafe _UnalignedUnsafeBufferPointer<UInt16>(
_base: base, _count: buffer.count / 2)
return unsafe String(decoding: utf16Buffer, as: UTF16.self)
}
}
}

Expand Down Expand Up @@ -104,3 +112,48 @@ extension String {
unsafe try self.init(_uncheckedParsingUTF16: &slice)
}
}

// MARK: - Unaligned buffer pointer

extension UnsafeRawPointer {
@_alwaysEmitIntoClient
@safe
func _isAligned<T>(for: T.Type) -> Bool {
Int(bitPattern: self) & (MemoryLayout<T>.alignment - 1) == 0
}
}

@unsafe
@usableFromInline
struct _UnalignedUnsafeBufferPointer<T: BitwiseCopyable> {
@usableFromInline
typealias Element = T
@usableFromInline
typealias Index = Int

@usableFromInline
var _base: UnsafeRawPointer
@usableFromInline
@safe var _count: Int

@_alwaysEmitIntoClient
init(_base: UnsafeRawPointer, _count: Int) {
unsafe self._base = _base
self._count = _count
}
}

extension _UnalignedUnsafeBufferPointer: @unsafe RandomAccessCollection {
@_alwaysEmitIntoClient
var startIndex: Int { 0 }
@_alwaysEmitIntoClient
var endIndex: Int { _count }

@_alwaysEmitIntoClient
subscript(position: Int) -> T {
get {
unsafe _base.loadUnaligned(
fromByteOffset: position * MemoryLayout<T>.stride, as: T.self)
}
}
}
14 changes: 14 additions & 0 deletions Tests/BinaryParsingTests/StringParsingTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,20 @@ struct StringParsingTests {
}
}

@Test
func parseUTF16Unaligned() throws {
// Prepend a single byte so that the UTF-16 code units that follow start
// at an odd offset within the backing buffer, forcing a misaligned load.
let buffer: [UInt8] = [0xAA] + testStringNonASCII.utf16Buffer

try buffer.withParserSpan { span in
_ = try UInt8(parsing: &span)
let str = try String(parsingUTF16: &span)
#expect(str == testStringNonASCII)
#expect(span.count == 0)
}
}

@Test
func testMultipleOperationsOnSameBuffer() throws {
let combinedString = "\(testString)\0\(testStringNonASCII)"
Expand Down
Loading