Skip to content

Commit 6493c76

Browse files
Overhaul diagnostics
1 parent a21d8e7 commit 6493c76

File tree

16 files changed

+305
-266
lines changed

16 files changed

+305
-266
lines changed

Sources/WasmKit/Execution/ConstEvaluation.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ extension ConstExpression {
5050

5151
private func _evaluate<C: ConstEvaluationContextProtocol>(context: C) throws -> Value {
5252
guard self.last == .end, self.count == 2 else {
53-
throw InstantiationError.unsupported("Expect `end` at the end of offset expression")
53+
throw ValidationError(.expectedEndAtOffsetExpression)
5454
}
5555
let constInst = self[0]
5656
switch constInst {
@@ -68,7 +68,7 @@ extension ConstExpression {
6868
case .refFunc(let functionIndex):
6969
return try .ref(context.functionRef(functionIndex))
7070
default:
71-
throw InstantiationError.unsupported("illegal const expression instruction: \(constInst)")
71+
throw ValidationError(.illegalConstExpressionInstruction(constInst))
7272
}
7373
}
7474
}
@@ -97,10 +97,10 @@ extension WasmParser.ElementSegment {
9797
case .ref(.function(let addr)):
9898
return .function(addr)
9999
default:
100-
throw Trap._raw("Unexpected global value type for element initializer expression")
100+
throw ValidationError(.unexpectedGlobalValueType)
101101
}
102102
default:
103-
throw Trap._raw("Unexpected element initializer expression: \(expression)")
103+
throw ValidationError(.unexpectedElementInitializer(expression: "\(expression)"))
104104
}
105105
}
106106
}
Lines changed: 155 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -1,114 +1,188 @@
11
import WasmTypes
22

