diff --git a/clippy_lints/src/no_mangle_with_rust_abi.rs b/clippy_lints/src/no_mangle_with_rust_abi.rs index 8fd9ae351a0d..60fd758a5502 100644 --- a/clippy_lints/src/no_mangle_with_rust_abi.rs +++ b/clippy_lints/src/no_mangle_with_rust_abi.rs @@ -1,32 +1,40 @@ use clippy_utils::diagnostics::span_lint_and_then; -use clippy_utils::source::snippet_with_applicability; +use clippy_utils::source::snippet_opt; use rustc_errors::Applicability; -use rustc_hir::{Item, ItemKind}; +use rustc_hir::{FnSig, Item, ItemKind, Ty}; use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::ty; use rustc_session::{declare_lint_pass, declare_tool_lint}; -use rustc_span::{BytePos, Pos}; +use rustc_span::{sym, BytePos, Pos}; use rustc_target::spec::abi::Abi; declare_clippy_lint! { /// ### What it does - /// Checks for Rust ABI functions with the `#[no_mangle]` attribute. + /// Checks for Rust ABI functions or statics with the `#[no_mangle]` attribute. /// /// ### Why is this bad? - /// The Rust ABI is not stable, but in many simple cases matches - /// enough with the C ABI that it is possible to forget to add - /// `extern "C"` to a function called from C. Changes to the - /// Rust ABI can break this at any point. + /// The Rust ABI is not stable, but in many simple cases matches enough with the C ABI + /// that it is possible to forget to add `extern "C"` to a function called from C. + /// Changes to the Rust ABI can break this at any point. /// /// ### Example /// ```rust - /// #[no_mangle] - /// fn example(arg_one: u32, arg_two: usize) {} - /// ``` + /// #[no_mangle] + /// fn example(arg_one: u32, arg_two: usize) {} /// + /// pub struct UsingMeInCIsUB(u32, u32); + /// #[no_mangle] + /// pub static ZERO: UsingMeInCIsUB = UsingMeInCIsUB(0, 0); + /// ``` /// Use instead: /// ```rust - /// #[no_mangle] - /// extern "C" fn example(arg_one: u32, arg_two: usize) {} + /// #[no_mangle] + /// extern "C" fn example(arg_one: u32, arg_two: usize) {} + /// + /// #[repr(C)] + /// pub struct UsingMeInCIsFine(u32, u32); + /// #[no_mangle] + /// pub static ZERO: UsingMeInCIsFine = UsingMeInCIsFine(0, 0); /// ``` #[clippy::version = "1.69.0"] pub NO_MANGLE_WITH_RUST_ABI, @@ -37,33 +45,80 @@ declare_lint_pass!(NoMangleWithRustAbi => [NO_MANGLE_WITH_RUST_ABI]); impl<'tcx> LateLintPass<'tcx> for NoMangleWithRustAbi { fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'tcx>) { - if let ItemKind::Fn(fn_sig, _, _) = &item.kind { - let attrs = cx.tcx.hir().attrs(item.hir_id()); - let mut app = Applicability::MaybeIncorrect; - let snippet = snippet_with_applicability(cx, fn_sig.span, "..", &mut app); - for attr in attrs { - if let Some(ident) = attr.ident() - && ident.name == rustc_span::sym::no_mangle - && fn_sig.header.abi == Abi::Rust - && let Some((fn_attrs, _)) = snippet.split_once("fn") - && !fn_attrs.contains("extern") - { - let sugg_span = fn_sig.span - .with_lo(fn_sig.span.lo() + BytePos::from_usize(fn_attrs.len())) - .shrink_to_lo(); + if cx.tcx.get_attr(item.owner_id, sym::no_mangle).is_none() { + return; + } + + match item.kind { + ItemKind::Fn(fn_sig, _, _) => check_fn(cx, fn_sig), + ItemKind::Static(ty, _, _) => check_static(cx, item, ty), + _ => {}, + } + } +} - span_lint_and_then( - cx, - NO_MANGLE_WITH_RUST_ABI, - fn_sig.span, - "`#[no_mangle]` set on a function with the default (`Rust`) ABI", - |diag| { - diag.span_suggestion(sugg_span, "set an ABI", "extern \"C\" ", app) - .span_suggestion(sugg_span, "or explicitly set the default", "extern \"Rust\" ", app); - }, +/// Check for functions that are implicitly using the Rust ABI. +fn check_fn(cx: &LateContext<'_>, fn_sig: FnSig<'_>) { + if fn_sig.header.abi == Abi::Rust + && let Some(snippet) = snippet_opt(cx, fn_sig.span) + && let Some((fn_attrs, _)) = snippet.split_once("fn") + && !fn_attrs.contains("extern") + { + let sugg_span = fn_sig + .span + .with_lo(fn_sig.span.lo() + BytePos::from_usize(fn_attrs.len())) + .shrink_to_lo(); + + span_lint_and_then( + cx, + NO_MANGLE_WITH_RUST_ABI, + fn_sig.span, + "`#[no_mangle]` set on a function with the default (`Rust`) ABI", + |diag| { + diag.note_once( + "this function's calling convention (ABI) is unstable, and changes to the `Rust` ABI can break \ + this at any time", + ) + .span_suggestion(sugg_span, "set an ABI", "extern \"C\" ", Applicability::MaybeIncorrect) + .span_suggestion( + sugg_span, + "or explicitly set the default", + "extern \"Rust\" ", + Applicability::MaybeIncorrect, + ); + }, + ); + } +} + +/// Check for static items with a type that is implicitly using the Rust ABI. +fn check_static(cx: &LateContext<'_>, item: &Item<'_>, ty: &Ty<'_>) { + // TODO(Centri3): Add a check here for `#[repr(Rust)]` once #114201 is merged. + + if let Some(is_local) = match cx.tcx.type_of(item.owner_id).skip_binder().kind() { + ty::Adt(def, _) => { + let repr = def.repr(); + (!repr.c() && !repr.transparent() && !repr.simd() && repr.int.is_none()).then(|| def.did().is_local()) + }, + ty::Tuple(tys) => (tys.len() != 0).then_some(false), + _ => return, + } { + span_lint_and_then( + cx, + NO_MANGLE_WITH_RUST_ABI, + item.span, + "`#[no_mangle]` set on a static with the default (`Rust`) ABI", + |diag| { + diag.span_note(ty.span, "this type is implicitly `#[repr(Rust)]`") + .note_once( + "this static's layout (ABI) is unstable, and changes to the `Rust` ABI can break this at any \ + time", ); + + if is_local { + diag.help("set an explicit ABI (like `#[repr(C)]`) on the type's definition"); } - } - } + }, + ); } } diff --git a/tests/ui/auxiliary/no_mangle_with_rust_abi.rs b/tests/ui/auxiliary/no_mangle_with_rust_abi.rs new file mode 100644 index 000000000000..ede0f2e2d268 --- /dev/null +++ b/tests/ui/auxiliary/no_mangle_with_rust_abi.rs @@ -0,0 +1 @@ +pub struct UsingMeInCIsUB(pub u32, pub u32); diff --git a/tests/ui/no_mangle_with_rust_abi.rs b/tests/ui/no_mangle_with_rust_abi.rs index 818119f7be57..068cee4027de 100644 --- a/tests/ui/no_mangle_with_rust_abi.rs +++ b/tests/ui/no_mangle_with_rust_abi.rs @@ -1,6 +1,45 @@ +//@aux-build:no_mangle_with_rust_abi.rs #![allow(unused)] #![warn(clippy::no_mangle_with_rust_abi)] +extern crate no_mangle_with_rust_abi as external; + +use std::ptr::null; + +pub struct UsingMeInCIsUB(u32, u32); +#[no_mangle] +pub static ZERO_UB: UsingMeInCIsUB = UsingMeInCIsUB(0, 0); + +#[repr(C)] +pub struct UsingMeInCIsFine(u32, u32); +#[no_mangle] +pub static ZERO_DB: UsingMeInCIsFine = UsingMeInCIsFine(0, 0); + +#[no_mangle] +pub static ZERO_UB_AGAIN: external::UsingMeInCIsUB = external::UsingMeInCIsUB(0, 0); + +#[no_mangle] +pub static U32_DB: u32 = 0; + +#[repr(transparent)] +pub struct PtrTransparent(*const u32); + +unsafe impl Send for PtrTransparent {} + +unsafe impl Sync for PtrTransparent {} + +pub struct Ptr(*const u32); + +unsafe impl Send for Ptr {} + +unsafe impl Sync for Ptr {} + +#[no_mangle] +pub static PTR_DB: PtrTransparent = PtrTransparent(null()); + +#[no_mangle] +pub static PTR_UB: Ptr = Ptr(null()); + #[no_mangle] fn rust_abi_fn_one(arg_one: u32, arg_two: usize) {} diff --git a/tests/ui/no_mangle_with_rust_abi.stderr b/tests/ui/no_mangle_with_rust_abi.stderr index da5d31d8f2d4..6ecbffdf0055 100644 --- a/tests/ui/no_mangle_with_rust_abi.stderr +++ b/tests/ui/no_mangle_with_rust_abi.stderr @@ -1,10 +1,50 @@ +error: `#[no_mangle]` set on a static with the default (`Rust`) ABI + --> $DIR/no_mangle_with_rust_abi.rs:11:1 + | +LL | pub static ZERO_UB: UsingMeInCIsUB = UsingMeInCIsUB(0, 0); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +note: this type is implicitly `#[repr(Rust)]` + --> $DIR/no_mangle_with_rust_abi.rs:11:21 + | +LL | pub static ZERO_UB: UsingMeInCIsUB = UsingMeInCIsUB(0, 0); + | ^^^^^^^^^^^^^^ + = note: this static's layout (ABI) is unstable, and changes to the `Rust` ABI can break this at any time + = help: set an explicit ABI (like `#[repr(C)]`) on the type's definition + = note: `-D clippy::no-mangle-with-rust-abi` implied by `-D warnings` + +error: `#[no_mangle]` set on a static with the default (`Rust`) ABI + --> $DIR/no_mangle_with_rust_abi.rs:19:1 + | +LL | pub static ZERO_UB_AGAIN: external::UsingMeInCIsUB = external::UsingMeInCIsUB(0, 0); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +note: this type is implicitly `#[repr(Rust)]` + --> $DIR/no_mangle_with_rust_abi.rs:19:27 + | +LL | pub static ZERO_UB_AGAIN: external::UsingMeInCIsUB = external::UsingMeInCIsUB(0, 0); + | ^^^^^^^^^^^^^^^^^^^^^^^^ + +error: `#[no_mangle]` set on a static with the default (`Rust`) ABI + --> $DIR/no_mangle_with_rust_abi.rs:41:1 + | +LL | pub static PTR_UB: Ptr = Ptr(null()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +note: this type is implicitly `#[repr(Rust)]` + --> $DIR/no_mangle_with_rust_abi.rs:41:20 + | +LL | pub static PTR_UB: Ptr = Ptr(null()); + | ^^^ + = help: set an explicit ABI (like `#[repr(C)]`) on the type's definition + error: `#[no_mangle]` set on a function with the default (`Rust`) ABI - --> $DIR/no_mangle_with_rust_abi.rs:5:1 + --> $DIR/no_mangle_with_rust_abi.rs:44:1 | LL | fn rust_abi_fn_one(arg_one: u32, arg_two: usize) {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = note: `-D clippy::no-mangle-with-rust-abi` implied by `-D warnings` + = note: this function's calling convention (ABI) is unstable, and changes to the `Rust` ABI can break this at any time help: set an ABI | LL | extern "C" fn rust_abi_fn_one(arg_one: u32, arg_two: usize) {} @@ -15,7 +55,7 @@ LL | extern "Rust" fn rust_abi_fn_one(arg_one: u32, arg_two: usize) {} | +++++++++++++ error: `#[no_mangle]` set on a function with the default (`Rust`) ABI - --> $DIR/no_mangle_with_rust_abi.rs:8:1 + --> $DIR/no_mangle_with_rust_abi.rs:47:1 | LL | pub fn rust_abi_fn_two(arg_one: u32, arg_two: usize) {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -30,7 +70,7 @@ LL | pub extern "Rust" fn rust_abi_fn_two(arg_one: u32, arg_two: usize) {} | +++++++++++++ error: `#[no_mangle]` set on a function with the default (`Rust`) ABI - --> $DIR/no_mangle_with_rust_abi.rs:13:1 + --> $DIR/no_mangle_with_rust_abi.rs:52:1 | LL | pub unsafe fn rust_abi_fn_three(arg_one: u32, arg_two: usize) {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -45,7 +85,7 @@ LL | pub unsafe extern "Rust" fn rust_abi_fn_three(arg_one: u32, arg_two: usize) | +++++++++++++ error: `#[no_mangle]` set on a function with the default (`Rust`) ABI - --> $DIR/no_mangle_with_rust_abi.rs:18:1 + --> $DIR/no_mangle_with_rust_abi.rs:57:1 | LL | unsafe fn rust_abi_fn_four(arg_one: u32, arg_two: usize) {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -60,7 +100,7 @@ LL | unsafe extern "Rust" fn rust_abi_fn_four(arg_one: u32, arg_two: usize) {} | +++++++++++++ error: `#[no_mangle]` set on a function with the default (`Rust`) ABI - --> $DIR/no_mangle_with_rust_abi.rs:21:1 + --> $DIR/no_mangle_with_rust_abi.rs:60:1 | LL | / fn rust_abi_multiline_function_really_long_name_to_overflow_args_to_multiple_lines( LL | | arg_one: u32, @@ -77,5 +117,5 @@ help: or explicitly set the default LL | extern "Rust" fn rust_abi_multiline_function_really_long_name_to_overflow_args_to_multiple_lines( | +++++++++++++ -error: aborting due to 5 previous errors +error: aborting due to 8 previous errors