Skip to content

Migrate lints to use 2024 edition practice of unsafe_op_in_unsafe_fn #4994

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 14 commits into from
Mar 21, 2025
Merged
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,7 @@ invalid_doc_attributes = "warn"
rust_2018_idioms = { level = "warn", priority = -1 }
rust_2021_prelude_collisions = "warn"
unused_lifetimes = "warn"
unsafe_op_in_unsafe_fn = "warn"

[workspace.lints.rustdoc]
broken_intra_doc_links = "warn"
Expand Down
59 changes: 22 additions & 37 deletions pyo3-build-config/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -168,44 +168,36 @@ fn resolve_cross_compile_config_path() -> Option<PathBuf> {
})
}

/// Helper to print a feature cfg with a minimum rust version required.
fn print_feature_cfg(minor_version_required: u32, cfg: &str) {
let minor_version = rustc_minor_version().unwrap_or(0);

if minor_version >= minor_version_required {
println!("cargo:rustc-cfg={}", cfg);
}

// rustc 1.80.0 stabilized `rustc-check-cfg` feature, don't emit before
if minor_version >= 80 {
println!("cargo:rustc-check-cfg=cfg({})", cfg);
}
}

/// Use certain features if we detect the compiler being used supports them.
///
/// Features may be removed or added as MSRV gets bumped or new features become available,
/// so this function is unstable.
#[doc(hidden)]
pub fn print_feature_cfgs() {
let rustc_minor_version = rustc_minor_version().unwrap_or(0);

if rustc_minor_version >= 70 {
println!("cargo:rustc-cfg=rustc_has_once_lock");
}

if rustc_minor_version >= 71 {
println!("cargo:rustc-cfg=rustc_has_extern_c_unwind");
}

// invalid_from_utf8 lint was added in Rust 1.74
if rustc_minor_version >= 74 {
println!("cargo:rustc-cfg=invalid_from_utf8_lint");
}

if rustc_minor_version >= 79 {
println!("cargo:rustc-cfg=c_str_lit");
}

print_feature_cfg(70, "rustc_has_once_lock");
print_feature_cfg(70, "cargo_toml_lints");
print_feature_cfg(71, "rustc_has_extern_c_unwind");
print_feature_cfg(74, "invalid_from_utf8_lint");
print_feature_cfg(79, "c_str_lit");
// Actually this is available on 1.78, but we should avoid
// https://github.com/rust-lang/rust/issues/124651 just in case
if rustc_minor_version >= 79 {
println!("cargo:rustc-cfg=diagnostic_namespace");
}

if rustc_minor_version >= 83 {
println!("cargo:rustc-cfg=io_error_more");
}

if rustc_minor_version >= 85 {
println!("cargo:rustc-cfg=fn_ptr_eq");
}
print_feature_cfg(79, "diagnostic_namespace");
print_feature_cfg(83, "io_error_more");
print_feature_cfg(85, "fn_ptr_eq");
}

/// Registers `pyo3`s config names as reachable cfg expressions
Expand All @@ -224,15 +216,8 @@ pub fn print_expected_cfgs() {
println!("cargo:rustc-check-cfg=cfg(PyPy)");
println!("cargo:rustc-check-cfg=cfg(GraalPy)");
println!("cargo:rustc-check-cfg=cfg(py_sys_config, values(\"Py_DEBUG\", \"Py_REF_DEBUG\", \"Py_TRACE_REFS\", \"COUNT_ALLOCS\"))");
println!("cargo:rustc-check-cfg=cfg(invalid_from_utf8_lint)");
println!("cargo:rustc-check-cfg=cfg(pyo3_disable_reference_pool)");
println!("cargo:rustc-check-cfg=cfg(pyo3_leak_on_drop_without_reference_pool)");
println!("cargo:rustc-check-cfg=cfg(diagnostic_namespace)");
println!("cargo:rustc-check-cfg=cfg(c_str_lit)");
println!("cargo:rustc-check-cfg=cfg(rustc_has_once_lock)");
println!("cargo:rustc-check-cfg=cfg(rustc_has_extern_c_unwind)");
println!("cargo:rustc-check-cfg=cfg(io_error_more)");
println!("cargo:rustc-check-cfg=cfg(fn_ptr_eq)");

// allow `Py_3_*` cfgs from the minimum supported version up to the
// maximum minor version (+1 for development for the next)
Expand Down
5 changes: 5 additions & 0 deletions pyo3-ffi/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -330,6 +330,11 @@
clippy::missing_safety_doc
)]
#![warn(elided_lifetimes_in_paths, unused_lifetimes)]
// This crate is a hand-maintained translation of CPython's headers, so requiring "unsafe"
// blocks within those translations increases maintenance burden without providing any
// additional safety. The safety of the functions in this crate is determined by the
// original CPython headers
#![allow(unsafe_op_in_unsafe_fn)]

