fix(plugin): guard plugin calls across host shutdown generations

Tests (and any host that shuts down then rescans) can hold instances of
a PREVIOUS plugin generation; their entry points dangle after dlclose,
so the next shutdown's destroyInstance notification jumped into
unmapped memory (SIGSEGV on Linux; masked on macOS). Plugin gains an
 flag set by unload_all before dlclose; call_action/call_entry
fail fast instead of calling into freed code.
This commit is contained in:
2026-08-21 07:33:18 +08:00
parent 74b18ea71a
commit 2794f8bb80
7 changed files with 32 additions and 0 deletions
+21
View File
@@ -605,6 +605,11 @@ pub struct Plugin {
/// OfxPlugin 结构指针(kOfxImageEffectPropPluginHandle 属性用;
/// 公开:测试构造假插件需要)。
pub ofx_plugin: *mut c_void,
/// 二进制已卸载(host shutdown / unload_all)。置位后任何
/// call_action/call_entry 直接失败而不触碰入口——插件入口指向
/// 已 dlclose 的代码,调用即 SIGSEGV(测试串行 shutdown/重扫描
/// 会产生跨代实例,它们持有的正是旧代插件记录)。
pub unloaded: std::sync::atomic::AtomicBool,
}
// dlopen 句柄与 OfxPlugin 指针是**不透明令牌**(只经 dlsym/插件
@@ -654,6 +659,9 @@ impl Plugin {
in_args: &PropertySet,
out_args: &PropertySet,
) -> i32 {
if self.unloaded.load(std::sync::atomic::Ordering::Acquire) {
return status::ERR_FATAL;
}
// 属性集经裸指针(标签 0)传给插件(property suite 接受)。
let in_ptr = in_args as *const PropertySet as *mut c_void;
let out_ptr = out_args as *const PropertySet as *mut c_void;
@@ -676,6 +684,9 @@ impl Plugin {
in_args: &PropertySet,
out_args: &PropertySet,
) -> i32 {
if self.unloaded.load(std::sync::atomic::Ordering::Acquire) {
return status::ERR_FATAL;
}
let in_ptr = in_args as *const PropertySet as *mut c_void;
let out_ptr = out_args as *const PropertySet as *mut c_void;
let action = cs(action);
@@ -991,6 +1002,7 @@ impl PluginCache {
lib,
entry,
ofx_plugin: ofx as *mut c_void,
unloaded: std::sync::atomic::AtomicBool::new(false),
};
// 根描述符属性(HS: effectDescriptorStuffofxhImageEffect.cpp:133-155)。
@@ -1070,7 +1082,16 @@ impl PluginCache {
/// 卸载全部(shutdown 路径)。`scanned_paths` 一并清空——
/// 否则再 init 后的扫描会被去重短路(缓存已空但路径仍在)。
/// 卸载前先给每个插件置 `unloaded`:实例可能跨代存活(测试的
/// 串行 shutdown/重扫描),它们持有的旧代入口已随 dlclose 失效,
/// 置位后 call_action 直接失败而非跳野指针。
pub(crate) fn unload_all(&self) {
{
let plugins = self.plugins.lock().unwrap_or_else(|e| e.into_inner());
for p in plugins.iter() {
p.unloaded.store(true, std::sync::atomic::Ordering::Release);
}
}
self.plugins
.lock()
.unwrap_or_else(|e| e.into_inner())
+2
View File
@@ -1131,6 +1131,7 @@ mod tests {
lib: std::ptr::null_mut(),
entry: dummy_entry,
ofx_plugin: std::ptr::null_mut(),
unloaded: std::sync::atomic::AtomicBool::new(false),
});
let mut params = ParamSetInstance { params: Vec::new() };
params.params.push(Box::new(ParamInstance::from_def(ParamDef::new(
@@ -1235,6 +1236,7 @@ mod tests {
lib: std::ptr::null_mut(),
entry: dummy_entry,
ofx_plugin: std::ptr::null_mut(),
unloaded: std::sync::atomic::AtomicBool::new(false),
});
let inst = crate::instance::Instance {
props: crate::property::PropertySet::new(),
@@ -574,6 +574,7 @@ mod tests {
lib: std::ptr::null_mut(),
entry: dummy_entry,
ofx_plugin: std::ptr::null_mut(),
unloaded: std::sync::atomic::AtomicBool::new(false),
}),
context: "OfxImageEffectContextFilter".into(),
params: crate::param::ParamSetInstance { params: vec![] },
+5
View File
@@ -575,6 +575,7 @@ mod tests {
lib: std::ptr::null_mut(),
entry: dummy_entry,
ofx_plugin: std::ptr::null_mut(),
unloaded: std::sync::atomic::AtomicBool::new(false),
};
let interact = Interact::new(Arc::new(plugin), dummy_entry, std::ptr::null_mut());
let handle = interact.handle();
@@ -648,6 +649,7 @@ mod tests {
lib: std::ptr::null_mut(),
entry: recording_entry,
ofx_plugin: std::ptr::null_mut(),
unloaded: std::sync::atomic::AtomicBool::new(false),
};
let interact = Interact::new(Arc::new(plugin), recording_entry, std::ptr::null_mut());
@@ -717,6 +719,7 @@ mod tests {
lib: std::ptr::null_mut(),
entry: capture_entry,
ofx_plugin: std::ptr::null_mut(),
unloaded: std::sync::atomic::AtomicBool::new(false),
};
let interact = Interact::new(Arc::new(plugin), capture_entry, std::ptr::null_mut());
// 设 pixelScale=2 → canonical = viewport/2。
@@ -768,6 +771,7 @@ mod tests {
lib: std::ptr::null_mut(),
entry: capture_entry,
ofx_plugin: std::ptr::null_mut(),
unloaded: std::sync::atomic::AtomicBool::new(false),
};
let interact = Interact::new(Arc::new(plugin), capture_entry, std::ptr::null_mut());
assert_eq!(interact.key_down(crate::host::KEY_RETURN, "", 1.0), status::OK);
@@ -805,6 +809,7 @@ mod tests {
lib: std::ptr::null_mut(),
entry: counting_entry,
ofx_plugin: std::ptr::null_mut(),
unloaded: std::sync::atomic::AtomicBool::new(false),
};
let interact = Interact::new(Arc::new(plugin), counting_entry, std::ptr::null_mut());
interact.destroy();
+1
View File
@@ -772,6 +772,7 @@ mod tests {
lib: std::ptr::null_mut(),
entry: dummy_entry,
ofx_plugin: std::ptr::null_mut(),
unloaded: std::sync::atomic::AtomicBool::new(false),
})
}
@@ -449,6 +449,7 @@ mod tests {
lib: std::ptr::null_mut(),
entry: dummy_entry,
ofx_plugin: std::ptr::null_mut(),
unloaded: std::sync::atomic::AtomicBool::new(false),
})
}
+1
View File
@@ -77,6 +77,7 @@ fn dummy_plugin(descriptor: EffectDescriptor) -> std::sync::Arc<oakplugin::host:
lib: std::ptr::null_mut(),
entry: dummy_entry,
ofx_plugin: std::ptr::null_mut(),
unloaded: std::sync::atomic::AtomicBool::new(false),
})
}