158 lines
5.4 KiB
Markdown
158 lines
5.4 KiB
Markdown
# WASM Bounds Checking Audit
|
|
|
|
This document tracks potentially unsafe array accesses that can cause "index out of bounds" RuntimeErrors in WASM builds with assertions enabled.
|
|
|
|
## Background
|
|
|
|
WASM builds with `-sASSERTIONS=1` perform runtime bounds checking on all memory accesses. Invalid accesses trigger a `RuntimeError: index out of bounds` that halts the module.
|
|
|
|
## Analysis Tools
|
|
|
|
Run the static analysis script to find potentially dangerous patterns:
|
|
```bash
|
|
./scripts/find-unsafe-array-access.sh
|
|
```
|
|
|
|
## Known Fixed Issues
|
|
|
|
### 1. object_drawer.cc - Tile Rendering (Fixed 2024-11)
|
|
**File:** `src/zelda3/dungeon/object_drawer.cc`
|
|
**Issue:** `tiledata[src_index]` access without bounds validation
|
|
**Fix:** Added `kMaxTileRow = 63` validation before access
|
|
|
|
### 2. background_buffer.cc - Background Tile Rendering (Fixed 2024-11)
|
|
**File:** `src/app/gfx/render/background_buffer.cc`
|
|
**Issue:** Same pattern as object_drawer.cc
|
|
**Fix:** Added same bounds checking
|
|
|
|
### 3. arena.h - Graphics Sheet Accessors (Fixed 2024-11)
|
|
**File:** `src/app/gfx/resource/arena.h`
|
|
**Issue:** `gfx_sheets_[i]` accessed without bounds check
|
|
**Fix:** Added bounds validation returning empty/null for invalid indices
|
|
|
|
### 4. bitmap.cc - Palette Application (Fixed 2024-11)
|
|
**File:** `src/app/gfx/core/bitmap.cc`
|
|
**Issue:** `palette[i]` accessed without checking palette size
|
|
**Fix:** Added bounds check against `sdl_palette->ncolors`
|
|
|
|
### 5. tilemap.cc - FetchTileDataFromGraphicsBuffer (Fixed 2024-11)
|
|
**File:** `src/app/gfx/render/tilemap.cc`
|
|
**Issue:** `data[src_index]` accessed without checking data vector size
|
|
**Fix:** Added `src_index >= 0 && src_index < data_size` validation
|
|
|
|
### 6. overworld.h - Map Accessors (Fixed 2025-11-26)
|
|
**File:** `src/zelda3/overworld/overworld.h`
|
|
**Issue:** `overworld_map(int i)` and `mutable_overworld_map(int i)` accessed vector without bounds check
|
|
**Fix:** Added bounds validation returning nullptr for invalid indices
|
|
|
|
### 7. overworld.h - Sprite Accessors (Fixed 2025-11-26)
|
|
**File:** `src/zelda3/overworld/overworld.h`
|
|
**Issue:** `sprites(int state)` accessed array without validating state (0-2)
|
|
**Fix:** Added bounds check returning empty vector for invalid state
|
|
|
|
### 8. overworld.h - Current Map Accessors (Fixed 2025-11-26)
|
|
**File:** `src/zelda3/overworld/overworld.h`
|
|
**Issue:** `current_graphics()`, `current_area_palette()`, etc. accessed `overworld_maps_[current_map_]` without validating `current_map_`
|
|
**Fix:** Added `is_current_map_valid()` helper and validation in all accessors
|
|
|
|
### 9. snes_palette.h - PaletteGroup Negative Index (Fixed 2025-11-26)
|
|
**File:** `src/app/gfx/types/snes_palette.h`
|
|
**Issue:** `operator[]` only checked upper bound, not negative indices
|
|
**Fix:** Added `i < 0` check to bounds validation
|
|
|
|
### 10. room.cc - LoadAnimatedGraphics sizeof vs size() (Fixed 2025-11-26)
|
|
**File:** `src/zelda3/dungeon/room.cc`
|
|
**Issue:** Used `sizeof(current_gfx16_)` instead of `.size()` for bounds checking
|
|
**Fix:** Changed to use `.size()` for clarity and maintainability
|
|
|
|
## Patterns Requiring Caution
|
|
|
|
### Critical Risk Patterns
|
|
|
|
These patterns directly access memory buffers and are most likely to crash:
|
|
|
|
1. **`tiledata[index]`** - Graphics buffer access
|
|
- Buffer size: 0x10000 (65536 bytes)
|
|
- Max tile row: 63 (rows 0-63)
|
|
- Stride: 128 bytes per row
|
|
- **Validation:** `tile_row <= 63` before computing `src_index`
|
|
|
|
2. **`buffer_[index]`** - Tile buffer access
|
|
- Check: `index < buffer_.size()`
|
|
|
|
3. **`canvas[index]`** - Pixel canvas access
|
|
- Check: `index < width * height`
|
|
|
|
4. **`.data()[index]`** - Vector data access
|
|
- Check: `index < vector.size()`
|
|
|
|
### High Risk Patterns
|
|
|
|
These access ROM data or game structures that may contain corrupt values:
|
|
|
|
1. **`rom.data()[offset]`** - ROM data access
|
|
- Check: `offset < rom.size()`
|
|
|
|
2. **`palette[index]`** - Palette color access
|
|
- Check: `index < palette.size()`
|
|
|
|
3. **`overworld_maps_[i]`** - Map access
|
|
- Check: `i < 160` (or appropriate constant)
|
|
|
|
4. **`rooms_[i]`** - Room access
|
|
- Check: `i < 296`
|
|
|
|
### Medium Risk Patterns
|
|
|
|
Usually safe but worth verifying:
|
|
|
|
1. **`gfx_sheet(i)`** - Already has bounds check returning empty Bitmap
|
|
2. **`vram[index]`, `cgram[index]`, `oam[index]`** - Usually masked with `& 0x7fff`, `& 0xff`
|
|
|
|
## Bounds Checking Template
|
|
|
|
```cpp
|
|
// For tile data access
|
|
constexpr int kGfxBufferSize = 0x10000;
|
|
constexpr int kMaxTileRow = 63;
|
|
|
|
int tile_row = tile_id / 16;
|
|
if (tile_row > kMaxTileRow) {
|
|
return; // Skip invalid tile
|
|
}
|
|
|
|
int src_index = (src_row * 128) + src_col + tile_base_x + tile_base_y;
|
|
if (src_index < 0 || src_index >= kGfxBufferSize) {
|
|
continue; // Skip invalid access
|
|
}
|
|
|
|
// For destination canvas
|
|
int dest_index = dest_y * width + dest_x;
|
|
if (dest_index < 0 || dest_index >= static_cast<int>(canvas.size())) {
|
|
continue; // Skip invalid access
|
|
}
|
|
```
|
|
|
|
## Testing
|
|
|
|
1. Run WASM build with assertions: `-sASSERTIONS=1`
|
|
2. Load ROMs with varying data quality
|
|
3. Open each editor and interact with all features
|
|
4. Monitor browser console for `RuntimeError: index out of bounds`
|
|
|
|
## Error Reporting
|
|
|
|
The crash reporter (`src/web/core/crash_reporter.js`) provides:
|
|
- Stack trace parsing to identify WASM function indices
|
|
- Auto-diagnosis of known error patterns
|
|
- Problems panel for non-fatal errors
|
|
- Console log history capture
|
|
|
|
## Recovery
|
|
|
|
The recovery system (`src/web/core/wasm_recovery.js`) provides:
|
|
- Automatic crash detection
|
|
- Non-blocking recovery overlay
|
|
- Module reinitialization (up to 3 attempts)
|
|
- ROM data preservation via IndexedDB
|