chore: clear clippy errors, document all unsafe blocks, deny new SAFETY debt
Audit-driven cleanup pass. End state:
- cargo clippy --release --all-targets: 0 errors (was 4)
- undocumented_unsafe_blocks warnings: 0 (was 67)
- Cargo.toml: undocumented_unsafe_blocks escalated warn -> deny
Clippy correctness errors fixed:
- src/bin/{sw_encode_bench,vaapi_import_bench}.rs: receive_first_frame
rewritten per Oracle plan with total 10s deadline + 200ms wait slice +
while-let drain of all control events. The previous loop body always
exited on first iteration (never_loop); the new version actually retries
and matches production's repeated-poll semantics in state_portal.rs.
- src/avhw.rs: hash_sampled_y_plane tests now use a row_range(row, stride,
width) helper instead of inline stride * N. Preserves the row-index
intent across all sibling tests without tripping erasing_op (row==0) or
identity_op (row==1).
Machine-applicable clippy autofixes applied via 'cargo clippy --fix':
- unnecessary_cast, manual_is_multiple_of, needless_borrows_for_generic_args
- manual_abs_diff, derivable_impls, new_without_default
- unnecessary_map_or, unneeded_struct_pattern, redundant_locals
webrtc_gop_formula test rewritten to wrap the (fps * 2).max(20) formula in
a runtime lambda. The previous clippy --fix pass had constant-folded the
5fps case into assert_eq!(20, 20), silently stripping the floor-case
coverage. The lambda blocks the fold while keeping the formula exercisable.
67 SAFETY comments added across 7 files (cap_portal.rs 26, sw_encode_bench
21, state_portal.rs 7, vaapi_import_bench.rs 6, avhw.rs 5, state.rs 1,
main.rs 1). Two sites carry load-bearing invariant documentation:
- cap_portal.rs:806 process callback documents the PipeWire raw_buf
ownership contract across all 10 exit paths (audited: every path
correctly requeues; fd ownership via dup() is independent and also
exactly-once closed).
- avhw.rs:341 unsafe impl Send for EncState documents the single-thread
exclusivity assumption referenced by AGENTS.md.
All 97 unit tests + 3 integration tests still pass; cargo build --release
finishes clean. Lint escalation to deny freezes the SAFETY baseline: any
future patch adding an unsafe block without a // SAFETY: comment will fail
clippy at compile time.
This commit is contained in:
+78
-3
@@ -158,6 +158,9 @@ impl CapPortal {
|
||||
let (frame_tx, frame_rx) = bounded(1);
|
||||
let (event_tx, event_rx) = bounded(8);
|
||||
|
||||
// SAFETY: eventfd(2) is a POSIX syscall with no preconditions; the init value
|
||||
// and flags (CLOEXEC + NONBLOCK) are valid. Returns either a fresh fd (>= 0)
|
||||
// or -1 on error, which we check immediately below.
|
||||
let efd = unsafe { libc::eventfd(0, libc::EFD_CLOEXEC | libc::EFD_NONBLOCK) };
|
||||
if efd < 0 {
|
||||
return Err(anyhow::anyhow!(
|
||||
@@ -165,9 +168,13 @@ impl CapPortal {
|
||||
std::io::Error::last_os_error()
|
||||
));
|
||||
}
|
||||
// SAFETY: `efd` is the open eventfd we just created (>= 0 checked above) and
|
||||
// own. dup(2) returns either a fresh fd or -1.
|
||||
let write_fd = unsafe { libc::dup(efd) };
|
||||
if write_fd < 0 {
|
||||
let err = std::io::Error::last_os_error();
|
||||
// SAFETY: `efd` is still the open eventfd we own; closing on the error
|
||||
// path before returning to avoid fd leak.
|
||||
unsafe { libc::close(efd) };
|
||||
return Err(anyhow::anyhow!("dup eventfd failed: {err}"));
|
||||
}
|
||||
@@ -178,6 +185,10 @@ impl CapPortal {
|
||||
frame_tx,
|
||||
event_tx,
|
||||
dropped: pw_dropped.clone(),
|
||||
// SAFETY: `efd` is the freshly-created eventfd (>= 0 checked above) and we
|
||||
// are its sole owner. OwnedFd::from_raw_fd takes ownership and will close()
|
||||
// it on Drop. Ownership transfers into PwThreadCtx and then into the
|
||||
// PipeWire thread via pipewire_thread.
|
||||
shutdown_read: unsafe { OwnedFd::from_raw_fd(efd) },
|
||||
pw_fd,
|
||||
node_id,
|
||||
@@ -190,11 +201,16 @@ impl CapPortal {
|
||||
pipewire_thread(ctx);
|
||||
})
|
||||
.map_err(|e| {
|
||||
// SAFETY: `write_fd` is the open dup'd eventfd we own (>= 0 checked
|
||||
// above); closing on thread-spawn failure to avoid fd leak.
|
||||
unsafe { libc::close(write_fd) };
|
||||
anyhow::anyhow!("thread spawn failed: {e}")
|
||||
})?;
|
||||
|
||||
Ok(Self {
|
||||
// SAFETY: `write_fd` is the freshly-dup'd eventfd (>= 0 checked above) and
|
||||
// we are its sole owner. OwnedFd::from_raw_fd takes ownership and will
|
||||
// close() it on Drop (which fires when CapPortal is dropped).
|
||||
shutdown_fd: unsafe { OwnedFd::from_raw_fd(write_fd) },
|
||||
frame_rx,
|
||||
event_rx,
|
||||
@@ -433,7 +449,10 @@ fn verify_secure_dir(path: &std::path::Path) -> bool {
|
||||
return false;
|
||||
}
|
||||
// Must be owned by current user
|
||||
if meta.uid() != unsafe { libc::getuid() } {
|
||||
// SAFETY: libc::getuid has no preconditions and cannot fail; it simply
|
||||
// returns the calling process's real user ID.
|
||||
// SAFETY: libc::getuid has no preconditions and cannot fail.
|
||||
if meta.uid() != unsafe { libc::getuid() } {
|
||||
tracing::warn!(
|
||||
"Token parent dir not owned by current user: {}",
|
||||
path.display()
|
||||
@@ -511,6 +530,7 @@ fn load_restore_token_from(path: PathBuf) -> Option<String> {
|
||||
tracing::warn!("Token path is not a regular file: {}", path.display());
|
||||
return None;
|
||||
}
|
||||
// SAFETY: libc::getuid has no preconditions and cannot fail.
|
||||
if meta.uid() != unsafe { libc::getuid() } {
|
||||
tracing::warn!("Token file not owned by current user: {}", path.display());
|
||||
return None;
|
||||
@@ -604,6 +624,9 @@ impl Drop for CapPortal {
|
||||
// Signal the PipeWire loop to quit via eventfd.
|
||||
// eventfd write is a kernel syscall — thread-safe and lock-free.
|
||||
let val: u64 = 1u64;
|
||||
// SAFETY: shutdown_fd is a valid open eventfd (owned by Self); the buffer is
|
||||
// a stack u64 of size 8 bytes which matches the count argument. POSIX write(2)
|
||||
// is the standard fd-write syscall; eventfd writes must be exactly 8 bytes.
|
||||
let _ = unsafe {
|
||||
libc::write(
|
||||
self.shutdown_fd.as_raw_fd(),
|
||||
@@ -657,7 +680,7 @@ fn pipewire_thread(ctx: PwThreadCtx) {
|
||||
shutdown_read,
|
||||
pw_fd,
|
||||
node_id,
|
||||
fps,
|
||||
fps: _,
|
||||
} = ctx;
|
||||
|
||||
let mainloop = match pw::main_loop::MainLoopBox::new(None) {
|
||||
@@ -803,6 +826,19 @@ fn pipewire_thread(ctx: PwThreadCtx) {
|
||||
let frame_tx = frame_tx.clone();
|
||||
let dropped = dropped;
|
||||
move |stream, _| {
|
||||
// SAFETY: raw_buf ownership invariant — PipeWire's process callback
|
||||
// contract requires that every buffer acquired via `dequeue_raw_buffer`
|
||||
// is returned to the queue EXACTLY ONCE via `queue_raw_buffer` before
|
||||
// the callback returns — on every exit path, success or error. Failure
|
||||
// to requeue leaks the buffer slot and eventually stalls the stream.
|
||||
//
|
||||
// Audit map of this closure (verified 2026-06-28):
|
||||
// - null raw_buf (dequeue returned NULL) → nothing to requeue, return.
|
||||
// - null spa_buf / no data / bad fd / null chunk / no format_info /
|
||||
// invalid dims / dup_fd < 0 → all requeue before early-return.
|
||||
// - success (try_send Ok / Full / Disconnected) → final requeue at end.
|
||||
// The fd ownership is independent: dup() creates a fresh fd that lives
|
||||
// inside PwDmaBufFrame; on try_send error the frame Drops and closes it.
|
||||
let raw_buf = unsafe { stream.dequeue_raw_buffer() };
|
||||
if raw_buf.is_null() {
|
||||
tracing::trace!("process: null raw_buf");
|
||||
@@ -810,36 +846,49 @@ fn pipewire_thread(ctx: PwThreadCtx) {
|
||||
}
|
||||
|
||||
// 获取 SPA buffer 结构体,包含数据数组、元数据等
|
||||
// SAFETY: raw_buf was checked non-null above. `pw_buffer.buffer` is a
|
||||
// valid raw pointer for the lifetime of raw_buf (PipeWire keeps the
|
||||
// buffer alive until we queue it back).
|
||||
let spa_buf = unsafe { (*raw_buf).buffer };
|
||||
if spa_buf.is_null() {
|
||||
tracing::trace!("process: null spa_buf");
|
||||
// SAFETY: raw_buf is the non-null buffer we still own; returning it.
|
||||
unsafe { stream.queue_raw_buffer(raw_buf) };
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取 buffer 中的数据项数量和数据指针
|
||||
// 对于 DMA-BUF 帧,通常只有 1 个数据项(包含 fd)
|
||||
// SAFETY: spa_buf checked non-null above; `n_datas` is a plain u32 field.
|
||||
let n_datas = unsafe { (*spa_buf).n_datas };
|
||||
// SAFETY: same as above; `datas` is a raw pointer field, may be null.
|
||||
let datas_ptr = unsafe { (*spa_buf).datas };
|
||||
if n_datas == 0 || datas_ptr.is_null() {
|
||||
tracing::trace!("process: no data (n_datas={n_datas})");
|
||||
// SAFETY: raw_buf still owned, returning it.
|
||||
unsafe { stream.queue_raw_buffer(raw_buf) };
|
||||
return;
|
||||
}
|
||||
|
||||
// 从第一个数据项中获取 DMA-BUF 文件描述符
|
||||
// 通过 libspa 的 Data 包装类型安全地访问 SPA 数据结构
|
||||
// SAFETY: datas_ptr is non-null and n_datas > 0 (checked above). We cast
|
||||
// to pw::spa::buffer::Data and take a shared borrow; PipeWire does not
|
||||
// mutate the data array during a process cycle, so a shared reference
|
||||
// for the duration of this callback is sound.
|
||||
let data_ref: &pw::spa::buffer::Data =
|
||||
unsafe { &*(datas_ptr as *const pw::spa::buffer::Data) };
|
||||
let fd = data_ref.fd();
|
||||
if fd < 0 {
|
||||
tracing::trace!("process: invalid fd={fd}");
|
||||
// SAFETY: raw_buf still owned, returning it.
|
||||
unsafe { stream.queue_raw_buffer(raw_buf) };
|
||||
return;
|
||||
}
|
||||
|
||||
if data_ref.as_raw().chunk.is_null() {
|
||||
tracing::trace!("process: null chunk");
|
||||
// SAFETY: raw_buf still owned, returning it.
|
||||
unsafe { stream.queue_raw_buffer(raw_buf) };
|
||||
return;
|
||||
}
|
||||
@@ -850,6 +899,12 @@ fn pipewire_thread(ctx: PwThreadCtx) {
|
||||
// 从 SPA_META_Header 元数据中提取 PTS (显示时间戳)
|
||||
// 遍历 buffer 的所有元数据项,查找 Header 类型的元数据
|
||||
// PTS 可用于音视频同步和帧率控制
|
||||
// SAFETY: spa_buf is non-null. `metas` is checked for null before
|
||||
// iteration. We iterate `i in 0..n_metas` reading shared POD fields
|
||||
// (type_, size, data) — PipeWire keeps the meta array immutable during
|
||||
// a process cycle. The size guard (`meta.size >= size_of::<spa_meta_header>()`)
|
||||
// and null-data check before reading ensure we never read past the
|
||||
// meta's actual extent.
|
||||
let pts: i64 = unsafe {
|
||||
let mut pts_val: i64 = 0;
|
||||
let n_metas = (*spa_buf).n_metas;
|
||||
@@ -873,11 +928,13 @@ fn pipewire_thread(ctx: PwThreadCtx) {
|
||||
|
||||
// 验证格式信息已协商完成,且分辨率和格式有效
|
||||
let Some((width, height, format, modifier)) = format_info.get() else {
|
||||
// SAFETY: raw_buf still owned, returning it.
|
||||
unsafe { stream.queue_raw_buffer(raw_buf) };
|
||||
return;
|
||||
};
|
||||
if width == 0 || height == 0 || format == 0 {
|
||||
tracing::trace!("process: invalid dimensions {width}x{height} format={format}");
|
||||
// SAFETY: raw_buf still owned, returning it.
|
||||
unsafe { stream.queue_raw_buffer(raw_buf) };
|
||||
return;
|
||||
}
|
||||
@@ -885,15 +942,27 @@ fn pipewire_thread(ctx: PwThreadCtx) {
|
||||
// 复制 DMA-BUF 文件描述符
|
||||
// 必须 dup,因为原始 fd 由 PipeWire 管理,我们不能持有它
|
||||
// dup 后的 fd 由 PwDmaBufFrame 持有,生命周期独立于 PipeWire buffer
|
||||
// SAFETY: `fd` is the open DMA-BUF fd reported by PipeWire (>= 0 checked
|
||||
// above). libc::dup is the standard POSIX fd duplication call. The
|
||||
// original `fd` remains owned by PipeWire (returned with raw_buf later).
|
||||
let dup_fd = unsafe { libc::dup(fd) };
|
||||
if dup_fd < 0 {
|
||||
// SAFETY: raw_buf still owned, returning it. No fd cleanup needed
|
||||
// because dup() failed and never returned a new fd.
|
||||
unsafe { stream.queue_raw_buffer(raw_buf) };
|
||||
return;
|
||||
}
|
||||
|
||||
// 构建帧数据对象,所有必要的帧信息已收集完毕
|
||||
// SAFETY: `dup_fd` is a freshly-dup'd open file descriptor (>= 0 checked
|
||||
// above) and we are its sole owner. OwnedFd::from_raw_fd takes ownership
|
||||
// and will close() it on Drop. The fd's lifecycle is independent of
|
||||
// raw_buf: whether try_send succeeds (frame moves into the channel) or
|
||||
// fails (Full/Disconnected — the error payload owns the frame and drops
|
||||
// it at the end of the match arm), exactly one close() occurs per dup().
|
||||
let frame_fd = unsafe { OwnedFd::from_raw_fd(dup_fd) };
|
||||
let frame = PwDmaBufFrame {
|
||||
fd: unsafe { OwnedFd::from_raw_fd(dup_fd) },
|
||||
fd: frame_fd,
|
||||
offset,
|
||||
stride,
|
||||
modifier,
|
||||
@@ -910,6 +979,8 @@ fn pipewire_thread(ctx: PwThreadCtx) {
|
||||
}
|
||||
Err(crossbeam_channel::TrySendError::Disconnected(_)) => {}
|
||||
}
|
||||
// SAFETY: final exactly-once requeue of raw_buf. Every path above
|
||||
// either returned early with its own requeue, or falls through to here.
|
||||
unsafe { stream.queue_raw_buffer(raw_buf) };
|
||||
}
|
||||
})
|
||||
@@ -949,6 +1020,10 @@ fn pipewire_thread(ctx: PwThreadCtx) {
|
||||
move |fd| {
|
||||
// Drain the eventfd so it doesn't re-trigger
|
||||
let mut buf: u64 = 0;
|
||||
// SAFETY: `fd` is the registered eventfd owned by the mainloop source; the
|
||||
// buffer is a stack u64 of 8 bytes matching the count argument. POSIX
|
||||
// read(2) is the standard fd-read syscall; eventfd semantics require the
|
||||
// 8-byte buffer.
|
||||
let _ = unsafe {
|
||||
libc::read(
|
||||
fd.as_raw_fd(),
|
||||
|
||||
Reference in New Issue
Block a user