CNA-BUG-183: The SDL3 file-dialog and tray-entry trampolines call application callbacks without a try/catch, so a throwing handler unwinds through SDL's C code
Evidence basis: source-verified at the pinned commit; executed for this entry (the Evidence section names exactly what was run). Claims on this page were checked by reading the CNA source at commit 009d40f5; unless a sentence says otherwise, nothing here was built or executed. Nothing on this page was executed unless the Evidence section says so.
ResultTrampoline and EntryClickTrampoline invoke the application's std::function directly from SDL C callbacks, so an exception escaping it crosses SDL frames (undefined behaviour, typically std::terminate).
- Identifier
CNA-BUG-183- Category
- Bug
- Subsystem
- Platforms
- Status
- Open
- Verified against
- CNA
009d40f5(009d40f5dd085c4e674d3479675fac84b12b3e0a) - Severity
- Low (a triage suggestion, not a project priority)
- Evidence basis
- Reproduced: executed for this entry (the Evidence section names exactly what was run)
- Tests touching this area
- None
- Affected contract
- CNA::Devices::FileDialog result callbacks and CNA::Devices::SystemTray entry-click callbacks on the SDL3 platform
Expected behaviour
CNA's own devices event contract (devices-event-contract.md, section 6, Exception semantics) states the decided policy: an unhandled exception escaping a user-supplied handler must never propagate across a C callback boundary, and the normative rule is log-and-continue, catching at the C-callback or thread-entry boundary and making the event observable. The Accelerometer and Gyroscope dispatch path follows it. FileDialog.hpp and SystemTray.hpp neither state that handlers must not throw nor catch on the caller's behalf.
Actual behaviour at TARGET
ResultTrampoline in Sdl3SystemServices.cpp builds the path list and calls context->callback(paths); EntryClickTrampoline in Sdl3Tray.cpp calls (*callback)(). Both are passed to SDL as C function pointers and have no try/catch. FileDialog.hpp notes the result may arrive “on a different thread than the one the call” was made on.
Source locations
modules/platform/src/Sdl3/Sdl3SystemServices.cpp— ResultTrampolinemodules/platform/src/Sdl3/Sdl3Tray.cpp— EntryClickTrampolinemodules/devices-ext/include/CNA/Devices/FileDialog.hpp— callback threading note; no exception rule
Evidence
Checked by reading at 009d40f5; the file-dialog half was also executed (see below), the tray half was only read.
Independent re-verification: Checked by reading at 009d40f5; not executed. The contract is CNA's own devices event contract, section 6 of which is titled 'decided but not yet uniformly implemented' and lists sensor paths only, so the SDL3 dialog and tray boundaries are an unlisted instance of that policy. Neither devices-ext wrapper adds a catch (FileDialog.cpp passes the callback straight to the platform and SystemTray.cpp forwards it), so a handler's exception reaches the trampoline. What SDL's C frames then do with it (undefined behaviour, typically std::terminate) depends on the toolchain and was not run.
Independent re-verification: Checked by reading at 009d40f5. Reproduced for the file dialog by the audit: TARGET's Sdl3SystemServices.cpp was compiled with g++ -O0 and linked with a system SDL 3.4.0 (not CNA's pinned SDL commit); Sdl3Dialogs::ShowOpenFileDialog was called with a callback that throws, SDL's zenity driver delivered the (cancelled) result on its own thread through ResultTrampoline, and the process ended with 'terminate called after throwing an instance of std::runtime_error' (SIGABRT). The SystemTray entry-click trampoline was read but not run. Neither devices-ext wrapper adds a catch (FileDialog.cpp passes the callback straight to the platform and SystemTray.cpp forwards it). What SDL's C frames do with an exception delivered on the calling thread depends on the toolchain: with default x86-64 unwind tables it unwinds through them, without unwind tables it terminates.
Focused reproduction
No focused reproduction is known. Nothing has been invented here; the evidence above is what exists.
Current tests
The trampolines are not driven by any test (see CNA-VGAP-007).
Regression test
A test that invokes each trampoline with a throwing callback and asserts the exception is contained (logged or reported); the fix is a catch-all around the call.
Blast radius
Applications whose dialog or tray handlers can throw, on the SDL3 platform.
Workaround
Catch every exception inside the handler.
Related pages
The same subject is explained at several altitudes. These are the neighbouring pages at each one.
- Internals
- Device extensions: dialogs
- Deep dives
- Host devices: lifetime
- Known issues
- Bug index