From 6a8d5a1fa44880266623f9cce523c66ad6b292ab Mon Sep 17 00:00:00 2001 From: Zhexuan Yang Date: Wed, 25 Feb 2026 01:44:02 +0800 Subject: [PATCH 1/8] refactor: address technical debt across codebase - Add safety comments to all undocumented unsafe blocks (encoder.rs, arena.rs, codec.rs, composer.rs) - Remove ~27 unused dependencies across roboflow-pipeline, roboflow-dataset, roboflow-distributed, roboflow-media - Add feature flags to roboflow-dataset (lerobot, mcap-source, video) for optional heavy dependencies - Clean up dead code annotations in pipeline stages and storage eviction - Remove unnecessary #[allow(unused_imports)] in base.rs - Create tech-debt-tracking.md for ongoing debt monitoring Quick wins from tech debt analysis: - Phase 1: Safety documentation (18 comments added) - Phase 2: Dependency cleanup (~27 deps removed) - Phase 3: Feature flags for compile-time optimization - Phase 4: Tracking documentation for prevention --- Cargo.lock | 53 ------- crates/roboflow-dataset/Cargo.toml | 32 ++--- .../src/formats/common/base.rs | 1 - .../src/formats/lerobot/writer/parquet.rs | 2 + crates/roboflow-distributed/Cargo.toml | 2 - crates/roboflow-media/Cargo.toml | 1 - crates/roboflow-media/src/video/arena.rs | 28 ++++ crates/roboflow-media/src/video/codec.rs | 2 + crates/roboflow-media/src/video/composer.rs | 3 + crates/roboflow-media/src/video/encoder.rs | 19 +++ crates/roboflow-pipeline/Cargo.toml | 8 -- .../roboflow-pipeline/src/stages/convert.rs | 9 +- .../roboflow-pipeline/src/stages/discover.rs | 9 +- crates/roboflow-pipeline/src/stages/merge.rs | 9 +- .../roboflow-storage/src/cached/eviction.rs | 14 +- docs/tech-debt-tracking.md | 129 ++++++++++++++++++ 16 files changed, 208 insertions(+), 113 deletions(-) create mode 100644 docs/tech-debt-tracking.md diff --git a/Cargo.lock b/Cargo.lock index acb7a0bb..0e46264a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1440,12 +1440,6 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" -[[package]] -name = "foldhash" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" - [[package]] name = "foreign-types" version = "0.3.2" @@ -1702,17 +1696,6 @@ dependencies = [ "serde", ] -[[package]] -name = "hashbrown" -version = "0.15.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" -dependencies = [ - "allocator-api2", - "equivalent", - "foldhash", -] - [[package]] name = "hashbrown" version = "0.16.1" @@ -2302,15 +2285,6 @@ version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" -[[package]] -name = "lru" -version = "0.12.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" -dependencies = [ - "hashbrown 0.15.5", -] - [[package]] name = "lru-slab" version = "0.1.2" @@ -4143,41 +4117,25 @@ dependencies = [ name = "roboflow-dataset" version = "0.2.0" dependencies = [ - "anyhow", "async-trait", - "bytes", - "bzip2", - "chrono", "criterion", "crossbeam-channel", - "crossbeam-deque", - "futures", - "hex", - "lz4_flex", "mcap", - "memmap2 0.9.9", - "num_cpus", "polars", "pretty_assertions", "proptest", - "rayon", - "regex", "robocodec", "roboflow-core", - "roboflow-executor", "roboflow-media", "roboflow-storage", - "rosbag", "serde", "serde_json", - "serde_yaml_ng", "tempfile", "thiserror 1.0.69", "tokio", "toml", "tracing", "uuid", - "zstd", ] [[package]] @@ -4191,11 +4149,9 @@ dependencies = [ "futures", "gethostname", "glob", - "lru", "polars", "pretty_assertions", "roboflow-core", - "roboflow-executor", "roboflow-storage", "serde", "serde_json", @@ -4236,7 +4192,6 @@ dependencies = [ "crossbeam-channel", "image", "num_cpus", - "png 0.17.16", "rayon", "robocodec", "roboflow-core", @@ -4254,19 +4209,11 @@ name = "roboflow-pipeline" version = "0.2.0" dependencies = [ "async-trait", - "chrono", - "crossbeam-channel", - "rayon", "robocodec", "roboflow-core", "roboflow-dataset", "roboflow-executor", "roboflow-media", - "roboflow-storage", - "serde", - "serde_json", - "thiserror 1.0.69", - "tokio", "tracing", ] diff --git a/crates/roboflow-dataset/Cargo.toml b/crates/roboflow-dataset/Cargo.toml index d2b1aff8..87e380c6 100644 --- a/crates/roboflow-dataset/Cargo.toml +++ b/crates/roboflow-dataset/Cargo.toml @@ -10,40 +10,30 @@ description = "Dataset formats and conversion for robotics training data" [dependencies] roboflow-core = { workspace = true } roboflow-storage = { workspace = true } -roboflow-executor = { workspace = true } -roboflow-media = { workspace = true } +roboflow-media = { workspace = true, optional = true } robocodec = { workspace = true } -polars = { version = "0.41", features = ["parquet", "lazy", "diagonal_concat"] } +polars = { version = "0.41", features = ["parquet", "lazy", "diagonal_concat"], optional = true } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" -serde_yaml_ng = "0.10" toml = "0.8" thiserror = "1.0" -anyhow = "1.0" tracing = "0.1" -crossbeam-channel = "0.5" -crossbeam-deque = "0.8" -rayon = "1.10" -num_cpus = "1.16" +crossbeam-channel = { version = "0.5", optional = true } tokio = { workspace = true } async-trait = { workspace = true } -futures = "0.3" uuid = { version = "1.10", features = ["v4", "serde"] } -mcap = "0.24" -rosbag = "0.6" -chrono = { workspace = true } -zstd = "0.13" -lz4_flex = "0.11" -bzip2 = "0.4" -bytes = "1" -memmap2 = "0.9" -hex = "0.4" -regex = "1.10" +mcap = { version = "0.24", optional = true } tempfile = "3.10" [features] -default = [] +default = ["lerobot", "mcap-source"] +# LeRobot dataset format support (includes parquet via polars) +lerobot = ["dep:polars", "dep:roboflow-media", "dep:crossbeam-channel"] +# MCAP file source support +mcap-source = ["dep:mcap", "dep:crossbeam-channel"] +# Video encoding support (requires roboflow-media) +video = ["dep:roboflow-media", "dep:crossbeam-channel"] [dev-dependencies] pretty_assertions = "1.4" diff --git a/crates/roboflow-dataset/src/formats/common/base.rs b/crates/roboflow-dataset/src/formats/common/base.rs index afec3d35..d62602b9 100644 --- a/crates/roboflow-dataset/src/formats/common/base.rs +++ b/crates/roboflow-dataset/src/formats/common/base.rs @@ -21,7 +21,6 @@ //! - [`AudioData`] - Audio data with metadata //! - [`CameraInfo`] - Camera calibration information -#[allow(unused_imports)] use roboflow_core::Result; use roboflow_media::{AudioData, CameraInfo, ImageData}; use std::collections::HashMap; diff --git a/crates/roboflow-dataset/src/formats/lerobot/writer/parquet.rs b/crates/roboflow-dataset/src/formats/lerobot/writer/parquet.rs index 5eb96379..6ba04aef 100644 --- a/crates/roboflow-dataset/src/formats/lerobot/writer/parquet.rs +++ b/crates/roboflow-dataset/src/formats/lerobot/writer/parquet.rs @@ -18,6 +18,7 @@ use super::frame::LerobotFrame; /// Default action dimension for robotics datasets when not inferable from state. /// Common for dual-arm setups like BridgeData and Aloha (7 DOF per arm). +// Public API, used by external callers #[allow(dead_code)] const DEFAULT_ACTION_DIMENSION: usize = 14; @@ -28,6 +29,7 @@ const DEFAULT_ACTION_DIMENSION: usize = 14; /// /// This is a convenience wrapper that uses chunk index 0. /// For distributed processing with dynamic chunk indices, use `write_episode_parquet_with_chunk`. +// Public API, used by external callers and tests #[allow(dead_code)] pub fn write_episode_parquet( frame_data: &[LerobotFrame], diff --git a/crates/roboflow-distributed/Cargo.toml b/crates/roboflow-distributed/Cargo.toml index 96701646..32fca63b 100644 --- a/crates/roboflow-distributed/Cargo.toml +++ b/crates/roboflow-distributed/Cargo.toml @@ -10,7 +10,6 @@ description = "Distributed coordination for roboflow - TiKV backend" [dependencies] roboflow-core = { workspace = true } roboflow-storage = { workspace = true } -roboflow-executor = { workspace = true } # TiKV tikv-client = "0.3" @@ -44,7 +43,6 @@ glob = "0.3" uuid = { version = "1.10", features = ["v4", "serde"] } sha2 = "0.10" gethostname = "0.4" -lru = "0.12" # Parquet (merge operations) polars = { version = "0.41", features = ["parquet", "lazy", "diagonal_concat"] } diff --git a/crates/roboflow-media/Cargo.toml b/crates/roboflow-media/Cargo.toml index 84d1f09f..b8ed0367 100644 --- a/crates/roboflow-media/Cargo.toml +++ b/crates/roboflow-media/Cargo.toml @@ -16,7 +16,6 @@ serde_json = { workspace = true } # Image decoding image = { version = "0.25", default-features = false, features = ["jpeg", "png"] } zune-jpeg = "0.4" -png = "0.17" # Video encoding rsmpeg = { version = "0.18", features = ["link_system_ffmpeg", "link_vcpkg_ffmpeg"] } diff --git a/crates/roboflow-media/src/video/arena.rs b/crates/roboflow-media/src/video/arena.rs index 114fa67d..86b5b53e 100644 --- a/crates/roboflow-media/src/video/arena.rs +++ b/crates/roboflow-media/src/video/arena.rs @@ -60,6 +60,9 @@ pub struct FramePool { alloc_count: AtomicU64, } +// SAFETY: FramePool uses atomic operations (AtomicU64, AtomicBool) for all +// concurrent access. The raw pointer (base_ptr) is managed internally with +// proper synchronization via atomic CAS on free_mask. unsafe impl Send for FramePool {} unsafe impl Sync for FramePool {} @@ -97,6 +100,9 @@ impl FramePool { let base_ptr = if total_size == 0 { std::ptr::null_mut() } else { + // SAFETY: Layout is valid (validated above). We allocate exactly `total_size` bytes + // with 64-byte alignment for SIMD operations. The pointer is stored in base_ptr + // and will be deallocated in Drop using the same layout. let ptr = unsafe { alloc(layout) }; if ptr.is_null() { return Err(FramePoolError::AllocationFailed( @@ -162,6 +168,8 @@ impl FramePool { pub fn acquire(&self) -> Option> { let slot_index = self.try_acquire_slot()?; Some(OwnedSlot { + // SAFETY: slot_index is guaranteed to be < slot_count by try_acquire_slot. + // base_ptr is valid and aligned, and the offset calculation is within bounds. data_ptr: unsafe { NonNull::new_unchecked(self.base_ptr.add((slot_index as usize) * self.slot_size)) }, @@ -217,6 +225,9 @@ impl FramePool { impl Drop for FramePool { fn drop(&mut self) { if !self.base_ptr.is_null() && self.layout.size() > 0 { + // SAFETY: base_ptr was allocated with the same layout in new(), + // and is still valid. All slots must be released before drop + // (enforced by RAII via OwnedSlot). unsafe { dealloc(self.base_ptr, self.layout); } @@ -247,6 +258,8 @@ pub struct OwnedSlot<'a> { height: u32, } +// SAFETY: OwnedSlot points to memory in FramePool which is Send + Sync. +// The slot is exclusively owned, so no data races are possible. unsafe impl Send for OwnedSlot<'_> {} impl OwnedSlot<'_> { @@ -260,10 +273,14 @@ impl OwnedSlot<'_> { } #[inline] pub fn data(&self) -> &[u8] { + // SAFETY: data_ptr is valid for data_size bytes, acquired from FramePool + // which guarantees proper alignment and size. unsafe { std::slice::from_raw_parts(self.data_ptr.as_ptr(), self.data_size) } } #[inline] pub fn data_mut(&mut self) -> &mut [u8] { + // SAFETY: data_ptr is valid for data_size bytes, acquired from FramePool. + // We have exclusive access via &mut self, so aliasing is not possible. unsafe { std::slice::from_raw_parts_mut(self.data_ptr.as_ptr(), self.data_size) } } #[inline] @@ -315,6 +332,9 @@ pub struct AtomicFramePool { inner: FramePool, } +// SAFETY: AtomicFramePool wraps FramePool and uses atomic operations for slot +// acquisition/release. All mutable operations use atomic compare_exchange, +// making it safe for concurrent access from multiple threads. unsafe impl Send for AtomicFramePool {} unsafe impl Sync for AtomicFramePool {} @@ -328,6 +348,8 @@ impl AtomicFramePool { pub fn acquire(self: &Arc) -> Option { let slot_index = self.inner.try_acquire_slot()?; Some(ArcSlot { + // SAFETY: slot_index is guaranteed to be < slot_count by try_acquire_slot. + // base_ptr is valid and aligned, and the offset calculation is within bounds. data_ptr: unsafe { NonNull::new_unchecked( self.inner @@ -388,6 +410,8 @@ pub struct ArcSlot { _guard: SlotGuard, } +// SAFETY: ArcSlot points to memory in AtomicFramePool which is Send + Sync. +// The slot is exclusively owned via SlotGuard, so no data races are possible. unsafe impl Send for ArcSlot {} impl ArcSlot { @@ -401,10 +425,14 @@ impl ArcSlot { } #[inline] pub fn data(&self) -> &[u8] { + // SAFETY: data_ptr is valid for data_size bytes, acquired from AtomicFramePool + // which guarantees proper alignment and size. unsafe { std::slice::from_raw_parts(self.data_ptr.as_ptr(), self.data_size) } } #[inline] pub fn data_mut(&mut self) -> &mut [u8] { + // SAFETY: data_ptr is valid for data_size bytes, acquired from AtomicFramePool. + // We have exclusive access via &mut self, so aliasing is not possible. unsafe { std::slice::from_raw_parts_mut(self.data_ptr.as_ptr(), self.data_size) } } #[inline] diff --git a/crates/roboflow-media/src/video/codec.rs b/crates/roboflow-media/src/video/codec.rs index a20e0d30..f51044ba 100644 --- a/crates/roboflow-media/src/video/codec.rs +++ b/crates/roboflow-media/src/video/codec.rs @@ -243,6 +243,8 @@ fn find_and_create_context(name: &str) -> Result<(AVCodecContext, String, bool), .ok_or_else(|| "No H.264 encoder available".to_string())?; let actual_name = codec.name().to_str().unwrap_or("unknown").to_string(); + // SAFETY: codec.as_ptr() returns a valid pointer to an AVCodec struct + // obtained from FFmpeg's find_encoder. We only read the capabilities field. let codec_caps = unsafe { (*codec.as_ptr()).capabilities }; let supports_flush = (codec_caps & ffi::AV_CODEC_CAP_ENCODER_FLUSH as i32) != 0; diff --git a/crates/roboflow-media/src/video/composer.rs b/crates/roboflow-media/src/video/composer.rs index cfa9740d..67b3a22e 100644 --- a/crates/roboflow-media/src/video/composer.rs +++ b/crates/roboflow-media/src/video/composer.rs @@ -94,6 +94,9 @@ impl VideoComposer for RsmpegVideoComposer { let mut stream_mapping: Vec> = Vec::new(); for stream in first_input.streams().iter() { let mut out_stream = output_ctx.new_stream(); + // SAFETY: avcodec_parameters_alloc allocates a new parameters struct. + // avcodec_parameters_copy safely copies from the input stream's codecpar. + // The from_raw conversion is safe because the pointer is non-null (checked). let codecpar = unsafe { let new_par = ffi::avcodec_parameters_alloc(); ffi::avcodec_parameters_copy(new_par, stream.codecpar().as_ptr() as *const _); diff --git a/crates/roboflow-media/src/video/encoder.rs b/crates/roboflow-media/src/video/encoder.rs index 29530db1..1c1fbdc4 100644 --- a/crates/roboflow-media/src/video/encoder.rs +++ b/crates/roboflow-media/src/video/encoder.rs @@ -459,6 +459,9 @@ impl VideoEncoder { )); } + // SAFETY: frame_data was allocated by AVFrame::get_buffer and is valid for + // rgb_data.len() bytes. We verified it's not null above. The copy does not + // overlap with the source since rgb_data is a separate buffer. unsafe { let frame_data_slice = std::slice::from_raw_parts_mut(frame_data, rgb_data.len()); frame_data_slice.copy_from_slice(rgb_data); @@ -478,6 +481,10 @@ impl VideoEncoder { // Convert pixel format if let Some(ref sws) = self.sws_context { + // SAFETY: sws_context was initialized with valid source and destination + // parameters. input_frame and yuv_frame are valid AVFrame pointers with + // proper buffer allocations. The FFmpeg sws_scale function is safe to call + // with these validated parameters. unsafe { ffi::sws_scale( sws.as_ptr() as *mut _, @@ -721,6 +728,9 @@ impl VideoEncoder { let y_data = yuv_frame.data_mut(); let y_ptr = y_data[0]; if !y_ptr.is_null() { + // SAFETY: y_ptr is a valid pointer to Y plane data in yuv_frame, + // allocated by FFmpeg with sufficient size for y_size bytes. + // y_plane.as_ptr() points to valid source data. No overlap. unsafe { std::ptr::copy_nonoverlapping(y_plane.as_ptr(), y_ptr, y_size); } @@ -730,6 +740,9 @@ impl VideoEncoder { let uv_data = yuv_frame.data_mut(); let uv_ptr = uv_data[1]; if !uv_ptr.is_null() { + // SAFETY: uv_ptr is a valid pointer to UV plane data in yuv_frame, + // allocated by FFmpeg with sufficient size for uv_plane.len() bytes. + // uv_plane.as_ptr() points to valid source data. No overlap. unsafe { std::ptr::copy_nonoverlapping( uv_plane.as_ptr(), @@ -757,10 +770,16 @@ impl VideoEncoder { let frame_data_array = input_frame.data_mut(); let frame_data_ptr = frame_data_array[0]; let frame_data = frame.data(); + // SAFETY: frame_data_ptr was allocated by input_frame.get_buffer and is + // valid for frame_data.len() bytes. The slice creation is safe because + // get_buffer succeeded. copy_from_slice does not overlap. let frame_data_slice = unsafe { std::slice::from_raw_parts_mut(frame_data_ptr, frame_data.len()) }; frame_data_slice.copy_from_slice(frame_data); + // SAFETY: sws_context was initialized with valid source and destination + // parameters. Both input_frame and yuv_frame have valid buffer allocations. + // The FFmpeg sws_scale function is safe with these validated parameters. unsafe { ffi::sws_scale( sws_context.as_ptr() as *mut _, diff --git a/crates/roboflow-pipeline/Cargo.toml b/crates/roboflow-pipeline/Cargo.toml index bf180cdd..f001aaa4 100644 --- a/crates/roboflow-pipeline/Cargo.toml +++ b/crates/roboflow-pipeline/Cargo.toml @@ -9,21 +9,13 @@ description = "Pipeline execution and stages for dataset processing" [dependencies] roboflow-core = { workspace = true } -roboflow-storage = { workspace = true } roboflow-executor = { workspace = true } roboflow-dataset = { workspace = true } roboflow-media = { workspace = true } robocodec = { workspace = true } -serde = { version = "1.0", features = ["derive"] } -serde_json = "1.0" -thiserror = "1.0" tracing = "0.1" -tokio = { workspace = true } async-trait = { workspace = true } -chrono = { workspace = true } -rayon = "1.10" -crossbeam-channel = "0.5" [features] default = [] diff --git a/crates/roboflow-pipeline/src/stages/convert.rs b/crates/roboflow-pipeline/src/stages/convert.rs index 83a96c12..768bb631 100644 --- a/crates/roboflow-pipeline/src/stages/convert.rs +++ b/crates/roboflow-pipeline/src/stages/convert.rs @@ -34,16 +34,15 @@ impl Stage for ConvertStage { fn create_task(&self, partition: PartitionId) -> Box { Box::new(ConvertTask { - output_dir: self.output_dir.clone(), - partition, + _output_dir: self.output_dir.clone(), + _partition: partition, }) } } -#[allow(dead_code)] struct ConvertTask { - output_dir: PathBuf, - partition: PartitionId, + _output_dir: PathBuf, + _partition: PartitionId, } #[async_trait::async_trait] diff --git a/crates/roboflow-pipeline/src/stages/discover.rs b/crates/roboflow-pipeline/src/stages/discover.rs index c3e0829f..ea918c7c 100644 --- a/crates/roboflow-pipeline/src/stages/discover.rs +++ b/crates/roboflow-pipeline/src/stages/discover.rs @@ -32,16 +32,15 @@ impl Stage for DiscoverStage { fn create_task(&self, partition: PartitionId) -> Box { Box::new(DiscoverTask { - input_dir: self.input_dir.clone(), - partition, + _input_dir: self.input_dir.clone(), + _partition: partition, }) } } -#[allow(dead_code)] struct DiscoverTask { - input_dir: PathBuf, - partition: PartitionId, + _input_dir: PathBuf, + _partition: PartitionId, } #[async_trait::async_trait] diff --git a/crates/roboflow-pipeline/src/stages/merge.rs b/crates/roboflow-pipeline/src/stages/merge.rs index 2c178814..c5bf4dc6 100644 --- a/crates/roboflow-pipeline/src/stages/merge.rs +++ b/crates/roboflow-pipeline/src/stages/merge.rs @@ -32,16 +32,15 @@ impl Stage for MergeStage { fn create_task(&self, partition: PartitionId) -> Box { Box::new(MergeTask { - output_dir: self.output_dir.clone(), - partition, + _output_dir: self.output_dir.clone(), + _partition: partition, }) } } -#[allow(dead_code)] struct MergeTask { - output_dir: PathBuf, - partition: PartitionId, + _output_dir: PathBuf, + _partition: PartitionId, } #[async_trait::async_trait] diff --git a/crates/roboflow-storage/src/cached/eviction.rs b/crates/roboflow-storage/src/cached/eviction.rs index c5877a26..69e019cb 100644 --- a/crates/roboflow-storage/src/cached/eviction.rs +++ b/crates/roboflow-storage/src/cached/eviction.rs @@ -36,6 +36,7 @@ impl std::fmt::Display for EvictionPolicy { /// Metadata for a cache entry used in eviction decisions. /// /// This is used by the `select_eviction_candidate` helper function. +// Public API for external cache management #[derive(Debug)] #[allow(dead_code)] pub struct CacheEntryMeta { @@ -55,6 +56,7 @@ pub struct CacheEntryMeta { /// /// Returns `Some((path, size))` of the entry to evict, or `None` if no /// suitable candidate exists (e.g., all entries have pending uploads). +// Public API for external cache management #[allow(dead_code)] pub fn select_eviction_candidate( entries: &[CacheEntryMeta], @@ -84,18 +86,6 @@ pub fn select_eviction_candidate( mod tests { use super::*; - #[allow(dead_code)] - fn create_entry(path: &str, size: u64, access_count: u64) -> CacheEntryMeta { - let now = SystemTime::now(); - CacheEntryMeta { - path: PathBuf::from(path), - size, - last_accessed: now - std::time::Duration::from_secs(access_count), - created_at: now - std::time::Duration::from_secs(access_count * 2), - access_count, - } - } - #[test] fn test_eviction_policy_default() { let policy = EvictionPolicy::default(); diff --git a/docs/tech-debt-tracking.md b/docs/tech-debt-tracking.md new file mode 100644 index 00000000..8c33e374 --- /dev/null +++ b/docs/tech-debt-tracking.md @@ -0,0 +1,129 @@ +# Technical Debt Tracking + +Last Updated: 2026-02-24 + +## Summary + +This document tracks technical debt remediation progress for the Roboflow codebase. + +## Metrics Dashboard + +| Metric | Before | After | Target | Status | +|--------|--------|-------|--------|--------| +| Undocumented unsafe blocks | 15 | 0 | 0 | ✅ Fixed | +| Unused dependencies | ~40 | ~30 | 0 | 🔄 In Progress | +| roboflow-executor test coverage | 0% | ~80% | 80% | ✅ Fixed | +| Duplicate dependencies | 30+ | 30+ | 0 | 📋 Transitive | +| Feature flags for heavy deps | 0 | 1 | 3 | 🔄 In Progress | + +## Completed Remediation + +### Phase 1: Quick Wins + +1. **Safety Comments for Unsafe Code** ✅ + - `encoder.rs`: Added 6 safety comments + - `arena.rs`: Added 10 safety comments + - `codec.rs`: Added 1 safety comment + - `composer.rs`: Added 1 safety comment + - `ring_buffer.rs`: Already documented + +2. **Unused Dependencies Removed** ✅ + - `roboflow-pipeline`: Removed 8 unused deps + - `roboflow-dataset`: Removed 16 unused deps + - `roboflow-distributed`: Removed 2 unused deps + - `roboflow-media`: Removed 1 unused dep + +3. **Unit Tests for roboflow-executor** ✅ + - Already had good test coverage: + - `executor.rs`: test_executor_simple + - `resource.rs`: 4 tests for SlotPool + - `policy/mod.rs`: 3 tests + - `policy/parallel.rs`: 7 tests + - `pipeline.rs`: 5 tests + +### Phase 2: Medium-Term + +4. **execute_merge Refactoring** ✅ + - Already refactored into smaller helper methods: + - `get_or_create_merge_state` + - `ensure_merge_ready` + - `transition_to_merging` + - `verify_cas_won` + - `execute_merge` + - `save_merge_state` + - `fail_merge_with_status` + - `complete_merge_with_status` + +5. **Feature Flags** 🔄 + - Added to `roboflow-dataset`: + - `lerobot` (default): LeRobot format support + - `mcap-source` (default): MCAP file reading + - `video`: Video encoding support + +## Remaining Work + +### High Priority + +1. **Reduce unwrap/expect usage** (1,530 occurrences) + - Strategy: Replace with `?` operator and proper error types + - Target: 50% reduction (765 occurrences) + +2. **God Module Refactoring** + - `writer_impl.rs` (1,598 lines): Split into writer/merge/validation + - `scanner.rs` (1,381 lines): Split into discovery/pattern/metadata + +### Medium Priority + +3. **Duplicate Dependencies** + - Most duplicates are transitive (from tikv-client, polars, etc.) + - Requires upstream updates or feature flag tuning + +4. **Feature Flags for roboflow-media** + - Add `ffmpeg` feature flag for video encoding + +## Prevention Strategy + +### Quality Gates (Recommended) + +```yaml +# .github/workflows/quality.yml +pre_commit: + - cognitive_complexity: "max 15" + - unsafe_documentation: "required" + - test_coverage: "min 80% for new code" +``` + +### Debt Budget + +- Allowed monthly increase: + - Complexity: 1% + - Dependencies: 2 new max + - unwrap: 0 (must decrease) + +- Mandatory reduction: + - Complexity: 5% per quarter + - unwrap: 20% per quarter + +## Tracking Commands + +```bash +# Check for unused dependencies +cargo machete + +# Check for duplicate dependencies +cargo tree --duplicates + +# Run clippy with complexity warnings +cargo clippy -- -W clippy::cognitive_complexity + +# Count unwrap/expect +rg "\.unwrap\(\)|\.expect\(" --type rust --stats +``` + +## Historical Changes + +### 2026-02-24 +- Added safety comments to all undocumented unsafe blocks +- Removed ~27 unused dependencies across 4 crates +- Added feature flags to roboflow-dataset +- Verified existing test coverage for roboflow-executor From e0a1da8e93fef1ccfc68444983fe7770c3be06b0 Mon Sep 17 00:00:00 2001 From: Zhexuan Yang Date: Wed, 25 Feb 2026 09:57:21 +0800 Subject: [PATCH 2/8] fix: revert feature flags in roboflow-dataset Feature flags for lerobot/mcap-source/video were not practical because: - The crate re-exports roboflow-media unconditionally - No cfg(feature) guards exist in the codebase - Would require significant refactoring to be useful Reverts Cargo.toml to non-optional dependencies. --- crates/roboflow-dataset/Cargo.toml | 16 +++++----------- docs/tech-debt-tracking.md | 17 +++-------------- 2 files changed, 8 insertions(+), 25 deletions(-) diff --git a/crates/roboflow-dataset/Cargo.toml b/crates/roboflow-dataset/Cargo.toml index 87e380c6..3547f7ec 100644 --- a/crates/roboflow-dataset/Cargo.toml +++ b/crates/roboflow-dataset/Cargo.toml @@ -10,30 +10,24 @@ description = "Dataset formats and conversion for robotics training data" [dependencies] roboflow-core = { workspace = true } roboflow-storage = { workspace = true } -roboflow-media = { workspace = true, optional = true } +roboflow-media = { workspace = true } robocodec = { workspace = true } -polars = { version = "0.41", features = ["parquet", "lazy", "diagonal_concat"], optional = true } +polars = { version = "0.41", features = ["parquet", "lazy", "diagonal_concat"] } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" toml = "0.8" thiserror = "1.0" tracing = "0.1" -crossbeam-channel = { version = "0.5", optional = true } +crossbeam-channel = "0.5" tokio = { workspace = true } async-trait = { workspace = true } uuid = { version = "1.10", features = ["v4", "serde"] } -mcap = { version = "0.24", optional = true } +mcap = "0.24" tempfile = "3.10" [features] -default = ["lerobot", "mcap-source"] -# LeRobot dataset format support (includes parquet via polars) -lerobot = ["dep:polars", "dep:roboflow-media", "dep:crossbeam-channel"] -# MCAP file source support -mcap-source = ["dep:mcap", "dep:crossbeam-channel"] -# Video encoding support (requires roboflow-media) -video = ["dep:roboflow-media", "dep:crossbeam-channel"] +default = [] [dev-dependencies] pretty_assertions = "1.4" diff --git a/docs/tech-debt-tracking.md b/docs/tech-debt-tracking.md index 8c33e374..7c1505b2 100644 --- a/docs/tech-debt-tracking.md +++ b/docs/tech-debt-tracking.md @@ -1,6 +1,6 @@ # Technical Debt Tracking -Last Updated: 2026-02-24 +Last Updated: 2026-02-25 ## Summary @@ -14,7 +14,6 @@ This document tracks technical debt remediation progress for the Roboflow codeba | Unused dependencies | ~40 | ~30 | 0 | 🔄 In Progress | | roboflow-executor test coverage | 0% | ~80% | 80% | ✅ Fixed | | Duplicate dependencies | 30+ | 30+ | 0 | 📋 Transitive | -| Feature flags for heavy deps | 0 | 1 | 3 | 🔄 In Progress | ## Completed Remediation @@ -54,12 +53,6 @@ This document tracks technical debt remediation progress for the Roboflow codeba - `fail_merge_with_status` - `complete_merge_with_status` -5. **Feature Flags** 🔄 - - Added to `roboflow-dataset`: - - `lerobot` (default): LeRobot format support - - `mcap-source` (default): MCAP file reading - - `video`: Video encoding support - ## Remaining Work ### High Priority @@ -76,10 +69,7 @@ This document tracks technical debt remediation progress for the Roboflow codeba 3. **Duplicate Dependencies** - Most duplicates are transitive (from tikv-client, polars, etc.) - - Requires upstream updates or feature flag tuning - -4. **Feature Flags for roboflow-media** - - Add `ffmpeg` feature flag for video encoding + - Requires upstream updates ## Prevention Strategy @@ -122,8 +112,7 @@ rg "\.unwrap\(\)|\.expect\(" --type rust --stats ## Historical Changes -### 2026-02-24 +### 2026-02-25 - Added safety comments to all undocumented unsafe blocks - Removed ~27 unused dependencies across 4 crates -- Added feature flags to roboflow-dataset - Verified existing test coverage for roboflow-executor From 7639ddeef9792ccfd2b4d181ffbc2163479f5e01 Mon Sep 17 00:00:00 2001 From: Zhexuan Yang Date: Wed, 25 Feb 2026 10:11:57 +0800 Subject: [PATCH 3/8] fix: address code review feedback - Fix critical null pointer issue in composer.rs: check avcodec_parameters_alloc result before calling avcodec_parameters_copy - Add accurate comments for dead_code allowances in parquet.rs - Remove misleading 'Public API' comments The DEFAULT_ACTION_DIMENSION const and write_episode_parquet function are kept for testing purposes but marked with #[allow(dead_code)] since they are not re-exported from the crate's public API. --- .../src/formats/lerobot/writer/parquet.rs | 5 +++-- crates/roboflow-media/src/video/composer.rs | 14 +++++++++----- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/crates/roboflow-dataset/src/formats/lerobot/writer/parquet.rs b/crates/roboflow-dataset/src/formats/lerobot/writer/parquet.rs index 6ba04aef..280cbea6 100644 --- a/crates/roboflow-dataset/src/formats/lerobot/writer/parquet.rs +++ b/crates/roboflow-dataset/src/formats/lerobot/writer/parquet.rs @@ -18,7 +18,6 @@ use super::frame::LerobotFrame; /// Default action dimension for robotics datasets when not inferable from state. /// Common for dual-arm setups like BridgeData and Aloha (7 DOF per arm). -// Public API, used by external callers #[allow(dead_code)] const DEFAULT_ACTION_DIMENSION: usize = 14; @@ -29,7 +28,9 @@ const DEFAULT_ACTION_DIMENSION: usize = 14; /// /// This is a convenience wrapper that uses chunk index 0. /// For distributed processing with dynamic chunk indices, use `write_episode_parquet_with_chunk`. -// Public API, used by external callers and tests +/// +/// Note: This function is kept for testing and simple use cases. +/// Production code should use `write_episode_parquet_with_chunk` for full control. #[allow(dead_code)] pub fn write_episode_parquet( frame_data: &[LerobotFrame], diff --git a/crates/roboflow-media/src/video/composer.rs b/crates/roboflow-media/src/video/composer.rs index 67b3a22e..16d8578e 100644 --- a/crates/roboflow-media/src/video/composer.rs +++ b/crates/roboflow-media/src/video/composer.rs @@ -95,14 +95,18 @@ impl VideoComposer for RsmpegVideoComposer { for stream in first_input.streams().iter() { let mut out_stream = output_ctx.new_stream(); // SAFETY: avcodec_parameters_alloc allocates a new parameters struct. + // We check for null before calling avcodec_parameters_copy. // avcodec_parameters_copy safely copies from the input stream's codecpar. - // The from_raw conversion is safe because the pointer is non-null (checked). + // The from_raw conversion is safe because we verified the pointer is non-null. let codecpar = unsafe { let new_par = ffi::avcodec_parameters_alloc(); - ffi::avcodec_parameters_copy(new_par, stream.codecpar().as_ptr() as *const _); - rsmpeg::avcodec::AVCodecParameters::from_raw( - std::ptr::NonNull::new(new_par).unwrap(), - ) + let new_par = std::ptr::NonNull::new(new_par) + .ok_or_else(|| RoboflowError::other("failed to allocate codec parameters"))?; + ffi::avcodec_parameters_copy( + new_par.as_ptr(), + stream.codecpar().as_ptr() as *const _, + ); + rsmpeg::avcodec::AVCodecParameters::from_raw(new_par) }; out_stream.set_codecpar(codecpar); out_stream.set_time_base(AVRational { From 98c8a44c1207ecd4c17abfdb6fdae7c07667e3b2 Mon Sep 17 00:00:00 2001 From: Zhexuan Yang Date: Wed, 25 Feb 2026 11:05:59 +0800 Subject: [PATCH 4/8] fix: check avcodec_parameters_copy return value avcodec_parameters_copy returns negative error code on failure (e.g., OOM). Previously this was silently ignored, potentially leading to corrupted AVCodecParameters structs. --- crates/roboflow-media/src/video/composer.rs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/crates/roboflow-media/src/video/composer.rs b/crates/roboflow-media/src/video/composer.rs index 16d8578e..2f78d8cb 100644 --- a/crates/roboflow-media/src/video/composer.rs +++ b/crates/roboflow-media/src/video/composer.rs @@ -96,16 +96,22 @@ impl VideoComposer for RsmpegVideoComposer { let mut out_stream = output_ctx.new_stream(); // SAFETY: avcodec_parameters_alloc allocates a new parameters struct. // We check for null before calling avcodec_parameters_copy. - // avcodec_parameters_copy safely copies from the input stream's codecpar. + // avcodec_parameters_copy returns 0 on success, negative on error. // The from_raw conversion is safe because we verified the pointer is non-null. let codecpar = unsafe { let new_par = ffi::avcodec_parameters_alloc(); let new_par = std::ptr::NonNull::new(new_par) .ok_or_else(|| RoboflowError::other("failed to allocate codec parameters"))?; - ffi::avcodec_parameters_copy( + let ret = ffi::avcodec_parameters_copy( new_par.as_ptr(), stream.codecpar().as_ptr() as *const _, ); + if ret < 0 { + return Err(RoboflowError::other(format!( + "avcodec_parameters_copy failed: error code {}", + ret + ))); + } rsmpeg::avcodec::AVCodecParameters::from_raw(new_par) }; out_stream.set_codecpar(codecpar); From 0b9e6a87b1c3cf1a8563302ac02f795e7a0655a5 Mon Sep 17 00:00:00 2001 From: Zhexuan Yang Date: Wed, 25 Feb 2026 11:17:19 +0800 Subject: [PATCH 5/8] fix: add null check for frame_data_ptr in encoder The second code path at line 771 skipped null validation before creating a mutable slice with from_raw_parts_mut. Added null check to match the defensive pattern used in the first code path (lines 454-460). --- crates/roboflow-media/src/video/encoder.rs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/crates/roboflow-media/src/video/encoder.rs b/crates/roboflow-media/src/video/encoder.rs index 1c1fbdc4..0c05c8b9 100644 --- a/crates/roboflow-media/src/video/encoder.rs +++ b/crates/roboflow-media/src/video/encoder.rs @@ -770,9 +770,17 @@ impl VideoEncoder { let frame_data_array = input_frame.data_mut(); let frame_data_ptr = frame_data_array[0]; let frame_data = frame.data(); + + if frame_data_ptr.is_null() { + return Err(RoboflowError::encode( + "VideoEncoder", + "Input frame data pointer is null", + )); + } + // SAFETY: frame_data_ptr was allocated by input_frame.get_buffer and is - // valid for frame_data.len() bytes. The slice creation is safe because - // get_buffer succeeded. copy_from_slice does not overlap. + // valid for frame_data.len() bytes. We verified the pointer is non-null. + // copy_from_slice does not overlap. let frame_data_slice = unsafe { std::slice::from_raw_parts_mut(frame_data_ptr, frame_data.len()) }; frame_data_slice.copy_from_slice(frame_data); From 22efe8abd7be6d0d7dbf962d2a846d8250ddc7de Mon Sep 17 00:00:00 2001 From: Zhexuan Yang Date: Wed, 25 Feb 2026 11:27:21 +0800 Subject: [PATCH 6/8] fix: free AVCodecParameters on error path in composer Memory leak: if avcodec_parameters_copy fails, the struct allocated by avcodec_parameters_alloc was never freed before early return. --- crates/roboflow-media/src/video/composer.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/crates/roboflow-media/src/video/composer.rs b/crates/roboflow-media/src/video/composer.rs index 2f78d8cb..ffdd40ce 100644 --- a/crates/roboflow-media/src/video/composer.rs +++ b/crates/roboflow-media/src/video/composer.rs @@ -97,6 +97,7 @@ impl VideoComposer for RsmpegVideoComposer { // SAFETY: avcodec_parameters_alloc allocates a new parameters struct. // We check for null before calling avcodec_parameters_copy. // avcodec_parameters_copy returns 0 on success, negative on error. + // On error, we free the allocated struct before returning. // The from_raw conversion is safe because we verified the pointer is non-null. let codecpar = unsafe { let new_par = ffi::avcodec_parameters_alloc(); @@ -107,6 +108,8 @@ impl VideoComposer for RsmpegVideoComposer { stream.codecpar().as_ptr() as *const _, ); if ret < 0 { + let mut ptr = new_par.as_ptr(); + ffi::avcodec_parameters_free(&mut ptr); return Err(RoboflowError::other(format!( "avcodec_parameters_copy failed: error code {}", ret From 122909f63c9efec226bb2f917ee53f4d9a572006 Mon Sep 17 00:00:00 2001 From: Zhexuan Yang Date: Wed, 25 Feb 2026 11:38:06 +0800 Subject: [PATCH 7/8] fix: address review comments on misleading comments and metrics MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Remove misleading 'Public API' comments from eviction.rs (CacheEntryMeta/select_eviction_candidate only used in tests) - Update tech-debt-tracking.md: - Change executor coverage from 0%→80% to Verified (~80%→~80%) - Update composer.rs entry to reflect actual bug fixes - Add historical entries for all bug fixes made --- crates/roboflow-storage/src/cached/eviction.rs | 4 ++-- docs/tech-debt-tracking.md | 12 ++++++++---- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/crates/roboflow-storage/src/cached/eviction.rs b/crates/roboflow-storage/src/cached/eviction.rs index 69e019cb..e088cdc0 100644 --- a/crates/roboflow-storage/src/cached/eviction.rs +++ b/crates/roboflow-storage/src/cached/eviction.rs @@ -36,7 +36,7 @@ impl std::fmt::Display for EvictionPolicy { /// Metadata for a cache entry used in eviction decisions. /// /// This is used by the `select_eviction_candidate` helper function. -// Public API for external cache management +/// Currently only used in tests; kept for future cache management integration. #[derive(Debug)] #[allow(dead_code)] pub struct CacheEntryMeta { @@ -56,7 +56,7 @@ pub struct CacheEntryMeta { /// /// Returns `Some((path, size))` of the entry to evict, or `None` if no /// suitable candidate exists (e.g., all entries have pending uploads). -// Public API for external cache management +/// Currently only used in tests; kept for future cache management integration. #[allow(dead_code)] pub fn select_eviction_candidate( entries: &[CacheEntryMeta], diff --git a/docs/tech-debt-tracking.md b/docs/tech-debt-tracking.md index 7c1505b2..4f799d10 100644 --- a/docs/tech-debt-tracking.md +++ b/docs/tech-debt-tracking.md @@ -12,18 +12,18 @@ This document tracks technical debt remediation progress for the Roboflow codeba |--------|--------|-------|--------|--------| | Undocumented unsafe blocks | 15 | 0 | 0 | ✅ Fixed | | Unused dependencies | ~40 | ~30 | 0 | 🔄 In Progress | -| roboflow-executor test coverage | 0% | ~80% | 80% | ✅ Fixed | +| roboflow-executor test coverage | ~80% | ~80% | 80% | ✅ Verified | | Duplicate dependencies | 30+ | 30+ | 0 | 📋 Transitive | ## Completed Remediation ### Phase 1: Quick Wins -1. **Safety Comments for Unsafe Code** ✅ - - `encoder.rs`: Added 6 safety comments +1. **Safety Comments and Bug Fixes for Unsafe Code** ✅ + - `encoder.rs`: Added 6 safety comments + fixed null pointer check - `arena.rs`: Added 10 safety comments - `codec.rs`: Added 1 safety comment - - `composer.rs`: Added 1 safety comment + - `composer.rs`: Fixed null-pointer dereference in avcodec_parameters_copy, added error checking, proper cleanup on failure - `ring_buffer.rs`: Already documented 2. **Unused Dependencies Removed** ✅ @@ -114,5 +114,9 @@ rg "\.unwrap\(\)|\.expect\(" --type rust --stats ### 2026-02-25 - Added safety comments to all undocumented unsafe blocks +- Fixed null-pointer dereference in composer.rs avcodec_parameters_copy +- Fixed memory leak on error path in composer.rs +- Added null check for frame_data_ptr in encoder.rs +- Added error checking for avcodec_parameters_copy return value - Removed ~27 unused dependencies across 4 crates - Verified existing test coverage for roboflow-executor From 4b942f31c5191e43ab7f87d9377a9a2f7cd36d73 Mon Sep 17 00:00:00 2001 From: Zhexuan Yang Date: Wed, 25 Feb 2026 11:42:04 +0800 Subject: [PATCH 8/8] chore: remove tech-debt-tracking.md --- docs/tech-debt-tracking.md | 122 ------------------------------------- 1 file changed, 122 deletions(-) delete mode 100644 docs/tech-debt-tracking.md diff --git a/docs/tech-debt-tracking.md b/docs/tech-debt-tracking.md deleted file mode 100644 index 4f799d10..00000000 --- a/docs/tech-debt-tracking.md +++ /dev/null @@ -1,122 +0,0 @@ -# Technical Debt Tracking - -Last Updated: 2026-02-25 - -## Summary - -This document tracks technical debt remediation progress for the Roboflow codebase. - -## Metrics Dashboard - -| Metric | Before | After | Target | Status | -|--------|--------|-------|--------|--------| -| Undocumented unsafe blocks | 15 | 0 | 0 | ✅ Fixed | -| Unused dependencies | ~40 | ~30 | 0 | 🔄 In Progress | -| roboflow-executor test coverage | ~80% | ~80% | 80% | ✅ Verified | -| Duplicate dependencies | 30+ | 30+ | 0 | 📋 Transitive | - -## Completed Remediation - -### Phase 1: Quick Wins - -1. **Safety Comments and Bug Fixes for Unsafe Code** ✅ - - `encoder.rs`: Added 6 safety comments + fixed null pointer check - - `arena.rs`: Added 10 safety comments - - `codec.rs`: Added 1 safety comment - - `composer.rs`: Fixed null-pointer dereference in avcodec_parameters_copy, added error checking, proper cleanup on failure - - `ring_buffer.rs`: Already documented - -2. **Unused Dependencies Removed** ✅ - - `roboflow-pipeline`: Removed 8 unused deps - - `roboflow-dataset`: Removed 16 unused deps - - `roboflow-distributed`: Removed 2 unused deps - - `roboflow-media`: Removed 1 unused dep - -3. **Unit Tests for roboflow-executor** ✅ - - Already had good test coverage: - - `executor.rs`: test_executor_simple - - `resource.rs`: 4 tests for SlotPool - - `policy/mod.rs`: 3 tests - - `policy/parallel.rs`: 7 tests - - `pipeline.rs`: 5 tests - -### Phase 2: Medium-Term - -4. **execute_merge Refactoring** ✅ - - Already refactored into smaller helper methods: - - `get_or_create_merge_state` - - `ensure_merge_ready` - - `transition_to_merging` - - `verify_cas_won` - - `execute_merge` - - `save_merge_state` - - `fail_merge_with_status` - - `complete_merge_with_status` - -## Remaining Work - -### High Priority - -1. **Reduce unwrap/expect usage** (1,530 occurrences) - - Strategy: Replace with `?` operator and proper error types - - Target: 50% reduction (765 occurrences) - -2. **God Module Refactoring** - - `writer_impl.rs` (1,598 lines): Split into writer/merge/validation - - `scanner.rs` (1,381 lines): Split into discovery/pattern/metadata - -### Medium Priority - -3. **Duplicate Dependencies** - - Most duplicates are transitive (from tikv-client, polars, etc.) - - Requires upstream updates - -## Prevention Strategy - -### Quality Gates (Recommended) - -```yaml -# .github/workflows/quality.yml -pre_commit: - - cognitive_complexity: "max 15" - - unsafe_documentation: "required" - - test_coverage: "min 80% for new code" -``` - -### Debt Budget - -- Allowed monthly increase: - - Complexity: 1% - - Dependencies: 2 new max - - unwrap: 0 (must decrease) - -- Mandatory reduction: - - Complexity: 5% per quarter - - unwrap: 20% per quarter - -## Tracking Commands - -```bash -# Check for unused dependencies -cargo machete - -# Check for duplicate dependencies -cargo tree --duplicates - -# Run clippy with complexity warnings -cargo clippy -- -W clippy::cognitive_complexity - -# Count unwrap/expect -rg "\.unwrap\(\)|\.expect\(" --type rust --stats -``` - -## Historical Changes - -### 2026-02-25 -- Added safety comments to all undocumented unsafe blocks -- Fixed null-pointer dereference in composer.rs avcodec_parameters_copy -- Fixed memory leak on error path in composer.rs -- Added null check for frame_data_ptr in encoder.rs -- Added error checking for avcodec_parameters_copy return value -- Removed ~27 unused dependencies across 4 crates -- Verified existing test coverage for roboflow-executor