fix(small crates): the proof lint now covers every first-party crate but one
Six crates were still unguarded, and all six are OURS — none vendored: pf-console-ui, pf-gpu, punktfunk-tray, clients/windows, tools/display-disturb, wdk-probe. Two of them ship (the tray and the Windows client), so "small" was about item count, not exposure. Five are closed here. Only 17 of their 75 unsafe items actually lacked a proof — pf-gpu, punktfunk-tray and display-disturb were already fully documented and needed nothing but the deny, which is the good case: the convention was being followed, just not enforced. The 17 that were missing are the usual Win32/COM shapes, and two were worth stating properly. `clients/windows`'s `GetCurrentPackageFullName` is called with `len = 0` and no buffer — that is the documented identity PROBE, which writes nothing, and reading it as a normal query would be a mistake. `pf-console-ui`'s two `destroy_image_view` calls are the load-bearing ones: the comment above one already argued that in-flight sampling of that slot ended two presents ago (the ring alternates and the presenter waits its fence before each record), which is exactly the kind of reasoning a `// SAFETY:` should carry and it was sitting there unlabelled. Also fixes a real Windows-only clippy error this uncovered: `pf-gpu` had a `#[cfg(target_os = "windows")]` fn AFTER its `mod tests`, tripping `items_after_test_module`. It never fired on Linux (the item does not exist there) and no CI job clippies pf-gpu on Windows, so it sat unseen. Moved above the test module. Remaining: `wdk-probe` (26 items) alone, and only because it needs the WDK to build — .47 cannot, so nothing here can verify a deny on it. Verified: Linux .21 fmt + both CI clippy steps rc=0; Windows .47 the four Windows-relevant crates at `-D warnings` rc=0.
This commit is contained in:
@@ -195,6 +195,9 @@ fn apply_window_icon_when_ready() {
|
||||
};
|
||||
let _ = std::thread::Builder::new()
|
||||
.name("pf-window-icon".into())
|
||||
// SAFETY: every call in this thread is a Win32 window/icon API taking either a static wide
|
||||
// literal, a handle it just obtained and checked, or the module handle of this process; none
|
||||
// of them dereference caller memory, and the loop gives up after 100 tries.
|
||||
.spawn(|| unsafe {
|
||||
for _ in 0..100 {
|
||||
if let Ok(hwnd) = FindWindowW(None, windows::core::w!("Punktfunk")) {
|
||||
|
||||
@@ -10,6 +10,8 @@ use windows::Win32::Graphics::Dxgi::{CreateDXGIFactory1, IDXGIAdapter, IDXGIFact
|
||||
|
||||
/// The adapter's human-readable description.
|
||||
fn adapter_name(adapter: &IDXGIAdapter) -> String {
|
||||
// SAFETY: a read-only COM call on the live `adapter` borrow, filling a descriptor returned by
|
||||
// value; `&IDXGIAdapter` is a reference-counted wrapper, so the borrow IS the liveness.
|
||||
unsafe {
|
||||
adapter
|
||||
.GetDesc()
|
||||
@@ -24,12 +26,16 @@ fn adapter_name(adapter: &IDXGIAdapter) -> String {
|
||||
|
||||
/// Every DXGI adapter, in enumeration order.
|
||||
fn all_adapters() -> Vec<IDXGIAdapter> {
|
||||
// SAFETY: DXGI factory creation takes no pointer and returns an owned factory or an error,
|
||||
// matched on below before anything uses it.
|
||||
let factory: IDXGIFactory1 = match unsafe { CreateDXGIFactory1() } {
|
||||
Ok(f) => f,
|
||||
Err(_) => return Vec::new(),
|
||||
};
|
||||
let mut v = Vec::new();
|
||||
let mut i = 0u32;
|
||||
// SAFETY: a COM call on the live factory above; it takes an index and yields an owned adapter,
|
||||
// and the `Ok` pattern is what proves one came back.
|
||||
while let Ok(a) = unsafe { factory.EnumAdapters1(i) } {
|
||||
i += 1;
|
||||
if let Ok(a) = a.cast::<IDXGIAdapter>() {
|
||||
@@ -54,6 +60,7 @@ pub fn adapter_names() -> Vec<String> {
|
||||
for a in all_adapters() {
|
||||
let desc1 = a
|
||||
.cast::<windows::Win32::Graphics::Dxgi::IDXGIAdapter1>()
|
||||
// SAFETY: a read-only COM call on the adapter just cast, filling a descriptor by value.
|
||||
.and_then(|a1| unsafe { a1.GetDesc1() })
|
||||
.ok();
|
||||
let name = adapter_name(&a);
|
||||
|
||||
@@ -14,6 +14,8 @@
|
||||
//! punktfunk-client --headless --speed-test --connect host[:port]
|
||||
//! (measure the path: probe burst → goodput / loss / recommended bitrate)
|
||||
|
||||
// Unsafe-proof program: every `unsafe {}` in this client carries a `// SAFETY:` proof.
|
||||
#![deny(clippy::undocumented_unsafe_blocks)]
|
||||
// Link as a GUI (windows) subsystem binary so the default windowed launch (MSIX / double-click)
|
||||
// does NOT pop a console window. The CLI paths (--headless/--discover) reattach to the launching
|
||||
// terminal's console at startup (see main), so their output is still visible when run from a shell.
|
||||
@@ -49,6 +51,9 @@ fn main() {
|
||||
// launch is window-free. AttachConsole only binds to an ALREADY-EXISTING parent console (it
|
||||
// never creates one), so when launched from a terminal — `--headless`/`--discover` — stdout and
|
||||
// the tracing writer below land in that terminal; from Explorer/MSIX it's a harmless no-op.
|
||||
// SAFETY: `AttachConsole` takes a plain process-id constant and binds to an already-existing
|
||||
// parent console; it allocates nothing and dereferences no caller memory, and failure (no parent
|
||||
// console) is ignored by design.
|
||||
unsafe {
|
||||
use windows::Win32::System::Console::{AttachConsole, ATTACH_PARENT_PROCESS};
|
||||
let _ = AttachConsole(ATTACH_PARENT_PROCESS);
|
||||
@@ -170,6 +175,9 @@ fn set_app_user_model_id() {
|
||||
use windows::Win32::Foundation::APPMODEL_ERROR_NO_PACKAGE;
|
||||
use windows::Win32::Storage::Packaging::Appx::GetCurrentPackageFullName;
|
||||
use windows::Win32::UI::Shell::SetCurrentProcessExplicitAppUserModelID;
|
||||
// SAFETY: `GetCurrentPackageFullName` is called with `len = 0` and no buffer, which is the
|
||||
// documented identity PROBE — it writes nothing and only reports whether this process is
|
||||
// packaged; `SetCurrentProcessExplicitAppUserModelID` takes a static wide literal.
|
||||
unsafe {
|
||||
let mut len: u32 = 0;
|
||||
// No buffer: just probe whether the process has package identity.
|
||||
|
||||
@@ -18,6 +18,9 @@ use windows::Win32::UI::WindowsAndMessaging::{
|
||||
static SHELL_HWND: AtomicIsize = AtomicIsize::new(0);
|
||||
|
||||
fn shell_hwnd() -> Option<HWND> {
|
||||
// SAFETY: the cached value is an `HWND` this process obtained itself; it is re-validated with
|
||||
// `IsWindow` before use (a stale handle is treated as absent), and the lookup calls take only
|
||||
// static wide literals.
|
||||
unsafe {
|
||||
let cached = SHELL_HWND.load(Ordering::Relaxed);
|
||||
if cached != 0 {
|
||||
@@ -37,6 +40,8 @@ fn shell_hwnd() -> Option<HWND> {
|
||||
/// the shell in view with its error banner).
|
||||
pub(crate) fn hide() {
|
||||
if let Some(h) = shell_hwnd() {
|
||||
// SAFETY: `h` is the validated shell window handle from `shell_hwnd`; `ShowWindow` takes it
|
||||
// plus a plain flag and dereferences nothing.
|
||||
unsafe {
|
||||
let _ = ShowWindow(h, SW_HIDE);
|
||||
}
|
||||
@@ -47,6 +52,8 @@ pub(crate) fn hide() {
|
||||
/// it was never hidden — showing a visible window is a no-op.
|
||||
pub(crate) fn restore() {
|
||||
if let Some(h) = shell_hwnd() {
|
||||
// SAFETY: as `hide` — `h` is the validated shell window handle, and both calls take only
|
||||
// that handle plus a plain flag.
|
||||
unsafe {
|
||||
let _ = ShowWindow(h, SW_SHOW);
|
||||
let _ = SetForegroundWindow(h);
|
||||
@@ -60,6 +67,7 @@ pub(crate) fn restore() {
|
||||
pub(crate) fn position() -> Option<(i32, i32)> {
|
||||
let h = shell_hwnd()?;
|
||||
let mut r = RECT::default();
|
||||
// SAFETY: `h` is the validated shell window handle and `r` is a live local the call fills.
|
||||
unsafe { GetWindowRect(h, &mut r).ok()? };
|
||||
Some((r.left, r.top))
|
||||
}
|
||||
|
||||
@@ -10,6 +10,9 @@
|
||||
//! own keyboard types through SDL text input) — plus the in-stream chrome: stats OSD,
|
||||
//! capture hint, start banner.
|
||||
|
||||
// Unsafe-proof program: every `unsafe {}` in the Skia/Vulkan overlay carries a `// SAFETY:` proof.
|
||||
#![deny(clippy::undocumented_unsafe_blocks)]
|
||||
|
||||
#[cfg(any(target_os = "linux", windows))]
|
||||
mod anim;
|
||||
#[cfg(any(target_os = "linux", windows))]
|
||||
|
||||
@@ -427,6 +427,9 @@ impl Shell {
|
||||
|
||||
fn draw_aurora(&self, canvas: &Canvas, w: f64, h: f64, t: f64) {
|
||||
let uniforms: [f32; 3] = [w as f32, h as f32, t as f32];
|
||||
// SAFETY: `uniforms` is a local `[f32; 3]` — exactly 12 bytes — and `f32` has no padding or
|
||||
// invalid bit patterns, so reading it as bytes is sound; the slice is copied by
|
||||
// `Data::new_copy` before `uniforms` goes out of scope.
|
||||
let bytes = unsafe { std::slice::from_raw_parts(uniforms.as_ptr().cast::<u8>(), 12) };
|
||||
match self.mesh.make_shader(Data::new_copy(bytes), &[], None) {
|
||||
Some(shader) => {
|
||||
|
||||
@@ -190,6 +190,9 @@ impl Drop for SkiaOverlay {
|
||||
fn drop(&mut self) {
|
||||
if let Some(gpu) = &mut self.gpu {
|
||||
for slot in self.slots.iter_mut().flat_map(Option::take) {
|
||||
// SAFETY: the view belongs to this slot and the overlay is being dropped, so no
|
||||
// further recording can reference it; the flush/submit + queue guard below is what
|
||||
// retires any work that still could.
|
||||
unsafe { gpu.device.destroy_image_view(slot.view, None) };
|
||||
drop(slot.surface);
|
||||
}
|
||||
@@ -208,6 +211,10 @@ impl Overlay for SkiaOverlay {
|
||||
let entry = shared.entry.clone();
|
||||
let instance = shared.instance.clone();
|
||||
let get_proc = move |of: skvk::GetProcOf| -> *const std::ffi::c_void {
|
||||
// SAFETY: Skia calls this loader with raw instance/device handles it received from the
|
||||
// `BackendContext` below — i.e. the very ones owned by `shared`, still live for the
|
||||
// overlay's lifetime — and `from_raw` only rewraps them for the ash entry points. Each
|
||||
// name is a NUL-terminated C string from Skia, borrowed for the call.
|
||||
unsafe {
|
||||
match of {
|
||||
skvk::GetProcOf::Instance(raw_instance, name) => entry
|
||||
@@ -223,6 +230,9 @@ impl Overlay for SkiaOverlay {
|
||||
}
|
||||
}
|
||||
};
|
||||
// SAFETY: the instance/physical-device/device handles come from `shared`, which owns them
|
||||
// and outlives this backend context, and `get_proc` above resolves through those same
|
||||
// handles. Skia stores them but does not take ownership — teardown stays ours.
|
||||
let backend = unsafe {
|
||||
skvk::BackendContext::new(
|
||||
shared.instance.handle().as_raw() as _,
|
||||
@@ -525,6 +535,9 @@ impl SkiaOverlay {
|
||||
if let Some(old) = self.slots[i].take() {
|
||||
// Any in-flight sampling of THIS slot ended two presents ago (the ring
|
||||
// alternates and the presenter waits its fence before each record).
|
||||
// SAFETY: the view belongs to the slot being replaced, and per the comment above any
|
||||
// in-flight sampling of THIS slot ended two presents ago — the ring alternates and the
|
||||
// presenter waits its fence before each record — so the GPU is done with it.
|
||||
unsafe { gpu.device.destroy_image_view(old.view, None) };
|
||||
}
|
||||
let info =
|
||||
@@ -549,6 +562,9 @@ impl SkiaOverlay {
|
||||
.vulkan_image_info()
|
||||
.context("backend texture is not Vulkan")?;
|
||||
let image = avk::Image::from_raw(*image_info.image() as u64);
|
||||
// SAFETY: a create call on the live device `gpu` owns, over a builder that is a local
|
||||
// outliving the call; `image` is the VkImage Skia just reported for this backend texture,
|
||||
// which the surface keeps alive. The returned view is owned by the slot stored below.
|
||||
let view = unsafe {
|
||||
gpu.device.create_image_view(
|
||||
&avk::ImageViewCreateInfo::default()
|
||||
|
||||
+39
-36
@@ -22,6 +22,9 @@
|
||||
//! running session keeps the device it opened on. [`session_begin`]/[`active`] record which GPU a
|
||||
//! live session actually encodes on, for the console's "in use" display.
|
||||
|
||||
// Unsafe-proof program: every `unsafe {}` in this leaf carries a `// SAFETY:` proof.
|
||||
#![deny(clippy::undocumented_unsafe_blocks)]
|
||||
|
||||
use anyhow::Result;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::path::PathBuf;
|
||||
@@ -716,6 +719,42 @@ pub fn active() -> Option<(ActiveGpu, u32)> {
|
||||
.map(|s| (s.gpu.clone(), s.sessions))
|
||||
}
|
||||
|
||||
/// Pick the render GPU LUID the Windows pipeline is created on: the IDD-push capturer's
|
||||
/// shared-texture ring, the IddCx `SET_RENDER_ADAPTER` pin, and (via the captured frame's device)
|
||||
/// NVENC/AMF/QSV all follow this one decision — see [`selected_gpu`] for the precedence (operator
|
||||
/// preference > `PUNKTFUNK_RENDER_ADAPTER` substring > max `DedicatedVideoMemory`). A configured
|
||||
/// preference that doesn't match a present GPU falls back to auto selection (with a warning) rather
|
||||
/// than returning `None`, so a stale preference never stops the host from streaming.
|
||||
///
|
||||
/// Lives here (not in a host module) so BOTH the capture and encode subsystem crates depend on it
|
||||
/// as a peer of GPU selection instead of the orchestrator — the plan's `windows/adapter.rs`, folded
|
||||
/// into `pf-gpu` (plan §W6). It was historically the SudoVDA backend's, then the host's
|
||||
/// `win_adapter.rs`; the LUID-shaped view of [`selected_gpu`] plus the per-decision logging.
|
||||
#[cfg(target_os = "windows")]
|
||||
pub fn resolve_render_adapter_luid() -> Option<windows::Win32::Foundation::LUID> {
|
||||
match selected_gpu() {
|
||||
Some(sel) => {
|
||||
tracing::info!(
|
||||
adapter = sel.info.name,
|
||||
vram_mb = sel.info.vram_bytes / (1024 * 1024),
|
||||
source = sel.source.tag(),
|
||||
"render adapter selected"
|
||||
);
|
||||
if sel.source == PickSource::PreferenceMissing {
|
||||
tracing::warn!(
|
||||
"the preferred GPU is not present — auto-selected the adapter above \
|
||||
(fix or clear the preference in the web console)"
|
||||
);
|
||||
}
|
||||
Some(sel.info.luid())
|
||||
}
|
||||
None => {
|
||||
tracing::warn!("no suitable render adapter found for SET_RENDER_ADAPTER");
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@@ -914,39 +953,3 @@ mod tests {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Pick the render GPU LUID the Windows pipeline is created on: the IDD-push capturer's
|
||||
/// shared-texture ring, the IddCx `SET_RENDER_ADAPTER` pin, and (via the captured frame's device)
|
||||
/// NVENC/AMF/QSV all follow this one decision — see [`selected_gpu`] for the precedence (operator
|
||||
/// preference > `PUNKTFUNK_RENDER_ADAPTER` substring > max `DedicatedVideoMemory`). A configured
|
||||
/// preference that doesn't match a present GPU falls back to auto selection (with a warning) rather
|
||||
/// than returning `None`, so a stale preference never stops the host from streaming.
|
||||
///
|
||||
/// Lives here (not in a host module) so BOTH the capture and encode subsystem crates depend on it
|
||||
/// as a peer of GPU selection instead of the orchestrator — the plan's `windows/adapter.rs`, folded
|
||||
/// into `pf-gpu` (plan §W6). It was historically the SudoVDA backend's, then the host's
|
||||
/// `win_adapter.rs`; the LUID-shaped view of [`selected_gpu`] plus the per-decision logging.
|
||||
#[cfg(target_os = "windows")]
|
||||
pub fn resolve_render_adapter_luid() -> Option<windows::Win32::Foundation::LUID> {
|
||||
match selected_gpu() {
|
||||
Some(sel) => {
|
||||
tracing::info!(
|
||||
adapter = sel.info.name,
|
||||
vram_mb = sel.info.vram_bytes / (1024 * 1024),
|
||||
source = sel.source.tag(),
|
||||
"render adapter selected"
|
||||
);
|
||||
if sel.source == PickSource::PreferenceMissing {
|
||||
tracing::warn!(
|
||||
"the preferred GPU is not present — auto-selected the adapter above \
|
||||
(fix or clear the preference in the web console)"
|
||||
);
|
||||
}
|
||||
Some(sel.info.luid())
|
||||
}
|
||||
None => {
|
||||
tracing::warn!("no suitable render adapter found for SET_RENDER_ADAPTER");
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,8 @@
|
||||
//! then the host's loopback-only unauthenticated `GET /api/v1/local/summary` for the streaming
|
||||
//! details. Windows-subsystem binary — a console exe in the HKLM Run key would flash a terminal
|
||||
//! window at every sign-in.
|
||||
// Unsafe-proof program: every `unsafe {}` in the tray carries a `// SAFETY:` proof.
|
||||
#![deny(clippy::undocumented_unsafe_blocks)]
|
||||
#![cfg_attr(windows, windows_subsystem = "windows")]
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
|
||||
@@ -19,6 +19,9 @@
|
||||
//! Usage: `display-disturb ddc [--interval-ms 2000] [--caps] [--vcp 0x10]`
|
||||
//! `display-disturb modeset [--interval-ms 2000]`
|
||||
|
||||
// Unsafe-proof program: every `unsafe {}` in this tool carries a `// SAFETY:` proof.
|
||||
#![deny(clippy::undocumented_unsafe_blocks)]
|
||||
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
fn main() {
|
||||
eprintln!("display-disturb is Windows-only (it exercises the WDDM display stack).");
|
||||
|
||||
Reference in New Issue
Block a user