Skip to content
Open
Show file tree
Hide file tree
Changes from 8 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 @@ -4267,6 +4267,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 @@ -448,6 +448,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! weak_only_lang_items {
($($item:ident,)*) => {
pub static WEAK_ONLY_LANG_ITEMS: &[LangItem] = &[$(LangItem::$item,)*];

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

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

weak_only_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 @@ -70,6 +70,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 @@ -113,6 +114,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 @@ -242,6 +244,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
206 changes: 206 additions & 0 deletions compiler/rustc_lint/src/runtime_symbols.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
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
///
#[cfg_attr(bootstrap, doc = "```rust")]
#[cfg_attr(not(bootstrap), doc = "```rust,compile_fail")]
#[cfg_attr(not(bootstrap), doc = "#[unsafe(no_mangle)]")]
/// pub fn strlen() {} // invalid definition of the `strlen` function
#[doc = "```"]
///
/// {{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 get 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