From 6dbbffa96c06b688db7fc8fb480c11093901ea5d Mon Sep 17 00:00:00 2001 From: Grzegorz Nosek Date: Sun, 31 May 2026 08:50:42 +0200 Subject: [PATCH 01/17] new(build): add dedicated miri feature flags Signed-off-by: Grzegorz Nosek --- falco_plugin/Cargo.toml | 4 ++++ falco_plugin_tests/Cargo.toml | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/falco_plugin/Cargo.toml b/falco_plugin/Cargo.toml index 381c3c0e..c0e2ad07 100644 --- a/falco_plugin/Cargo.toml +++ b/falco_plugin/Cargo.toml @@ -36,8 +36,12 @@ unexpected_cfgs = { level = "allow", check-cfg = ['cfg(linkage, values("static") [[example]] name = "dummy_source" crate-type = ["cdylib"] +required-features = ["dylib-examples"] [features] +default = ["dylib-examples"] +miri = ["thread-safe-tables"] +dylib-examples = [] thread-safe-tables = ["dep:parking_lot"] [dependencies] diff --git a/falco_plugin_tests/Cargo.toml b/falco_plugin_tests/Cargo.toml index 39d4bc95..5e55cfd2 100644 --- a/falco_plugin_tests/Cargo.toml +++ b/falco_plugin_tests/Cargo.toml @@ -43,7 +43,7 @@ harness = false anyhow = "1.0.88" cxx = { version = "1.0.124", features = ["c++17"] } falco_event_schema = { version = "0.5.0", path = "../falco_event_schema", features = ["derive_deftly"] } -falco_plugin = { version = "0.5.0", path = "../falco_plugin", features = ["thread-safe-tables"] } +falco_plugin = { version = "0.5.0", path = "../falco_plugin", default-features = false, features = ["thread-safe-tables"] } falco_plugin_runner = { version = "0.5.0", path = "../falco_plugin_runner" } log = "0.4.22" typed-path = "0.11.0" From bf1d0f83e394da7f1c8709a5c5fec991a3e09d27 Mon Sep 17 00:00:00 2001 From: Grzegorz Nosek Date: Sun, 31 May 2026 09:03:38 +0200 Subject: [PATCH 02/17] new(build): skip unsupported tests under miri Signed-off-by: Grzegorz Nosek --- falco_plugin_tests/benches/plugin_custom_tables.rs | 4 ++-- falco_plugin_tests/benches/plugin_extract_static.rs | 2 +- falco_plugin_tests/benches/plugin_source_batch.rs | 2 +- falco_plugin_tests/benches/plugin_source_parse_noop.rs | 2 +- falco_plugin_tests/benches/plugin_threadinfo.rs | 10 +++++----- falco_plugin_tests/src/bin/dump_raw_events.rs | 4 ++-- falco_plugin_tests/src/lib.rs | 10 ++++++---- falco_plugin_tests/tests/capture_listen_resched.rs | 7 ++++++- falco_plugin_tests/tests/capture_listen_run_forever.rs | 7 ++++++- falco_plugin_tests/tests/scap.rs | 2 +- falco_plugin_tests/tests/scap_import_table.rs | 2 +- .../tests/scap_import_table_bad_key_type.rs | 2 +- 12 files changed, 33 insertions(+), 21 deletions(-) diff --git a/falco_plugin_tests/benches/plugin_custom_tables.rs b/falco_plugin_tests/benches/plugin_custom_tables.rs index 0133507c..6520699d 100644 --- a/falco_plugin_tests/benches/plugin_custom_tables.rs +++ b/falco_plugin_tests/benches/plugin_custom_tables.rs @@ -267,7 +267,7 @@ fn bench_plugin_custom_table_extract_only(c: &mut Criterion) { g.throughput(Throughput::Elements(NUM_EVENTS as u64)); bench_plugin_custom_table_extract_only_impl::(&mut g); - #[cfg(have_libsinsp)] + #[cfg(all(have_libsinsp, not(miri)))] bench_plugin_custom_table_extract_only_impl::(&mut g); g.finish(); @@ -365,7 +365,7 @@ fn bench_plugin_custom_table_insert_and_extract(c: &mut Criterion) { bench_plugin_custom_table_insert_and_extract_impl::( &mut g, ); - #[cfg(have_libsinsp)] + #[cfg(all(have_libsinsp, not(miri)))] bench_plugin_custom_table_insert_and_extract_impl::(&mut g); g.finish(); diff --git a/falco_plugin_tests/benches/plugin_extract_static.rs b/falco_plugin_tests/benches/plugin_extract_static.rs index 27b2b005..7aa600a5 100644 --- a/falco_plugin_tests/benches/plugin_extract_static.rs +++ b/falco_plugin_tests/benches/plugin_extract_static.rs @@ -73,7 +73,7 @@ fn plugin_extract_static(c: &mut Criterion) { g.throughput(Throughput::Elements(NUM_EVENTS as u64)); plugin_extract_static_impl::(&mut g); - #[cfg(have_libsinsp)] + #[cfg(all(have_libsinsp, not(miri)))] plugin_extract_static_impl::(&mut g); g.finish(); diff --git a/falco_plugin_tests/benches/plugin_source_batch.rs b/falco_plugin_tests/benches/plugin_source_batch.rs index 1dc68f74..270f9eb6 100644 --- a/falco_plugin_tests/benches/plugin_source_batch.rs +++ b/falco_plugin_tests/benches/plugin_source_batch.rs @@ -41,7 +41,7 @@ fn plugin_source_batch(c: &mut Criterion) { g.throughput(Throughput::Elements(NUM_EVENTS as u64)); bench_plugin_source_batch_impl::(&mut g); - #[cfg(have_libsinsp)] + #[cfg(all(have_libsinsp, not(miri)))] bench_plugin_source_batch_impl::(&mut g); g.finish(); diff --git a/falco_plugin_tests/benches/plugin_source_parse_noop.rs b/falco_plugin_tests/benches/plugin_source_parse_noop.rs index ca20ecfc..621e8990 100644 --- a/falco_plugin_tests/benches/plugin_source_parse_noop.rs +++ b/falco_plugin_tests/benches/plugin_source_parse_noop.rs @@ -67,7 +67,7 @@ fn plugin_source_parse_noop(c: &mut Criterion) { g.throughput(Throughput::Elements(NUM_EVENTS as u64)); bench_plugin_source_parse_noop_impl::(&mut g); - #[cfg(have_libsinsp)] + #[cfg(all(have_libsinsp, not(miri)))] bench_plugin_source_parse_noop_impl::(&mut g); g.finish(); diff --git a/falco_plugin_tests/benches/plugin_threadinfo.rs b/falco_plugin_tests/benches/plugin_threadinfo.rs index cce72fc3..f0d27ff7 100644 --- a/falco_plugin_tests/benches/plugin_threadinfo.rs +++ b/falco_plugin_tests/benches/plugin_threadinfo.rs @@ -110,7 +110,7 @@ impl ParsePlugin for ParseThreadInfoSetCustomField { static_plugin!(PARSE_THREADINFO_SET_CUSTOM_FIELD = ParseThreadInfoSetCustomField); -#[cfg_attr(not(have_libsinsp), allow(unused))] +#[cfg_attr(any(not(have_libsinsp), miri), allow(unused))] fn bench_plugin_threadinfo_tid(g: &mut BenchmarkGroup) { let (mut driver, _plugin) = init_plugin::(&BATCHED_EMPTY_EVENT, c"1").unwrap(); let extract_plugin = driver.register_plugin(&EXTRACT_THREADINFO, c"").unwrap(); @@ -139,7 +139,7 @@ fn bench_plugin_threadinfo_tid(g: &mut BenchmarkG ); } -#[cfg_attr(not(have_libsinsp), allow(unused))] +#[cfg_attr(any(not(have_libsinsp), miri), allow(unused))] fn bench_plugin_threadinfo_missing_custom_field( g: &mut BenchmarkGroup, ) { @@ -171,7 +171,7 @@ fn bench_plugin_threadinfo_missing_custom_field( ); } -#[cfg_attr(not(have_libsinsp), allow(unused))] +#[cfg_attr(any(not(have_libsinsp), miri), allow(unused))] fn bench_plugin_threadinfo_only_set_custom_field( g: &mut BenchmarkGroup, ) { @@ -200,7 +200,7 @@ fn bench_plugin_threadinfo_only_set_custom_field( ); } -#[cfg_attr(not(have_libsinsp), allow(unused))] +#[cfg_attr(any(not(have_libsinsp), miri), allow(unused))] fn bench_plugin_threadinfo_custom_field(g: &mut BenchmarkGroup) { let (mut driver, _plugin) = init_plugin::(&BATCHED_EMPTY_EVENT, c"1").unwrap(); driver @@ -236,7 +236,7 @@ fn plugin_threadinfo(c: &mut Criterion) { let mut g = c.benchmark_group("plugin_threadinfo"); g.throughput(Throughput::Elements(NUM_EVENTS as u64)); - #[cfg(have_libsinsp)] + #[cfg(all(have_libsinsp, not(miri)))] { crate::bench_plugin_threadinfo_tid::(&mut g); crate::bench_plugin_threadinfo_missing_custom_field::( diff --git a/falco_plugin_tests/src/bin/dump_raw_events.rs b/falco_plugin_tests/src/bin/dump_raw_events.rs index 0e9349f8..3516e581 100644 --- a/falco_plugin_tests/src/bin/dump_raw_events.rs +++ b/falco_plugin_tests/src/bin/dump_raw_events.rs @@ -37,12 +37,12 @@ impl ParsePlugin for DumperPlugin { static_plugin!(DUMPER_PLUGIN = DumperPlugin); -#[cfg(not(have_libsinsp))] +#[cfg(any(not(have_libsinsp), miri))] fn main() { panic!("libsinsp not available"); } -#[cfg(have_libsinsp)] +#[cfg(all(have_libsinsp, not(miri)))] fn main() { use falco_plugin_tests::CapturingTestDriver; use falco_plugin_tests::SavefileTestDriver; diff --git a/falco_plugin_tests/src/lib.rs b/falco_plugin_tests/src/lib.rs index 250ec0e6..e677a9dd 100644 --- a/falco_plugin_tests/src/lib.rs +++ b/falco_plugin_tests/src/lib.rs @@ -2,7 +2,7 @@ //! //! This crate isn't really intended for public use, except maybe as a collection of sample plugins. -#[cfg(have_libsinsp)] +#[cfg(all(have_libsinsp, not(miri)))] pub mod ffi; use std::ffi::CStr; @@ -26,20 +26,22 @@ pub fn init_plugin( #[macro_export] macro_rules! instantiate_tests { - ($($func:ident);*) => { + ($($(#[$meta:meta])* $func:ident);*) => { mod native { $( #[test] + $(#[$meta])* fn $func() { super::$func::<$crate::native::Driver>() } )* } - #[cfg(have_libsinsp)] + #[cfg(all(have_libsinsp, not(miri)))] mod ffi { $( #[test] + $(#[$meta])* fn $func() { super::$func::<$crate::ffi::Driver>() } @@ -51,7 +53,7 @@ macro_rules! instantiate_tests { #[macro_export] macro_rules! instantiate_sinsp_tests { ($($func:ident);*) => { - #[cfg(have_libsinsp)] + #[cfg(all(have_libsinsp, not(miri)))] mod ffi { $( #[test] diff --git a/falco_plugin_tests/tests/capture_listen_resched.rs b/falco_plugin_tests/tests/capture_listen_resched.rs index ceac3e55..813049d2 100644 --- a/falco_plugin_tests/tests/capture_listen_resched.rs +++ b/falco_plugin_tests/tests/capture_listen_resched.rs @@ -116,5 +116,10 @@ mod tests { } } - instantiate_tests!(test_listen); + // Miri: ignored because this test relies on real-time threading (background thread scheduling, + // sleeps, and wall-clock timeouts) that Miri cannot simulate reliably. + instantiate_tests!( + #[cfg_attr(miri, ignore)] + test_listen + ); } diff --git a/falco_plugin_tests/tests/capture_listen_run_forever.rs b/falco_plugin_tests/tests/capture_listen_run_forever.rs index 54eeb2c9..0f808f82 100644 --- a/falco_plugin_tests/tests/capture_listen_run_forever.rs +++ b/falco_plugin_tests/tests/capture_listen_run_forever.rs @@ -129,5 +129,10 @@ mod tests { } } - instantiate_tests!(test_listen); + // Miri: ignored because this test relies on real-time threading (background thread scheduling, + // sleeps, and wall-clock timeouts) that Miri cannot simulate reliably. + instantiate_tests!( + #[cfg_attr(miri, ignore)] + test_listen + ); } diff --git a/falco_plugin_tests/tests/scap.rs b/falco_plugin_tests/tests/scap.rs index b53d0539..ef08260f 100644 --- a/falco_plugin_tests/tests/scap.rs +++ b/falco_plugin_tests/tests/scap.rs @@ -59,7 +59,7 @@ impl ParsePlugin for DummyPlugin { static_plugin!(PARSE_API = DummyPlugin); #[cfg(test)] -#[cfg_attr(not(have_libsinsp), allow(dead_code))] +#[cfg_attr(any(not(have_libsinsp), miri), allow(dead_code))] mod tests { use falco_plugin_tests::{ init_plugin, instantiate_sinsp_tests, CapturingTestDriver, SavefileTestDriver, ScapStatus, diff --git a/falco_plugin_tests/tests/scap_import_table.rs b/falco_plugin_tests/tests/scap_import_table.rs index f70c939f..f74dbd05 100644 --- a/falco_plugin_tests/tests/scap_import_table.rs +++ b/falco_plugin_tests/tests/scap_import_table.rs @@ -151,7 +151,7 @@ impl ParsePlugin for DummyPlugin { static_plugin!(PARSE_API = DummyPlugin); #[cfg(test)] -#[cfg_attr(not(have_libsinsp), allow(dead_code))] +#[cfg_attr(any(not(have_libsinsp), miri), allow(dead_code))] mod tests { use crate::TEST_DONE; use falco_plugin_tests::{ diff --git a/falco_plugin_tests/tests/scap_import_table_bad_key_type.rs b/falco_plugin_tests/tests/scap_import_table_bad_key_type.rs index cb46532f..4e1ab6ff 100644 --- a/falco_plugin_tests/tests/scap_import_table_bad_key_type.rs +++ b/falco_plugin_tests/tests/scap_import_table_bad_key_type.rs @@ -73,7 +73,7 @@ impl ParsePlugin for DummyPlugin { static_plugin!(PARSE_API = DummyPlugin); #[cfg(test)] -#[cfg_attr(not(have_libsinsp), allow(dead_code))] +#[cfg_attr(any(not(have_libsinsp), miri), allow(dead_code))] mod tests { use falco_plugin_tests::{init_plugin, instantiate_sinsp_tests, TestDriver}; From 745139c283608a1e4386acb7c83833e8fe4530d1 Mon Sep 17 00:00:00 2001 From: Grzegorz Nosek Date: Sun, 31 May 2026 09:09:18 +0200 Subject: [PATCH 03/17] fix(runner): avoid retag UB in async_event handler test tests::native::test_async ... error: Undefined Behavior: trying to retag from <129475> for Unique permission at alloc40967[0xf8], but that tag does not exist in the borrow stack for this location --> falco_plugin_runner/src/plugin/async_event.rs:143:25 | = note: this error occurs as part of retag at alloc40967[0xf8..0x140] | = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information help: <129475> was created by a SharedReadWrite retag at offsets [0xf8..0x140] --> falco_plugin_runner/src/plugin/async_event.rs:49:8 help: <129475> was later invalidated at offsets [0x0..0x198] by a Unique retag --> falco_plugin_runner/src/lib.rs:87:22 = note: this is on thread `unnamed-2` = note: stack backtrace: 0: falco_plugin_runner::plugin::async_event::async_handler_inner at falco_plugin_runner/src/plugin/async_event.rs:143:26: 143:59 1: falco_plugin_runner::plugin::async_event::async_handler::{closure#0} at falco_plugin_runner/src/plugin/async_event.rs:115:9: 115:47 2: <{closure@falco_plugin_runner::plugin::async_event::async_handler::{closure#0}} as std::ops::FnOnce<()>>::call_once - shim at .../lib/rustlib/src/rust/library/core/src/ops/function.rs:250:5: 250:71 3: as std::ops::FnOnce<()>>::call_once at .../lib/rustlib/src/rust/library/core/src/panic/unwind_safe.rs:274:9: 274:19 4: std::panicking::catch_unwind::do_call::, i32> at .../lib/rustlib/src/rust/library/std/src/panicking.rs:581:40: 581:43 5: std::panicking::catch_unwind::> at .../lib/rustlib/src/rust/library/std/src/panicking.rs:544:19: 544:88 6: std::panic::catch_unwind::, i32> at .../lib/rustlib/src/rust/library/std/src/panic.rs:359:14: 359:40 7: falco_plugin_runner::plugin::async_event::async_handler at falco_plugin_runner/src/plugin/async_event.rs:114:18: 116:8 8: falco_plugin::async_event::AsyncHandler::emit::>> at falco_plugin/src/async_event/async_handler.rs:39:13: 39:87 9: ::start_async::{closure#0} at falco_plugin_tests/tests/async.rs:74:13: 74:70 10: falco_plugin::async_event::BackgroundTask::spawn::<{closure@falco_plugin_tests/tests/async.rs:72:72: 72:79}>::{closure#0} at falco_plugin/src/async_event/background_task.rs:97:17: 97:23 note: the last function in that backtrace got called indirectly due to this code --> falco_plugin/src/async_event/background_task.rs:95:11 Signed-off-by: Grzegorz Nosek --- falco_plugin_runner/src/plugin/async_event.rs | 24 ++++++++++++------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/falco_plugin_runner/src/plugin/async_event.rs b/falco_plugin_runner/src/plugin/async_event.rs index 8b10a378..1b76321d 100644 --- a/falco_plugin_runner/src/plugin/async_event.rs +++ b/falco_plugin_runner/src/plugin/async_event.rs @@ -8,13 +8,17 @@ use std::collections::VecDeque; use std::ffi::{c_char, CStr}; use std::sync::{Arc, Mutex}; +struct AsyncOwner { + async_events: Vec, + event_queue: Arc>>>, +} + pub struct AsyncPlugin { plugin: *mut ss_plugin_t, api: *const plugin_api__bindgen_ty_4, - async_events: Vec, + owner: Box, last_event: Option>, - event_queue: Arc>>>, } impl AsyncPlugin { @@ -35,9 +39,11 @@ impl AsyncPlugin { Self { plugin, api, - async_events, + owner: Box::new(AsyncOwner { + async_events, + event_queue: Arc::new(Mutex::new(VecDeque::new())), + }), last_event: None, - event_queue: Arc::new(Mutex::new(VecDeque::new())), } } @@ -45,8 +51,10 @@ impl AsyncPlugin { unsafe { &*self.api } } - fn owner(&mut self) -> *mut ss_plugin_owner_t { - self as *mut _ as *mut _ + fn owner(&self) -> *mut ss_plugin_owner_t { + (&*self.owner as *const AsyncOwner) + .cast_mut() + .cast::() } pub fn on_capture_start(&mut self) -> Result<(), ss_plugin_rc> { @@ -78,7 +86,7 @@ impl AsyncPlugin { } pub fn next_event(&mut self) -> Result<*mut ss_plugin_event, ss_plugin_rc> { - self.last_event = self.event_queue.lock().unwrap().pop_front(); + self.last_event = self.owner.event_queue.lock().unwrap().pop_front(); match &self.last_event { Some(evt) => Ok(evt.as_ptr().cast::().cast_mut()), None => Err(ss_plugin_rc_SS_PLUGIN_TIMEOUT), @@ -140,7 +148,7 @@ unsafe fn async_handler_inner( err: *mut c_char, ) -> i32 { let err = unsafe { std::slice::from_raw_parts_mut(err as *mut _, PLUGIN_MAX_ERRLEN as usize) }; - let owner = unsafe { &mut *(owner as *mut AsyncPlugin) }; + let owner = unsafe { &*(owner as *const AsyncOwner) }; let evt_len = unsafe { (*event).len as usize }; let event = event as *const u8; From f1c1413610baec3bcfa107feb00b93741fee6c09 Mon Sep 17 00:00:00 2001 From: Grzegorz Nosek Date: Sun, 31 May 2026 09:10:47 +0200 Subject: [PATCH 04/17] fix(plugin): avoid CStr borrow invalidation in async emit test tests::native::test_async ... error: Undefined Behavior: attempting a read access using <176063> at alloc58248[0x0], but that tag does not exist in the borrow stack for this location --> .../lib/rustlib/src/rust/library/core/src/ffi/c_str.rs:766:22 | 766 | unsafe { strlen(s) } | ^^^^^^^^^ this error occurs as part of an access at alloc58248[0x0..0x1] | = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information help: <176063> was created by a SharedReadOnly retag at offsets [0x0..0x400] --> falco_plugin/src/async_event/async_handler.rs:35:22 help: <176063> was later invalidated at offsets [0x0..0x400] by a Unique retag --> falco_plugin/src/async_event/async_handler.rs:39:69 = note: this is on thread `unnamed-2` = note: stack backtrace: 0: core::ffi::c_str::strlen::runtime at .../lib/rustlib/src/rust/library/core/src/ffi/c_str.rs:766:22: 766:31 1: core::ffi::c_str::strlen at .../lib/rustlib/src/rust/library/core/src/intrinsics/mod.rs:2447:9: 2447:61 2: std::ffi::CStr::from_ptr::<'_> at .../lib/rustlib/src/rust/library/core/src/ffi/c_str.rs:256:28: 256:39 3: falco_plugin::strings::from_ptr::try_str_from_ptr_with_lifetime::<*const i8> at falco_plugin/src/strings/from_ptr.rs:22:25: 22:51 4: falco_plugin::strings::from_ptr::try_str_from_ptr at falco_plugin/src/strings/from_ptr.rs:28:14: 28:55 5: falco_plugin::async_event::AsyncHandler::emit::>> at falco_plugin/src/async_event/async_handler.rs:43:27: 43:53 6: ::start_async::{closure#0} at falco_plugin_tests/tests/async.rs:75:21: 76:74 7: falco_plugin::async_event::BackgroundTask::spawn::<{closure@falco_plugin_tests/tests/async.rs:72:72: 72:79}>::{closure#0} at falco_plugin/src/async_event/background_task.rs:97:17: 97:23 note: the last function in that backtrace got called indirectly due to this code --> falco_plugin/src/async_event/background_task.rs:95:11 Signed-off-by: Grzegorz Nosek --- falco_plugin/src/async_event/async_handler.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/falco_plugin/src/async_event/async_handler.rs b/falco_plugin/src/async_event/async_handler.rs index 0b34e07e..9563d7f4 100644 --- a/falco_plugin/src/async_event/async_handler.rs +++ b/falco_plugin/src/async_event/async_handler.rs @@ -32,7 +32,6 @@ impl AsyncHandler { pub fn emit(&self, event: impl EventToBytes) -> Result<(), anyhow::Error> { let mut err = [0 as c_char; PLUGIN_MAX_ERRLEN as usize]; let mut buf = Vec::new(); - let err_ptr = &err as *const [c_char] as *const c_char; event.write(&mut buf)?; match unsafe { @@ -40,6 +39,7 @@ impl AsyncHandler { } { Ok(()) => Ok(()), Err(e) => { + let err_ptr = err.as_ptr(); let msg = try_str_from_ptr(&err_ptr)?; Err(e).context(msg.to_string()) } From 7a2e4470770354e04c28bf2b717e035059c285c3 Mon Sep 17 00:00:00 2001 From: Grzegorz Nosek Date: Sun, 31 May 2026 09:16:26 +0200 Subject: [PATCH 05/17] fix(runner): avoid mutable retag in plugin owner access test tests::native::test_async ... error: Undefined Behavior: trying to retag from <123301> for Unique permission at alloc38945[0x0], but that tag only grants SharedReadOnly permission for this location --> .../lib/rustlib/src/rust/library/core/src/ptr/mut_ptr.rs:586:57 | 586 | if self.is_null() { None } else { unsafe { Some(&mut *self) } } | ^^^^^^^^^^ this error occurs as part of retag at alloc38945[0x0..0x180] | = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information help: <123301> was created by a SharedReadOnly retag at offsets [0x0..0x80] --> falco_plugin_runner/src/plugin/mod.rs:189:8 = note: this is on thread `tests::native::` = note: stack backtrace: 0: std::ptr::mut_ptr::::as_mut::<'_> at .../lib/rustlib/src/rust/library/core/src/ptr/mut_ptr.rs:586:57: 586:67 1: falco_plugin_runner::plugin::add_table at falco_plugin_runner/src/plugin/mod.rs:551:33: 551:64 2: falco_plugin::tables::export::tables_input::>::add_table:: at falco_plugin/src/tables/export/tables_input.rs:64:18: 64:72 3: ::new at falco_plugin_tests/tests/async_tables.rs:45:21: 45:86 4: falco_plugin::base::wrappers::plugin_init::::{closure#0} at falco_plugin/src/base/wrappers.rs:107:13: 107:50 5: <{closure@falco_plugin::base::wrappers::plugin_init::{closure#0}} as std::ops::FnOnce<()>>::call_once - shim at .../lib/rustlib/src/rust/library/core/src/ops/function.rs:250:5: 250:71 6: ::{closure#0}}> as std::ops::FnOnce<()>>::call_once at .../lib/rustlib/src/rust/library/core/src/panic/unwind_safe.rs:274:9: 274:19 7: falco_plugin::error::panic::catch_panic::<*mut falco_plugin::base::wrappers::PluginWrapper, std::panic::AssertUnwindSafe<{closure@falco_plugin::base::wrappers::plugin_init::{closure#0}}>>::{closure#0} at falco_plugin/src/error/panic.rs:10:38: 10:41 8: std::panicking::catch_unwind::do_call::<{closure@falco_plugin::error::panic::catch_panic<*mut falco_plugin::base::wrappers::PluginWrapper, std::panic::AssertUnwindSafe<{closure@falco_plugin::base::wrappers::plugin_init::{closure#0}}>>::{closure#0}}, std::result::Result<*mut falco_plugin::base::wrappers::PluginWrapper, anyhow::Error>> at .../lib/rustlib/src/rust/library/std/src/panicking.rs:581:40: 581:43 9: std::panicking::catch_unwind::, anyhow::Error>, {closure@falco_plugin::error::panic::catch_panic<*mut falco_plugin::base::wrappers::PluginWrapper, std::panic::AssertUnwindSafe<{closure@falco_plugin::base::wrappers::plugin_init::{closure#0}}>>::{closure#0}}> at .../lib/rustlib/src/rust/library/std/src/panicking.rs:544:19: 544:88 10: std::panic::catch_unwind::<{closure@falco_plugin::error::panic::catch_panic<*mut falco_plugin::base::wrappers::PluginWrapper, std::panic::AssertUnwindSafe<{closure@falco_plugin::base::wrappers::plugin_init::{closure#0}}>>::{closure#0}}, std::result::Result<*mut falco_plugin::base::wrappers::PluginWrapper, anyhow::Error>> at .../lib/rustlib/src/rust/library/std/src/panic.rs:359:14: 359:40 11: falco_plugin::error::panic::catch_panic::<*mut falco_plugin::base::wrappers::PluginWrapper, std::panic::AssertUnwindSafe<{closure@falco_plugin::base::wrappers::plugin_init::{closure#0}}>> at falco_plugin/src/error/panic.rs:10:5: 10:42 12: falco_plugin::base::wrappers::plugin_init:: at falco_plugin/src/base/wrappers.rs:77:15: 110:7 13: DUMMY_ASYNC_PLUGIN::{constant#0}::plugin_init at falco_plugin/src/base/wrappers.rs:276:13: 276:48 14: falco_plugin_runner::plugin::Plugin::init at falco_plugin_runner/src/plugin/mod.rs:219:31: 219:52 15: falco_plugin_runner::plugin::Plugin::new at falco_plugin_runner/src/plugin/mod.rs:128:9: 128:26 16: falco_plugin_runner::PluginRunner::register_plugin at falco_plugin_runner/src/lib.rs:50:22: 50:74 17: ::register_plugin at falco_plugin_tests/src/native.rs:46:9: 46:44 18: falco_plugin_tests::init_plugin:: at falco_plugin_tests/src/lib.rs:22:18: 22:53 19: tests::test_async:: at falco_plugin_tests/tests/async_tables.rs:220:37: 220:86 20: tests::native::test_async at falco_plugin_tests/src/lib.rs:35:17: 35:57 21: tests::native::test_async::{closure#0} at falco_plugin_tests/src/lib.rs:34:23: 34:23 Signed-off-by: Grzegorz Nosek --- falco_plugin_runner/src/plugin/mod.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/falco_plugin_runner/src/plugin/mod.rs b/falco_plugin_runner/src/plugin/mod.rs index 628b56a7..44d98d73 100644 --- a/falco_plugin_runner/src/plugin/mod.rs +++ b/falco_plugin_runner/src/plugin/mod.rs @@ -185,11 +185,12 @@ impl Plugin { self.api().__bindgen_anon_5.capture_open.is_some() // ... etc. } - fn owner(&self) -> *mut ss_plugin_owner_t { - self as *const _ as *mut ss_plugin_owner_t + fn owner(&mut self) -> *mut ss_plugin_owner_t { + self as *mut _ as *mut ss_plugin_owner_t } fn init(&mut self, config: &CStr) -> anyhow::Result<()> { + let owner = self.owner(); let tables = self.tables.borrow(); let tables_input = falco_plugin_api::ss_plugin_init_tables_input { list_tables: Some(list_tables), @@ -203,7 +204,7 @@ impl Plugin { let input = falco_plugin_api::ss_plugin_init_input { config: config.as_ptr(), - owner: self.owner(), + owner, get_owner_last_error: Some(get_last_owner_error), tables: &tables_input, log_fn: Some(log), From 2e9d1efc6ffa5f28d79eda141d9222a5945bf7af Mon Sep 17 00:00:00 2001 From: Grzegorz Nosek Date: Mon, 1 Jun 2026 22:39:21 +0200 Subject: [PATCH 06/17] fix(runner): store table handles as raw pointers test tests::native::test_async ... error: Undefined Behavior: trying to retag from <138291> for Unique permission at alloc40178[0x0], but that tag only grants SharedReadOnly permission for this location --> .../lib/rustlib/src/rust/library/core/src/ptr/mut_ptr.rs:586:57 | 586 | if self.is_null() { None } else { unsafe { Some(&mut *self) } } | ^^^^^^^^^^ this error occurs as part of retag at alloc40178[0x0..0x98] | = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information help: <138291> was created by a SharedReadOnly retag at offsets [0x0..0x98] --> falco_plugin_runner/src/plugin/mod.rs:543:23 = note: this is on thread `tests::native::` = note: stack backtrace: 0: std::ptr::mut_ptr::::as_mut::<'_> at .../lib/rustlib/src/rust/library/core/src/ptr/mut_ptr.rs:586:57: 586:67 1: falco_plugin_runner::tables::get_table_field at falco_plugin_runner/src/tables.rs:23:36: 23:56 2: falco_plugin::tables::vtable::fields::TableFields::<'_>::get_table_field at falco_plugin/src/tables/vtable/fields.rs:42:16: 47:14 3: falco_plugin::tables::import::RawTable::get_field:: at falco_plugin/src/tables/import/table/raw.rs:99:21: 103:10 4: ::new at falco_plugin/src/tables/import/macros.rs:11:31: 11:78 5: as falco_plugin::tables::import::traits::TableMetadata>::new at falco_plugin/src/tables/import/traits/mod.rs:24:21: 24:52 6: falco_plugin::tables::import::table_input::>::get_table::>, std::sync::Arc>, u64> at falco_plugin/src/tables/import/table_input.rs:30:28: 30:58 7: ::new at falco_plugin_tests/tests/async_tables.rs:134:30: 134:55 8: falco_plugin::base::wrappers::plugin_init::::{closure#0} at falco_plugin/src/base/wrappers.rs:107:13: 107:50 9: <{closure@falco_plugin::base::wrappers::plugin_init::{closure#0}} as std::ops::FnOnce<()>>::call_once - shim at .../lib/rustlib/src/rust/library/core/src/ops/function.rs:250:5: 250:71 10: ::{closure#0}}> as std::ops::FnOnce<()>>::call_once at .../lib/rustlib/src/rust/library/core/src/panic/unwind_safe.rs:274:9: 274:19 11: falco_plugin::error::panic::catch_panic::<*mut falco_plugin::base::wrappers::PluginWrapper, std::panic::AssertUnwindSafe<{closure@falco_plugin::base::wrappers::plugin_init::{closure#0}}>>::{closure#0} at falco_plugin/src/error/panic.rs:10:38: 10:41 12: std::panicking::catch_unwind::do_call::<{closure@falco_plugin::error::panic::catch_panic<*mut falco_plugin::base::wrappers::PluginWrapper, std::panic::AssertUnwindSafe<{closure@falco_plugin::base::wrappers::plugin_init::{closure#0}}>>::{closure#0}}, std::result::Result<*mut falco_plugin::base::wrappers::PluginWrapper, anyhow::Error>> at .../lib/rustlib/src/rust/library/std/src/panicking.rs:581:40: 581:43 13: std::panicking::catch_unwind::, anyhow::Error>, {closure@falco_plugin::error::panic::catch_panic<*mut falco_plugin::base::wrappers::PluginWrapper, std::panic::AssertUnwindSafe<{closure@falco_plugin::base::wrappers::plugin_init::{closure#0}}>>::{closure#0}}> at .../lib/rustlib/src/rust/library/std/src/panicking.rs:544:19: 544:88 14: std::panic::catch_unwind::<{closure@falco_plugin::error::panic::catch_panic<*mut falco_plugin::base::wrappers::PluginWrapper, std::panic::AssertUnwindSafe<{closure@falco_plugin::base::wrappers::plugin_init::{closure#0}}>>::{closure#0}}, std::result::Result<*mut falco_plugin::base::wrappers::PluginWrapper, anyhow::Error>> at .../lib/rustlib/src/rust/library/std/src/panic.rs:359:14: 359:40 15: falco_plugin::error::panic::catch_panic::<*mut falco_plugin::base::wrappers::PluginWrapper, std::panic::AssertUnwindSafe<{closure@falco_plugin::base::wrappers::plugin_init::{closure#0}}>> at falco_plugin/src/error/panic.rs:10:5: 10:42 16: falco_plugin::base::wrappers::plugin_init:: at falco_plugin/src/base/wrappers.rs:77:15: 110:7 17: DUMMY_PLUGIN::{constant#0}::plugin_init at falco_plugin/src/base/wrappers.rs:276:13: 276:48 18: falco_plugin_runner::plugin::Plugin::init at falco_plugin_runner/src/plugin/mod.rs:220:31: 220:52 19: falco_plugin_runner::plugin::Plugin::new at falco_plugin_runner/src/plugin/mod.rs:128:9: 128:26 20: falco_plugin_runner::PluginRunner::register_plugin at falco_plugin_runner/src/lib.rs:50:22: 50:74 21: ::register_plugin at falco_plugin_tests/src/native.rs:46:9: 46:44 22: tests::test_async:: at falco_plugin_tests/tests/async_tables.rs:221:9: 221:58 23: tests::native::test_async at falco_plugin_tests/src/lib.rs:35:17: 35:57 24: tests::native::test_async::{closure#0} at falco_plugin_tests/src/lib.rs:34:23: 34:23 Signed-off-by: Grzegorz Nosek --- falco_plugin_runner/src/plugin/mod.rs | 2 +- falco_plugin_runner/src/tables.rs | 47 +++++++++++++++++++++------ 2 files changed, 38 insertions(+), 11 deletions(-) diff --git a/falco_plugin_runner/src/plugin/mod.rs b/falco_plugin_runner/src/plugin/mod.rs index 44d98d73..1991a22a 100644 --- a/falco_plugin_runner/src/plugin/mod.rs +++ b/falco_plugin_runner/src/plugin/mod.rs @@ -538,7 +538,7 @@ pub unsafe extern "C" fn get_table( } let name = unsafe { CStr::from_ptr(name) }; - let tables = owner.tables.borrow(); + let mut tables = owner.tables.borrow_mut(); match tables.get_table(name, key_type) { Some(table) => table as *const _ as *mut _, None => std::ptr::null_mut(), diff --git a/falco_plugin_runner/src/tables.rs b/falco_plugin_runner/src/tables.rs index 1e294a67..5265da38 100644 --- a/falco_plugin_runner/src/tables.rs +++ b/falco_plugin_runner/src/tables.rs @@ -8,9 +8,36 @@ use falco_plugin_api::{ use std::collections::btree_map::Entry; use std::collections::BTreeMap; use std::ffi::{c_char, CStr, CString}; +use std::ptr::NonNull; + +/// A non-retagging Box-like wrapper to avoid Miri's Stacked Borrows issues. +/// Unlike Box, moving this struct does not transitively retag the pointee. +struct RawBox(NonNull); + +impl RawBox { + fn new(val: ss_plugin_table_input) -> Self { + Self(unsafe { NonNull::new_unchecked(Box::into_raw(Box::new(val))) }) + } + fn as_ptr(&self) -> *mut ss_plugin_table_input { + self.0.as_ptr() + } +} + +impl std::ops::Deref for RawBox { + type Target = ss_plugin_table_input; + fn deref(&self) -> &ss_plugin_table_input { + unsafe { self.0.as_ref() } + } +} + +impl Drop for RawBox { + fn drop(&mut self) { + unsafe { drop(Box::from_raw(self.0.as_ptr())) } + } +} pub struct Tables { - tables: BTreeMap>, + tables: BTreeMap, reader_ext_store: Vec, writer_ext_store: Vec, fields_ext_store: Vec, @@ -20,14 +47,14 @@ pub struct Tables { macro_rules! delegate_table_method { ($table:expr => $vtable:ident . $method:ident or $error:expr) => {{ let table_input = $table as *mut ss_plugin_table_input; - let table_input = unsafe { table_input.as_mut() }; - let Some(table_input) = table_input else { + if table_input.is_null() { #[allow(clippy::unused_unit)] return $error; - }; + } let vtable = unsafe { - let Some(vtable) = table_input.$vtable.as_ref() else { + let vtable_ptr = std::ptr::addr_of!((*table_input).$vtable); + let Some(vtable) = (*vtable_ptr).as_ref() else { #[allow(clippy::unused_unit)] return $error; }; @@ -39,7 +66,7 @@ macro_rules! delegate_table_method { return $error; }; - (method, table_input.table) + (method, unsafe { (*table_input).table }) }}; } @@ -254,15 +281,15 @@ impl Tables { } pub fn get_table( - &self, + &mut self, name: &CStr, key_type: ss_plugin_state_type, - ) -> Option<&ss_plugin_table_input> { + ) -> Option<*mut ss_plugin_table_input> { let table = self.tables.get(name)?; if table.key_type != key_type { return None; } - Some(table) + Some(table.as_ptr()) } pub fn add_table(&mut self, name: &CStr, table_input: &ss_plugin_table_input) -> ss_plugin_rc { @@ -292,7 +319,7 @@ impl Tables { table_input.writer_ext = writer_ext; table_input.fields_ext = fields_ext; - entry.insert(Box::new(table_input)); + entry.insert(RawBox::new(table_input)); self.table_info_cache.clear(); ss_plugin_rc_SS_PLUGIN_SUCCESS } From 18d3b376ca1388a900c18dfa1d0bbebb16dc5b5d Mon Sep 17 00:00:00 2001 From: Grzegorz Nosek Date: Mon, 1 Jun 2026 23:28:26 +0200 Subject: [PATCH 07/17] chore(plugin)!: replace Box wrapper with Table Signed-off-by: Grzegorz Nosek --- falco_plugin/src/tables/data.rs | 2 +- falco_plugin/src/tables/export/field/table.rs | 7 +- .../src/tables/export/field_value/table.rs | 12 +- falco_plugin/src/tables/export/mod.rs | 7 +- falco_plugin/src/tables/export/table.rs | 27 +- falco_plugin/src/tables/export/table_box.rs | 230 ++++++++++++++++++ .../src/tables/export/tables_input.rs | 7 +- falco_plugin/src/tables/export/vtable.rs | 16 +- falco_plugin/src/tables/export/wrappers.rs | 28 +-- .../benches/plugin_custom_tables.rs | 4 +- .../parse/remaining_into_nested_table.rs | 2 +- .../parse/remaining_into_table_api.rs | 2 +- .../parse/remaining_into_table_direct.rs | 2 +- .../tables/remaining_export.rs | 2 +- falco_plugin_tests/tests/async_tables.rs | 2 +- 15 files changed, 288 insertions(+), 62 deletions(-) create mode 100644 falco_plugin/src/tables/export/table_box.rs diff --git a/falco_plugin/src/tables/data.rs b/falco_plugin/src/tables/data.rs index bc9ba4fa..73233867 100644 --- a/falco_plugin/src/tables/data.rs +++ b/falco_plugin/src/tables/data.rs @@ -102,7 +102,7 @@ pub trait Value: TableData { /// Given a raw table, fetch the field's metadata /// - /// The only interesting implementation is for `Box
`, which gets all the fields + /// The only interesting implementation is for `Table`, which gets all the fields /// of a nested table and stores it in the subtable metadata. All others are no-ops. /// /// # Safety diff --git a/falco_plugin/src/tables/export/field/table.rs b/falco_plugin/src/tables/export/field/table.rs index 7bcfd9ec..578dc2e5 100644 --- a/falco_plugin/src/tables/export/field/table.rs +++ b/falco_plugin/src/tables/export/field/table.rs @@ -3,13 +3,14 @@ use crate::tables::export::entry::table_metadata::traits::TableMetadata; use crate::tables::export::entry::traits::Entry; use crate::tables::export::metadata::HasMetadata; use crate::tables::export::ref_shared::RefShared; -use crate::tables::export::table::Table; +use crate::tables::export::table::TableData; +use crate::tables::export::Table; use crate::tables::Key; use anyhow::Error; use std::borrow::Borrow; use std::ffi::CStr; -impl HasMetadata for Box> +impl HasMetadata for Table where K: Key + Ord, K: Borrow<::Borrowed>, @@ -20,6 +21,6 @@ where type Metadata = RefShared>; fn new_with_metadata(tag: &'static CStr, meta: &Self::Metadata) -> Result { - Ok(Box::new(Table::new_with_metadata(tag, meta)?)) + Ok(Table::wrap(TableData::new_with_metadata(tag, meta)?)) } } diff --git a/falco_plugin/src/tables/export/field_value/table.rs b/falco_plugin/src/tables/export/field_value/table.rs index fc681e80..0ceb349a 100644 --- a/falco_plugin/src/tables/export/field_value/table.rs +++ b/falco_plugin/src/tables/export/field_value/table.rs @@ -3,12 +3,12 @@ use crate::tables::export::entry::traits::Entry; use crate::tables::export::field_value::dynamic::DynamicFieldValue; use crate::tables::export::field_value::traits::FieldValue; use crate::tables::export::field_value::traits::{seal, StaticField}; -use crate::tables::export::table::Table; +use crate::tables::export::table_box::Table; use crate::tables::{FieldTypeId, Key}; use falco_plugin_api::ss_plugin_state_data; use std::borrow::Borrow; -impl seal::Sealed for Box> +impl seal::Sealed for Table where K: Key + Ord, K: Borrow<::Borrowed>, @@ -18,7 +18,7 @@ where { } -impl FieldValue for Box> +impl FieldValue for Table where K: Key + Ord, K: Borrow<::Borrowed>, @@ -34,14 +34,14 @@ where if type_id != FieldTypeId::Table { anyhow::bail!("Type mismatch, requested {:?}, got table", type_id) } - let vtable = self.get_boxed_vtable(); + let vtable = self.get_vtable(); out.table = vtable.cast(); Ok(()) } } -impl StaticField for Box> +impl StaticField for Table where K: Key + Ord, K: Borrow<::Borrowed>, @@ -53,7 +53,7 @@ where const READONLY: bool = true; } -impl TryFrom for Box> +impl TryFrom for Table where K: Key + Ord, K: Borrow<::Borrowed>, diff --git a/falco_plugin/src/tables/export/mod.rs b/falco_plugin/src/tables/export/mod.rs index 8cd8a2d7..2723bb55 100644 --- a/falco_plugin/src/tables/export/mod.rs +++ b/falco_plugin/src/tables/export/mod.rs @@ -6,7 +6,7 @@ //! //! Every field in the entry struct must be wrapped in [`Public`](`crate::tables::export::Public`), //! [`Private`](`crate::tables::export::Private`) or [`Readonly`](`crate::tables::export::Readonly`), -//! except for nested tables. These just need to be a `Box>`, as it makes no sense +//! except for nested tables. These just need to be a `Table`, as it makes no sense //! to have a private nested table and the distinction between writable and readonly is meaningless //! for tables (they have no setter to replace the whole table and you can always add/remove //! entries from the nested table). @@ -32,7 +32,7 @@ //! struct MyPlugin { //! // you can use methods on this instance to access fields bypassing the Falco table API //! // (for performance within your own plugin) -//! exported_table: Box>, +//! exported_table: export::Table, //! } //! //! // implement the base::Plugin trait @@ -71,6 +71,7 @@ mod metadata; mod ref_shared; mod static_field_specialization; mod table; +mod table_box; mod tables_input; mod vtable; mod wrappers; @@ -78,7 +79,7 @@ mod wrappers; pub use field::private::Private; pub use field::public::Public; pub use field::readonly::Readonly; -pub use table::Table; +pub use table_box::Table; // for macro use only #[doc(hidden)] diff --git a/falco_plugin/src/tables/export/table.rs b/falco_plugin/src/tables/export/table.rs index 4144253c..e93798ec 100644 --- a/falco_plugin/src/tables/export/table.rs +++ b/falco_plugin/src/tables/export/table.rs @@ -18,25 +18,14 @@ use std::collections::BTreeMap; use std::ffi::CStr; use std::fmt::{Debug, Formatter}; -/// # A table exported to other plugins +/// The inner data storage for an exported table. /// -/// An instance of this type can be exposed to other plugins via -/// [`tables::TablesInput::add_table`](`crate::tables::TablesInput::add_table`) -/// -/// The generic parameters are: key type and entry type. The key type is anything -/// usable as a table key, while the entry type is a type that can be stored in the table. -/// You can obtain such a type by `#[derive]`ing Entry on a struct describing all the table fields. -/// -/// Supported key types include: -/// - integer types (u8/i8, u16/i16, u32/i32, u64/i64) -/// - [`crate::tables::import::Bool`] (an API equivalent of bool) -/// - &CStr (spelled as just `CStr` when used as a generic argument) -/// -/// See [`crate::tables::export`] for details. -/// -/// The implementation is thread-safe when the `thread-safe-tables` feature is enabled. +/// This type holds the actual table data (entries, metadata, field descriptors). +/// Users should not interact with this type directly; use [`super::Table`] instead, +/// which wraps `TableData` and provides the public API. #[must_use] -pub struct Table +#[doc(hidden)] +pub struct TableData where K: Key + Ord, K: Borrow<::Borrowed>, @@ -52,7 +41,7 @@ where pub(crate) vtable: RefCounted>>, } -impl Debug for Table +impl Debug for TableData where K: Key + Ord + Debug, K: Borrow<::Borrowed>, @@ -72,7 +61,7 @@ where type TableMetadataType = RefShared::Metadata>>; pub(crate) type TableEntryType = RefGuard>; -impl Table +impl TableData where K: Key + Ord, K: Borrow<::Borrowed>, diff --git a/falco_plugin/src/tables/export/table_box.rs b/falco_plugin/src/tables/export/table_box.rs new file mode 100644 index 00000000..01c2d045 --- /dev/null +++ b/falco_plugin/src/tables/export/table_box.rs @@ -0,0 +1,230 @@ +use crate::tables::export::entry::extensible::ExtensibleEntry; +use crate::tables::export::table::{TableData, TableEntryType}; +use crate::tables::export::traits::{Entry, TableMetadata}; +use crate::tables::export::{FieldRef, HasMetadata, RefShared}; +use crate::tables::{FieldTypeId, Key}; +use falco_plugin_api::{ss_plugin_state_data, ss_plugin_table_fieldinfo, ss_plugin_table_input}; +use std::borrow::Borrow; +use std::collections::BTreeMap; +use std::ffi::CStr; +use std::fmt::{Debug, Formatter}; + +/// # A table exported to other plugins +/// +/// An instance of this type can be exposed to other plugins via +/// [`tables::TablesInput::add_table`](`crate::tables::TablesInput::add_table`) +/// +/// The generic parameters are: key type and entry type. The key type is anything +/// usable as a table key, while the entry type is a type that can be stored in the table. +/// You can obtain such a type by `#[derive]`ing Entry on a struct describing all the table fields. +/// +/// Supported key types include: +/// - integer types (u8/i8, u16/i16, u32/i32, u64/i64) +/// - [`crate::tables::import::Bool`] (an API equivalent of bool) +/// - &CStr (spelled as just `CStr` when used as a generic argument) +/// +/// See [`crate::tables::export`] for details. +/// +/// The implementation is thread-safe when the `thread-safe-tables` feature is enabled. +pub struct Table +where + K: Key + Ord, + K: Borrow<::Borrowed>, + ::Borrowed: Ord + ToOwned, + E: Entry, + E::Metadata: TableMetadata, +{ + ptr: Box>, +} + +impl Table +where + K: Key + Ord, + K: Borrow<::Borrowed>, + ::Borrowed: Ord + ToOwned, + E: Entry, + E::Metadata: TableMetadata, +{ + /// Wrap a `TableData` into a `Table`. + pub(crate) fn wrap(value: TableData) -> Self { + Self { + ptr: Box::new(value), + } + } + + /// Returns a raw mutable pointer to the contained data without creating a reference. + pub(crate) fn as_mut_ptr(this: &Self) -> *mut TableData { + &raw const *this.ptr as *mut TableData + } + + /// Get or create the vtable for this table, for use in FFI. + pub(crate) fn get_vtable(&self) -> *mut ss_plugin_table_input { + let table_ptr = Self::as_mut_ptr(self); + self.ptr.get_vtable_with_ptr(table_ptr) + } + + /// Create a new table + pub fn new(name: &'static CStr) -> Result { + Ok(Self::wrap(TableData::new(name)?)) + } + + /// Create a new table using provided metadata + /// + /// This is only expected to be used by the derive macro. + pub fn new_with_metadata( + tag: &'static CStr, + metadata: &::Metadata, + ) -> Result { + Ok(Self::wrap(TableData::new_with_metadata(tag, metadata)?)) + } + + /// Get an accessor to the underlying data + /// + /// This method returns a reference to the underlying BTreeMap, containing all the table's data. + /// It can be useful for: + /// - accessing the table from a different thread (with the `thread-safe-tables` feature enabled) + /// - bypassing the table API for convenience or more control over locking + /// + /// To actually access the BTreeMap, you first need to lock the returned object for reading + /// (`data.read()`) or writing (`data.write()`). + pub fn data(&self) -> RefShared>>> { + self.ptr.data() + } + + /// Return the table name. + pub fn name(&self) -> &'static CStr { + self.ptr.name() + } + + /// Return the number of entries in the table. + pub fn size(&self) -> usize { + self.ptr.size() + } + + /// Get an entry corresponding to a particular key. + pub fn lookup(&self, key: &Q) -> Option> + where + K: Borrow, + Q: Ord + ?Sized, + { + self.ptr.lookup(key) + } + + /// Get the value for a field in an entry. + pub fn get_field_value( + &self, + entry: &TableEntryType, + field: &crate::tables::export::field_descriptor::FieldDescriptor, + out: &mut ss_plugin_state_data, + ) -> Result<(), anyhow::Error> { + self.ptr.get_field_value(entry, field, out) + } + + /// Execute a closure on all entries in the table with read-only access. + /// + /// The iteration continues until all entries are visited or the closure returns false. + // TODO(upstream) the closure cannot store away the entry but we could use explicit docs + pub fn iterate_entries(&mut self, func: F) -> bool + where + F: FnMut(&mut TableEntryType) -> bool, + { + self.ptr.iterate_entries(func) + } + + /// Remove all entries from the table. + pub fn clear(&mut self) { + self.ptr.clear(); + } + + /// Erase an entry by key. + pub fn erase(&mut self, key: &Q) -> Option> + where + K: Borrow, + Q: Ord + ?Sized, + { + self.ptr.erase(key) + } + + /// Create a new table entry. + /// + /// This is a detached entry that can be later inserted into the table using [`Table::insert`]. + pub fn create_entry(&self) -> Result, anyhow::Error> { + self.ptr.create_entry() + } + + /// Return a closure for creating table entries + /// + /// The `Table` object itself cannot be shared between threads safely even with + /// the `thread-safe-tables` feature enabled, but almost full functionality can be achieved + /// using two objects that can: + /// 1. The underlying BTreeMap, obtained from [Table::data] + /// 2. A closure capable of creating a new entry (returned from this function) + /// + /// The only functionality missing is listing table fields, and until a use case comes along, + /// it's likely to remain unimplemented. + /// + /// The entry obtained by calling the closure returned from `create_entry_fn` can be later + /// inserted into the table e.g. by calling [BTreeMap::insert]. + /// + /// To actually access the entry's fields, you first need to lock the returned object for reading + /// (`data.read()`) or writing (`data.write()`). + pub fn create_entry_fn( + &self, + ) -> impl Fn() -> Result>, anyhow::Error> + use { + self.ptr.create_entry_fn() + } + + /// Attach an entry to a table key + pub fn insert(&mut self, key: &Q, entry: TableEntryType) -> Option> + where + K: Borrow, + Q: Ord + ToOwned + ?Sized, + { + self.ptr.insert(key, entry) + } + + /// Write a value to a field of an entry + pub fn write( + &self, + entry: &mut TableEntryType, + field: &crate::tables::export::field_descriptor::FieldDescriptor, + value: &ss_plugin_state_data, + ) -> Result<(), anyhow::Error> { + self.ptr.write(entry, field, value) + } + + /// Return a list of fields as a slice of raw FFI objects + pub fn list_fields(&mut self) -> &[ss_plugin_table_fieldinfo] { + self.ptr.list_fields() + } + + /// Return a field descriptor for a particular field + /// + /// The requested `field_type` must match the actual type of the field + pub fn get_field(&self, name: &CStr, field_type: FieldTypeId) -> Option { + self.ptr.get_field(name, field_type) + } + + /// Add a new field to the table + pub fn add_field( + &mut self, + name: &CStr, + field_type: FieldTypeId, + read_only: bool, + ) -> Option { + self.ptr.add_field(name, field_type, read_only) + } +} + +impl Debug for Table +where + K: Key + Ord + Debug, + K: Borrow<::Borrowed>, + ::Borrowed: Ord + ToOwned, + E: Entry + Debug, + E::Metadata: TableMetadata + Debug, +{ + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + self.ptr.fmt(f) + } +} diff --git a/falco_plugin/src/tables/export/tables_input.rs b/falco_plugin/src/tables/export/tables_input.rs index 6b8a00f3..65433c50 100644 --- a/falco_plugin/src/tables/export/tables_input.rs +++ b/falco_plugin/src/tables/export/tables_input.rs @@ -12,10 +12,10 @@ use std::borrow::Borrow; impl TablesInput<'_> { /// # Export a table to the Falco plugin API /// - /// This method returns a Box, which you need to store in your plugin instance + /// This method returns a [`Table`], which you need to store in your plugin instance /// even if you don't intend to use the table yourself (the table is destroyed when /// going out of scope, which will lead to crashes in plugins using your table). - pub fn add_table(&self, table: Table) -> Result>, anyhow::Error> + pub fn add_table(&self, table: Table) -> Result, anyhow::Error> where K: Key + Ord, K: Borrow<::Borrowed>, @@ -27,8 +27,7 @@ impl TablesInput<'_> { let mut writer_vtable_ext = writer_vtable::(); let mut fields_vtable_ext = fields_vtable::(); - let mut table = Box::new(table); - let table_ptr = table.as_mut() as *mut Table; + let table_ptr = Table::as_mut_ptr(&table); // Note: we lend the ss_plugin_table_input to the FFI api and do not need // to hold on to it (everything is copied out), but the name field is copied diff --git a/falco_plugin/src/tables/export/vtable.rs b/falco_plugin/src/tables/export/vtable.rs index 32a582ef..c296f35c 100644 --- a/falco_plugin/src/tables/export/vtable.rs +++ b/falco_plugin/src/tables/export/vtable.rs @@ -1,7 +1,7 @@ use crate::tables::export::entry::table_metadata::traits::TableMetadata; use crate::tables::export::entry::traits::Entry; +use crate::tables::export::table::TableData; use crate::tables::export::wrappers::{fields_vtable, reader_vtable, writer_vtable}; -use crate::tables::export::Table; use crate::tables::Key; use falco_plugin_api::{ ss_plugin_state_type, ss_plugin_table_fields_vtable, ss_plugin_table_fields_vtable_ext, @@ -17,7 +17,7 @@ pub(crate) struct Vtable { fields_ext: ss_plugin_table_fields_vtable_ext, } -impl Table +impl TableData where K: Key + Ord, K: Borrow<::Borrowed>, @@ -25,9 +25,15 @@ where E: Entry, E::Metadata: TableMetadata, { - #[allow(clippy::borrowed_box)] - pub(crate) fn get_boxed_vtable(self: &Box) -> *mut ss_plugin_table_input { - let table_ptr = self.as_ref() as *const Table as *mut Table; + /// Get or create the vtable for this table. + /// + /// `table_ptr` must be a raw pointer to `self` with write provenance + /// (e.g. from `Table::as_mut_ptr`). This is necessary because the FFI layer + /// will use the stored pointer for mutable access. + pub(crate) fn get_vtable_with_ptr( + &self, + table_ptr: *mut TableData, + ) -> *mut ss_plugin_table_input { let mut vtable_place = self.vtable.write(); if let Some(ref mut vtable) = *vtable_place { diff --git a/falco_plugin/src/tables/export/wrappers.rs b/falco_plugin/src/tables/export/wrappers.rs index 1570070e..a935b1a6 100644 --- a/falco_plugin/src/tables/export/wrappers.rs +++ b/falco_plugin/src/tables/export/wrappers.rs @@ -2,7 +2,7 @@ use crate::error::ffi_result::FfiResult; use crate::tables::export::entry::table_metadata::traits::TableMetadata; use crate::tables::export::entry::traits::Entry; use crate::tables::export::field_descriptor::FieldDescriptor; -use crate::tables::export::table::{Table, TableEntryType}; +use crate::tables::export::table::{TableData, TableEntryType}; use crate::tables::{FieldTypeId, Key}; use falco_plugin_api::{ ss_plugin_bool, ss_plugin_rc, ss_plugin_rc_SS_PLUGIN_FAILURE, ss_plugin_rc_SS_PLUGIN_SUCCESS, @@ -25,7 +25,7 @@ where E::Metadata: TableMetadata, { unsafe { - let Some(table) = (table as *mut Table).as_mut() else { + let Some(table) = (table as *mut TableData).as_mut() else { return std::ptr::null_mut(); }; table.name().as_ptr() @@ -42,7 +42,7 @@ where E::Metadata: TableMetadata, { unsafe { - let Some(table) = (table as *mut Table).as_mut() else { + let Some(table) = (table as *mut TableData).as_mut() else { return 0; }; table.size() as u64 @@ -63,7 +63,7 @@ where E::Metadata: TableMetadata, { unsafe { - let Some(table) = (table as *mut Table).as_mut() else { + let Some(table) = (table as *mut TableData).as_mut() else { return std::ptr::null_mut(); }; let Some(key) = key.as_ref() else { @@ -93,7 +93,7 @@ where E::Metadata: TableMetadata, { unsafe { - let Some(table) = (table as *mut Table).as_mut() else { + let Some(table) = (table as *mut TableData).as_mut() else { return ss_plugin_rc_SS_PLUGIN_FAILURE; }; let Some(entry) = (entry as *mut TableEntryType).as_mut() else { @@ -142,7 +142,7 @@ where return 0; }; unsafe { - let Some(table) = (table as *mut Table).as_mut() else { + let Some(table) = (table as *mut TableData).as_mut() else { return 0; }; @@ -165,7 +165,7 @@ where E::Metadata: TableMetadata, { unsafe { - let Some(table) = (table as *mut Table).as_mut() else { + let Some(table) = (table as *mut TableData).as_mut() else { return ss_plugin_rc_SS_PLUGIN_FAILURE; }; table.clear(); @@ -186,7 +186,7 @@ where E::Metadata: TableMetadata, { unsafe { - let Some(table) = (table as *mut Table).as_mut() else { + let Some(table) = (table as *mut TableData).as_mut() else { return ss_plugin_rc_SS_PLUGIN_FAILURE; }; let Some(key) = key.as_ref() else { @@ -212,7 +212,7 @@ where E::Metadata: TableMetadata, { unsafe { - let Some(table) = (table as *mut Table).as_mut() else { + let Some(table) = (table as *mut TableData).as_mut() else { return std::ptr::null_mut(); }; @@ -241,7 +241,7 @@ where } unsafe { - let Some(table) = (table as *mut Table).as_mut() else { + let Some(table) = (table as *mut TableData).as_mut() else { return std::ptr::null_mut(); }; let Some(key) = key.as_ref() else { @@ -272,7 +272,7 @@ where E::Metadata: TableMetadata, { unsafe { - let Some(table) = (table as *mut Table).as_mut() else { + let Some(table) = (table as *mut TableData).as_mut() else { return ss_plugin_rc_SS_PLUGIN_FAILURE; }; let Some(entry) = (entry as *mut TableEntryType).as_mut() else { @@ -301,7 +301,7 @@ where E::Metadata: TableMetadata, { unsafe { - let Some(table) = (table as *mut Table).as_mut() else { + let Some(table) = (table as *mut TableData).as_mut() else { return std::ptr::null_mut(); }; let fields = table.list_fields(); @@ -324,7 +324,7 @@ where E::Metadata: TableMetadata, { unsafe { - let Some(table) = (table as *mut Table).as_mut() else { + let Some(table) = (table as *mut TableData).as_mut() else { return std::ptr::null_mut(); }; let Some(data_type) = FieldTypeId::from_usize(data_type as usize) else { @@ -356,7 +356,7 @@ where E::Metadata: TableMetadata, { unsafe { - let Some(table) = (table as *mut Table).as_mut() else { + let Some(table) = (table as *mut TableData).as_mut() else { return std::ptr::null_mut(); }; let Some(data_type) = FieldTypeId::from_usize(data_type as usize) else { diff --git a/falco_plugin_tests/benches/plugin_custom_tables.rs b/falco_plugin_tests/benches/plugin_custom_tables.rs index 6520699d..8db229cf 100644 --- a/falco_plugin_tests/benches/plugin_custom_tables.rs +++ b/falco_plugin_tests/benches/plugin_custom_tables.rs @@ -38,7 +38,7 @@ struct ImportedCustomMetadata { struct CustomTableApi { #[allow(unused)] - exported_custom_table: Box>, + exported_custom_table: export::Table, imported_custom_table: import::Table, insert_val2_on_parse: bool, @@ -123,7 +123,7 @@ static_plugin!(CUSTOM_TABLE_API = CustomTableApi); struct CustomTableDirect { #[allow(unused)] - exported_custom_table: Box>, + exported_custom_table: export::Table, } impl Plugin for CustomTableDirect { diff --git a/falco_plugin_tests/src/plugin_collection/parse/remaining_into_nested_table.rs b/falco_plugin_tests/src/plugin_collection/parse/remaining_into_nested_table.rs index 96ce0dec..0cc961e3 100644 --- a/falco_plugin_tests/src/plugin_collection/parse/remaining_into_nested_table.rs +++ b/falco_plugin_tests/src/plugin_collection/parse/remaining_into_nested_table.rs @@ -11,7 +11,7 @@ use falco_plugin::tables::TablesInput; use std::ffi::CStr; struct ParseIntoNestedTable { - remaining_table: Box, + remaining_table: RemainingEntryTable, } impl Plugin for ParseIntoNestedTable { diff --git a/falco_plugin_tests/src/plugin_collection/parse/remaining_into_table_api.rs b/falco_plugin_tests/src/plugin_collection/parse/remaining_into_table_api.rs index 866c45c0..830d5c98 100644 --- a/falco_plugin_tests/src/plugin_collection/parse/remaining_into_table_api.rs +++ b/falco_plugin_tests/src/plugin_collection/parse/remaining_into_table_api.rs @@ -14,7 +14,7 @@ use std::ffi::CStr; struct ParseIntoTableApiPlugin { #[allow(unused)] - remaining_table: Box, + remaining_table: RemainingEntryTable, remaining_table_import: RemainingCounterImportTable, } diff --git a/falco_plugin_tests/src/plugin_collection/parse/remaining_into_table_direct.rs b/falco_plugin_tests/src/plugin_collection/parse/remaining_into_table_direct.rs index d7fc1286..835082eb 100644 --- a/falco_plugin_tests/src/plugin_collection/parse/remaining_into_table_direct.rs +++ b/falco_plugin_tests/src/plugin_collection/parse/remaining_into_table_direct.rs @@ -11,7 +11,7 @@ use falco_plugin::tables::TablesInput; use std::ffi::CStr; struct ParseIntoTableDirectPlugin { - remaining_table: Box, + remaining_table: RemainingEntryTable, } impl Plugin for ParseIntoTableDirectPlugin { diff --git a/falco_plugin_tests/src/plugin_collection/tables/remaining_export.rs b/falco_plugin_tests/src/plugin_collection/tables/remaining_export.rs index 474781d2..875fc190 100644 --- a/falco_plugin_tests/src/plugin_collection/tables/remaining_export.rs +++ b/falco_plugin_tests/src/plugin_collection/tables/remaining_export.rs @@ -6,7 +6,7 @@ pub type RemainingEntryTable = export::Table; pub struct RemainingCounter { pub remaining: export::Public, pub readonly: export::Readonly, - pub countdown: Box, + pub countdown: CountdownTable, } pub type CountdownTable = export::Table; diff --git a/falco_plugin_tests/tests/async_tables.rs b/falco_plugin_tests/tests/async_tables.rs index 97573f95..55303ccd 100644 --- a/falco_plugin_tests/tests/async_tables.rs +++ b/falco_plugin_tests/tests/async_tables.rs @@ -27,7 +27,7 @@ struct DummyAsyncPlugin { task: Arc, thread: Option>>, - table: Box>, + table: export::Table, } impl Plugin for DummyAsyncPlugin { From ccd712d6aa009dde1dfb0b1159f0814598ff2cee Mon Sep 17 00:00:00 2001 From: Grzegorz Nosek Date: Mon, 1 Jun 2026 23:46:48 +0200 Subject: [PATCH 08/17] fix(plugin): switch table box internals to NonNull test tests::native::test_async ... error: Undefined Behavior: trying to retag from <124942> for Unique permission at alloc39673[0x0], but that tag does not exist in the borrow stack for this location --> .../lib/rustlib/src/rust/library/core/src/ptr/mut_ptr.rs:586:57 | 586 | if self.is_null() { None } else { unsafe { Some(&mut *self) } } | ^^^^^^^^^^ this error occurs as part of retag at alloc39673[0x0..0x48] | = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information help: <124942> was created by a SharedReadOnly retag at offsets [0x0..0x38] --> falco_plugin/src/tables/export/table_box.rs:57:8 help: <124942> was later invalidated at offsets [0x0..0x48] by a Unique retag (of a reference/box inside this compound value) --> falco_plugin/src/tables/export/tables_input.rs:66:11 = note: this is on thread `tests::native::` = note: stack backtrace: 0: std::ptr::mut_ptr::>::as_mut::<'_> at .../lib/rustlib/src/rust/library/core/src/ptr/mut_ptr.rs:586:57: 586:67 1: falco_plugin::tables::export::wrappers::get_table_field:: at falco_plugin/src/tables/export/wrappers.rs:327:27: 327:67 2: falco_plugin_runner::tables::get_table_field at falco_plugin_runner/src/tables.rs:188:14: 188:53 3: falco_plugin::tables::vtable::fields::TableFields::<'_>::get_table_field at falco_plugin/src/tables/vtable/fields.rs:42:16: 47:14 4: falco_plugin::tables::import::RawTable::get_field:: at falco_plugin/src/tables/import/table/raw.rs:99:21: 103:10 5: ::new at falco_plugin/src/tables/import/macros.rs:11:31: 11:78 6: as falco_plugin::tables::import::traits::TableMetadata>::new at falco_plugin/src/tables/import/traits/mod.rs:24:21: 24:52 7: falco_plugin::tables::import::table_input::>::get_table::>, std::sync::Arc>, u64> at falco_plugin/src/tables/import/table_input.rs:30:28: 30:58 8: ::new at falco_plugin_tests/tests/async_tables.rs:134:30: 134:55 9: falco_plugin::base::wrappers::plugin_init::::{closure#0} at falco_plugin/src/base/wrappers.rs:107:13: 107:50 10: <{closure@falco_plugin::base::wrappers::plugin_init::{closure#0}} as std::ops::FnOnce<()>>::call_once - shim at .../lib/rustlib/src/rust/library/core/src/ops/function.rs:250:5: 250:71 11: ::{closure#0}}> as std::ops::FnOnce<()>>::call_once at .../lib/rustlib/src/rust/library/core/src/panic/unwind_safe.rs:274:9: 274:19 12: falco_plugin::error::panic::catch_panic::<*mut falco_plugin::base::wrappers::PluginWrapper, std::panic::AssertUnwindSafe<{closure@falco_plugin::base::wrappers::plugin_init::{closure#0}}>>::{closure#0} at falco_plugin/src/error/panic.rs:10:38: 10:41 13: std::panicking::catch_unwind::do_call::<{closure@falco_plugin::error::panic::catch_panic<*mut falco_plugin::base::wrappers::PluginWrapper, std::panic::AssertUnwindSafe<{closure@falco_plugin::base::wrappers::plugin_init::{closure#0}}>>::{closure#0}}, std::result::Result<*mut falco_plugin::base::wrappers::PluginWrapper, anyhow::Error>> at .../lib/rustlib/src/rust/library/std/src/panicking.rs:581:40: 581:43 14: std::panicking::catch_unwind::, anyhow::Error>, {closure@falco_plugin::error::panic::catch_panic<*mut falco_plugin::base::wrappers::PluginWrapper, std::panic::AssertUnwindSafe<{closure@falco_plugin::base::wrappers::plugin_init::{closure#0}}>>::{closure#0}}> at .../lib/rustlib/src/rust/library/std/src/panicking.rs:544:19: 544:88 15: std::panic::catch_unwind::<{closure@falco_plugin::error::panic::catch_panic<*mut falco_plugin::base::wrappers::PluginWrapper, std::panic::AssertUnwindSafe<{closure@falco_plugin::base::wrappers::plugin_init::{closure#0}}>>::{closure#0}}, std::result::Result<*mut falco_plugin::base::wrappers::PluginWrapper, anyhow::Error>> at .../lib/rustlib/src/rust/library/std/src/panic.rs:359:14: 359:40 16: falco_plugin::error::panic::catch_panic::<*mut falco_plugin::base::wrappers::PluginWrapper, std::panic::AssertUnwindSafe<{closure@falco_plugin::base::wrappers::plugin_init::{closure#0}}>> at falco_plugin/src/error/panic.rs:10:5: 10:42 17: falco_plugin::base::wrappers::plugin_init:: at falco_plugin/src/base/wrappers.rs:77:15: 110:7 18: DUMMY_PLUGIN::{constant#0}::plugin_init at falco_plugin/src/base/wrappers.rs:276:13: 276:48 19: falco_plugin_runner::plugin::Plugin::init at falco_plugin_runner/src/plugin/mod.rs:220:31: 220:52 20: falco_plugin_runner::plugin::Plugin::new at falco_plugin_runner/src/plugin/mod.rs:128:9: 128:26 21: falco_plugin_runner::PluginRunner::register_plugin at falco_plugin_runner/src/lib.rs:50:22: 50:74 22: ::register_plugin at falco_plugin_tests/src/native.rs:46:9: 46:44 23: tests::test_async:: at falco_plugin_tests/tests/async_tables.rs:221:9: 221:58 24: tests::native::test_async at falco_plugin_tests/src/lib.rs:35:17: 35:57 25: tests::native::test_async::{closure#0} at falco_plugin_tests/src/lib.rs:34:23: 34:23 Signed-off-by: Grzegorz Nosek --- falco_plugin/src/tables/export/table_box.rs | 113 ++++++++++++++++---- 1 file changed, 93 insertions(+), 20 deletions(-) diff --git a/falco_plugin/src/tables/export/table_box.rs b/falco_plugin/src/tables/export/table_box.rs index 01c2d045..db466458 100644 --- a/falco_plugin/src/tables/export/table_box.rs +++ b/falco_plugin/src/tables/export/table_box.rs @@ -8,6 +8,8 @@ use std::borrow::Borrow; use std::collections::BTreeMap; use std::ffi::CStr; use std::fmt::{Debug, Formatter}; +use std::ops::{Deref, DerefMut}; +use std::ptr::NonNull; /// # A table exported to other plugins /// @@ -26,6 +28,10 @@ use std::fmt::{Debug, Formatter}; /// See [`crate::tables::export`] for details. /// /// The implementation is thread-safe when the `thread-safe-tables` feature is enabled. +/// +/// Internally, this uses `NonNull` instead of `Box` to avoid Miri's Stacked Borrows +/// transitive retagging, which would conflict with FFI callbacks that access the table +/// through raw pointers. pub struct Table where K: Key + Ord, @@ -34,7 +40,7 @@ where E: Entry, E::Metadata: TableMetadata, { - ptr: Box>, + ptr: NonNull>, } impl Table @@ -47,20 +53,22 @@ where { /// Wrap a `TableData` into a `Table`. pub(crate) fn wrap(value: TableData) -> Self { + let ptr = Box::into_raw(Box::new(value)); + // SAFETY: Box::into_raw never returns null Self { - ptr: Box::new(value), + ptr: unsafe { NonNull::new_unchecked(ptr) }, } } /// Returns a raw mutable pointer to the contained data without creating a reference. pub(crate) fn as_mut_ptr(this: &Self) -> *mut TableData { - &raw const *this.ptr as *mut TableData + this.ptr.as_ptr() } /// Get or create the vtable for this table, for use in FFI. pub(crate) fn get_vtable(&self) -> *mut ss_plugin_table_input { let table_ptr = Self::as_mut_ptr(self); - self.ptr.get_vtable_with_ptr(table_ptr) + (**self).get_vtable_with_ptr(table_ptr) } /// Create a new table @@ -88,17 +96,17 @@ where /// To actually access the BTreeMap, you first need to lock the returned object for reading /// (`data.read()`) or writing (`data.write()`). pub fn data(&self) -> RefShared>>> { - self.ptr.data() + (**self).data() } /// Return the table name. pub fn name(&self) -> &'static CStr { - self.ptr.name() + (**self).name() } /// Return the number of entries in the table. pub fn size(&self) -> usize { - self.ptr.size() + (**self).size() } /// Get an entry corresponding to a particular key. @@ -107,7 +115,7 @@ where K: Borrow, Q: Ord + ?Sized, { - self.ptr.lookup(key) + (**self).lookup(key) } /// Get the value for a field in an entry. @@ -117,7 +125,7 @@ where field: &crate::tables::export::field_descriptor::FieldDescriptor, out: &mut ss_plugin_state_data, ) -> Result<(), anyhow::Error> { - self.ptr.get_field_value(entry, field, out) + (**self).get_field_value(entry, field, out) } /// Execute a closure on all entries in the table with read-only access. @@ -128,12 +136,12 @@ where where F: FnMut(&mut TableEntryType) -> bool, { - self.ptr.iterate_entries(func) + (**self).iterate_entries(func) } /// Remove all entries from the table. pub fn clear(&mut self) { - self.ptr.clear(); + (**self).clear() } /// Erase an entry by key. @@ -142,14 +150,14 @@ where K: Borrow, Q: Ord + ?Sized, { - self.ptr.erase(key) + (**self).erase(key) } /// Create a new table entry. /// /// This is a detached entry that can be later inserted into the table using [`Table::insert`]. pub fn create_entry(&self) -> Result, anyhow::Error> { - self.ptr.create_entry() + (**self).create_entry() } /// Return a closure for creating table entries @@ -171,7 +179,7 @@ where pub fn create_entry_fn( &self, ) -> impl Fn() -> Result>, anyhow::Error> + use { - self.ptr.create_entry_fn() + (**self).create_entry_fn() } /// Attach an entry to a table key @@ -180,7 +188,7 @@ where K: Borrow, Q: Ord + ToOwned + ?Sized, { - self.ptr.insert(key, entry) + (**self).insert(key, entry) } /// Write a value to a field of an entry @@ -190,19 +198,19 @@ where field: &crate::tables::export::field_descriptor::FieldDescriptor, value: &ss_plugin_state_data, ) -> Result<(), anyhow::Error> { - self.ptr.write(entry, field, value) + (**self).write(entry, field, value) } /// Return a list of fields as a slice of raw FFI objects pub fn list_fields(&mut self) -> &[ss_plugin_table_fieldinfo] { - self.ptr.list_fields() + (**self).list_fields() } /// Return a field descriptor for a particular field /// /// The requested `field_type` must match the actual type of the field pub fn get_field(&self, name: &CStr, field_type: FieldTypeId) -> Option { - self.ptr.get_field(name, field_type) + (**self).get_field(name, field_type) } /// Add a new field to the table @@ -212,7 +220,52 @@ where field_type: FieldTypeId, read_only: bool, ) -> Option { - self.ptr.add_field(name, field_type, read_only) + (**self).add_field(name, field_type, read_only) + } +} + +impl Deref for Table +where + K: Key + Ord, + K: Borrow<::Borrowed>, + ::Borrowed: Ord + ToOwned, + E: Entry, + E::Metadata: TableMetadata, +{ + type Target = TableData; + fn deref(&self) -> &TableData { + // SAFETY: the pointer is valid as long as self is alive + unsafe { self.ptr.as_ref() } + } +} + +impl DerefMut for Table +where + K: Key + Ord, + K: Borrow<::Borrowed>, + ::Borrowed: Ord + ToOwned, + E: Entry, + E::Metadata: TableMetadata, +{ + fn deref_mut(&mut self) -> &mut TableData { + // SAFETY: the pointer is valid as long as self is alive and we have &mut self + unsafe { self.ptr.as_mut() } + } +} + +impl Drop for Table +where + K: Key + Ord, + K: Borrow<::Borrowed>, + ::Borrowed: Ord + ToOwned, + E: Entry, + E::Metadata: TableMetadata, +{ + fn drop(&mut self) { + // SAFETY: we own the allocation and it hasn't been freed + unsafe { + drop(Box::from_raw(self.ptr.as_ptr())); + } } } @@ -225,6 +278,26 @@ where E::Metadata: TableMetadata + Debug, { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - self.ptr.fmt(f) + (**self).fmt(f) } } + +// SAFETY: Table has the same semantics as Box +unsafe impl Send for Table +where + K: Key + Ord + Send, + K: Borrow<::Borrowed>, + ::Borrowed: Ord + ToOwned, + E: Entry + Send, + E::Metadata: TableMetadata, +{ +} +unsafe impl Sync for Table +where + K: Key + Ord + Sync, + K: Borrow<::Borrowed>, + ::Borrowed: Ord + ToOwned, + E: Entry + Sync, + E::Metadata: TableMetadata, +{ +} From 37c285c6552e149d3088b1a07eaaec32ebaa7482 Mon Sep 17 00:00:00 2001 From: Grzegorz Nosek Date: Mon, 1 Jun 2026 23:48:40 +0200 Subject: [PATCH 09/17] fix(plugin): remove invalid mutable ref conversion test tests::native::test_async ... error: Undefined Behavior: writing to alloc16 which is read-only --> .../lib/rustlib/src/rust/library/core/src/ptr/mut_ptr.rs:586:57 | 586 | if self.is_null() { None } else { unsafe { Some(&mut *self) } } | ^^^^^^^^^^ Undefined Behavior occurred here | = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information = note: this is on thread `tests::native::` = note: stack backtrace: 0: std::ptr::mut_ptr::::as_mut::<'_> at .../lib/rustlib/src/rust/library/core/src/ptr/mut_ptr.rs:586:57: 586:67 1: falco_plugin::tables::import::RawTable::get_field:: at falco_plugin/src/tables/import/table/raw.rs:105:13: 106:26 2: ::new at falco_plugin/src/tables/import/macros.rs:11:31: 11:78 3: as falco_plugin::tables::import::traits::TableMetadata>::new at falco_plugin/src/tables/import/traits/mod.rs:24:21: 24:52 4: falco_plugin::tables::import::table_input::>::get_table::>, std::sync::Arc>, u64> at falco_plugin/src/tables/import/table_input.rs:30:28: 30:58 5: ::new at falco_plugin_tests/tests/async_tables.rs:134:30: 134:55 6: falco_plugin::base::wrappers::plugin_init::::{closure#0} at falco_plugin/src/base/wrappers.rs:107:13: 107:50 7: <{closure@falco_plugin::base::wrappers::plugin_init::{closure#0}} as std::ops::FnOnce<()>>::call_once - shim at .../lib/rustlib/src/rust/library/core/src/ops/function.rs:250:5: 250:71 8: ::{closure#0}}> as std::ops::FnOnce<()>>::call_once at .../lib/rustlib/src/rust/library/core/src/panic/unwind_safe.rs:274:9: 274:19 9: falco_plugin::error::panic::catch_panic::<*mut falco_plugin::base::wrappers::PluginWrapper, std::panic::AssertUnwindSafe<{closure@falco_plugin::base::wrappers::plugin_init::{closure#0}}>>::{closure#0} at falco_plugin/src/error/panic.rs:10:38: 10:41 10: std::panicking::catch_unwind::do_call::<{closure@falco_plugin::error::panic::catch_panic<*mut falco_plugin::base::wrappers::PluginWrapper, std::panic::AssertUnwindSafe<{closure@falco_plugin::base::wrappers::plugin_init::{closure#0}}>>::{closure#0}}, std::result::Result<*mut falco_plugin::base::wrappers::PluginWrapper, anyhow::Error>> at .../lib/rustlib/src/rust/library/std/src/panicking.rs:581:40: 581:43 11: std::panicking::catch_unwind::, anyhow::Error>, {closure@falco_plugin::error::panic::catch_panic<*mut falco_plugin::base::wrappers::PluginWrapper, std::panic::AssertUnwindSafe<{closure@falco_plugin::base::wrappers::plugin_init::{closure#0}}>>::{closure#0}}> at .../lib/rustlib/src/rust/library/std/src/panicking.rs:544:19: 544:88 12: std::panic::catch_unwind::<{closure@falco_plugin::error::panic::catch_panic<*mut falco_plugin::base::wrappers::PluginWrapper, std::panic::AssertUnwindSafe<{closure@falco_plugin::base::wrappers::plugin_init::{closure#0}}>>::{closure#0}}, std::result::Result<*mut falco_plugin::base::wrappers::PluginWrapper, anyhow::Error>> at .../lib/rustlib/src/rust/library/std/src/panic.rs:359:14: 359:40 13: falco_plugin::error::panic::catch_panic::<*mut falco_plugin::base::wrappers::PluginWrapper, std::panic::AssertUnwindSafe<{closure@falco_plugin::base::wrappers::plugin_init::{closure#0}}>> at falco_plugin/src/error/panic.rs:10:5: 10:42 14: falco_plugin::base::wrappers::plugin_init:: at falco_plugin/src/base/wrappers.rs:77:15: 110:7 15: DUMMY_PLUGIN::{constant#0}::plugin_init at falco_plugin/src/base/wrappers.rs:276:13: 276:48 16: falco_plugin_runner::plugin::Plugin::init at falco_plugin_runner/src/plugin/mod.rs:220:31: 220:52 17: falco_plugin_runner::plugin::Plugin::new at falco_plugin_runner/src/plugin/mod.rs:128:9: 128:26 18: falco_plugin_runner::PluginRunner::register_plugin at falco_plugin_runner/src/lib.rs:50:22: 50:74 19: ::register_plugin at falco_plugin_tests/src/native.rs:46:9: 46:44 20: tests::test_async:: at falco_plugin_tests/tests/async_tables.rs:221:9: 221:58 21: tests::native::test_async at falco_plugin_tests/src/lib.rs:35:17: 35:57 22: tests::native::test_async::{closure#0} at falco_plugin_tests/src/lib.rs:34:23: 34:23 Signed-off-by: Grzegorz Nosek --- falco_plugin/src/tables/import/table/raw.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/falco_plugin/src/tables/import/table/raw.rs b/falco_plugin/src/tables/import/table/raw.rs index b96d6e7b..bf8b284a 100644 --- a/falco_plugin/src/tables/import/table/raw.rs +++ b/falco_plugin/src/tables/import/table/raw.rs @@ -103,7 +103,7 @@ impl RawTable { )?; let raw_field = unsafe { field - .as_mut() + .as_ref() .ok_or_else(|| anyhow::anyhow!("Failed to get table field {:?}", name)) .with_last_error(&tables_input.last_error)?; field From 9e37cb33ca438e2d79571ebf110ad5f1e5fef32d Mon Sep 17 00:00:00 2001 From: Grzegorz Nosek Date: Mon, 1 Jun 2026 23:53:05 +0200 Subject: [PATCH 10/17] fix(runner): avoid slice borrow for event batches test tests::native::test_async ... error: Undefined Behavior: attempting a read access using <301640> at alloc95909[0x178], but that tag does not exist in the borrow stack for this location --> falco_plugin_runner/src/plugin/source.rs:80:24 | = note: this error occurs as part of an access at alloc95909[0x178..0x180] | = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information help: <301640> was created by a SharedReadOnly retag at offsets [0x178..0x180] --> falco_plugin/src/source/wrappers.rs:246:24 help: <301640> was later invalidated at offsets [0x178..0x180] by a Unique retag --> falco_plugin/src/source/wrappers.rs:256:4 = note: this is on thread `tests::native::` = note: stack backtrace: 0: falco_plugin_runner::plugin::source::SourcePlugin::next_event at falco_plugin_runner/src/plugin/source.rs:80:25: 80:29 1: falco_plugin_runner::plugin::Plugin::next_event at falco_plugin_runner/src/plugin/mod.rs:435:25: 435:44 2: falco_plugin_runner::CapturingPluginRunner::get_next_event at falco_plugin_runner/src/lib.rs:88:25: 88:44 3: falco_plugin_runner::CapturingPluginRunner::next_event at falco_plugin_runner/src/lib.rs:105:21: 105:42 4: ::next_event at falco_plugin_tests/src/native.rs:93:15: 93:34 5: tests::test_async:: at falco_plugin_tests/tests/async_tables.rs:226:9: 226:28 6: tests::native::test_async at falco_plugin_tests/src/lib.rs:35:17: 35:57 7: tests::native::test_async::{closure#0} at falco_plugin_tests/src/lib.rs:34:23: 34:23 Signed-off-by: Grzegorz Nosek --- falco_plugin/src/source/event_batch.rs | 4 ++-- falco_plugin/src/source/wrappers.rs | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/falco_plugin/src/source/event_batch.rs b/falco_plugin/src/source/event_batch.rs index 7156a869..9b7a4803 100644 --- a/falco_plugin/src/source/event_batch.rs +++ b/falco_plugin/src/source/event_batch.rs @@ -51,7 +51,7 @@ impl EventBatch<'_> { self.pointers.reserve(num_events); } - pub(super) fn get_events(&self) -> &[*const u8] { - self.pointers.as_slice() + pub(super) fn get_events_ptr_len(&mut self) -> (*mut *const u8, usize) { + (self.pointers.as_mut_ptr(), self.pointers.len()) } } diff --git a/falco_plugin/src/source/wrappers.rs b/falco_plugin/src/source/wrappers.rs index 95d20872..819b8b7e 100644 --- a/falco_plugin/src/source/wrappers.rs +++ b/falco_plugin/src/source/wrappers.rs @@ -241,9 +241,9 @@ pub unsafe extern "C" fn plugin_next_batch( })); match batch_result { Ok(()) => { - let events = batch.get_events(); - *nevts = events.len() as u32; - *evts = events as *const _ as *mut _; + let (events, events_len) = batch.get_events_ptr_len(); + *nevts = events_len as u32; + *evts = events.cast(); ss_plugin_rc_SS_PLUGIN_SUCCESS } Err(e) => { From 7f758b3c2c698ba630ab0b388a0dd4d159d4ff6c Mon Sep 17 00:00:00 2001 From: Grzegorz Nosek Date: Tue, 2 Jun 2026 00:15:30 +0200 Subject: [PATCH 11/17] fix(plugin): pass exported tables through *const Table test tests::native::test_async ... error: Undefined Behavior: not granting access to tag <124905> because that would remove [Unique for <340142>] which is strongly protected --> .../lib/rustlib/src/rust/library/core/src/ptr/mut_ptr.rs:586:57 | 586 | if self.is_null() { None } else { unsafe { Some(&mut *self) } } | ^^^^^^^^^^ Undefined Behavior occurred here | = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information help: <124905> was created by a SharedReadWrite retag at offsets [0x0..0x48] --> falco_plugin/src/tables/export/table_box.rs:56:18 help: <340142> is this argument --> falco_plugin/src/tables/export/table.rs:151:30 = note: this is on thread `tests::native::` = note: stack backtrace: 0: std::ptr::mut_ptr::>::as_mut::<'_> at .../lib/rustlib/src/rust/library/core/src/ptr/mut_ptr.rs:586:57: 586:67 1: falco_plugin::tables::export::wrappers::read_entry_field:: at falco_plugin/src/tables/export/wrappers.rs:96:27: 96:67 2: falco_plugin_runner::tables::read_entry_field at falco_plugin_runner/src/tables.rs:100:14: 100:56 3: as falco_plugin::tables::vtable::reader::private::TableReaderImpl>::read_entry_field at falco_plugin/src/tables/vtable/reader.rs:165:13: 167:69 4: falco_plugin::tables::import::entry::raw::RawEntry::read_field_with_assoc::<'_, u64, falco_plugin::tables::LazyTableReader<'_>> at falco_plugin/src/tables/import/entry/raw.rs:25:21: 25:96 5: falco_plugin::tables::import::Entry::>::read_field::> at falco_plugin/src/tables/import/entry/mod.rs:55:13: 56:96 6: _:: for falco_plugin::tables::import::Entry>>::get_num::> at falco_plugin/src/tables/import/macros.rs:92:21: 92:62 7: ::parse_event::{closure#0} at falco_plugin_tests/tests/async_tables.rs:158:27: 158:57 8: falco_plugin::tables::import::Table::>, std::sync::Arc>::iter_entries_mut::<{closure@falco_plugin_tests/tests/async_tables.rs:157:52: 157:55}, falco_plugin::tables::LazyTableReader<'_>>::{closure#0} at falco_plugin/src/tables/import/table/mod.rs:271:13: 271:29 9: falco_plugin::tables::import::RawTable::iter_entries_mut::<{closure@falco_plugin::tables::import::Table>, std::sync::Arc>::iter_entries_mut<{closure@falco_plugin_tests/tests/async_tables.rs:157:52: 157:55}, falco_plugin::tables::LazyTableReader<'_>>::{closure#0}}, falco_plugin::tables::LazyTableReader<'_>>::{closure#0} at falco_plugin/src/tables/import/table/raw.rs:293:17: 293:32 10: falco_plugin::tables::import::table::raw::iter_inner::iter_wrapper::<{closure@falco_plugin::tables::import::RawTable::iter_entries_mut<{closure@falco_plugin::tables::import::Table>, std::sync::Arc>::iter_entries_mut<{closure@falco_plugin_tests/tests/async_tables.rs:157:52: 157:55}, falco_plugin::tables::LazyTableReader<'_>>::{closure#0}}, falco_plugin::tables::LazyTableReader<'_>>::{closure#0}}> at falco_plugin/src/tables/import/table/raw.rs:394:23: 394:37 11: falco_plugin::tables::export::wrappers::iterate_entries::::{closure#0} at falco_plugin/src/tables/export/wrappers.rs:151:13: 151:31 12: falco_plugin::tables::export::table::TableData::::iterate_entries::<{closure@falco_plugin::tables::export::wrappers::iterate_entries::{closure#0}}> at falco_plugin/src/tables/export/table.rs:156:17: 156:45 13: falco_plugin::tables::export::wrappers::iterate_entries:: at falco_plugin/src/tables/export/wrappers.rs:149:9: 152:11 14: falco_plugin_runner::tables::iterate_entries at falco_plugin_runner/src/tables.rs:118:14: 118:49 15: falco_plugin::tables::import::table::raw::iter_inner::<{closure@falco_plugin::tables::import::RawTable::iter_entries_mut<{closure@falco_plugin::tables::import::Table>, std::sync::Arc>::iter_entries_mut<{closure@falco_plugin_tests/tests/async_tables.rs:157:52: 157:55}, falco_plugin::tables::LazyTableReader<'_>>::{closure#0}}, falco_plugin::tables::LazyTableReader<'_>>::{closure#0}}> at falco_plugin/src/tables/import/table/raw.rs:404:9: 408:10 16: falco_plugin::tables::import::RawTable::iter_entries_mut::<{closure@falco_plugin::tables::import::Table>, std::sync::Arc>::iter_entries_mut<{closure@falco_plugin_tests/tests/async_tables.rs:157:52: 157:55}, falco_plugin::tables::LazyTableReader<'_>>::{closure#0}}, falco_plugin::tables::LazyTableReader<'_>> at falco_plugin/src/tables/import/table/raw.rs:282:12: 295:10 17: falco_plugin::tables::import::Table::>, std::sync::Arc>::iter_entries_mut::<{closure@falco_plugin_tests/tests/async_tables.rs:157:52: 157:55}, falco_plugin::tables::LazyTableReader<'_>> at falco_plugin/src/tables/import/table/mod.rs:269:9: 272:11 18: ::parse_event at falco_plugin_tests/tests/async_tables.rs:156:9: 163:15 19: falco_plugin::parse::wrappers::plugin_parse_event::::{closure#0} at falco_plugin/src/parse/wrappers.rs:118:13: 118:67 20: <{closure@falco_plugin::parse::wrappers::plugin_parse_event::{closure#0}} as std::ops::FnOnce<()>>::call_once - shim at .../lib/rustlib/src/rust/library/core/src/ops/function.rs:250:5: 250:71 21: ::{closure#0}}> as std::ops::FnOnce<()>>::call_once at .../lib/rustlib/src/rust/library/core/src/panic/unwind_safe.rs:274:9: 274:19 22: falco_plugin::error::panic::catch_panic::<(), std::panic::AssertUnwindSafe<{closure@falco_plugin::parse::wrappers::plugin_parse_event::{closure#0}}>>::{closure#0} at falco_plugin/src/error/panic.rs:10:38: 10:41 23: std::panicking::catch_unwind::do_call::<{closure@falco_plugin::error::panic::catch_panic<(), std::panic::AssertUnwindSafe<{closure@falco_plugin::parse::wrappers::plugin_parse_event::{closure#0}}>>::{closure#0}}, std::result::Result<(), anyhow::Error>> at .../lib/rustlib/src/rust/library/std/src/panicking.rs:581:40: 581:43 24: std::panicking::catch_unwind::, {closure@falco_plugin::error::panic::catch_panic<(), std::panic::AssertUnwindSafe<{closure@falco_plugin::parse::wrappers::plugin_parse_event::{closure#0}}>>::{closure#0}}> at .../lib/rustlib/src/rust/library/std/src/panicking.rs:544:19: 544:88 25: std::panic::catch_unwind::<{closure@falco_plugin::error::panic::catch_panic<(), std::panic::AssertUnwindSafe<{closure@falco_plugin::parse::wrappers::plugin_parse_event::{closure#0}}>>::{closure#0}}, std::result::Result<(), anyhow::Error>> at .../lib/rustlib/src/rust/library/std/src/panic.rs:359:14: 359:40 26: falco_plugin::error::panic::catch_panic::<(), std::panic::AssertUnwindSafe<{closure@falco_plugin::parse::wrappers::plugin_parse_event::{closure#0}}>> at falco_plugin/src/error/panic.rs:10:5: 10:42 27: falco_plugin::parse::wrappers::plugin_parse_event:: at falco_plugin/src/parse/wrappers.rs:117:9: 119:12 28: falco_plugin_runner::plugin::parse::ParsePlugin::on_event at falco_plugin_runner/src/plugin/parse.rs:53:27: 53:73 29: falco_plugin_runner::plugin::Plugin::on_event at falco_plugin_runner/src/plugin/mod.rs:457:13: 457:34 30: falco_plugin_runner::CapturingPluginRunner::next_event at falco_plugin_runner/src/lib.rs:108:13: 108:36 31: ::next_event at falco_plugin_tests/src/native.rs:93:15: 93:34 32: tests::test_async:: at falco_plugin_tests/tests/async_tables.rs:226:9: 226:28 33: tests::native::test_async at falco_plugin_tests/src/lib.rs:35:17: 35:57 34: tests::native::test_async::{closure#0} at falco_plugin_tests/src/lib.rs:34:23: 34:23 Signed-off-by: Grzegorz Nosek --- falco_plugin/src/tables/export/table.rs | 2 +- falco_plugin/src/tables/export/table_box.rs | 2 +- falco_plugin/src/tables/export/wrappers.rs | 14 +++++++------- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/falco_plugin/src/tables/export/table.rs b/falco_plugin/src/tables/export/table.rs index e93798ec..b94bb315 100644 --- a/falco_plugin/src/tables/export/table.rs +++ b/falco_plugin/src/tables/export/table.rs @@ -148,7 +148,7 @@ where /// /// The iteration continues until all entries are visited or the closure returns false. // TODO(upstream) the closure cannot store away the entry but we could use explicit docs - pub fn iterate_entries(&mut self, mut func: F) -> bool + pub fn iterate_entries(&self, mut func: F) -> bool where F: FnMut(&mut TableEntryType) -> bool, { diff --git a/falco_plugin/src/tables/export/table_box.rs b/falco_plugin/src/tables/export/table_box.rs index db466458..0a30e277 100644 --- a/falco_plugin/src/tables/export/table_box.rs +++ b/falco_plugin/src/tables/export/table_box.rs @@ -132,7 +132,7 @@ where /// /// The iteration continues until all entries are visited or the closure returns false. // TODO(upstream) the closure cannot store away the entry but we could use explicit docs - pub fn iterate_entries(&mut self, func: F) -> bool + pub fn iterate_entries(&self, func: F) -> bool where F: FnMut(&mut TableEntryType) -> bool, { diff --git a/falco_plugin/src/tables/export/wrappers.rs b/falco_plugin/src/tables/export/wrappers.rs index a935b1a6..bd5bed47 100644 --- a/falco_plugin/src/tables/export/wrappers.rs +++ b/falco_plugin/src/tables/export/wrappers.rs @@ -25,7 +25,7 @@ where E::Metadata: TableMetadata, { unsafe { - let Some(table) = (table as *mut TableData).as_mut() else { + let Some(table) = (table as *const TableData).as_ref() else { return std::ptr::null_mut(); }; table.name().as_ptr() @@ -42,7 +42,7 @@ where E::Metadata: TableMetadata, { unsafe { - let Some(table) = (table as *mut TableData).as_mut() else { + let Some(table) = (table as *const TableData).as_ref() else { return 0; }; table.size() as u64 @@ -63,7 +63,7 @@ where E::Metadata: TableMetadata, { unsafe { - let Some(table) = (table as *mut TableData).as_mut() else { + let Some(table) = (table as *const TableData).as_ref() else { return std::ptr::null_mut(); }; let Some(key) = key.as_ref() else { @@ -93,7 +93,7 @@ where E::Metadata: TableMetadata, { unsafe { - let Some(table) = (table as *mut TableData).as_mut() else { + let Some(table) = (table as *const TableData).as_ref() else { return ss_plugin_rc_SS_PLUGIN_FAILURE; }; let Some(entry) = (entry as *mut TableEntryType).as_mut() else { @@ -142,7 +142,7 @@ where return 0; }; unsafe { - let Some(table) = (table as *mut TableData).as_mut() else { + let Some(table) = (table as *const TableData).as_ref() else { return 0; }; @@ -212,7 +212,7 @@ where E::Metadata: TableMetadata, { unsafe { - let Some(table) = (table as *mut TableData).as_mut() else { + let Some(table) = (table as *const TableData).as_ref() else { return std::ptr::null_mut(); }; @@ -324,7 +324,7 @@ where E::Metadata: TableMetadata, { unsafe { - let Some(table) = (table as *mut TableData).as_mut() else { + let Some(table) = (table as *const TableData).as_ref() else { return std::ptr::null_mut(); }; let Some(data_type) = FieldTypeId::from_usize(data_type as usize) else { From 603608766398aa0235027c88338d8b3df66a9562 Mon Sep 17 00:00:00 2001 From: Grzegorz Nosek Date: Tue, 2 Jun 2026 00:26:13 +0200 Subject: [PATCH 12/17] fix(tests): use miri-safe lock implementation test tests::native::test_async ... warning: integer-to-pointer cast --> /home/blackfire/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/parking_lot_core-0.9.11/src/word_lock.rs:320:9 | 320 | (self & QUEUE_MASK) as *const ThreadData | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ integer-to-pointer cast | = help: this program is using integer-to-pointer casts or (equivalently) `ptr::with_exposed_provenance`, which means that Miri might miss pointer bugs in this program = help: see https://doc.rust-lang.org/nightly/std/ptr/fn.with_exposed_provenance.html for more details on that operation = help: to ensure that Miri does not miss bugs in your program, use Strict Provenance APIs (https://doc.rust-lang.org/nightly/std/ptr/index.html#strict-provenance, https://crates.io/crates/sptr) instead = help: you can then set `MIRIFLAGS=-Zmiri-strict-provenance` to ensure you are not relying on `with_exposed_provenance` semantics = help: alternatively, `MIRIFLAGS=-Zmiri-permissive-provenance` disables this warning = note: this is on thread `unnamed-2` = note: stack backtrace: 0: ::queue_head at /home/blackfire/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/parking_lot_core-0.9.11/src/word_lock.rs:320:9: 320:49 1: parking_lot_core::word_lock::WordLock::unlock at /home/blackfire/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/parking_lot_core-0.9.11/src/word_lock.rs:104:39: 104:57 2: parking_lot_core::parking_lot::park::<{closure@parking_lot::raw_rwlock::RawRwLock::lock_common<{closure@parking_lot::raw_rwlock::RawRwLock::lock_exclusive_slow::{closure#0}}>::{closure#0}}, {closure@parking_lot::raw_rwlock::RawRwLock::lock_common<{closure@parking_lot::raw_rwlock::RawRwLock::lock_exclusive_slow::{closure#0}}>::{closure#1}}, {closure@parking_lot::raw_rwlock::RawRwLock::lock_common<{closure@parking_lot::raw_rwlock::RawRwLock::lock_exclusive_slow::{closure#0}}>::{closure#2}}>::{closure#0} at /home/blackfire/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/parking_lot_core-0.9.11/src/parking_lot.rs:624:9: 624:30 3: parking_lot_core::parking_lot::with_thread_data::::{closure#0}}, {closure@parking_lot::raw_rwlock::RawRwLock::lock_common<{closure@parking_lot::raw_rwlock::RawRwLock::lock_exclusive_slow::{closure#0}}>::{closure#1}}, {closure@parking_lot::raw_rwlock::RawRwLock::lock_common<{closure@parking_lot::raw_rwlock::RawRwLock::lock_exclusive_slow::{closure#0}}>::{closure#2}}>::{closure#0}}> at /home/blackfire/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/parking_lot_core-0.9.11/src/parking_lot.rs:207:5: 207:36 4: parking_lot_core::parking_lot::park::<{closure@parking_lot::raw_rwlock::RawRwLock::lock_common<{closure@parking_lot::raw_rwlock::RawRwLock::lock_exclusive_slow::{closure#0}}>::{closure#0}}, {closure@parking_lot::raw_rwlock::RawRwLock::lock_common<{closure@parking_lot::raw_rwlock::RawRwLock::lock_exclusive_slow::{closure#0}}>::{closure#1}}, {closure@parking_lot::raw_rwlock::RawRwLock::lock_common<{closure@parking_lot::raw_rwlock::RawRwLock::lock_exclusive_slow::{closure#0}}>::{closure#2}}> at /home/blackfire/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/parking_lot_core-0.9.11/src/parking_lot.rs:600:5: 705:7 5: parking_lot::raw_rwlock::RawRwLock::lock_common::<{closure@parking_lot::raw_rwlock::RawRwLock::lock_exclusive_slow::{closure#0}}> at /home/blackfire/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/parking_lot-0.12.4/src/raw_rwlock.rs:1123:17: 1123:96 6: parking_lot::raw_rwlock::RawRwLock::lock_exclusive_slow at /home/blackfire/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/parking_lot-0.12.4/src/raw_rwlock.rs:636:26: 641:10 7: ::lock_exclusive at /home/blackfire/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/parking_lot-0.12.4/src/raw_rwlock.rs:73:26: 73:56 8: lock_api::rwlock::RwLock::>>>>::write at /home/blackfire/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/lock_api-0.4.13/src/rwlock.rs:500:9: 500:34 9: ::start_async::{closure#0} at falco_plugin_tests/tests/async_tables.rs:84:13: 84:25 10: falco_plugin::async_event::BackgroundTask::spawn::<{closure@falco_plugin_tests/tests/async_tables.rs:81:72: 81:79}>::{closure#0} at falco_plugin/src/async_event/background_task.rs:97:17: 97:23 note: the last function in that backtrace got called indirectly due to this code --> falco_plugin/src/async_event/background_task.rs:95:11 Signed-off-by: Grzegorz Nosek --- falco_plugin/src/tables/export/ref_shared.rs | 103 ++++++++++++++++++- 1 file changed, 102 insertions(+), 1 deletion(-) diff --git a/falco_plugin/src/tables/export/ref_shared.rs b/falco_plugin/src/tables/export/ref_shared.rs index 09e123f3..a3cde59a 100644 --- a/falco_plugin/src/tables/export/ref_shared.rs +++ b/falco_plugin/src/tables/export/ref_shared.rs @@ -1,11 +1,112 @@ use std::sync::Arc; -#[cfg(feature = "thread-safe-tables")] +#[cfg(all(feature = "thread-safe-tables", not(miri)))] use parking_lot::RawRwLock as LockImpl; +#[cfg(all(feature = "thread-safe-tables", miri))] +use miri_lock::StdRawRwLock as LockImpl; + #[cfg(not(feature = "thread-safe-tables"))] use refcell_lock_api::raw::CellRwLock as LockImpl; +/// A [`lock_api::RawRwLock`] implementation backed by `std::sync` primitives. +/// +/// `parking_lot`'s implementation uses integer-to-pointer casts that Miri warns about +/// (see ). +/// When running under Miri we therefore substitute `parking_lot` with this implementation, +/// which relies only on `std::sync::Mutex` and `std::sync::Condvar` — both of which have +/// native Miri support. +#[cfg(all(feature = "thread-safe-tables", miri))] +mod miri_lock { + use std::sync::{Condvar, Mutex}; + + struct State { + readers: u32, + writing: bool, + } + + #[allow(missing_debug_implementations)] + pub struct StdRawRwLock { + state: Mutex, + cvar: Condvar, + } + + unsafe impl lock_api::RawRwLock for StdRawRwLock { + #[allow(clippy::declare_interior_mutable_const)] + const INIT: Self = Self { + state: Mutex::new(State { + readers: 0, + writing: false, + }), + cvar: Condvar::new(), + }; + + type GuardMarker = lock_api::GuardSend; + + fn lock_shared(&self) { + let mut state = self.state.lock().unwrap(); + loop { + if !state.writing { + state.readers += 1; + return; + } + state = self.cvar.wait(state).unwrap(); + } + } + + fn try_lock_shared(&self) -> bool { + let mut state = self.state.lock().unwrap(); + if !state.writing { + state.readers += 1; + true + } else { + false + } + } + + unsafe fn unlock_shared(&self) { + let mut state = self.state.lock().unwrap(); + state.readers -= 1; + if state.readers == 0 { + drop(state); + self.cvar.notify_all(); + } + } + + fn lock_exclusive(&self) { + let mut state = self.state.lock().unwrap(); + loop { + if !state.writing && state.readers == 0 { + state.writing = true; + return; + } + state = self.cvar.wait(state).unwrap(); + } + } + + fn try_lock_exclusive(&self) -> bool { + let mut state = self.state.lock().unwrap(); + if !state.writing && state.readers == 0 { + state.writing = true; + true + } else { + false + } + } + + unsafe fn unlock_exclusive(&self) { + let mut state = self.state.lock().unwrap(); + state.writing = false; + drop(state); + self.cvar.notify_all(); + } + } + + // SAFETY: the lock is entirely based on std::sync primitives which are Send+Sync. + unsafe impl Send for StdRawRwLock {} + unsafe impl Sync for StdRawRwLock {} +} + /// like `RefCell` pub type RefCounted = lock_api::RwLock; From 6f10b47e59051cfa5e9f46708ecdd4d7c0b52150 Mon Sep 17 00:00:00 2001 From: Grzegorz Nosek Date: Tue, 2 Jun 2026 00:31:23 +0200 Subject: [PATCH 13/17] fix(plugin): close panic-path memory leak in source wrappers error: memory leaked: alloc93616 (Rust heap, size: 24, align: 8), allocated here: --> falco_plugin/src/source/wrappers.rs:162:30 | = note: stack backtrace: 0: falco_plugin::source::wrappers::plugin_open:: at falco_plugin/src/source/wrappers.rs:162:31: 165:19 1: falco_plugin_runner::plugin::source::SourcePlugin::on_capture_start at falco_plugin_runner/src/plugin/source.rs:36:33: 36:67 2: falco_plugin_runner::plugin::Plugin::on_capture_start at falco_plugin_runner/src/plugin/mod.rs:294:13: 294:38 3: falco_plugin_runner::PluginRunner::start_capture at falco_plugin_runner/src/lib.rs:58:13: 59:36 4: ::start_capture at falco_plugin_tests/src/native.rs:77:25: 77:47 5: panic::listen::test_listen_panic:: at falco_plugin_tests/tests/panic/listen.rs:68:15: 68:88 6: panic::listen::native::test_listen_panic at falco_plugin_tests/src/lib.rs:35:17: 35:57 7: panic::listen::native::test_listen_panic::{closure#0} at falco_plugin_tests/src/lib.rs:34:23: 34:23 Signed-off-by: Grzegorz Nosek --- falco_plugin_runner/src/plugin/mod.rs | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/falco_plugin_runner/src/plugin/mod.rs b/falco_plugin_runner/src/plugin/mod.rs index 1991a22a..94c1d013 100644 --- a/falco_plugin_runner/src/plugin/mod.rs +++ b/falco_plugin_runner/src/plugin/mod.rs @@ -299,22 +299,33 @@ impl Plugin { })?; } + let rollback_source = |this: &mut Self| { + if let Some(ref mut source) = this.source { + let _ = source.on_capture_stop(); + } + }; + if let Some(ref mut async_event) = self.async_event { - async_event.on_capture_start().map_err(|e| { - anyhow!( + if let Err(e) = async_event.on_capture_start() { + rollback_source(self); + return Err(anyhow!( "failed to notify async capture start, rc {e}, err {:?}", self.last_error() - ) - })?; + )); + } } if let Some(ref mut capture_listen) = self.capture_listen { - capture_listen.on_capture_start().map_err(|e| { - anyhow!( + if let Err(e) = capture_listen.on_capture_start() { + if let Some(ref mut async_event) = self.async_event { + let _ = async_event.on_capture_stop(); + } + rollback_source(self); + return Err(anyhow!( "failed to notify capture_listen plugin, rc {e}, err {:?}", self.last_error() - ) - })?; + )); + } } self.capturing = true; From be76da97d4f068f2b2affd01dd2ea0aadbd85a7a Mon Sep 17 00:00:00 2001 From: Grzegorz Nosek Date: Tue, 2 Jun 2026 00:49:32 +0200 Subject: [PATCH 14/17] fix(plugin): use raw pointers for table vtable wiring test native::test_parse_table_api ... error: Undefined Behavior: trying to retag from <180676> for Unique permission at alloc56556[0x60], but that tag does not exist in the borrow stack for this location --> .../lib/rustlib/src/rust/library/core/src/ptr/mut_ptr.rs:586:57 | 586 | if self.is_null() { None } else { unsafe { Some(&mut *self) } } | ^^^^^^^^^^ this error occurs as part of retag at alloc56556[0x60..0xf8] | = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information help: <180676> was created by a SharedReadWrite retag at offsets [0x60..0xf8] --> falco_plugin/src/tables/export/vtable.rs:86:18 help: <180676> was later invalidated at offsets [0x0..0x110] by a Unique retag --> falco_plugin/src/tables/export/vtable.rs:87:29 = note: this is on thread `native::test_pa` = note: stack backtrace: 0: std::ptr::mut_ptr::::as_mut::<'_> at .../lib/rustlib/src/rust/library/core/src/ptr/mut_ptr.rs:586:57: 586:67 1: falco_plugin::tables::import::RawTable::with_subtable::>, std::sync::Arc> as falco_plugin::tables::Value>::get_assoc_from_raw_table::{closure#0}}, std::result::Result, falco_plugin::anyhow::Error>> at falco_plugin/src/tables/import/table/raw.rs:332:30: 332:44 2: >, std::sync::Arc> as falco_plugin::tables::Value>::get_assoc_from_raw_table at falco_plugin/src/tables/import/table/mod.rs:314:13: 316:15 3: falco_plugin::tables::import::RawTable::get_field::>, std::sync::Arc>> at falco_plugin/src/tables/import/table/raw.rs:112:30: 112:88 4: ::new at falco_plugin/src/tables/import/macros.rs:11:31: 11:78 5: as falco_plugin::tables::import::traits::TableMetadata>::new at falco_plugin/src/tables/import/traits/mod.rs:24:21: 24:52 6: falco_plugin::tables::import::table_input::>::get_table::>, std::sync::Arc>, u64> at falco_plugin/src/tables/import/table_input.rs:30:28: 30:58 7: ::new at falco_plugin_tests/src/plugin_collection/extract/extra_fields.rs:30:31: 30:60 8: falco_plugin::base::wrappers::plugin_init::::{closure#0} at falco_plugin/src/base/wrappers.rs:107:13: 107:50 9: <{closure@falco_plugin::base::wrappers::plugin_init::{closure#0}} as std::ops::FnOnce<()>>::call_once - shim at .../lib/rustlib/src/rust/library/core/src/ops/function.rs:250:5: 250:71 10: ::{closure#0}}> as std::ops::FnOnce<()>>::call_once at .../lib/rustlib/src/rust/library/core/src/panic/unwind_safe.rs:274:9: 274:19 11: falco_plugin::error::panic::catch_panic::<*mut falco_plugin::base::wrappers::PluginWrapper, std::panic::AssertUnwindSafe<{closure@falco_plugin::base::wrappers::plugin_init::{closure#0}}>>::{closure#0} at falco_plugin/src/error/panic.rs:10:38: 10:41 12: std::panicking::catch_unwind::do_call::<{closure@falco_plugin::error::panic::catch_panic<*mut falco_plugin::base::wrappers::PluginWrapper, std::panic::AssertUnwindSafe<{closure@falco_plugin::base::wrappers::plugin_init::{closure#0}}>>::{closure#0}}, std::result::Result<*mut falco_plugin::base::wrappers::PluginWrapper, falco_plugin::anyhow::Error>> at .../lib/rustlib/src/rust/library/std/src/panicking.rs:581:40: 581:43 13: std::panicking::catch_unwind::, falco_plugin::anyhow::Error>, {closure@falco_plugin::error::panic::catch_panic<*mut falco_plugin::base::wrappers::PluginWrapper, std::panic::AssertUnwindSafe<{closure@falco_plugin::base::wrappers::plugin_init::{closure#0}}>>::{closure#0}}> at .../lib/rustlib/src/rust/library/std/src/panicking.rs:544:19: 544:88 14: std::panic::catch_unwind::<{closure@falco_plugin::error::panic::catch_panic<*mut falco_plugin::base::wrappers::PluginWrapper, std::panic::AssertUnwindSafe<{closure@falco_plugin::base::wrappers::plugin_init::{closure#0}}>>::{closure#0}}, std::result::Result<*mut falco_plugin::base::wrappers::PluginWrapper, falco_plugin::anyhow::Error>> at .../lib/rustlib/src/rust/library/std/src/panic.rs:359:14: 359:40 15: falco_plugin::error::panic::catch_panic::<*mut falco_plugin::base::wrappers::PluginWrapper, std::panic::AssertUnwindSafe<{closure@falco_plugin::base::wrappers::plugin_init::{closure#0}}>> at falco_plugin/src/error/panic.rs:10:5: 10:42 16: falco_plugin::base::wrappers::plugin_init:: at falco_plugin/src/base/wrappers.rs:77:15: 110:7 17: falco_plugin_tests::plugin_collection::extract::extra_fields::EXTRACT_EXTRA_FIELDS_API::{constant#0}::plugin_init at falco_plugin/src/base/wrappers.rs:276:13: 276:48 18: falco_plugin_runner::plugin::Plugin::init at falco_plugin_runner/src/plugin/mod.rs:220:31: 220:52 19: falco_plugin_runner::plugin::Plugin::new at falco_plugin_runner/src/plugin/mod.rs:128:9: 128:26 20: falco_plugin_runner::PluginRunner::register_plugin at falco_plugin_runner/src/lib.rs:50:22: 50:74 21: ::register_plugin at falco_plugin_tests/src/native.rs:46:9: 46:44 22: test_parse_table_api:: at falco_plugin_tests/tests/parse_table_api.rs:25:26: 26:57 23: native::test_parse_table_api at falco_plugin_tests/src/lib.rs:35:17: 35:57 24: native::test_parse_table_api::{closure#0} at falco_plugin_tests/src/lib.rs:34:23: 34:23 Signed-off-by: Grzegorz Nosek --- falco_plugin/src/tables/export/vtable.rs | 44 ++++++++++++++++-------- 1 file changed, 29 insertions(+), 15 deletions(-) diff --git a/falco_plugin/src/tables/export/vtable.rs b/falco_plugin/src/tables/export/vtable.rs index c296f35c..8f8563cb 100644 --- a/falco_plugin/src/tables/export/vtable.rs +++ b/falco_plugin/src/tables/export/vtable.rs @@ -9,8 +9,13 @@ use falco_plugin_api::{ ss_plugin_table_writer_vtable, ss_plugin_table_writer_vtable_ext, }; use std::borrow::Borrow; +use std::cell::UnsafeCell; pub(crate) struct Vtable { + pub(crate) inner: UnsafeCell, +} + +pub(crate) struct VtableInner { pub(crate) input: ss_plugin_table_input, reader_ext: ss_plugin_table_reader_vtable_ext, writer_ext: ss_plugin_table_writer_vtable_ext, @@ -36,10 +41,11 @@ where ) -> *mut ss_plugin_table_input { let mut vtable_place = self.vtable.write(); - if let Some(ref mut vtable) = *vtable_place { + if let Some(ref vtable) = *vtable_place { + let inner = vtable.inner.get(); // the ss_plugin_table_t value should never change - debug_assert_eq!(vtable.input.table, table_ptr.cast()); - return &mut vtable.input as *mut _; + debug_assert_eq!(unsafe { (*inner).input.table }, table_ptr.cast()); + return unsafe { std::ptr::addr_of_mut!((*inner).input) }; } let reader_vtable_ext = reader_vtable::(); @@ -74,21 +80,29 @@ where fields_ext: std::ptr::null_mut(), }; - let mut vtable = Box::new(Vtable { - input: table_input, - reader_ext: reader_vtable_ext, - writer_ext: writer_vtable_ext, - fields_ext: fields_vtable_ext, + let vtable = Box::new(Vtable { + inner: UnsafeCell::new(VtableInner { + input: table_input, + reader_ext: reader_vtable_ext, + writer_ext: writer_vtable_ext, + fields_ext: fields_vtable_ext, + }), }); - // we can init these fields only now, when the target struct is allocated on the heap - vtable.input.reader_ext = &mut vtable.reader_ext as *mut _; - vtable.input.writer_ext = &mut vtable.writer_ext as *mut _; - vtable.input.fields_ext = &mut vtable.fields_ext as *mut _; - - let ptr = &mut vtable.input as *mut _; + // Store the vtable first, then set up self-referential pointers + // through the stored Box's UnsafeCell to preserve pointer provenance. *vtable_place = Some(vtable); - ptr + let inner = vtable_place.as_ref().unwrap().inner.get(); + unsafe { + let reader_ext_ptr = std::ptr::addr_of_mut!((*inner).reader_ext); + let writer_ext_ptr = std::ptr::addr_of_mut!((*inner).writer_ext); + let fields_ext_ptr = std::ptr::addr_of_mut!((*inner).fields_ext); + (*inner).input.reader_ext = reader_ext_ptr; + (*inner).input.writer_ext = writer_ext_ptr; + (*inner).input.fields_ext = fields_ext_ptr; + } + + unsafe { std::ptr::addr_of_mut!((*inner).input) } } } From d797d2d311ec03ec5f804c73c193f8ae761548dc Mon Sep 17 00:00:00 2001 From: Grzegorz Nosek Date: Tue, 2 Jun 2026 00:51:12 +0200 Subject: [PATCH 15/17] fix(plugin): remove extra mutable reference conversion test native::test_parse_table_api ... error: Undefined Behavior: trying to retag from <184210> for Unique permission at alloc57249[0x10], but that tag only grants SharedReadOnly permission for this location --> .../lib/rustlib/src/rust/library/core/src/ptr/mut_ptr.rs:586:57 | 586 | if self.is_null() { None } else { unsafe { Some(&mut *self) } } | ^^^^^^^^^^ this error occurs as part of retag at alloc57249[0x10..0x11] | = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information help: <184210> was created by a SharedReadOnly retag at offsets [0x10..0x28] --> falco_plugin/src/tables/export/wrappers.rs:371:27 = note: this is on thread `native::test_pa` = note: stack backtrace: 0: std::ptr::mut_ptr::::as_mut::<'_> at .../lib/rustlib/src/rust/library/core/src/ptr/mut_ptr.rs:586:57: 586:67 1: falco_plugin::tables::import::RawTable::add_field:: at falco_plugin/src/tables/import/table/raw.rs:137:13: 138:26 2: ::new at falco_plugin/src/tables/import/macros.rs:11:31: 11:78 3: as falco_plugin::tables::import::traits::TableMetadata>::new at falco_plugin/src/tables/import/traits/mod.rs:24:21: 24:52 4: >, std::sync::Arc> as falco_plugin::tables::Value>::get_assoc_from_raw_table::{closure#0} at falco_plugin/src/tables/import/table/mod.rs:315:17: 315:47 5: falco_plugin::tables::import::RawTable::with_subtable::>, std::sync::Arc> as falco_plugin::tables::Value>::get_assoc_from_raw_table::{closure#0}}, std::result::Result, falco_plugin::anyhow::Error>> at falco_plugin/src/tables/import/table/raw.rs:346:19: 346:35 6: >, std::sync::Arc> as falco_plugin::tables::Value>::get_assoc_from_raw_table at falco_plugin/src/tables/import/table/mod.rs:314:13: 316:15 7: falco_plugin::tables::import::RawTable::get_field::>, std::sync::Arc>> at falco_plugin/src/tables/import/table/raw.rs:112:30: 112:88 8: ::new at falco_plugin/src/tables/import/macros.rs:11:31: 11:78 9: as falco_plugin::tables::import::traits::TableMetadata>::new at falco_plugin/src/tables/import/traits/mod.rs:24:21: 24:52 10: falco_plugin::tables::import::table_input::>::get_table::>, std::sync::Arc>, u64> at falco_plugin/src/tables/import/table_input.rs:30:28: 30:58 11: ::new at falco_plugin_tests/src/plugin_collection/extract/extra_fields.rs:30:31: 30:60 12: falco_plugin::base::wrappers::plugin_init::::{closure#0} at falco_plugin/src/base/wrappers.rs:107:13: 107:50 13: <{closure@falco_plugin::base::wrappers::plugin_init::{closure#0}} as std::ops::FnOnce<()>>::call_once - shim at .../lib/rustlib/src/rust/library/core/src/ops/function.rs:250:5: 250:71 14: ::{closure#0}}> as std::ops::FnOnce<()>>::call_once at .../lib/rustlib/src/rust/library/core/src/panic/unwind_safe.rs:274:9: 274:19 15: falco_plugin::error::panic::catch_panic::<*mut falco_plugin::base::wrappers::PluginWrapper, std::panic::AssertUnwindSafe<{closure@falco_plugin::base::wrappers::plugin_init::{closure#0}}>>::{closure#0} at falco_plugin/src/error/panic.rs:10:38: 10:41 16: std::panicking::catch_unwind::do_call::<{closure@falco_plugin::error::panic::catch_panic<*mut falco_plugin::base::wrappers::PluginWrapper, std::panic::AssertUnwindSafe<{closure@falco_plugin::base::wrappers::plugin_init::{closure#0}}>>::{closure#0}}, std::result::Result<*mut falco_plugin::base::wrappers::PluginWrapper, falco_plugin::anyhow::Error>> at .../lib/rustlib/src/rust/library/std/src/panicking.rs:581:40: 581:43 17: std::panicking::catch_unwind::, falco_plugin::anyhow::Error>, {closure@falco_plugin::error::panic::catch_panic<*mut falco_plugin::base::wrappers::PluginWrapper, std::panic::AssertUnwindSafe<{closure@falco_plugin::base::wrappers::plugin_init::{closure#0}}>>::{closure#0}}> at .../lib/rustlib/src/rust/library/std/src/panicking.rs:544:19: 544:88 18: std::panic::catch_unwind::<{closure@falco_plugin::error::panic::catch_panic<*mut falco_plugin::base::wrappers::PluginWrapper, std::panic::AssertUnwindSafe<{closure@falco_plugin::base::wrappers::plugin_init::{closure#0}}>>::{closure#0}}, std::result::Result<*mut falco_plugin::base::wrappers::PluginWrapper, falco_plugin::anyhow::Error>> at .../lib/rustlib/src/rust/library/std/src/panic.rs:359:14: 359:40 19: falco_plugin::error::panic::catch_panic::<*mut falco_plugin::base::wrappers::PluginWrapper, std::panic::AssertUnwindSafe<{closure@falco_plugin::base::wrappers::plugin_init::{closure#0}}>> at falco_plugin/src/error/panic.rs:10:5: 10:42 20: falco_plugin::base::wrappers::plugin_init:: at falco_plugin/src/base/wrappers.rs:77:15: 110:7 21: falco_plugin_tests::plugin_collection::extract::extra_fields::EXTRACT_EXTRA_FIELDS_API::{constant#0}::plugin_init at falco_plugin/src/base/wrappers.rs:276:13: 276:48 22: falco_plugin_runner::plugin::Plugin::init at falco_plugin_runner/src/plugin/mod.rs:220:31: 220:52 23: falco_plugin_runner::plugin::Plugin::new at falco_plugin_runner/src/plugin/mod.rs:128:9: 128:26 24: falco_plugin_runner::PluginRunner::register_plugin at falco_plugin_runner/src/lib.rs:50:22: 50:74 25: ::register_plugin at falco_plugin_tests/src/native.rs:46:9: 46:44 26: test_parse_table_api:: at falco_plugin_tests/tests/parse_table_api.rs:25:26: 26:57 27: native::test_parse_table_api at falco_plugin_tests/src/lib.rs:35:17: 35:57 28: native::test_parse_table_api::{closure#0} at falco_plugin_tests/src/lib.rs:34:23: 34:23 Signed-off-by: Grzegorz Nosek --- falco_plugin/src/tables/import/table/raw.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/falco_plugin/src/tables/import/table/raw.rs b/falco_plugin/src/tables/import/table/raw.rs index bf8b284a..d38905e2 100644 --- a/falco_plugin/src/tables/import/table/raw.rs +++ b/falco_plugin/src/tables/import/table/raw.rs @@ -135,7 +135,7 @@ impl RawTable { )?; let raw_field = unsafe { field - .as_mut() + .as_ref() .ok_or_else(|| anyhow::anyhow!("Failed to add table field {:?}", name)) .with_last_error(&tables_input.last_error)?; field From 07cb3c0a500035b77e29f22ddc42afab6fb188f2 Mon Sep 17 00:00:00 2001 From: Grzegorz Nosek Date: Tue, 2 Jun 2026 22:57:37 +0200 Subject: [PATCH 16/17] fix(plugin): avoid creating &mut from FFI tabledata ptr test native::test_parse_table_nested ... error: Undefined Behavior: not granting access to tag <315709> because that would remove [SharedReadOnly for <335407>] which is strongly protected --> .../lib/rustlib/src/rust/library/core/src/ptr/mut_ptr.rs:586:57 | 586 | if self.is_null() { None } else { unsafe { Some(&mut *self) } } | ^^^^^^^^^^ Undefined Behavior occurred here | = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information help: <315709> was created by a SharedReadWrite retag at offsets [0x0..0x58] --> falco_plugin/src/tables/export/table_box.rs:56:18 help: <335407> is this argument --> falco_plugin/src/tables/export/table.rs:151:30 = note: this is on thread `native::test_pa` = note: stack backtrace: 0: std::ptr::mut_ptr::>::as_mut::<'_> at .../lib/rustlib/src/rust/library/core/src/ptr/mut_ptr.rs:586:57: 586:67 1: falco_plugin::tables::export::wrappers::write_entry_field:: at falco_plugin/src/tables/export/wrappers.rs:275:27: 275:67 2: falco_plugin_runner::tables::write_entry_field at falco_plugin_runner/src/tables.rs:169:14: 169:59 3: as falco_plugin::tables::vtable::writer::private::TableWriterImpl>::write_entry_field at falco_plugin/src/tables/vtable/writer.rs:210:16: 215:14 4: falco_plugin::tables::import::entry::raw::RawEntry::write_field::> at falco_plugin/src/tables/import/entry/raw.rs:41:18: 41:90 5: falco_plugin::tables::import::Entry::>::write_field::> at falco_plugin/src/tables/import/entry/mod.rs:71:13: 72:72 6: > as falco_plugin::tables::import::traits::EntryWrite<&falco_plugin::tables::import::Field>>, falco_plugin::tables::import::Bool>>::write_field::> at falco_plugin/src/tables/import/entry/mod.rs:86:9: 86:53 7: falco_plugin_tests::plugin_collection::tables::remaining_import_extra_fields::_:: for falco_plugin::tables::import::Entry>>::set_is_final::> at falco_plugin/src/tables/import/macros.rs:132:21: 132:70 8: ::parse_event::{closure#1} at falco_plugin_tests/src/plugin_collection/parse/nested_table_extra_fields.rs:71:13: 71:46 9: falco_plugin::tables::import::Table::>, std::sync::Arc>::iter_entries_mut::<{closure@::parse_event::{closure#1}}, falco_plugin::tables::LazyTableReader<'_>>::{closure#0} at falco_plugin/src/tables/import/table/mod.rs:271:13: 271:29 10: falco_plugin::tables::import::RawTable::iter_entries_mut::<{closure@falco_plugin::tables::import::Table>, std::sync::Arc>::iter_entries_mut<{closure@::parse_event::{closure#1}}, falco_plugin::tables::LazyTableReader<'_>>::{closure#0}}, falco_plugin::tables::LazyTableReader<'_>>::{closure#0} at falco_plugin/src/tables/import/table/raw.rs:293:17: 293:32 11: falco_plugin::tables::import::table::raw::iter_inner::iter_wrapper::<{closure@falco_plugin::tables::import::RawTable::iter_entries_mut<{closure@falco_plugin::tables::import::Table>, std::sync::Arc>::iter_entries_mut<{closure@::parse_event::{closure#1}}, falco_plugin::tables::LazyTableReader<'_>>::{closure#0}}, falco_plugin::tables::LazyTableReader<'_>>::{closure#0}}> at falco_plugin/src/tables/import/table/raw.rs:394:23: 394:37 12: falco_plugin::tables::export::wrappers::iterate_entries::::{closure#0} at falco_plugin/src/tables/export/wrappers.rs:151:13: 151:31 13: falco_plugin::tables::export::table::TableData::::iterate_entries::<{closure@falco_plugin::tables::export::wrappers::iterate_entries::{closure#0}}> at falco_plugin/src/tables/export/table.rs:156:17: 156:45 14: falco_plugin::tables::export::wrappers::iterate_entries:: at falco_plugin/src/tables/export/wrappers.rs:149:9: 152:11 15: falco_plugin_runner::tables::iterate_entries at falco_plugin_runner/src/tables.rs:118:14: 118:49 16: falco_plugin::tables::import::table::raw::iter_inner::<{closure@falco_plugin::tables::import::RawTable::iter_entries_mut<{closure@falco_plugin::tables::import::Table>, std::sync::Arc>::iter_entries_mut<{closure@::parse_event::{closure#1}}, falco_plugin::tables::LazyTableReader<'_>>::{closure#0}}, falco_plugin::tables::LazyTableReader<'_>>::{closure#0}}> at falco_plugin/src/tables/import/table/raw.rs:404:9: 408:10 17: falco_plugin::tables::import::RawTable::iter_entries_mut::<{closure@falco_plugin::tables::import::Table>, std::sync::Arc>::iter_entries_mut<{closure@::parse_event::{closure#1}}, falco_plugin::tables::LazyTableReader<'_>>::{closure#0}}, falco_plugin::tables::LazyTableReader<'_>> at falco_plugin/src/tables/import/table/raw.rs:282:12: 295:10 18: falco_plugin::tables::import::Table::>, std::sync::Arc>::iter_entries_mut::<{closure@::parse_event::{closure#1}}, falco_plugin::tables::LazyTableReader<'_>> at falco_plugin/src/tables/import/table/mod.rs:269:9: 272:11 19: ::parse_event at falco_plugin_tests/src/plugin_collection/parse/nested_table_extra_fields.rs:63:9: 74:11 20: falco_plugin::parse::wrappers::plugin_parse_event::::{closure#0} at falco_plugin/src/parse/wrappers.rs:118:13: 118:67 21: <{closure@falco_plugin::parse::wrappers::plugin_parse_event::{closure#0}} as std::ops::FnOnce<()>>::call_once - shim at .../lib/rustlib/src/rust/library/core/src/ops/function.rs:250:5: 250:71 22: ::{closure#0}}> as std::ops::FnOnce<()>>::call_once at .../lib/rustlib/src/rust/library/core/src/panic/unwind_safe.rs:274:9: 274:19 23: falco_plugin::error::panic::catch_panic::<(), std::panic::AssertUnwindSafe<{closure@falco_plugin::parse::wrappers::plugin_parse_event::{closure#0}}>>::{closure#0} at falco_plugin/src/error/panic.rs:10:38: 10:41 24: std::panicking::catch_unwind::do_call::<{closure@falco_plugin::error::panic::catch_panic<(), std::panic::AssertUnwindSafe<{closure@falco_plugin::parse::wrappers::plugin_parse_event::{closure#0}}>>::{closure#0}}, std::result::Result<(), falco_plugin::anyhow::Error>> at .../lib/rustlib/src/rust/library/std/src/panicking.rs:581:40: 581:43 25: std::panicking::catch_unwind::, {closure@falco_plugin::error::panic::catch_panic<(), std::panic::AssertUnwindSafe<{closure@falco_plugin::parse::wrappers::plugin_parse_event::{closure#0}}>>::{closure#0}}> at .../lib/rustlib/src/rust/library/std/src/panicking.rs:544:19: 544:88 26: std::panic::catch_unwind::<{closure@falco_plugin::error::panic::catch_panic<(), std::panic::AssertUnwindSafe<{closure@falco_plugin::parse::wrappers::plugin_parse_event::{closure#0}}>>::{closure#0}}, std::result::Result<(), falco_plugin::anyhow::Error>> at .../lib/rustlib/src/rust/library/std/src/panic.rs:359:14: 359:40 27: falco_plugin::error::panic::catch_panic::<(), std::panic::AssertUnwindSafe<{closure@falco_plugin::parse::wrappers::plugin_parse_event::{closure#0}}>> at falco_plugin/src/error/panic.rs:10:5: 10:42 28: falco_plugin::parse::wrappers::plugin_parse_event:: at falco_plugin/src/parse/wrappers.rs:117:9: 119:12 29: falco_plugin_runner::plugin::parse::ParsePlugin::on_event at falco_plugin_runner/src/plugin/parse.rs:53:27: 53:73 30: falco_plugin_runner::plugin::Plugin::on_event at falco_plugin_runner/src/plugin/mod.rs:468:13: 468:34 31: falco_plugin_runner::CapturingPluginRunner::next_event at falco_plugin_runner/src/lib.rs:108:13: 108:36 32: ::next_event at falco_plugin_tests/src/native.rs:93:15: 93:34 33: test_parse_table_nested:: at falco_plugin_tests/tests/parse_table_nested.rs:46:17: 46:36 34: native::test_parse_table_nested at falco_plugin_tests/src/lib.rs:35:17: 35:57 35: native::test_parse_table_nested::{closure#0} at falco_plugin_tests/src/lib.rs:34:23: 34:23 Signed-off-by: Grzegorz Nosek --- falco_plugin/src/tables/export/table.rs | 32 +++++++++++++-------- falco_plugin/src/tables/export/table_box.rs | 4 +-- falco_plugin/src/tables/export/wrappers.rs | 12 ++++---- 3 files changed, 28 insertions(+), 20 deletions(-) diff --git a/falco_plugin/src/tables/export/table.rs b/falco_plugin/src/tables/export/table.rs index b94bb315..0929f3c0 100644 --- a/falco_plugin/src/tables/export/table.rs +++ b/falco_plugin/src/tables/export/table.rs @@ -14,6 +14,7 @@ use crate::tables::{FieldTypeId, Key}; use crate::FailureReason; use falco_plugin_api::{ss_plugin_state_data, ss_plugin_table_fieldinfo}; use std::borrow::Borrow; +use std::cell::UnsafeCell; use std::collections::BTreeMap; use std::ffi::CStr; use std::fmt::{Debug, Formatter}; @@ -34,7 +35,7 @@ where E::Metadata: TableMetadata, { name: &'static CStr, - field_descriptors: Vec, + field_descriptors: UnsafeCell>, metadata: RefShared>, data: RefShared>>>, @@ -78,7 +79,7 @@ where ) -> Result { let table = Self { name: tag, - field_descriptors: vec![], + field_descriptors: UnsafeCell::new(vec![]), metadata: metadata.clone(), data: new_shared_ref(BTreeMap::new()), @@ -92,7 +93,7 @@ where pub fn new(name: &'static CStr) -> Result { Ok(Self { name, - field_descriptors: vec![], + field_descriptors: UnsafeCell::new(vec![]), metadata: new_shared_ref(ExtensibleEntryMetadata::new()?), data: new_shared_ref(BTreeMap::new()), @@ -161,12 +162,12 @@ where } /// Remove all entries from the table. - pub fn clear(&mut self) { + pub fn clear(&self) { self.data.write().clear() } /// Erase an entry by key. - pub fn erase(&mut self, key: &Q) -> Option> + pub fn erase(&self, key: &Q) -> Option> where K: Borrow, Q: Ord + ?Sized, @@ -215,7 +216,7 @@ where } /// Attach an entry to a table key - pub fn insert(&mut self, key: &Q, entry: TableEntryType) -> Option> + pub fn insert(&self, key: &Q, entry: TableEntryType) -> Option> where K: Borrow, Q: Ord + ToOwned + ?Sized, @@ -253,10 +254,15 @@ where } /// Return a list of fields as a slice of raw FFI objects - pub fn list_fields(&mut self) -> &[ss_plugin_table_fieldinfo] { - self.field_descriptors.clear(); - self.field_descriptors.extend(self.metadata.list_fields()); - self.field_descriptors.as_slice() + pub fn list_fields(&self) -> &[ss_plugin_table_fieldinfo] { + // SAFETY: `list_fields` is never called re-entrantly; the slice is valid + // for the lifetime of `&self` because `field_descriptors` is owned by self. + unsafe { + let v = &mut *self.field_descriptors.get(); + v.clear(); + v.extend(self.metadata.list_fields()); + std::slice::from_raw_parts(v.as_ptr(), v.len()) + } } /// Return a field descriptor for a particular field @@ -270,12 +276,14 @@ where /// Add a new field to the table pub fn add_field( - &mut self, + &self, name: &CStr, field_type: FieldTypeId, read_only: bool, ) -> Option { - self.metadata.add_field(name, field_type, read_only) + self.metadata + .write_arc() + .add_field(name, field_type, read_only) } } diff --git a/falco_plugin/src/tables/export/table_box.rs b/falco_plugin/src/tables/export/table_box.rs index 0a30e277..28f61b6f 100644 --- a/falco_plugin/src/tables/export/table_box.rs +++ b/falco_plugin/src/tables/export/table_box.rs @@ -202,7 +202,7 @@ where } /// Return a list of fields as a slice of raw FFI objects - pub fn list_fields(&mut self) -> &[ss_plugin_table_fieldinfo] { + pub fn list_fields(&self) -> &[ss_plugin_table_fieldinfo] { (**self).list_fields() } @@ -215,7 +215,7 @@ where /// Add a new field to the table pub fn add_field( - &mut self, + &self, name: &CStr, field_type: FieldTypeId, read_only: bool, diff --git a/falco_plugin/src/tables/export/wrappers.rs b/falco_plugin/src/tables/export/wrappers.rs index bd5bed47..a31a0f52 100644 --- a/falco_plugin/src/tables/export/wrappers.rs +++ b/falco_plugin/src/tables/export/wrappers.rs @@ -165,7 +165,7 @@ where E::Metadata: TableMetadata, { unsafe { - let Some(table) = (table as *mut TableData).as_mut() else { + let Some(table) = (table as *const TableData).as_ref() else { return ss_plugin_rc_SS_PLUGIN_FAILURE; }; table.clear(); @@ -186,7 +186,7 @@ where E::Metadata: TableMetadata, { unsafe { - let Some(table) = (table as *mut TableData).as_mut() else { + let Some(table) = (table as *const TableData).as_ref() else { return ss_plugin_rc_SS_PLUGIN_FAILURE; }; let Some(key) = key.as_ref() else { @@ -241,7 +241,7 @@ where } unsafe { - let Some(table) = (table as *mut TableData).as_mut() else { + let Some(table) = (table as *const TableData).as_ref() else { return std::ptr::null_mut(); }; let Some(key) = key.as_ref() else { @@ -272,7 +272,7 @@ where E::Metadata: TableMetadata, { unsafe { - let Some(table) = (table as *mut TableData).as_mut() else { + let Some(table) = (table as *const TableData).as_ref() else { return ss_plugin_rc_SS_PLUGIN_FAILURE; }; let Some(entry) = (entry as *mut TableEntryType).as_mut() else { @@ -301,7 +301,7 @@ where E::Metadata: TableMetadata, { unsafe { - let Some(table) = (table as *mut TableData).as_mut() else { + let Some(table) = (table as *const TableData).as_ref() else { return std::ptr::null_mut(); }; let fields = table.list_fields(); @@ -356,7 +356,7 @@ where E::Metadata: TableMetadata, { unsafe { - let Some(table) = (table as *mut TableData).as_mut() else { + let Some(table) = (table as *const TableData).as_ref() else { return std::ptr::null_mut(); }; let Some(data_type) = FieldTypeId::from_usize(data_type as usize) else { From fbb157320d92961e3fde6164567e41c11db3045e Mon Sep 17 00:00:00 2001 From: Grzegorz Nosek Date: Wed, 3 Jun 2026 07:28:48 +0200 Subject: [PATCH 17/17] new(ci): run miri tests in CI Signed-off-by: Grzegorz Nosek --- .github/workflows/pr.yaml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/.github/workflows/pr.yaml b/.github/workflows/pr.yaml index 0039aa12..e6df2c65 100644 --- a/.github/workflows/pr.yaml +++ b/.github/workflows/pr.yaml @@ -61,3 +61,18 @@ jobs: run: cargo clippy --all-features --all-targets - name: Render docs run: cargo doc --all-features --no-deps + + miri: + name: Miri test + runs-on: ubuntu-latest + env: + RUSTFLAGS: -D warnings + steps: + - name: Checkout repository + uses: actions/checkout@v4 + - name: Setup Rust + uses: dtolnay/rust-toolchain@nightly + - name: Install Miri + run: rustup component add miri + - name: Build and test + run: cargo +nightly miri test --no-default-features --features=miri