Skip to content
Open
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
1 change: 1 addition & 0 deletions Cargo.lock
Original file line number Diff line number Diff line change
Expand Up @@ -4175,6 +4175,7 @@ dependencies = [
"rustc_parse_format",
"rustc_session",
"rustc_span",
"rustc_symbol_mangling",
"rustc_target",
"rustc_trait_selection",
"smallvec",
Expand Down
2 changes: 1 addition & 1 deletion compiler/rustc_error_codes/src/error_codes/E0755.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ side effects or infinite loops:

extern "C" {
#[unsafe(ffi_pure)] // ok!
pub fn strlen(s: *const i8) -> isize;
pub fn strlen(s: *const std::ffi::c_char) -> usize;
}
# fn main() {}
```
Expand Down
2 changes: 1 addition & 1 deletion compiler/rustc_error_codes/src/error_codes/E0756.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ which have no side effects except for their return value:

extern "C" {
#[unsafe(ffi_const)] // ok!
pub fn strlen(s: *const i8) -> i32;
pub fn strlen(s: *const std::ffi::c_char) -> usize;
}
# fn main() {}
```
Expand Down
8 changes: 8 additions & 0 deletions compiler/rustc_hir/src/lang_items.rs
Original file line number Diff line number Diff line change
Expand Up @@ -447,6 +447,14 @@ language_item_table! {

// Used to fallback `{float}` to `f32` when `f32: From<{float}>`
From, sym::From, from_trait, Target::Trait, GenericRequirement::Exact(1);

// Runtime symbols
MemCpy, sym::memcpy_fn, memcpy_fn, Target::Fn, GenericRequirement::None;
MemMove, sym::memmove_fn, memmove_fn, Target::Fn, GenericRequirement::None;
MemSet, sym::memset_fn, memset_fn, Target::Fn, GenericRequirement::None;
MemCmp, sym::memcmp_fn, memcmp_fn, Target::Fn, GenericRequirement::None;
Bcmp, sym::bcmp_fn, bcmp_fn, Target::Fn, GenericRequirement::None;
StrLen, sym::strlen_fn, strlen_fn, Target::Fn, GenericRequirement::None;
}

/// The requirement imposed on the generics of a lang item
Expand Down
21 changes: 21 additions & 0 deletions compiler/rustc_hir/src/weak_lang_items.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,29 @@ macro_rules! weak_lang_items {
}
}

macro_rules! decl_lang_items {
($($item:ident,)*) => {
pub static DECL_LANG_ITEMS: &[LangItem] = &[$(LangItem::$item,)*];

impl LangItem {
pub fn is_decl(self) -> bool {
matches!(self, $(LangItem::$item)|*)
}
}
}
}

weak_lang_items! {
PanicImpl, rust_begin_unwind;
EhPersonality, rust_eh_personality;
EhCatchTypeinfo, rust_eh_catch_typeinfo;
}

decl_lang_items! {
MemCpy,
MemMove,
MemSet,
MemCmp,
Bcmp,
StrLen,
}
1 change: 1 addition & 0 deletions compiler/rustc_lint/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ rustc_middle = { path = "../rustc_middle" }
rustc_parse_format = { path = "../rustc_parse_format" }
rustc_session = { path = "../rustc_session" }
rustc_span = { path = "../rustc_span" }
rustc_symbol_mangling = { path = "../rustc_symbol_mangling" }
rustc_target = { path = "../rustc_target" }
rustc_trait_selection = { path = "../rustc_trait_selection" }
smallvec = { version = "1.8.1", features = ["union", "may_dangle"] }
Expand Down
3 changes: 3 additions & 0 deletions compiler/rustc_lint/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ mod precedence;
mod ptr_nulls;
mod redundant_semicolon;
mod reference_casting;
mod runtime_symbols;
mod shadowed_into_iter;
mod static_mut_refs;
mod traits;
Expand Down Expand Up @@ -112,6 +113,7 @@ use precedence::*;
use ptr_nulls::*;
use redundant_semicolon::*;
use reference_casting::*;
use runtime_symbols::*;
use rustc_hir::def_id::LocalModDefId;
use rustc_middle::query::Providers;
use rustc_middle::ty::TyCtxt;
Expand Down Expand Up @@ -241,6 +243,7 @@ late_lint_methods!(
AsyncFnInTrait: AsyncFnInTrait,
NonLocalDefinitions: NonLocalDefinitions::default(),
InteriorMutableConsts: InteriorMutableConsts,
RuntimeSymbols: RuntimeSymbols,
ImplTraitOvercaptures: ImplTraitOvercaptures,
IfLetRescope: IfLetRescope::default(),
StaticMutRefs: StaticMutRefs,
Expand Down
27 changes: 27 additions & 0 deletions compiler/rustc_lint/src/lints.rs
Original file line number Diff line number Diff line change
Expand Up @@ -848,6 +848,33 @@ pub(crate) enum UseLetUnderscoreIgnoreSuggestion {
},
}

