From c434b1914623a55518fd37f68c2ce4c733e2cd52 Mon Sep 17 00:00:00 2001 From: Martin Kuntz Jacobsen Date: Sat, 14 Nov 2026 04:38:59 +0100 Subject: [PATCH 1/2] bazelrc: deny clippy::panic and unsafe_code repo-wide (part of #12395) Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01Xut2hws3L7Tg4oUvpSvuYD --- .bazelrc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.bazelrc b/.bazelrc index b02f88eece..6587c87a45 100644 --- a/.bazelrc +++ b/.bazelrc @@ -37,6 +37,7 @@ build --@rules_rust//rust/settings:clippy.toml=//tools/workspace:clippy.toml # Strict tier lints rules_dx enforces. common --@rules_dx//config:clippy_flag=-Dclippy::unwrap_used common --@rules_dx//config:clippy_flag=-Dclippy::expect_used +common --@rules_dx//config:clippy_flag=-Dclippy::panic common --@rules_dx//config:clippy_flag=-Dclippy::unreachable common --@rules_dx//config:clippy_flag=-Dclippy::exit common --@rules_dx//config:clippy_flag=-Dclippy::todo @@ -47,6 +48,7 @@ common --@rules_dx//config:clippy_flag=-Dclippy::unwrap_in_result common --@rules_dx//config:clippy_flag=-Dclippy::panic_in_result_fn common --@rules_dx//config:clippy_flag=-Dclippy::allow_attributes common --@rules_dx//config:clippy_flag=-Dclippy::allow_attributes_without_reason +common --@rules_dx//config:clippy_flag=-Dunsafe_code common --@rules_dx//config:clippy_flag=-Dunsafe_op_in_unsafe_fn common --@rules_dx//config:clippy_flag=-Dunused_must_use build:linux --@rules_rust//rust/settings:pipelined_compilation=true From d4a75d93b7871309f47297ab468d80944d43ecdd Mon Sep 17 00:00:00 2001 From: Martin Kuntz Jacobsen Date: Sat, 14 Nov 2026 05:17:43 +0100 Subject: [PATCH 2/2] cli/cli: satisfy denied unsafe_code (part of #12395) Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01Xut2hws3L7Tg4oUvpSvuYD --- cli/cli/src/exec/error_boundary_launcher_tests.rs | 2 ++ cli/cli/src/exec/stream_probe_tests.rs | 2 ++ cli/cli/src/exec/test_support.rs | 1 + cli/cli/src/main.rs | 14 ++++++++++++++ 4 files changed, 19 insertions(+) diff --git a/cli/cli/src/exec/error_boundary_launcher_tests.rs b/cli/cli/src/exec/error_boundary_launcher_tests.rs index 83e3a21f7b..0181cfe1d1 100644 --- a/cli/cli/src/exec/error_boundary_launcher_tests.rs +++ b/cli/cli/src/exec/error_boundary_launcher_tests.rs @@ -1,3 +1,5 @@ +#![expect(unsafe_code, reason = "pipe and process-group tests call libc directly")] + use super::super::test_support::{Harness, hang_up}; use quality_result::proto::FileSnapshot; use std::fs; diff --git a/cli/cli/src/exec/stream_probe_tests.rs b/cli/cli/src/exec/stream_probe_tests.rs index bdf0ffd835..3491f8fc1a 100644 --- a/cli/cli/src/exec/stream_probe_tests.rs +++ b/cli/cli/src/exec/stream_probe_tests.rs @@ -1,3 +1,5 @@ +#![expect(unsafe_code, reason = "signal, process-group and pty tests call libc directly")] + use super::super::test_support::{closed_pipe, hang_up, reader_gone}; use std::ffi::{CStr, CString}; use std::fs; diff --git a/cli/cli/src/exec/test_support.rs b/cli/cli/src/exec/test_support.rs index 8eec752a0c..2602d09703 100644 --- a/cli/cli/src/exec/test_support.rs +++ b/cli/cli/src/exec/test_support.rs @@ -1369,6 +1369,7 @@ pub(crate) fn failed_action( #[cfg(unix)] /// Reports whether the pipe behind `writer` has no reader left in any process. #[cfg(unix)] +#[expect(unsafe_code, reason = "poll has no safe std API")] pub(crate) fn reader_gone(writer: &io::PipeWriter, timeout_ms: i32) -> bool { let mut fd = libc::pollfd { fd: writer.as_raw_fd(), diff --git a/cli/cli/src/main.rs b/cli/cli/src/main.rs index d4b4ca0812..ea012730c9 100644 --- a/cli/cli/src/main.rs +++ b/cli/cli/src/main.rs @@ -19,16 +19,24 @@ use dx_process::lifecycle::{self, EnvPolicy, StreamOutput, StreamSpec, StreamStd use dx_process::{discover_real, operational_code, pre_exec_code, stdout_io_code}; #[cfg(unix)] +#[expect( + unsafe_code, + reason = "a signal handler saves and restores errno through a raw pointer" +)] extern "C" fn forward_to_child(signo: libc::c_int) { let errno = dx_process::errno_slot(); + // SAFETY: `errno_slot` returns this thread's valid errno location. let saved = unsafe { *errno }; lifecycle::forward_stop(signo); + // SAFETY: same location as above, still valid on this thread. unsafe { *errno = saved }; } #[cfg(unix)] +#[expect(unsafe_code, reason = "sigaction has no safe std API")] fn install_forwarding() { for signo in lifecycle::STOP_SIGNALS { + // SAFETY: zeroed `sigaction` is valid; the handler only calls async-signal-safe functions. unsafe { let mut action: libc::sigaction = std::mem::zeroed(); action.sa_sigaction = forward_to_child as *const () as libc::sighandler_t; @@ -52,9 +60,11 @@ fn install_forwarding() { fn install_forwarding() {} #[cfg(unix)] +#[expect(unsafe_code, reason = "re-raising a signal has no safe std API")] fn die_by_signal(signo: i32) { if signo > 0 { let _cleaned = dx_plan::plan::clean_for_stop(); + // SAFETY: restoring the default disposition and raising a signal has no memory effects. unsafe { libc::signal(signo, libc::SIG_DFL); libc::raise(signo); @@ -160,6 +170,7 @@ fn usage_error( ) } +#[expect(unsafe_code, reason = "set_var is unsafe in edition 2024")] fn main() { if let Some(code) = run_as_tool() { std::process::exit(code); @@ -195,6 +206,7 @@ fn run_as_tool() -> Option { } // LCOV_EXCL_STOP - reason: end thin multicall shim, issue: 6517, policy: docs/cli/commands/build-test-coverage.md +#[expect(unsafe_code, reason = "set_var is unsafe in edition 2024")] fn run() -> i32 { // LCOV_EXCL_START - reason: thin run shim, issue: 1055, policy: docs/cli/commands/build-test-coverage.md let args: Vec = std::env::args_os().skip(1).collect(); @@ -273,7 +285,9 @@ fn run() -> i32 { let exists = |path: &Path| path.is_file(); let read = |path: &Path| std::fs::read(path).ok(); match resolve_bazel_sh(&|name| std::env::var_os(name), &exists, &read) { + // SAFETY: startup is single-threaded at this point. Ok(BazelSh::Set(bash)) => unsafe { std::env::set_var("BAZEL_SH", bash) }, + // SAFETY: startup is single-threaded at this point. Ok(BazelSh::Unset) => unsafe { std::env::remove_var("BAZEL_SH") }, Ok(BazelSh::Keep) => {} Err(error) => {