From d87c0123f708fcab7714d63d1cb4b5c73597aa30 Mon Sep 17 00:00:00 2001 From: Geoffrey Oxberry Date: Sat, 23 May 2026 00:41:47 +0000 Subject: [PATCH] refactor(lading): annotate intentional-panic network generators Attach fn-level #[expect(clippy::expect_used, reason = "...")] to the construction and spin sites in the network protocol generators, plus Cat-3 mechanical conversions for procfs path literals. - generator/http.rs::{new, spin} (OnceCell + Semaphore) - generator/grpc.rs::{new, connect} (target_uri parsing, FIXME on new) - generator/splunk_hec.rs::{new, spin, send_hec_request} (FIXME on parse) - generator/tcp.rs::new (FIXME: addr parsing) - generator/procfs.rs (Cat-3 /proc paths) - generator/kubernetes/resource.rs::get_name (set_name contract) - generator/splunk_hec/acknowledgements.rs::send (ack-id contract) Co-Authored-By: Claude Opus 4.7 --- lading/src/generator/grpc.rs | 8 ++++++++ lading/src/generator/http.rs | 8 ++++++++ lading/src/generator/kubernetes/resource.rs | 4 ++++ lading/src/generator/procfs.rs | 9 ++++++--- lading/src/generator/splunk_hec.rs | 12 ++++++++++++ lading/src/generator/splunk_hec/acknowledgements.rs | 4 ++++ lading/src/generator/tcp.rs | 10 +++++++--- 7 files changed, 49 insertions(+), 6 deletions(-) diff --git a/lading/src/generator/grpc.rs b/lading/src/generator/grpc.rs index 822e9c06b..a504c6cc5 100644 --- a/lading/src/generator/grpc.rs +++ b/lading/src/generator/grpc.rs @@ -192,6 +192,10 @@ impl Grpc { /// Function will panic if user has passed zero values for any byte /// values. Sharp corners. #[expect(clippy::cast_possible_truncation)] + #[expect( + clippy::expect_used, + reason = "FIXME: config.target_uri is user-supplied; parsing and the required path_and_query check should surface as Error variants instead of panicking. Tracked for follow-up." + )] pub fn new( general: General, config: Config, @@ -241,6 +245,10 @@ impl Grpc { } /// Establish a connection with the configured RPC server + #[expect( + clippy::expect_used, + reason = "Uri::from_parts is reconstructing self.target_uri's parts after replacing path_and_query with an empty static; the parts already came from a valid Uri" + )] async fn connect(&self) -> Result, Error> { let mut parts = self.target_uri.clone().into_parts(); parts.path_and_query = Some(PathAndQuery::from_static("")); diff --git a/lading/src/generator/http.rs b/lading/src/generator/http.rs index 423849300..90d91ec56 100644 --- a/lading/src/generator/http.rs +++ b/lading/src/generator/http.rs @@ -141,6 +141,10 @@ impl Http { /// Function will panic if user has passed non-zero values for any byte /// values. Sharp corners. #[expect(clippy::cast_possible_truncation)] + #[expect( + clippy::expect_used, + reason = "OnceCell::set is called exactly once at HTTP generator startup" + )] pub fn new( general: General, config: Config, @@ -212,6 +216,10 @@ impl Http { /// /// Function will panic if it is unable to create HTTP requests for the /// target. + #[expect( + clippy::expect_used, + reason = "OnceCell::get on a value set during `new`, and Semaphore::acquire panics only after `Semaphore::close`; we never close the throttle semaphore" + )] pub async fn spin(mut self) -> Result<(), Error> { let client = Client::builder(TokioExecutor::new()) .pool_max_idle_per_host(self.concurrency.connection_count() as usize) diff --git a/lading/src/generator/kubernetes/resource.rs b/lading/src/generator/kubernetes/resource.rs index 8810e197e..a30929cb5 100644 --- a/lading/src/generator/kubernetes/resource.rs +++ b/lading/src/generator/kubernetes/resource.rs @@ -76,6 +76,10 @@ impl Resource { } } + #[expect( + clippy::expect_used, + reason = "callers must set the name via `set_name` before calling `get_name`; this is a documented internal API contract" + )] pub(super) fn get_name(&self) -> &str { self.meta() .name diff --git a/lading/src/generator/procfs.rs b/lading/src/generator/procfs.rs index baac10350..d745e62b0 100644 --- a/lading/src/generator/procfs.rs +++ b/lading/src/generator/procfs.rs @@ -33,8 +33,10 @@ pub enum Error { fn default_copy_from_host() -> Vec { vec![ - PathBuf::from_str("/proc/uptime").expect("failed to convert /proc/uptime to PathBuf"), - PathBuf::from_str("/proc/stat").expect("failed to convert /proc/stat to PathBuf"), + PathBuf::from_str("/proc/uptime") + .unwrap_or_else(|_| unreachable!("\"/proc/uptime\" is a valid PathBuf")), + PathBuf::from_str("/proc/stat") + .unwrap_or_else(|_| unreachable!("\"/proc/stat\" is a valid PathBuf")), ] } @@ -141,7 +143,8 @@ impl ProcFs { } // SAFETY: By construction this pathbuf cannot fail to be created. - let prefix = PathBuf::from_str("/proc").expect("failed to convert /proc to PathBuf"); + let prefix = PathBuf::from_str("/proc") + .unwrap_or_else(|_| unreachable!("\"/proc\" is a valid PathBuf")); for path in &config.copy_from_host { let base = path.strip_prefix(&prefix)?; diff --git a/lading/src/generator/splunk_hec.rs b/lading/src/generator/splunk_hec.rs index 685bce16b..7a4035cf8 100644 --- a/lading/src/generator/splunk_hec.rs +++ b/lading/src/generator/splunk_hec.rs @@ -192,6 +192,10 @@ impl SplunkHec { /// Function will panic if user has passed non-zero values for any byte /// values. Sharp corners. #[expect(clippy::cast_possible_truncation)] + #[expect( + clippy::expect_used, + reason = "OnceCell::set is called exactly once at Splunk HEC generator startup" + )] pub fn new( general: General, config: Config, @@ -267,6 +271,10 @@ impl SplunkHec { /// /// Function will panic if it is unable to create HTTP requests for the /// target. + #[expect( + clippy::expect_used, + reason = "channel iterator is constructed from a non-empty Vec; OnceCell::get and Semaphore::acquire follow the same contract as the HTTP generator" + )] pub async fn spin(mut self) -> Result<(), Error> { let client = Client::builder(TokioExecutor::new()) .pool_max_idle_per_host(self.parallel_connections as usize) @@ -339,6 +347,10 @@ impl SplunkHec { } #[expect(clippy::too_many_arguments)] +#[expect( + clippy::expect_used, + reason = "FIXME: server response parsing on Splunk HEC ack-id should surface as an Error variant rather than panic on malformed remote responses. Tracked for follow-up." +)] async fn send_hec_request( permit: SemaphorePermit<'_>, block_length: usize, diff --git a/lading/src/generator/splunk_hec/acknowledgements.rs b/lading/src/generator/splunk_hec/acknowledgements.rs index 59e3590f7..4c66aaa3f 100644 --- a/lading/src/generator/splunk_hec/acknowledgements.rs +++ b/lading/src/generator/splunk_hec/acknowledgements.rs @@ -52,6 +52,10 @@ impl Channel { } } + #[expect( + clippy::expect_used, + reason = "callers route Some(ack_id) producers into Channel::Ack; the None branch is unreachable per the worker/ack-service contract" + )] pub(crate) async fn send(&self, msg: Fut) -> Result<(), Error> where Fut: Future>, diff --git a/lading/src/generator/tcp.rs b/lading/src/generator/tcp.rs index 73027310b..e3e2a3146 100644 --- a/lading/src/generator/tcp.rs +++ b/lading/src/generator/tcp.rs @@ -131,6 +131,10 @@ impl Tcp { /// Function will panic if user has passed zero values for any byte /// values. Sharp corners. #[expect(clippy::cast_possible_truncation)] + #[expect( + clippy::expect_used, + reason = "FIXME: config.addr is user-supplied; socket address parsing failures should surface as Error variants instead of panicking. Tracked for follow-up." + )] pub fn new( general: General, config: &Config, @@ -168,9 +172,9 @@ impl Tcp { for i in 0..worker_count { let throttle = create_throttle(config.throttle.as_ref(), config.bytes_per_second.as_ref())? - .divide( - NonZeroU32::new(worker_count.into()).expect("worker_count is always >= 1"), - )?; + .divide(NonZeroU32::new(worker_count.into()).unwrap_or_else(|| { + unreachable!("worker_count is NonZeroU16, always >= 1") + }))?; let mut worker_labels = labels.clone(); if worker_count > 1 {