// runtime_symbols.rs
#[derive(Diagnostic)]
pub(crate) enum RedefiningRuntimeSymbolsDiag<'tcx> {
#[diag(
"invalid definition of the runtime `{$symbol_name}` symbol used by the standard library"
)]
#[note(
"expected `{$expected_fn_sig}`
found `{$found_fn_sig}`"
)]
#[help(
"either fix the signature or remove any attributes like `#[unsafe(no_mangle)]`, `#[unsafe(export_name = \"{$symbol_name}\")]`, or `#[link_name = \"{$symbol_name}\"]`"
)]
FnDef { symbol_name: String, expected_fn_sig: Ty<'tcx>, found_fn_sig: Ty<'tcx> },
#[diag(
"invalid definition of the runtime `{$symbol_name}` symbol used by the standard library"
)]
#[note(
"expected `{$expected_fn_sig}`
found `static {$symbol_name}: {$static_ty}`"
)]
#[help(
"either fix the signature or remove any attributes `#[unsafe(no_mangle)]` or `#[unsafe(export_name = \"{$symbol_name}\")]`"
)]
Static { symbol_name: String, static_ty: Ty<'tcx>, expected_fn_sig: Ty<'tcx> },
}

// drop_forget_useless.rs
#[derive(Diagnostic)]
#[diag("calls to `std::mem::drop` with a reference instead of an owned value does nothing")]
Expand Down
205 changes: 205 additions & 0 deletions compiler/rustc_lint/src/runtime_symbols.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
use rustc_hir::def_id::{DefId, LocalDefId};
use rustc_hir::{self as hir, FnSig, ForeignItemKind, LanguageItems};
use rustc_infer::infer::DefineOpaqueTypes;
use rustc_middle::ty::{self, Instance, Ty};
use rustc_session::{declare_lint, declare_lint_pass};
use rustc_span::Span;
use rustc_trait_selection::infer::TyCtxtInferExt;

use crate::lints::RedefiningRuntimeSymbolsDiag;
use crate::{LateContext, LateLintPass, LintContext};

declare_lint! {
/// The `invalid_runtime_symbol_definitions` lint checks the signature of items whose
/// symbol name is a runtime symbols expected by `core`.
///
/// ### Example
///
/// ```rust,compile_fail
/// #[unsafe(no_mangle)]
/// pub fn strlen() {} // invalid definition of the `strlen` function
/// ```
///
/// {{produces}}
///
/// ### Explanation
///
/// Up-most care is required when defining runtime symbols assumed and
Copy link
Copy Markdown
Contributor

@PatchMixolydic PatchMixolydic Apr 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit:

Suggested change
/// Up-most care is required when defining runtime symbols assumed and
/// Utmost care is required when defining runtime symbols assumed and

View changes since the review

/// used by the standard library. They must follow the C specification, not use any
/// standard-library facility or undefined behavior may occur.
///
/// The symbols currently checked are `memcpy`, `memmove`, `memset`, `memcmp`,
/// `bcmp` and `strlen`.
///
/// [^1]: https://doc.rust-lang.org/core/index.html#how-to-use-the-core-library
pub INVALID_RUNTIME_SYMBOL_DEFINITIONS,
Deny,
"invalid definition of a symbol used by the standard library"
}

declare_lint_pass!(RuntimeSymbols => [INVALID_RUNTIME_SYMBOL_DEFINITIONS]);

static EXPECTED_SYMBOLS: &[ExpectedSymbol] = &[
ExpectedSymbol { symbol: "memcpy", lang: LanguageItems::memcpy_fn },
ExpectedSymbol { symbol: "memmove", lang: LanguageItems::memmove_fn },
ExpectedSymbol { symbol: "memset", lang: LanguageItems::memset_fn },
ExpectedSymbol { symbol: "memcmp", lang: LanguageItems::memcmp_fn },
ExpectedSymbol { symbol: "bcmp", lang: LanguageItems::bcmp_fn },
ExpectedSymbol { symbol: "strlen", lang: LanguageItems::strlen_fn },
];

#[derive(Copy, Clone, Debug)]
struct ExpectedSymbol {
symbol: &'static str,
lang: fn(&LanguageItems) -> Option<DefId>,
}

