pcbjam/docs/features/wasm-exceptions/04-kicad-audit.md
Viktor Vaczi 66ce367703 docs: asyncify currData + wasm-exceptions research dossiers
Two research dossiers under docs/features:
- async/: Asyncify currData contention — the clipboard crash and post-idle
  tool hang as one disease (single global suspension slot, three producers),
  asyncify internals walkthroughs, prior art, arbiter designs A/B.
- wasm-exceptions/: -fexceptions vs -fwasm-exceptions — measured invoke-driven
  share of the asyncify tax on pcbnew (59% raw / 64% gzip; download 64.5->36 MB
  if migrated), KiCad dialog-in-catch audit (85 direct / 93 review of 636,
  catch_audit.py included), binaryen toolchain status (partial EH support since
  v125; TryTable unsupported), and a catch-arm-hoisting pre-pass design that
  would obsolete the KiCad-side refactor. Cross-linked with the parked
  KICAD_WASM_EH end-to-end experiment (docs/wasm-exceptions-experiment.md).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 17:02:59 +02:00

3.4 KiB
Raw Blame 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:

catch( const IO_ERROR& ioe )
{
    DisplayErrorMessage( this, ioe.What() );   // → ShowModal → Asyncify suspension
}

Method

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 wxLogErrors → benign.
  • needs_review — calls functions not classifiable as benign; requires a transitive look.

Full output incl. all site locations: 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:

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.