feat(plugin): wrap external-plugin calls in a panic guard

A buggy external plugin that panicked anywhere — during load (version,
register, manifest), during ribbon build / dispatch, or inside an
interactive command (prompt, on_point, on_enter, on_object_pick) — took
the host down with it (#134). Run every cdylib entry point under
catch_unwind so the host stays up: the plugin's call is treated as
inert (load fails, dispatch returns "didn't handle it", interactive
hooks resolve to Cancel) and a "Plugin <hook> panicked: …" line lands
in the command line on the next tick. (#145)

Scope is Rust panics across the cdylib boundary, not genuine UB —
real isolation (segfault-proof) still requires out-of-process plugins.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Hakan SevenandClaude Opus 4.8 committed 2026-06-20 20:17:37 +03:00
1 parent 2a260be31d
commit f860fb39c2
5 files changed
+122 -14

No files matched your search

+14 -3
View File
@@ -23,8 +23,15 @@ pub fn ribbon_modules_enabled(
let mut addons: Vec<(i32, Box<dyn CadModule>)> = Vec::new();
crate::plugin::external::with_loaded(|loaded| {
for lp in loaded {
if !disabled.contains(lp.id.as_str()) {
addons.push((lp.plugin().manifest().ribbon_order, lp.plugin().ribbon()));
if disabled.contains(lp.id.as_str()) {
continue;
}
// Guard the plugin's ribbon build so a panic there can't take
// down the host — the plugin just contributes no tab. (#145)
if let Some(entry) = crate::plugin::guard("ribbon", || {
(lp.plugin().manifest().ribbon_order, lp.plugin().ribbon())
}) {
addons.push(entry);
}
}
});
@@ -48,7 +55,11 @@ pub(crate) fn try_dispatch(app: &mut OpenCADStudio, tab: usize, cmd: &str) -> bo
if disabled.contains(lp.id.as_str()) {
continue;
}
if lp.plugin().dispatch(&mut host, cmd) {
// A panic inside the plugin's dispatch must not crash the host;
// treat a panicking plugin as "didn't handle it". (#145)
if crate::plugin::guard("dispatch", || lp.plugin().dispatch(&mut host, cmd))
.unwrap_or(false)
{
return true;
}
}