// Until `extern type` is stabilized, use the recommended approach to
// model opaque types:
Expand Down
8 changes: 5 additions & 3 deletions src/err/err_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -240,9 +240,11 @@ impl PyErrStateNormalized {
ptraceback: *mut ffi::PyObject,
) -> Self {
PyErrStateNormalized {
ptype: Py::from_owned_ptr_or_opt(py, ptype).expect("Exception type missing"),
pvalue: Py::from_owned_ptr_or_opt(py, pvalue).expect("Exception value missing"),
ptraceback: Py::from_owned_ptr_or_opt(py, ptraceback),
ptype: unsafe { Py::from_owned_ptr_or_opt(py, ptype).expect("Exception type missing") },
pvalue: unsafe {
Py::from_owned_ptr_or_opt(py, pvalue).expect("Exception value missing")
},
ptraceback: unsafe { Py::from_owned_ptr_or_opt(py, ptraceback) },
}
}

Expand Down
16 changes: 8 additions & 8 deletions src/ffi_ptr_ext.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,46 +40,46 @@ pub(crate) trait FfiPtrExt: Sealed {
impl FfiPtrExt for *mut ffi::PyObject {
#[inline]
unsafe fn assume_owned_or_err(self, py: Python<'_>) -> PyResult<Bound<'_, PyAny>> {
Bound::from_owned_ptr_or_err(py, self)
unsafe { Bound::from_owned_ptr_or_err(py, self) }
}

#[inline]
unsafe fn assume_owned_or_opt(self, py: Python<'_>) -> Option<Bound<'_, PyAny>> {
Bound::from_owned_ptr_or_opt(py, self)
unsafe { Bound::from_owned_ptr_or_opt(py, self) }
}

#[inline]
#[track_caller]
unsafe fn assume_owned(self, py: Python<'_>) -> Bound<'_, PyAny> {
Bound::from_owned_ptr(py, self)
unsafe { Bound::from_owned_ptr(py, self) }
}

#[inline]
unsafe fn assume_owned_unchecked(self, py: Python<'_>) -> Bound<'_, PyAny> {
Bound::from_owned_ptr_unchecked(py, self)
unsafe { Bound::from_owned_ptr_unchecked(py, self) }
}

#[inline]
unsafe fn assume_borrowed_or_err<'a>(
self,
py: Python<'_>,
) -> PyResult<Borrowed<'a, '_, PyAny>> {
Borrowed::from_ptr_or_err(py, self)
unsafe { Borrowed::from_ptr_or_err(py, self) }
}

#[inline]
unsafe fn assume_borrowed_or_opt<'a>(self, py: Python<'_>) -> Option<Borrowed<'a, '_, PyAny>> {
Borrowed::from_ptr_or_opt(py, self)
unsafe { Borrowed::from_ptr_or_opt(py, self) }
}

#[inline]
#[track_caller]
unsafe fn assume_borrowed<'a>(self, py: Python<'_>) -> Borrowed<'a, '_, PyAny> {
Borrowed::from_ptr(py, self)
unsafe { Borrowed::from_ptr(py, self) }
}

#[inline]
unsafe fn assume_borrowed_unchecked<'a>(self, py: Python<'_>) -> Borrowed<'a, '_, PyAny> {
Borrowed::from_ptr_unchecked(py, self)
unsafe { Borrowed::from_ptr_unchecked(py, self) }
}
}
32 changes: 17 additions & 15 deletions src/gil.rs
Original file line number Diff line number Diff line change
Expand Up @@ -111,15 +111,15 @@ where
F: for<'p> FnOnce(Python<'p>) -> R,
{
assert_eq!(
ffi::Py_IsInitialized(),
unsafe { ffi::Py_IsInitialized() },
0,
"called `with_embedded_python_interpreter` but a Python interpreter is already running."
);

ffi::Py_InitializeEx(0);
unsafe { ffi::Py_InitializeEx(0) };

let result = {
let guard = GILGuard::assume();
let guard = unsafe { GILGuard::assume() };
let py = guard.python();
// Import the threading module - this ensures that it will associate this thread as the "main"
// thread, which is important to avoid an `AssertionError` at finalization.
Expand All @@ -130,7 +130,7 @@ where
};

// Finalize the Python interpreter.
ffi::Py_Finalize();
unsafe { ffi::Py_Finalize() };

result
}
Expand Down Expand Up @@ -201,15 +201,15 @@ impl GILGuard {
/// as part of multi-phase interpreter initialization.
pub(crate) unsafe fn acquire_unchecked() -> Self {
if gil_is_acquired() {
return Self::assume();
return unsafe { Self::assume() };
}

let gstate = ffi::PyGILState_Ensure(); // acquire GIL
let gstate = unsafe { ffi::PyGILState_Ensure() }; // acquire GIL
increment_gil_count();

#[cfg(not(pyo3_disable_reference_pool))]
if let Some(pool) = Lazy::get(&POOL) {
pool.update_counts(Python::assume_gil_acquired());
pool.update_counts(unsafe { Python::assume_gil_acquired() });
}
GILGuard::Ensured { gstate }
}
Expand Down Expand Up @@ -300,7 +300,7 @@ pub(crate) struct SuspendGIL {
impl SuspendGIL {
pub(crate) unsafe fn new() -> Self {
let count = GIL_COUNT.with(|c| c.replace(0));
let tstate = ffi::PyEval_SaveThread();
let tstate = unsafe { ffi::PyEval_SaveThread() };

Self { count, tstate }
}
Expand Down Expand Up @@ -364,7 +364,7 @@ impl Drop for LockGIL {
#[track_caller]
pub unsafe fn register_incref(obj: NonNull<ffi::PyObject>) {
if gil_is_acquired() {
ffi::Py_INCREF(obj.as_ptr())
unsafe { ffi::Py_INCREF(obj.as_ptr()) }
} else {
panic!("Cannot clone pointer into Python heap without the GIL being held.");
}
Expand All @@ -381,7 +381,7 @@ pub unsafe fn register_incref(obj: NonNull<ffi::PyObject>) {
#[track_caller]
pub unsafe fn register_decref(obj: NonNull<ffi::PyObject>) {
if gil_is_acquired() {
ffi::Py_DECREF(obj.as_ptr())
unsafe { ffi::Py_DECREF(obj.as_ptr()) }
} else {
#[cfg(not(pyo3_disable_reference_pool))]
POOL.register_decref(obj);
Expand Down Expand Up @@ -617,13 +617,15 @@ mod tests {
unsafe extern "C" fn capsule_drop(capsule: *mut ffi::PyObject) {
// This line will implicitly call update_counts
// -> and so cause deadlock if update_counts is not handling recursion correctly.
let pool = GILGuard::assume();
let pool = unsafe { GILGuard::assume() };

// Rebuild obj so that it can be dropped
PyObject::from_owned_ptr(
pool.python(),
ffi::PyCapsule_GetPointer(capsule, std::ptr::null()) as _,
);
unsafe {
PyObject::from_owned_ptr(
pool.python(),
ffi::PyCapsule_GetPointer(capsule, std::ptr::null()) as _,
)
};
}

let ptr = obj.into_ptr();
Expand Down
31 changes: 18 additions & 13 deletions src/impl_/extract_argument.rs
Original file line number Diff line number Diff line change
Expand Up @@ -296,9 +296,10 @@ impl FunctionDescription {
// the rest are varargs.
let positional_args_to_consume =
num_positional_parameters.min(positional_args_provided);
let (positional_parameters, remaining) =
let (positional_parameters, remaining) = unsafe {
std::slice::from_raw_parts(args, positional_args_provided)
.split_at(positional_args_to_consume);
.split_at(positional_args_to_consume)
};
output[..positional_args_to_consume].copy_from_slice(positional_parameters);
remaining
};
Expand All @@ -309,14 +310,17 @@ impl FunctionDescription {

// Safety: kwnames is known to be a pointer to a tuple, or null
// - we both have the GIL and can borrow this input reference for the `'py` lifetime.
let kwnames: Option<Borrowed<'_, '_, PyTuple>> =
Borrowed::from_ptr_or_opt(py, kwnames).map(|kwnames| kwnames.downcast_unchecked());
let kwnames: Option<Borrowed<'_, '_, PyTuple>> = unsafe {
Borrowed::from_ptr_or_opt(py, kwnames).map(|kwnames| kwnames.downcast_unchecked())
};
if let Some(kwnames) = kwnames {
let kwargs = ::std::slice::from_raw_parts(
// Safety: PyArg has the same memory layout as `*mut ffi::PyObject`
args.offset(nargs).cast::<PyArg<'py>>(),
kwnames.len(),
);
let kwargs = unsafe {
::std::slice::from_raw_parts(
// Safety: PyArg has the same memory layout as `*mut ffi::PyObject`
args.offset(nargs).cast::<PyArg<'py>>(),
kwnames.len(),
)
};

self.handle_kwargs::<K, _>(
kwnames.iter_borrowed().zip(kwargs.iter().copied()),
Expand Down Expand Up @@ -362,9 +366,10 @@ impl FunctionDescription {
// - `kwargs` is known to be a dict or null
// - we both have the GIL and can borrow these input references for the `'py` lifetime.
let args: Borrowed<'py, 'py, PyTuple> =
Borrowed::from_ptr(py, args).downcast_unchecked::<PyTuple>();
let kwargs: Option<Borrowed<'py, 'py, PyDict>> =
Borrowed::from_ptr_or_opt(py, kwargs).map(|kwargs| kwargs.downcast_unchecked());
unsafe { Borrowed::from_ptr(py, args).downcast_unchecked::<PyTuple>() };
let kwargs: Option<Borrowed<'py, 'py, PyDict>> = unsafe {
Borrowed::from_ptr_or_opt(py, kwargs).map(|kwargs| kwargs.downcast_unchecked())
};

let num_positional_parameters = self.positional_parameter_names.len();

Expand All @@ -391,7 +396,7 @@ impl FunctionDescription {
let mut varkeywords = K::Varkeywords::default();
if let Some(kwargs) = kwargs {
self.handle_kwargs::<K, _>(
kwargs.iter_borrowed(),
unsafe { kwargs.iter_borrowed() },
&mut varkeywords,
num_positional_parameters,
output,
Expand Down
Loading
Loading