`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.
105 lines
4.3 KiB
Rust
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);
|
|
}
|
|
}
|