impl<'tcx> LateLintPass<'tcx> for RuntimeSymbols {
fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::Item<'tcx>) {
// Bail-out if the item is not a function/method or static.
match item.kind {
hir::ItemKind::Fn { sig, ident: _, generics, body: _, has_body: _ } => {
// Generic functions cannot have the same runtime symbol as we do not allow
// any symbol attributes.
if !generics.params.is_empty() {
return;
}

// Try to the overridden symbol name of this function (our mangling
// cannot ever conflict with runtime symbols, so no need to check for those).
let Some(symbol_name) = rustc_symbol_mangling::symbol_name_from_attrs(
cx.tcx,
rustc_middle::ty::InstanceKind::Item(item.owner_id.to_def_id()),
) else {
return;
};

check_fn(cx, &symbol_name, sig, item.owner_id.def_id);
}
hir::ItemKind::Static(..) => {
// Compute the symbol name of this static (without mangling, as our mangling
// cannot ever conflict with runtime symbols).
let Some(symbol_name) = rustc_symbol_mangling::symbol_name_from_attrs(
cx.tcx,
rustc_middle::ty::InstanceKind::Item(item.owner_id.to_def_id()),
) else {
return;
};

let def_id = item.owner_id.def_id;

check_static(cx, &symbol_name, def_id, item.span);
}
hir::ItemKind::ForeignMod { abi: _, items } => {
for item in items {
let item = cx.tcx.hir_foreign_item(*item);

let did = item.owner_id.def_id;
let instance = Instance::new_raw(
did.to_def_id(),
ty::List::identity_for_item(cx.tcx, did),
);
let symbol_name = cx.tcx.symbol_name(instance);

match item.kind {
ForeignItemKind::Fn(fn_sig, _idents, _generics) => {
check_fn(cx, &symbol_name.name, fn_sig, did);
}
ForeignItemKind::Static(..) => {
check_static(cx, &symbol_name.name, did, item.span);
}
ForeignItemKind::Type => return,
}
}
}
_ => return,
}
}
}

fn check_fn(cx: &LateContext<'_>, symbol_name: &str, sig: FnSig<'_>, did: LocalDefId) {
let Some(expected_symbol) = EXPECTED_SYMBOLS.iter().find(|es| es.symbol == symbol_name) else {
// The symbol name does not correspond to a runtime symbols, bail out
return;
};

let Some(expected_def_id) = (expected_symbol.lang)(&cx.tcx.lang_items()) else {
// Can't find the corresponding language item, bail out
return;
};

// Get the two function signatures
let lang_sig = cx.tcx.normalize_erasing_regions(
cx.typing_env(),
cx.tcx.fn_sig(expected_def_id).instantiate_identity(),
);
let user_sig = cx
.tcx
.normalize_erasing_regions(cx.typing_env(), cx.tcx.fn_sig(did).instantiate_identity());

// Compare the two signatures with an inference context
let infcx = cx.tcx.infer_ctxt().build(cx.typing_mode());
let cause = rustc_middle::traits::ObligationCause::misc(sig.span, did);
let result = infcx.at(&cause, cx.param_env).eq(DefineOpaqueTypes::No, lang_sig, user_sig);

// If they don't match, emit our own mismatch signatures
if let Err(_terr) = result {
// Create fn pointers for diagnostics purpose
let expected = Ty::new_fn_ptr(cx.tcx, lang_sig);
let actual = Ty::new_fn_ptr(cx.tcx, user_sig);

cx.emit_span_lint(
INVALID_RUNTIME_SYMBOL_DEFINITIONS,
sig.span,
RedefiningRuntimeSymbolsDiag::FnDef {
symbol_name: symbol_name.to_string(),
found_fn_sig: actual,
expected_fn_sig: expected,
},
);
}
}

fn check_static<'tcx>(cx: &LateContext<'tcx>, symbol_name: &str, did: LocalDefId, sp: Span) {
let Some(expected_symbol) = EXPECTED_SYMBOLS.iter().find(|es| es.symbol == symbol_name) else {
// The symbol name does not correspond to a runtime symbols, bail out
return;
};

let Some(expected_def_id) = (expected_symbol.lang)(&cx.tcx.lang_items()) else {
// Can't find the corresponding language item, bail out
return;
};

// Get the static type
let static_ty = cx.tcx.type_of(did).instantiate_identity().skip_norm_wip();

// Peel Option<...> and get the inner type (see std weak! macro with #[linkage = "extern_weak"])
let inner_static_ty: Ty<'_> = match static_ty.kind() {
ty::Adt(def, args) if Some(def.did()) == cx.tcx.lang_items().option_type() => {
args.type_at(0)
}
_ => static_ty,
};

// Get the expected symbol function signature
let lang_sig = cx.tcx.normalize_erasing_regions(
cx.typing_env(),
cx.tcx.fn_sig(expected_def_id).instantiate_identity(),
);

let expected = Ty::new_fn_ptr(cx.tcx, lang_sig);

// Compare the expected function signature with the static type, report an error if they don't match
if expected != inner_static_ty {
cx.emit_span_lint(
INVALID_RUNTIME_SYMBOL_DEFINITIONS,
sp,
RedefiningRuntimeSymbolsDiag::Static {
static_ty,
symbol_name: symbol_name.to_string(),
expected_fn_sig: expected,
},
);
}
}
Loading
Loading