From e24474b5fdf98761dff4befe76358b4aa58154cc Mon Sep 17 00:00:00 2001 From: Geoffrey Oxberry Date: Sat, 23 May 2026 00:23:44 +0000 Subject: [PATCH] refactor(lading): annotate intentional-panic file_gen sites Attach fn-level #[expect(clippy::expect_used, reason = "...")] to the logrotate generator and the logrotate_fs FUSE filesystem callbacks. The FUSE state mutex is held only briefly inside each callback so a poisoned mutex is unrecoverable; the model itself maintains the parent/file-type/ name invariants used inside `readdir`. - generator/file_gen/logrotate.rs::Child::spin (non-empty names invariant) - generator/file_gen/logrotate_fs.rs::Server::new (FIXME: fuse_mount2 spawn) - generator/file_gen/logrotate_fs.rs::{lookup,getattr,read,release,readdir,open} - generator/file_gen/logrotate_fs/model.rs::advance_time_inner - generator/file_gen/logrotate_fs/model.rs::read Co-Authored-By: Claude Opus 4.7 --- lading/src/generator/file_gen/logrotate.rs | 4 +++ lading/src/generator/file_gen/logrotate_fs.rs | 28 +++++++++++++++++++ .../generator/file_gen/logrotate_fs/model.rs | 8 ++++++ 3 files changed, 40 insertions(+) diff --git a/lading/src/generator/file_gen/logrotate.rs b/lading/src/generator/file_gen/logrotate.rs index 07a519cad..9ab46a1ca 100644 --- a/lading/src/generator/file_gen/logrotate.rs +++ b/lading/src/generator/file_gen/logrotate.rs @@ -343,6 +343,10 @@ impl Child { } } + #[expect( + clippy::expect_used, + reason = "self.names is constructed non-empty at LogrotateGenerator startup" + )] async fn spin(mut self) -> Result<(), Error> { let mut handle = self.block_cache.handle(); let buffer_capacity = self diff --git a/lading/src/generator/file_gen/logrotate_fs.rs b/lading/src/generator/file_gen/logrotate_fs.rs index c0f9ae5a0..e59aea5be 100644 --- a/lading/src/generator/file_gen/logrotate_fs.rs +++ b/lading/src/generator/file_gen/logrotate_fs.rs @@ -170,6 +170,10 @@ impl Server { /// # Panics /// /// Function will panic if the filesystem cannot be started. + #[expect( + clippy::expect_used, + reason = "FIXME: fuse_mount2 spawn failure should propagate as an Error variant rather than panic; tracked for follow-up" + )] pub fn new( _: generator::General, config: Config, @@ -340,6 +344,10 @@ impl Filesystem for LogrotateFS { } #[tracing::instrument(skip(self, reply))] + #[expect( + clippy::expect_used, + reason = "the inner state mutex is held only briefly within fuse callbacks; poisoning indicates a panic during a prior callback and is unrecoverable" + )] fn lookup(&mut self, _: &Request, parent: u64, name: &OsStr, reply: ReplyEntry) { let tick = self.get_current_tick(); let mut state = self.state.lock().expect("lock poisoned"); @@ -362,6 +370,10 @@ impl Filesystem for LogrotateFS { } #[tracing::instrument(skip(self, reply))] + #[expect( + clippy::expect_used, + reason = "the inner state mutex is held only briefly within fuse callbacks; poisoning indicates a panic during a prior callback and is unrecoverable" + )] fn getattr(&mut self, _: &Request, ino: u64, _: Option, reply: ReplyAttr) { let tick = self.get_current_tick(); let mut state = self.state.lock().expect("lock poisoned"); @@ -377,6 +389,10 @@ impl Filesystem for LogrotateFS { } #[tracing::instrument(skip(self, reply))] + #[expect( + clippy::expect_used, + reason = "the inner state mutex is held only briefly within fuse callbacks; poisoning indicates a panic during a prior callback and is unrecoverable" + )] fn read( &mut self, _: &Request, @@ -416,6 +432,10 @@ impl Filesystem for LogrotateFS { } #[tracing::instrument(skip(self, reply))] + #[expect( + clippy::expect_used, + reason = "the inner state mutex is held only briefly within fuse callbacks; poisoning indicates a panic during a prior callback and is unrecoverable" + )] fn release( &mut self, _: &Request, @@ -448,6 +468,10 @@ impl Filesystem for LogrotateFS { } #[tracing::instrument(skip(self, reply))] + #[expect( + clippy::expect_used, + reason = "the inner state mutex is held only briefly within fuse callbacks; mutex poisoning, parent-inode lookup, file-type lookup, and name lookup are all internal invariants maintained by the fs model" + )] fn readdir(&mut self, _: &Request, ino: u64, _: u64, offset: i64, mut reply: ReplyDirectory) { let tick = self.get_current_tick(); let mut state = self.state.lock().expect("lock poisoned"); @@ -510,6 +534,10 @@ impl Filesystem for LogrotateFS { } #[tracing::instrument(skip(self, _req, reply))] + #[expect( + clippy::expect_used, + reason = "the inner state mutex is held only briefly within fuse callbacks; poisoning indicates a panic during a prior callback and is unrecoverable" + )] fn open(&mut self, _req: &Request, ino: u64, flags: i32, reply: fuser::ReplyOpen) { let tick = self.get_current_tick(); let mut state = self.state.lock().expect("lock poisoned"); diff --git a/lading/src/generator/file_gen/logrotate_fs/model.rs b/lading/src/generator/file_gen/logrotate_fs/model.rs index a9f5b4230..5c54ef1ca 100644 --- a/lading/src/generator/file_gen/logrotate_fs/model.rs +++ b/lading/src/generator/file_gen/logrotate_fs/model.rs @@ -653,6 +653,10 @@ impl State { #[inline] #[expect(clippy::too_many_lines)] + #[expect( + clippy::expect_used, + reason = "node/inode lookups operate on entries the model itself just inserted; invariants enforced inside `State`" + )] fn advance_time_inner(&mut self, now: Tick) { assert!(now >= self.now); @@ -972,6 +976,10 @@ impl State { /// be advanced -- and a slice up to `size` bytes will be returned or `None` /// if no bytes are available to be read. #[tracing::instrument(skip(self))] + #[expect( + clippy::expect_used, + reason = "the bytes-written value is bounded by usize and cannot exceed a machine word here" + )] pub(crate) fn read( &mut self, file_handle: FileHandle,