Files
punktfunk/crates/pf-ffvk/src/lib.rs
T
enricobuehler e1ddd49e37 fix(client-core,ffvk): close the proof-lint hole in two of the three unguarded crates
`clippy::undocumented_unsafe_blocks` is what makes the SAFETY convention a rule rather than a habit,
and three crates had never adopted it — pf-client-core (91 unsafe items), pf-presenter (123) and
punktfunk-core (167) — while every other subsystem crate denied it. That gap is why the decoders'
`unsafe impl Send`s carried a one-line aside instead of an argument: nothing required one.

pf-client-core and pf-ffvk now deny it, with a proof written for all 58 + 3 sites they had.

⚠️ 44 of those 58 were WINDOWS-ONLY — `clipboard.rs` 24 and `video_d3d11.rs` 20 — and invisible to
the Linux measurement that sized this work at 14. Same trap as the E0133 sweep: a Linux-only survey
of a cross-platform crate undercounts by whatever the `cfg` hides, here by 3x. Landing the deny on
the strength of that number alone would have re-broken Windows CI, which is exactly the mistake this
session already made once with the `warn`-that-was-really-`deny`.

The proofs say what is actually load-bearing rather than restating the call. In `clipboard.rs` that
is the ownership split Win32 requires and nothing in the code stated: `GetClipboardData` returns a
handle BORROWED from the clipboard (never freed here), while `GlobalAlloc` + `SetClipboardData`
TRANSFERS ownership to it (which is why nothing frees that one either) — two opposite rules, three
lines apart. In `video_d3d11.rs` the recurring one is that libav's `get_format` list is
NUL-terminated by `AV_PIX_FMT_NONE`, which is what keeps the walk in bounds.

Remaining: punktfunk-core (~146, of which `abi.rs` is 141) and pf-presenter (~108). Both want the
"state the contract once" treatment — abi.rs's sites are a handful of repeating shapes (`opt_cstr`
on caller C strings, null-guarded out-param writes, forwarding calls), not 141 distinct arguments.
Note the vendored `fec-rs` (18 sites) is a separate path-dependency crate, so it is out of scope
rather than something to prove.

Verified: Linux .21 fmt + both CI clippy steps rc=0; Windows .47 `-p pf-client-core` clippy
`-D warnings` rc=0 (the only place the 44 are visible), plus the full Windows CI clippy set and
pf-capture's 18 tests.
2026-07-29 08:48:42 +02:00

105 lines
4.3 KiB
Rust

//! FFmpeg's Vulkan hwcontext surface (`AVVulkanDeviceContext`, `AVVulkanFramesContext`,
//! `AVVkFrame`), bindgen-generated from the system headers at build time — see build.rs
//! for why this must not be hand-transcribed.
//!
//! The raw bindings use vulkan.h's own handle types (pointers on 64-bit). The [`ash`]
//! conversion helpers below cross between them and ash's u64-newtype handles; both sides
//! are the same underlying Vulkan object handles, so the casts are value-preserving.
// Unsafe-proof program: every `unsafe {}` here carries a `// SAFETY:` proof.
#![deny(clippy::undocumented_unsafe_blocks)]
#![allow(non_upper_case_globals)]
#![allow(non_camel_case_types)]
#![allow(non_snake_case)]
#![allow(clippy::missing_safety_doc)]
// bindgen's layout tests deref-null-pointer by design; silence the lints they trip.
#![allow(deref_nullptr)]
#![allow(unnecessary_transmutes)]
#[cfg(any(target_os = "linux", windows))]
include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
/// Conversions between the generated vulkan.h handle types and ash's.
#[cfg(any(target_os = "linux", windows))]
pub mod ashx {
use super::*;
use ash::vk::Handle as _;
/// vulkan.h non-dispatchable handles are `*mut T` on 64-bit; ash's are `u64`
/// newtypes. Same bits either way.
pub fn image(h: VkImage) -> ash::vk::Image {
ash::vk::Image::from_raw(h as u64)
}
pub fn semaphore(h: VkSemaphore) -> ash::vk::Semaphore {
ash::vk::Semaphore::from_raw(h as u64)
}
// bindgen's enum repr is target-dependent: u32 on Linux (clang default), i32 on
// MSVC — so the cast is required on one target and a same-type no-op on the other.
#[allow(clippy::unnecessary_cast)]
pub fn image_layout(l: VkImageLayout) -> ash::vk::ImageLayout {
ash::vk::ImageLayout::from_raw(l as i32)
}
// --- ash → vulkan.h (filling AVVulkanDeviceContext) ---------------------------------
pub fn to_instance(h: ash::vk::Instance) -> VkInstance {
h.as_raw() as VkInstance
}
pub fn to_physical_device(h: ash::vk::PhysicalDevice) -> VkPhysicalDevice {
h.as_raw() as VkPhysicalDevice
}
pub fn to_device(h: ash::vk::Device) -> VkDevice {
h.as_raw() as VkDevice
}
/// ash's loader-level `vkGetInstanceProcAddr` as the header's PFN type. Both are the
/// same C ABI function pointer (`extern "system"` == `extern "C"` on the platforms
/// this crate builds for).
pub fn to_get_proc_addr(
f: unsafe extern "system" fn(
ash::vk::Instance,
*const std::ffi::c_char,
) -> ash::vk::PFN_vkVoidFunction,
) -> PFN_vkGetInstanceProcAddr {
// SAFETY: both sides are `extern "system"` fn pointers with the identical signature —
// `(VkInstance, *const c_char) -> PFN_vkVoidFunction`. The transmute only reinterprets the
// ash-side type alias as our bindgen-side one, which are the same ABI type.
unsafe { std::mem::transmute(f) }
}
}
#[cfg(all(test, any(target_os = "linux", windows)))]
mod tests {
use super::*;
/// The allocator runs (links against the system libavutil) and the struct is
/// readable at the offsets bindgen computed — sem_value zero-initialized.
#[test]
fn vk_frame_alloc_links_and_zeroes() {
// SAFETY: `av_vk_frame_alloc` is libavutil's own allocator and returns either null —
// asserted against before any field is read — or a zero-initialized `AVVkFrame` valid for
// the reads below. The frame is deliberately leaked, so nothing frees it twice.
unsafe {
let f = av_vk_frame_alloc();
assert!(!f.is_null(), "av_vk_frame_alloc returned NULL");
assert_eq!((*f).sem_value[0], 0);
assert_eq!((*f).queue_family[0], 0);
// Leak the one test frame rather than binding av_free here.
}
}
/// AV_NUM_DATA_POINTERS-sized arrays came through with the right length.
#[test]
fn frame_arrays_are_av_num_data_pointers() {
// SAFETY: `AVVkFrame` is a `repr(C)` POD of scalars, handles and fixed-size arrays, so
// all-zeroes is a valid bit pattern for it; the test only reads array lengths.
let f: AVVkFrame = unsafe { std::mem::zeroed() };
assert_eq!(f.img.len(), 8);
assert_eq!(f.sem_value.len(), 8);
}
}