pcbjam/docs/features/wasm-exceptions/04-kicad-audit.md

75 lines
3.4 KiB
Markdown
Raw Normal View History

# 04 — The KiCad catch-block audit
## Why an audit
Binaryen's asyncify cannot (yet) handle a suspension that begins while execution is inside
a wasm catch handler (see 05). Any C++ `catch` whose handler (directly or transitively)
opens a modal dialog, touches the async clipboard, etc., is therefore illegal under
`-fwasm-exceptions` + ASYNCIFY=1 today. KiCad's standard error pattern is exactly that:
```cpp
catch( const IO_ERROR& ioe )
{
DisplayErrorMessage( this, ioe.What() ); // → ShowModal → Asyncify suspension
}
```
## Method
[`catch_audit.py`](catch_audit.py): walks `kicad/**/*.cpp` (excluding `thirdparty/`, `qa/`),
extracts every catch block with real brace matching (string/comment aware), and classifies
each handler body:
- **direct_suspend** — contains a known suspending call (`DisplayError[Message]`,
`DisplayInfoMessage`, `wxMessageBox`, `ShowModal`, `ShowQuasiModal`, `KIDIALOG`,
`IsOK(`, clipboard ops, `wxFileDialog`, …).
- **infobar** — only `ShowInfoBar*` (non-modal, non-suspending).
- **trivial** — only rethrow / capture / format / logging. Note `wxLogError` is *safe*:
its GUI display is deferred to the idle-time log flush, outside any catch.
`PGM_BASE::HandleException` (`common/pgm_base.cpp:805`) was manually verified — it only
`wxLogError`s → benign.
- **needs_review** — calls functions not classifiable as benign; requires a transitive look.
Full output incl. all site locations: [`audit-results.txt`](audit-results.txt).
Re-run: `python3 catch_audit.py` (path to the kicad tree is hardcoded at the top).
## Results (2026-06-10, kicad @ wasm-port head)
| Category | Count |
|---|---|
| **direct_suspend** | **85** |
| needs_review | 93 (tail looks mostly benign — accessors/file ops; expect ~1020 to become refactors on inspection) |
| trivial/safe | 458 |
| infobar-only | 0 |
| **total** | **636** |
Per app (direct/review): eeschema 37/17, pcbnew 32/29, common 9/35, cvpcb 3/1, rest
scattered. Concentrated exactly on the file-load error paths the e2e tests exercise:
`pcbnew/files.cpp`, `eeschema/files-io.cpp`, `footprint_libraries_utils.cpp`,
`symbol_library_manager.cpp`, the design-block utils.
wxWidgets adds essentially nothing (zero catch-with-dialog sites in its own code).
## The hand-refactor option (superseded by the fork, kept for the record)
Mechanical hoist per site:
```cpp
wxString err;
try { ... }
catch( const IO_ERROR& ioe ) { err = ioe.What(); } // capture only
if( !err.IsEmpty() ) DisplayErrorMessage( this, err ); // suspend OUTSIDE the catch
```
Effort: 85 hoists (~1530 min each; error-UX paths with near-zero test coverage) ≈ 35
dev-days; 93 reviews ≈ 23 dev-days; destructor-during-unwind audit ≈ half day (cleanup
pads = `catch_all`; dialogs from destructors expected zero); **plus** permanent
upstream-sync policing (every KiCad merge adds new `catch { DisplayError }` sites —
`catch_audit.py` as a CI gate automates detection), **plus** the fork-divergence cost
against the "stay close to upstream" policy. Total ~23 weeks one-time + maintenance tax,
with hard-crash failure modes for any missed transitive site (asyncify asserts mode traps
deterministically — good in CI, fatal in production).
**Verdict:** with the catch-arm-hoisting fork (05) this entire refactor becomes
unnecessary — all 85 direct sites are C++ catches, which the fork makes legal. Only
suspend-inside-`catch_all`-cleanup remains forbidden, which KiCad does not do.