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

+10 -3
View File
@@ -311,7 +311,10 @@ mod loader {
let version: libloading::Symbol<extern "C" fn() -> u32> = lib
.get(b"ocs_plugin_api_version")
.map_err(|_| "missing ocs_plugin_api_version symbol".to_string())?;
let v = version();
// Wrap the version + register calls in a panic guard so a plugin
// that panics during load can't take down the host. (#145)
let v = crate::plugin::guard("version", || version())
.ok_or_else(|| "ocs_plugin_api_version panicked".to_string())?;
if v != ocs_plugin_api::API_VERSION {
return Err(format!(
"API version {v} != host {}",
@@ -324,12 +327,16 @@ mod loader {
> = lib
.get(b"ocs_plugin_register")
.map_err(|_| "missing ocs_plugin_register symbol".to_string())?;
let raw = register();
let raw = crate::plugin::guard("register", || register())
.ok_or_else(|| "ocs_plugin_register panicked".to_string())?;
if raw.is_null() {
return Err("ocs_plugin_register returned null".into());
}
let plugin = *Box::from_raw(raw);
let id = plugin.manifest().id.to_string();
// The manifest read happens once at load; guard it too — a buggy
// manifest() that panics here would otherwise crash startup. (#145)
let id = crate::plugin::guard("manifest", || plugin.manifest().id.to_string())
.ok_or_else(|| "plugin manifest() panicked".to_string())?;
Ok(LoadedPlugin {
plugin,
_lib: lib,