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

+21 -7
View File
@@ -226,23 +226,37 @@ impl crate::command::CadCommand for PluginInteractiveAdapter {
fn name(&self) -> &'static str {
"PLUGIN"
}
// Every call into the plugin runs under a panic guard (#145): a buggy plugin
// that panics mid-command leaves the host running — the command just ends.
fn prompt(&self) -> String {
self.inner.prompt()
crate::plugin::guard("InteractiveCommand::prompt", || self.inner.prompt())
.unwrap_or_default()
}
fn on_point(&mut self, pt: glam::Vec3) -> crate::command::CmdResult {
plugin_step_to_result(self.inner.on_point([pt.x as f64, pt.y as f64, pt.z as f64]))
crate::plugin::guard("InteractiveCommand::on_point", || {
self.inner.on_point([pt.x as f64, pt.y as f64, pt.z as f64])
})
.map(plugin_step_to_result)
.unwrap_or(crate::command::CmdResult::Cancel)
}
fn on_enter(&mut self) -> crate::command::CmdResult {
plugin_step_to_result(self.inner.on_enter())
crate::plugin::guard("InteractiveCommand::on_enter", || self.inner.on_enter())
.map(plugin_step_to_result)
.unwrap_or(crate::command::CmdResult::Cancel)
}
fn needs_entity_pick(&self) -> bool {
self.inner.needs_object_pick()
crate::plugin::guard("InteractiveCommand::needs_object_pick", || {
self.inner.needs_object_pick()
})
.unwrap_or(false)
}
fn on_entity_pick(&mut self, handle: Handle, pt: glam::Vec3) -> crate::command::CmdResult {
plugin_step_to_result(
crate::plugin::guard("InteractiveCommand::on_object_pick", || {
self.inner
.on_object_pick(handle, [pt.x as f64, pt.y as f64, pt.z as f64]),
)
.on_object_pick(handle, [pt.x as f64, pt.y as f64, pt.z as f64])
})
.map(plugin_step_to_result)
.unwrap_or(crate::command::CmdResult::Cancel)
}
}