0
# Error Handling
1
2
Comprehensive error handling system including WebAssembly traps, stack traces, runtime errors, WASI exit codes, and detailed debugging information for robust WebAssembly application development and debugging.
3
4
## Capabilities
5
6
### Runtime Errors
7
8
Base exception hierarchy for WebAssembly runtime errors providing structured error reporting and debugging information for compilation, instantiation, and execution failures.
9
10
```python { .api }
11
class WasmtimeError(Exception):
12
"""Base exception for all Wasmtime-related errors"""
13
```
14
15
### WebAssembly Traps
16
17
Exception representing WebAssembly execution traps with detailed stack trace information, trap codes, and frame-by-frame execution context for debugging runtime failures.
18
19
```python { .api }
20
class Trap(Exception):
21
def __init__(self, message: str):
22
"""
23
Create a trap with the given message.
24
25
Parameters:
26
- message: Human-readable trap description
27
"""
28
29
@property
30
def message(self) -> str:
31
"""
32
Get the trap message.
33
34
Returns:
35
Human-readable trap description
36
"""
37
38
@property
39
def frames(self) -> List[Frame]:
40
"""
41
Get the stack trace frames.
42
43
Returns:
44
List of stack frames from the trap location
45
"""
46
47
@property
48
def trace(self) -> List[str]:
49
"""
50
Get the stack trace as formatted strings.
51
52
Returns:
53
List of formatted stack trace lines
54
"""
55
```
56
57
### Stack Frames
58
59
Stack frame information providing detailed execution context including function names, module names, function indices, and code offsets for precise debugging.
60
61
```python { .api }
62
class Frame:
63
@property
64
def func_name(self) -> Optional[str]:
65
"""
66
Get the function name if available.
67
68
Returns:
69
Function name from debug info, or None if not available
70
"""
71
72
@property
73
def module_name(self) -> Optional[str]:
74
"""
75
Get the module name if available.
76
77
Returns:
78
Module name from debug info, or None if not available
79
"""
80
81
@property
82
def func_index(self) -> int:
83
"""
84
Get the function index within the module.
85
86
Returns:
87
Zero-based function index
88
"""
89
90
@property
91
def module_offset(self) -> int:
92
"""
93
Get the byte offset within the module.
94
95
Returns:
96
Byte offset from start of module
97
"""
98
```
99
100
### Trap Codes
101
102
Enumeration of standard WebAssembly trap codes providing categorization of different trap conditions for programmatic error handling and recovery.
103
104
```python { .api }
105
class TrapCode:
106
STACK_OVERFLOW: 'TrapCode' # Stack overflow
107
MEMORY_OUT_OF_BOUNDS: 'TrapCode' # Memory access out of bounds
108
HEAP_MISALIGNED: 'TrapCode' # Misaligned memory access
109
TABLE_OUT_OF_BOUNDS: 'TrapCode' # Table access out of bounds
110
INDIRECT_CALL_TO_NULL: 'TrapCode' # Indirect call to null
111
BAD_SIGNATURE: 'TrapCode' # Indirect call signature mismatch
112
INTEGER_OVERFLOW: 'TrapCode' # Integer overflow
113
INTEGER_DIVISION_BY_ZERO: 'TrapCode' # Division by zero
114
BAD_CONVERSION_TO_INTEGER: 'TrapCode' # Invalid float to int conversion
115
UNREACHABLE: 'TrapCode' # Unreachable instruction executed
116
INTERRUPT: 'TrapCode' # Execution interrupted
117
```
118
119
### WASI Exit Codes
120
121
Exception representing WASI program exit with exit code information, supporting standard Unix exit code conventions and proper process termination handling.
122
123
```python { .api }
124
class ExitTrap(Exception):
125
def __init__(self, code: int):
126
"""
127
Create an exit trap with the given exit code.
128
129
Parameters:
130
- code: Exit code (0 for success, non-zero for error)
131
"""
132
133
@property
134
def code(self) -> int:
135
"""
136
Get the exit code.
137
138
Returns:
139
Exit code from the WebAssembly program
140
"""
141
```
142
143
## Usage Examples
144
145
### Basic Error Handling
146
147
```python
148
import wasmtime
149
150
def safe_wasm_execution():
151
"""Demonstrate basic WebAssembly error handling"""
152
153
try:
154
engine = wasmtime.Engine()
155
store = wasmtime.Store(engine)
156
157
# Try to load a WebAssembly module
158
wasm_bytes = wasmtime.wat2wasm('''
159
(module
160
(func (export "divide") (param i32 i32) (result i32)
161
local.get 0
162
local.get 1
163
i32.div_s) ;; This can trap on division by zero
164
)
165
''')
166
167
module = wasmtime.Module(engine, wasm_bytes)
168
instance = wasmtime.Instance(store, module, [])
169
170
# Get the divide function
171
divide_func = instance.exports(store)["divide"]
172
173
# Safe division
174
result = divide_func(store, 10, 2)
175
print(f"10 / 2 = {result}")
176
177
# This will cause a trap
178
result = divide_func(store, 10, 0)
179
180
except wasmtime.Trap as trap:
181
print(f"WebAssembly trap: {trap.message}")
182
print("Stack trace:")
183
for frame in trap.frames:
184
func_name = frame.func_name or f"func[{frame.func_index}]"
185
module_name = frame.module_name or "unknown"
186
print(f" at {func_name} in {module_name} (offset: {frame.module_offset})")
187
188
except wasmtime.WasmtimeError as error:
189
print(f"Wasmtime error: {error}")
190
191
except Exception as error:
192
print(f"Unexpected error: {error}")
193
194
safe_wasm_execution()
195
```
196
197
### Comprehensive Error Analysis
198
199
```python
200
import wasmtime
201
202
def analyze_wasm_error(error):
203
"""Analyze and categorize WebAssembly errors"""
204
205
if isinstance(error, wasmtime.Trap):
206
print(f"TRAP: {error.message}")
207
208
# Analyze stack trace
209
if error.frames:
210
print(f"Stack depth: {len(error.frames)} frames")
211
for i, frame in enumerate(error.frames):
212
print(f" Frame {i}:")
213
print(f" Function: {frame.func_name or f'func[{frame.func_index}]'}")
214
print(f" Module: {frame.module_name or 'unknown'}")
215
print(f" Offset: 0x{frame.module_offset:x}")
216
else:
217
print(" No stack trace available")
218
219
# Check for common trap patterns
220
message_lower = error.message.lower()
221
if "division by zero" in message_lower:
222
print(" → This is a division by zero error")
223
print(" → Check divisor values before division")
224
elif "out of bounds" in message_lower:
225
print(" → This is a bounds check failure")
226
print(" → Verify array/memory access indices")
227
elif "unreachable" in message_lower:
228
print(" → Unreachable code was executed")
229
print(" → Check control flow logic")
230
231
elif isinstance(error, wasmtime.ExitTrap):
232
print(f"EXIT: Program exited with code {error.code}")
233
if error.code == 0:
234
print(" → Normal termination")
235
else:
236
print(" → Error termination")
237
238
elif isinstance(error, wasmtime.WasmtimeError):
239
print(f"RUNTIME ERROR: {error}")
240
241
# Check for common runtime errors
242
error_str = str(error).lower()
243
if "validation" in error_str:
244
print(" → WebAssembly validation failed")
245
print(" → Check module binary format and contents")
246
elif "import" in error_str:
247
print(" → Import resolution failed")
248
print(" → Verify all required imports are provided")
249
elif "type" in error_str:
250
print(" → Type mismatch error")
251
print(" → Check function signatures and value types")
252
253
else:
254
print(f"OTHER ERROR: {type(error).__name__}: {error}")
255
256
# Example usage with different error types
257
def test_various_errors():
258
"""Test different types of WebAssembly errors"""
259
260
engine = wasmtime.Engine()
261
store = wasmtime.Store(engine)
262
263
# Test 1: Division by zero trap
264
try:
265
wasm_bytes = wasmtime.wat2wasm('(module (func (export "div") (param i32 i32) (result i32) local.get 0 local.get 1 i32.div_s))')
266
module = wasmtime.Module(engine, wasm_bytes)
267
instance = wasmtime.Instance(store, module, [])
268
div_func = instance.exports(store)["div"]
269
div_func(store, 10, 0) # Division by zero
270
except Exception as e:
271
print("=== Division by Zero Test ===")
272
analyze_wasm_error(e)
273
print()
274
275
# Test 2: Memory out of bounds
276
try:
277
wasm_bytes = wasmtime.wat2wasm('''
278
(module
279
(memory 1)
280
(func (export "read_oob") (param i32) (result i32)
281
local.get 0
282
i32.load))
283
''')
284
module = wasmtime.Module(engine, wasm_bytes)
285
instance = wasmtime.Instance(store, module, [])
286
read_func = instance.exports(store)["read_oob"]
287
read_func(store, 100000) # Way out of bounds
288
except Exception as e:
289
print("=== Out of Bounds Test ===")
290
analyze_wasm_error(e)
291
print()
292
293
# Test 3: Import resolution error
294
try:
295
wasm_bytes = wasmtime.wat2wasm('''
296
(module
297
(import "env" "missing_func" (func (param i32) (result i32)))
298
(func (export "test") (result i32)
299
i32.const 42
300
call 0))
301
''')
302
module = wasmtime.Module(engine, wasm_bytes)
303
instance = wasmtime.Instance(store, module, []) # Missing import
304
except Exception as e:
305
print("=== Import Resolution Test ===")
306
analyze_wasm_error(e)
307
print()
308
309
test_various_errors()
310
```
311
312
### WASI Exit Code Handling
313
314
```python
315
import wasmtime
316
317
def run_wasi_program_with_exit_handling(wasm_path: str):
318
"""Run a WASI program with proper exit code handling"""
319
320
try:
321
# Set up WASI environment
322
wasi_config = wasmtime.WasiConfig()
323
wasi_config.inherit_argv()
324
wasi_config.inherit_env()
325
wasi_config.inherit_stdin()
326
wasi_config.inherit_stdout()
327
wasi_config.inherit_stderr()
328
329
engine = wasmtime.Engine()
330
store = wasmtime.Store(engine)
331
linker = wasmtime.Linker(engine)
332
linker.define_wasi(store, wasi_config)
333
334
# Load and instantiate WASI module
335
module = wasmtime.Module.from_file(engine, wasm_path)
336
instance = linker.instantiate(store, module)
337
338
# Run the program
339
start_func = instance.get_export(store, "_start")
340
if start_func:
341
print(f"Running WASI program: {wasm_path}")
342
start_func(store)
343
print("Program completed successfully (exit code 0)")
344
return 0
345
else:
346
print("Error: No _start function found")
347
return 1
348
349
except wasmtime.ExitTrap as exit_trap:
350
exit_code = exit_trap.code
351
if exit_code == 0:
352
print("Program completed successfully (explicit exit 0)")
353
else:
354
print(f"Program exited with error code: {exit_code}")
355
return exit_code
356
357
except wasmtime.Trap as trap:
358
print(f"Program trapped: {trap.message}")
359
360
# Print detailed stack trace
361
if trap.frames:
362
print("Stack trace:")
363
for i, frame in enumerate(trap.frames):
364
func_name = frame.func_name or f"func[{frame.func_index}]"
365
module_name = frame.module_name or "unknown"
366
print(f" {i}: {func_name} in {module_name} at 0x{frame.module_offset:x}")
367
368
return 2 # Trap exit code
369
370
except wasmtime.WasmtimeError as error:
371
print(f"Runtime error: {error}")
372
return 3 # Runtime error exit code
373
374
except FileNotFoundError:
375
print(f"Error: File not found: {wasm_path}")
376
return 4 # File not found exit code
377
378
except Exception as error:
379
print(f"Unexpected error: {error}")
380
return 5 # Unexpected error exit code
381
382
# Example usage
383
# exit_code = run_wasi_program_with_exit_handling("my_program.wasm")
384
# sys.exit(exit_code)
385
```
386
387
### Custom Error Recovery
388
389
```python
390
import wasmtime
391
import time
392
393
class WasmExecutor:
394
def __init__(self):
395
self.engine = wasmtime.Engine()
396
self.retry_count = 0
397
self.max_retries = 3
398
399
def safe_execute(self, wasm_bytes: bytes, func_name: str, *args):
400
"""Execute WebAssembly function with error recovery"""
401
402
for attempt in range(self.max_retries + 1):
403
try:
404
store = wasmtime.Store(self.engine)
405
module = wasmtime.Module(self.engine, wasm_bytes)
406
instance = wasmtime.Instance(store, module, [])
407
408
func = instance.exports(store)[func_name]
409
result = func(store, *args)
410
411
# Reset retry count on success
412
self.retry_count = 0
413
return result
414
415
except wasmtime.Trap as trap:
416
print(f"Attempt {attempt + 1}: Trapped - {trap.message}")
417
418
# Check if this is a recoverable error
419
if self._is_recoverable_trap(trap):
420
if attempt < self.max_retries:
421
wait_time = (2 ** attempt) * 0.1 # Exponential backoff
422
print(f"Retrying in {wait_time:.1f} seconds...")
423
time.sleep(wait_time)
424
continue
425
426
# Non-recoverable or max retries exceeded
427
print(f"Giving up after {attempt + 1} attempts")
428
raise
429
430
except wasmtime.WasmtimeError as error:
431
print(f"Attempt {attempt + 1}: Runtime error - {error}")
432
433
# Most runtime errors are not recoverable
434
if "fuel" in str(error).lower() and attempt < self.max_retries:
435
print("Fuel exhausted, retrying with more fuel...")
436
continue
437
else:
438
raise
439
440
def _is_recoverable_trap(self, trap: wasmtime.Trap) -> bool:
441
"""Determine if a trap might be recoverable with retry"""
442
message = trap.message.lower()
443
444
# Some traps might be recoverable (this is application-specific)
445
recoverable_patterns = [
446
"interrupt", # Execution interrupted
447
"epoch", # Epoch deadline reached
448
"fuel" # Fuel exhausted
449
]
450
451
return any(pattern in message for pattern in recoverable_patterns)
452
453
# Example usage
454
executor = WasmExecutor()
455
456
try:
457
wasm_bytes = wasmtime.wat2wasm('''
458
(module
459
(func (export "compute") (param i32) (result i32)
460
local.get 0
461
i32.const 2
462
i32.mul))
463
''')
464
465
result = executor.safe_execute(wasm_bytes, "compute", 21)
466
print(f"Result: {result}")
467
468
except Exception as final_error:
469
print(f"Final error after all retry attempts: {final_error}")
470
```
471
472
### Error Logging and Debugging
473
474
```python
475
import wasmtime
476
import logging
477
import traceback
478
from datetime import datetime
479
480
# Configure logging
481
logging.basicConfig(
482
level=logging.DEBUG,
483
format='%(asctime)s - %(levelname)s - %(message)s'
484
)
485
logger = logging.getLogger(__name__)
486
487
class DebugWasmRunner:
488
def __init__(self, enable_debug: bool = True):
489
# Create engine with debug info if available
490
config = wasmtime.Config()
491
if enable_debug:
492
config.debug_info(True)
493
494
self.engine = wasmtime.Engine(config)
495
self.execution_log = []
496
497
def run_with_logging(self, wasm_path: str, func_name: str, *args):
498
"""Run WebAssembly function with comprehensive logging"""
499
500
execution_id = datetime.now().isoformat()
501
logger.info(f"[{execution_id}] Starting WebAssembly execution")
502
logger.info(f" Module: {wasm_path}")
503
logger.info(f" Function: {func_name}")
504
logger.info(f" Arguments: {args}")
505
506
try:
507
store = wasmtime.Store(self.engine)
508
509
# Load module with validation logging
510
logger.debug("Loading WebAssembly module...")
511
module = wasmtime.Module.from_file(self.engine, wasm_path)
512
logger.debug(f"Module loaded: {len(module.imports)} imports, {len(module.exports)} exports")
513
514
# Log imports and exports
515
for imp in module.imports:
516
logger.debug(f" Import: {imp.module}.{imp.name} ({imp.type})")
517
for exp in module.exports:
518
logger.debug(f" Export: {exp.name} ({exp.type})")
519
520
# Instantiate with logging
521
logger.debug("Instantiating module...")
522
instance = wasmtime.Instance(store, module, [])
523
logger.debug("Module instantiated successfully")
524
525
# Execute function
526
logger.debug(f"Calling function {func_name}...")
527
func = instance.exports(store)[func_name]
528
result = func(store, *args)
529
530
logger.info(f"[{execution_id}] Execution completed successfully")
531
logger.info(f" Result: {result}")
532
533
# Record successful execution
534
self.execution_log.append({
535
"id": execution_id,
536
"status": "success",
537
"module": wasm_path,
538
"function": func_name,
539
"args": args,
540
"result": result,
541
"error": None
542
})
543
544
return result
545
546
except Exception as error:
547
logger.error(f"[{execution_id}] Execution failed: {type(error).__name__}: {error}")
548
549
# Detailed error logging
550
if isinstance(error, wasmtime.Trap):
551
logger.error(f" Trap message: {error.message}")
552
logger.error(f" Stack frames: {len(error.frames)}")
553
for i, frame in enumerate(error.frames):
554
logger.error(f" Frame {i}: {frame.func_name or f'func[{frame.func_index}]'} "
555
f"in {frame.module_name or 'unknown'} at 0x{frame.module_offset:x}")
556
557
elif isinstance(error, wasmtime.ExitTrap):
558
logger.error(f" Exit code: {error.code}")
559
560
elif isinstance(error, wasmtime.WasmtimeError):
561
logger.error(f" Runtime error details: {error}")
562
563
# Log full Python traceback for debugging
564
logger.debug("Python traceback:")
565
logger.debug(traceback.format_exc())
566
567
# Record failed execution
568
self.execution_log.append({
569
"id": execution_id,
570
"status": "error",
571
"module": wasm_path,
572
"function": func_name,
573
"args": args,
574
"result": None,
575
"error": {
576
"type": type(error).__name__,
577
"message": str(error),
578
"details": self._extract_error_details(error)
579
}
580
})
581
582
raise
583
584
def _extract_error_details(self, error):
585
"""Extract detailed error information for logging"""
586
details = {}
587
588
if isinstance(error, wasmtime.Trap):
589
details["trap_message"] = error.message
590
details["stack_frames"] = []
591
for frame in error.frames:
592
details["stack_frames"].append({
593
"func_name": frame.func_name,
594
"module_name": frame.module_name,
595
"func_index": frame.func_index,
596
"module_offset": frame.module_offset
597
})
598
elif isinstance(error, wasmtime.ExitTrap):
599
details["exit_code"] = error.code
600
601
return details
602
603
def get_execution_summary(self):
604
"""Get summary of all executions"""
605
total = len(self.execution_log)
606
successful = sum(1 for log in self.execution_log if log["status"] == "success")
607
failed = total - successful
608
609
return {
610
"total_executions": total,
611
"successful": successful,
612
"failed": failed,
613
"success_rate": successful / total if total > 0 else 0,
614
"recent_errors": [log["error"] for log in self.execution_log[-5:] if log["error"]]
615
}
616
617
# Example usage
618
runner = DebugWasmRunner(enable_debug=True)
619
620
try:
621
result = runner.run_with_logging("test.wasm", "add", 5, 3)
622
print(f"Execution result: {result}")
623
except Exception as e:
624
print(f"Execution failed: {e}")
625
626
# Print execution summary
627
summary = runner.get_execution_summary()
628
print(f"Execution summary: {summary}")
629
```