diff --git a/Sources/WasmParser/CMakeLists.txt b/Sources/WasmParser/CMakeLists.txt index b5b05612..5b9abd12 100644 --- a/Sources/WasmParser/CMakeLists.txt +++ b/Sources/WasmParser/CMakeLists.txt @@ -1,7 +1,6 @@ add_wasmkit_library(WasmParser Stream/ByteStream.swift Stream/FileHandleStream.swift - Stream/Stream.swift BinaryInstructionDecoder.swift InstructionVisitor.swift LEB.swift diff --git a/Sources/WasmParser/Stream/ByteStream.swift b/Sources/WasmParser/Stream/ByteStream.swift index 68590d65..95013362 100644 --- a/Sources/WasmParser/Stream/ByteStream.swift +++ b/Sources/WasmParser/Stream/ByteStream.swift @@ -1,5 +1,23 @@ -public protocol ByteStream: Stream where Element == UInt8 {} +public protocol ByteStream: ~Copyable { + var currentIndex: Int { get } + func consumeAny() throws(WasmParserError) -> UInt8 + func consume(_ expected: Set) throws(WasmParserError) -> UInt8 + func consume(count: Int) throws(WasmParserError) -> ArraySlice + + func peek() throws(WasmParserError) -> UInt8? +} + +extension ByteStream { + func consume(_ expected: UInt8) throws(WasmParserError) -> UInt8 { + try consume(Set([expected])) + } + + @usableFromInline + func hasReachedEnd() throws(WasmParserError) -> Bool { + try peek() == nil + } +} public final class StaticByteStream: ByteStream { public let bytes: ArraySlice public var currentIndex: Int @@ -15,9 +33,9 @@ public final class StaticByteStream: ByteStream { } @discardableResult - public func consumeAny() throws -> UInt8 { + public func consumeAny() throws(WasmParserError) -> UInt8 { guard bytes.indices.contains(currentIndex) else { - throw StreamError.unexpectedEnd(expected: nil) + throw WasmParserError(kind: .unexpectedEnd(expected: nil), offset: self.currentIndex) } let consumed = bytes[currentIndex] @@ -26,26 +44,31 @@ public final class StaticByteStream: ByteStream { } @discardableResult - public func consume(_ expected: Set) throws -> UInt8 { + public func consume(_ expected: Set) throws(WasmParserError) -> UInt8 { guard bytes.indices.contains(currentIndex) else { - throw StreamError.unexpectedEnd(expected: Set(expected)) + throw WasmParserError(kind: .unexpectedEnd(expected: Set(expected)), offset: currentIndex) } let consumed = bytes[currentIndex] guard expected.contains(consumed) else { - throw StreamError.unexpected(consumed, index: currentIndex, expected: Set(expected)) + throw WasmParserError( + kind: .unexpectedByte( + consumed, + index: currentIndex, + expected: Set(expected) + ), offset: currentIndex) } currentIndex = bytes.index(after: currentIndex) return consumed } - public func consume(count: Int) throws -> ArraySlice { + public func consume(count: Int) throws(WasmParserError) -> ArraySlice { guard count > 0 else { return [] } let updatedIndex = currentIndex + count guard bytes.indices.contains(updatedIndex - 1) else { - throw StreamError.unexpectedEnd(expected: nil) + throw WasmParserError(kind: .unexpectedEnd(expected: nil), offset: currentIndex) } defer { currentIndex = updatedIndex } diff --git a/Sources/WasmParser/Stream/FileHandleStream.swift b/Sources/WasmParser/Stream/FileHandleStream.swift index 5da02bf6..cb8950d6 100644 --- a/Sources/WasmParser/Stream/FileHandleStream.swift +++ b/Sources/WasmParser/Stream/FileHandleStream.swift @@ -17,18 +17,22 @@ public final class FileHandleStream: ByteStream { try readMoreIfNeeded() } - private func readMoreIfNeeded() throws { + private func readMoreIfNeeded() throws(WasmParserError) { guard Int(endOffset) == currentIndex else { return } startOffset = currentIndex - let data = try fileHandle.read(upToCount: bufferLength) + do { + let data = try fileHandle.read(upToCount: bufferLength) - bytes = [UInt8](data) + bytes = [UInt8](data) + } catch { + throw WasmParserError(kind: .unclassified(error), offset: currentIndex) + } endOffset = startOffset + bytes.count } @discardableResult - public func consumeAny() throws -> UInt8 { + public func consumeAny() throws(WasmParserError) -> UInt8 { guard let consumed = try peek() else { throw WasmParserError(.unexpectedEnd, offset: currentIndex) } @@ -37,18 +41,23 @@ public final class FileHandleStream: ByteStream { } @discardableResult - public func consume(_ expected: Set) throws -> UInt8 { + public func consume(_ expected: Set) throws(WasmParserError) -> UInt8 { guard let consumed = try peek() else { - throw StreamError.unexpectedEnd(expected: Set(expected)) + throw WasmParserError(kind: .unexpectedEnd(expected: Set(expected)), offset: currentIndex) } guard expected.contains(consumed) else { - throw StreamError.unexpected(consumed, index: currentIndex, expected: Set(expected)) + throw WasmParserError( + kind: .unexpectedByte( + consumed, + index: currentIndex, + expected: Set(expected) + ), offset: currentIndex) } currentIndex = bytes.index(after: currentIndex) return consumed } - public func consume(count: Int) throws -> ArraySlice { + public func consume(count: Int) throws(WasmParserError) -> ArraySlice { let bytesToRead = currentIndex + count - endOffset guard bytesToRead > 0 else { @@ -58,9 +67,14 @@ public final class FileHandleStream: ByteStream { return result } - let data = try fileHandle.read(upToCount: bytesToRead) + let data: [UInt8] + do { + data = try fileHandle.read(upToCount: bytesToRead) + } catch { + throw WasmParserError(kind: .unclassified(error), offset: currentIndex) + } guard data.count == bytesToRead else { - throw StreamError.unexpectedEnd(expected: nil) + throw WasmParserError(kind: .unexpectedEnd(expected: nil), offset: currentIndex) } bytes.append(contentsOf: [UInt8](data)) @@ -74,7 +88,7 @@ public final class FileHandleStream: ByteStream { return result } - public func peek() throws -> UInt8? { + public func peek() throws(WasmParserError) -> UInt8? { try readMoreIfNeeded() let index = currentIndex - startOffset diff --git a/Sources/WasmParser/Stream/Stream.swift b/Sources/WasmParser/Stream/Stream.swift deleted file mode 100644 index 46225c12..00000000 --- a/Sources/WasmParser/Stream/Stream.swift +++ /dev/null @@ -1,28 +0,0 @@ -@usableFromInline -enum StreamError: Swift.Error, Equatable where Element: Hashable & Sendable { - case unexpectedEnd(expected: Set?) - case unexpected(Element, index: Int, expected: Set?) -} - -public protocol Stream { - associatedtype Element: Hashable - - var currentIndex: Int { get } - - func consumeAny() throws -> Element - func consume(_ expected: Set) throws -> Element - func consume(count: Int) throws -> ArraySlice - - func peek() throws -> Element? -} - -extension Stream { - func consume(_ expected: Element) throws -> Element { - try consume(Set([expected])) - } - - @usableFromInline - func hasReachedEnd() throws -> Bool { - try peek() == nil - } -} diff --git a/Sources/WasmParser/WasmParser.swift b/Sources/WasmParser/WasmParser.swift index 90372ad0..f08e61a2 100644 --- a/Sources/WasmParser/WasmParser.swift +++ b/Sources/WasmParser/WasmParser.swift @@ -217,19 +217,64 @@ public struct WasmParserError: Swift.Error { } } - let message: Message - let offset: Int + @usableFromInline + enum Kind: Sendable { + case message(Message) + case unexpectedEnd(expected: Set?) + case unexpectedByte(UInt8, index: Int, expected: Set?) + case unclassified(any Error) + } + + let kind: Kind + let offset: Int? + @usableFromInline + init(kind: Kind, offset: Int) { + self.kind = kind + self.offset = offset + } +} + +extension WasmParserError { @usableFromInline init(_ message: Message, offset: Int) { - self.message = message + self.kind = .message(message) self.offset = offset } } +extension BinaryInteger { + var hexString: String { + "0x\(String(self, radix: 16))" + } +} + extension WasmParserError: CustomStringConvertible { public var description: String { - return "\"\(message)\" at offset 0x\(String(offset, radix: 16))" + var result: String + switch self.kind { + case .message(let message): + result = message.text + case .unexpectedEnd(let expected): + var result = "Unexpected end of byte sequence." + if let expected, expected.count > 0 { + result.append(contentsOf: " Expected one of \(expected.map {$0.hexString}).") + } + return result + case .unexpectedByte(let byte, let index, let expected): + result = "Unexpected byte \(byte.hexString) at index \(index.hexString)." + if let expected, expected.count > 0 { + result.append(contentsOf: " Expected one of \(expected.map {$0.hexString}).") + } + case .unclassified(let error): + result = "\(error)" + } + + if let offset { + return "\"\(result)\" raised at offset 0x\(String(offset, radix: 16))" + } else { + return result + } } } @@ -595,7 +640,10 @@ extension Parser { case 0x6F: elementType = .externRef default: - throw StreamError.unexpected(b, index: offset, expected: [0x6F, 0x70]) + throw WasmParserError( + kind: .unexpectedByte(b, index: offset, expected: [0x6F, 0x70]), + offset: stream.currentIndex + ) } let limits = try parseLimits()