3-
public enum Trap: Error {
4-
// FIXME: for debugging purposes, to be eventually deleted
5-
case _raw(String)
3+
import struct WasmParser.Import
64

7-
case unreachable
5+
/// An error that occurs during execution of a WebAssembly module.
6+
public struct Trap: Error, CustomStringConvertible {
7+
/// The reason for the trap.
8+
var reason: TrapReason
9+
10+
/// The backtrace of the trap.
11+
struct Backtrace {
12+
}
13+
14+
/// The backtrace of the trap.
15+
private(set) var backtrace: Backtrace?
816

9-
// Stack
10-
/// Stack overflow
11-
case stackOverflow
12-
/// The stack value type does not match the expected type
13-
case stackValueTypesMismatch(expected: WasmTypes.ValueType, actual: WasmTypes.ValueType)
17+
init(_ code: TrapReason, backtrace: Backtrace? = nil) {
18+
self.reason = code
19+
self.backtrace = backtrace
20+
}
21+
22+
init(_ message: TrapReason.Message, backtrace: Backtrace? = nil) {
23+
self.init(.message(message), backtrace: backtrace)
24+
}
25+
26+
/// The description of the trap.
27+
public var description: String {
28+
var desc = "Trap: \(reason)"
29+
if let backtrace = backtrace {
30+
desc += "\n\(backtrace)"
31+
}
32+
return desc
33+
}
34+
35+
func withBacktrace(_ backtrace: Backtrace) -> Trap {
36+
var trap = self
37+
trap.backtrace = backtrace
38+
return trap
39+
}
40+
}
41+
42+
/// A reason for a trap that occurred during execution of a WebAssembly module.
43+
enum TrapReason: Error, CustomStringConvertible {
44+
struct Message {
45+
let text: String
46+
47+
init(_ text: String) {
48+
self.text = text
49+
}
50+
}
51+
/// A trap with a string message
52+
case message(Message)
53+
/// `unreachable` instruction executed
54+
case unreachable
1455
/// Too deep call stack
56+
///
57+
/// Note: When this trap occurs, consider extending ``EngineConfiguration/stackSize``.
1558
case callStackExhausted
16-
17-
// Store
1859
/// Out of bounds table access
19-
case outOfBoundsTableAccess(Int)
20-
/// Reading a dropped reference
21-
case readingDroppedReference(index: Int)
22-
23-
// Invocation
24-
/// Exported function not found
25-
case exportedFunctionNotFound(Instance, name: String)
26-
/// The table element is not initialized
27-
case tableUninitialized(Int)
28-
/// Undefined element in the table
29-
case undefinedElement
30-
/// Table size overflow
31-
case tableSizeOverflow
32-
/// Indirect call type mismatch
33-
case callIndirectFunctionTypeMismatch(actual: FunctionType, expected: FunctionType)
60+
case tableOutOfBounds(Int)
3461
/// Out of bounds memory access
35-
case outOfBoundsMemoryAccess
36-
/// Invalid function index
37-
case invalidFunctionIndex(Int)
62+
case memoryOutOfBounds
63+
/// `call_indirect` instruction called an uninitialized table element.
64+
case indirectCallToNull(Int)
65+
/// Indirect call type mismatch
66+
case typeMismatchCall(actual: FunctionType, expected: FunctionType)
3867
/// Integer divided by zero
3968
case integerDividedByZero
4069
/// Integer overflowed during arithmetic operation
41-
case integerOverflowed
70+
case integerOverflow
4271
/// Invalid conversion to integer
4372
case invalidConversionToInteger
4473

45-
/// Human-readable text representation of the trap that `.wast` text format expects in assertions
46-
public var assertionText: String {
74+
/// The description of the trap reason.
75+
var description: String {
4776
switch self {
48-
case .outOfBoundsMemoryAccess:
77+
case .message(let message):
78+
return message.text
79+
case .unreachable:
80+
return "unreachable"
81+
case .callStackExhausted:
82+
return "call stack exhausted"
83+
case .memoryOutOfBounds:
4984
return "out of bounds memory access"
5085
case .integerDividedByZero:
5186
return "integer divide by zero"
52-
case .integerOverflowed:
87+
case .integerOverflow:
5388
return "integer overflow"
5489
case .invalidConversionToInteger:
5590
return "invalid conversion to integer"
56-
case .undefinedElement:
57-
return "undefined element"
58-
case let .tableUninitialized(elementIndex):
59-
return "uninitialized element \(elementIndex)"
60-
case .callIndirectFunctionTypeMismatch:
61-
return "indirect call type mismatch"
62-
case .outOfBoundsTableAccess, .tableSizeOverflow:
63-
return "out of bounds table access"
64-
case .callStackExhausted:
65-
return "call stack exhausted"
66-
default:
67-
return String(describing: self)
91+
case let .indirectCallToNull(elementIndex):
92+
return "indirect call to null element (uninitialized element \(elementIndex))"
93+
case .typeMismatchCall(let actual, let expected):
94+
return "indirect call type mismatch, expected \(expected), got \(actual)"
95+
case .tableOutOfBounds(let index):
96+
return "out of bounds table access at \(index) (undefined element)"
6897
}
6998
}
7099
}
71100

72-
public enum InstantiationError: Error {
73-
case importsAndExternalValuesMismatch
74-
case invalidTableExpression
75-
case outOfBoundsTableAccess
76-
case outOfBoundsMemoryAccess
77-
case unsupported(String)
78-
case exportIndexOutOfBounds(kind: String, index: Int, count: Int)
101+
extension TrapReason.Message {
102+
static func initialTableSizeExceedsLimit(numberOfElements: Int) -> Self {
103+
Self("initial table size exceeds the resource limit: \(numberOfElements) elements")
104+
}
105+
static func initialMemorySizeExceedsLimit(byteSize: Int) -> Self {
106+
Self("initial memory size exceeds the resource limit: \(byteSize) bytes")
107+
}
108+
static func parameterTypesMismatch(expected: [ValueType], got: [Value]) -> Self {
109+
Self("parameter types don't match, expected \(expected), got \(got)")
110+
}
111+
static func resultTypesMismatch(expected: [ValueType], got: [Value]) -> Self {
112+
Self("result types don't match, expected \(expected), got \(got)")
113+
}
114+
static var cannotAssignToImmutableGlobal: Self {
115+
Self("cannot assign to an immutable global")
116+
}
117+
static func noGlobalExportWithName(globalName: String, instance: Instance) -> Self {
118+
Self("no global export with name \(globalName) in a module instance \(instance)")
119+
}
120+
static func exportedFunctionNotFound(name: String, instance: Instance) -> Self {
121+
Self("exported function \(name) not found in instance \(instance)")
122+
}
123+
}
79124

80-
/// Human-readable text representation of the trap that `.wast` text format expects in assertions
81-
public var assertionText: String {
82-
switch self {
83-
case .outOfBoundsTableAccess:
84-
return "out of bounds table access"
85-
case .outOfBoundsMemoryAccess:
86-
return "out of bounds memory access"
87-
case .unsupported(let message):
88-
return "unsupported operation: \(message)"
89-
default:
90-
return String(describing: self)
125+
struct ImportError: Error {
126+
struct Message {
127+
let text: String
128+
129+
init(_ text: String) {
130+
self.text = text
91131
}
92132
}
93-
}
94133

95-
public enum ImportError: Error {
96-
case unknownImport(moduleName: String, externalName: String)
97-
case incompatibleImportType
98-
case importedEntityFromDifferentStore
99-
case moduleInstanceAlreadyRegistered(String)
134+
let message: Message
100135

101-
/// Human-readable text representation of the trap that `.wast` text format expects in assertions
102-
public var assertionText: String {
103-
switch self {
104-
case .unknownImport:
105-
return "unknown import"
106-
case .incompatibleImportType:
107-
return "incompatible import type"
108-
case .importedEntityFromDifferentStore:
109-
return "imported entity from different store"
110-
case let .moduleInstanceAlreadyRegistered(name):
111-
return "a module instance is already registered under a name `\(name)"
136+
init(_ message: Message) {
137+
self.message = message
138+
}
139+
}
140+
141+
extension ImportError.Message {
142+
static func missing(moduleName: String, externalName: String) -> Self {
143+
Self("unknown import \(moduleName).\(externalName)")
144+
}
145+
static func incompatibleType(_ importEntry: Import, entity: InternalExternalValue) -> Self {
146+
let expected: String
147+
switch importEntry.descriptor {
148+
case .function:
149+
expected = "function"
150+
case .global:
151+
expected = "global"
152+
case .memory:
153+
expected = "memory"
154+
case .table:
155+
expected = "table"
156+
}
157+
let got: String
158+
switch entity {
159+
case .function:
160+
got = "function"
161+
case .global:
162+
got = "global"
163+
case .memory:
164+
got = "memory"
165+
case .table:
166+
got = "table"
112167
}
168+
return Self("incompatible import type for \(importEntry.module).\(importEntry.name), expected \(expected), got \(got)")
169+
}
170+
static func incompatibleFunctionType(_ importEntry: Import, actual: FunctionType, expected: FunctionType) -> Self {
171+
Self("incompatible import type: function type for \(importEntry.module).\(importEntry.name), expected \(expected), got \(actual)")
172+
}
173+
static func incompatibleTableType(_ importEntry: Import, actual: TableType, expected: TableType) -> Self {
174+
Self("incompatible import type: table type for \(importEntry.module).\(importEntry.name), expected \(expected), got \(actual)")
175+
}
176+
static func incompatibleMemoryType(_ importEntry: Import, actual: MemoryType, expected: MemoryType) -> Self {
177+
Self("incompatible import type: memory type for \(importEntry.module).\(importEntry.name), expected \(expected), got \(actual)")
178+
}
179+
static func incompatibleGlobalType(_ importEntry: Import, actual: GlobalType, expected: GlobalType) -> Self {
180+
Self("incompatible import type: global type for \(importEntry.module).\(importEntry.name), expected \(expected), got \(actual)")
181+
}
182+
static func importedEntityFromDifferentStore(_ importEntry: Import) -> Self {
183+
Self("imported entity from different store: \(importEntry.module).\(importEntry.name)")
184+
}
185+
static func moduleInstanceAlreadyRegistered(_ name: String) -> Self {
186+
Self("a module instance is already registered under a name `\(name)")
113187
}
114188
}

Sources/WasmKit/Execution/Execution.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ struct Execution {
5353
) throws -> Sp {
5454
let newSp = sp.advanced(by: Int(spAddend))
5555
guard newSp.advanced(by: iseq.maxStackHeight) < stackEnd else {
56-
throw Trap.callStackExhausted
56+
throw Trap(.callStackExhausted)
5757
}
5858
// Initialize the locals with zeros (all types of value have the same representation)
5959
newSp.initialize(repeating: UntypedValue.default.storage, count: numberOfNonParameterLocals)

Sources/WasmKit/Execution/Function.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,7 @@ struct InternalFunction: Equatable, Hashable {
156156

157157
extension InternalFunction: ValidatableEntity {
158158
static func createOutOfBoundsError(index: Int, count: Int) -> any Error {
159-
Trap.invalidFunctionIndex(index)
159+
ValidationError(.indexOutOfBounds("function", index, max: count))
160160
}
161161
}
162162

@@ -199,13 +199,13 @@ extension InternalFunction {
199199

200200
private func check(functionType: FunctionType, parameters: [Value]) throws {
201201
guard check(expectedTypes: functionType.parameters, values: parameters) else {
202-
throw Trap._raw("parameters types don't match, expected \(functionType.parameters), got \(parameters)")
202+
throw Trap(.parameterTypesMismatch(expected: functionType.parameters, got: parameters))
203203
}
204204
}
205205

206206
private func check(functionType: FunctionType, results: [Value]) throws {
207207
guard check(expectedTypes: functionType.results, values: results) else {
208-
throw Trap._raw("result types don't match, expected \(functionType.results), got \(results)")
208+
throw Trap(.resultTypesMismatch(expected: functionType.results, got: results))
209209
}
210210
}
211211

0 commit comments

Comments
 (0)