Skip to content
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

[no_mangle_with_rust_abi]: Check for statics with a non #[repr(Rust)] type #11253

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
131 changes: 93 additions & 38 deletions clippy_lints/src/no_mangle_with_rust_abi.rs
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -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");
}
}
}
},
);
}
}
1 change: 1 addition & 0 deletions tests/ui/auxiliary/no_mangle_with_rust_abi.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pub struct UsingMeInCIsUB(pub u32, pub u32);
39 changes: 39 additions & 0 deletions tests/ui/no_mangle_with_rust_abi.rs
Original file line number Diff line number Diff line change
@@ -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);

Alexendoo marked this conversation as resolved.
Show resolved Hide resolved
#[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) {}

Expand Down
54 changes: 47 additions & 7 deletions tests/ui/no_mangle_with_rust_abi.stderr
Original file line number Diff line number Diff line change
@@ -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) {}
Expand All @@ -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) {}
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Expand All @@ -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) {}
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Expand All @@ -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) {}
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Expand All @@ -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,
Expand All @@ -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