Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Add lint warn about clashing function names with fundamental functions
  • Loading branch information
Urgau committed Sep 13, 2025
commit 24de5ec5c63b8d2f6f61f79a2783afdf1c84a49a
1 change: 1 addition & 0 deletions Cargo.lock
Original file line number Diff line number Diff line change
Expand Up @@ -4042,6 +4042,7 @@ dependencies = [
"rustc_parse_format",
"rustc_session",
"rustc_span",
"rustc_symbol_mangling",
"rustc_target",
"rustc_trait_selection",
"smallvec",
Expand Down
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
5 changes: 5 additions & 0 deletions compiler/rustc_lint/messages.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,11 @@ lint_cfg_attr_no_attributes =

lint_check_name_unknown_tool = unknown lint tool: `{$tool_name}`

lint_clashing_function_names_with_fundamental_functions = this function symbol name `{$symbol_name}` clashes with the fundamental functions expected with `core` and `std`
.match_exactly = extra care must be taken when exposing a function with those symbol names, they must match exactly (ABI, function arguments, function return type, behavior, ...)
.learn_more = see <https://doc.rust-lang.org/core/index.html#how-to-use-the-core-library> for the more details
.help = either allow this lint or remove any `#[unsafe(no_mangle)]` or `#[unsafe(export_name = "{$symbol_name}")]` if present

lint_closure_returning_async_block = closure returning async block can be made into an async closure
.label = this async block can be removed, and the closure can be turned into an async closure
.suggestion = turn this into an async closure
Expand Down
71 changes: 71 additions & 0 deletions compiler/rustc_lint/src/fundamental_functions.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
use rustc_hir as hir;
use rustc_session::{declare_lint, declare_lint_pass};

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

declare_lint! {
/// The `clashing_function_names_with_fundamental_functions` lint checks for function
/// name whose name clash with a fundamental functions expected by `core` and `std`.
///
/// ### Example
///
/// ```rust,compile_fail
/// #![deny(clashing_function_names_with_fundamental_functions)]
///
/// #[unsafe(no_mangle)]
/// pub fn strlen() {} // clash with the libc `strlen` function
/// // care must be taken when implementing this function
/// ```
///
/// {{produces}}
///
/// ### Explanation
///
/// Up-most care is required when overriding those fundamental functions assumed and
/// 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 respectively:
/// - from `core`[^1]: `memcpy`, `memmove`, `memset`, `memcmp`, `bcmp`, `strlen`
/// - from `std`: `read`, `write`, `open`, `close`
///
/// [^1]: https://doc.rust-lang.org/core/index.html#how-to-use-the-core-library
pub CLASHING_FUNCTION_NAMES_WITH_FUNDAMENTAL_FUNCTIONS,
Warn,
"using a function name that clashes with fundamental function names"
}

declare_lint_pass!(FundamentalFunctions => [CLASHING_FUNCTION_NAMES_WITH_FUNDAMENTAL_FUNCTIONS]);

static CORE_FUNDAMENTAL_FUNCTION_NAMES: &[&str] =
&["memcpy", "memmove", "memset", "memcmp", "bcmp", "strlen"];

static STD_FUNDAMENTAL_FUNCTION_NAMES: &[&str] = &["open", "read", "write", "close"];

impl<'tcx> LateLintPass<'tcx> for FundamentalFunctions {
fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::Item<'tcx>) {
let hir::ItemKind::Fn { sig: _, ident: _, generics: _, body: _, has_body: true } =
item.kind
else {
return;
};

let Some(symbol_name) = rustc_symbol_mangling::symbol_name_without_mangling(
cx.tcx,
rustc_middle::ty::InstanceKind::Item(item.owner_id.to_def_id()),
) else {
return;
};

if CORE_FUNDAMENTAL_FUNCTION_NAMES.contains(&&*symbol_name)
|| STD_FUNDAMENTAL_FUNCTION_NAMES.contains(&&*symbol_name)
{
cx.emit_span_lint(
CLASHING_FUNCTION_NAMES_WITH_FUNDAMENTAL_FUNCTIONS,
item.span,
ClashingFunctionNamesWithFundamentalFunctions { symbol_name },
);
}
}
}
3 changes: 3 additions & 0 deletions compiler/rustc_lint/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ mod errors;
mod expect;
mod for_loops_over_fallibles;
mod foreign_modules;
mod fundamental_functions;
mod if_let_rescope;
mod impl_trait_overcaptures;
mod internal;
Expand Down Expand Up @@ -92,6 +93,7 @@ use deref_into_dyn_supertrait::*;
use drop_forget_useless::*;
use enum_intrinsics_non_enums::EnumIntrinsicsNonEnums;
use for_loops_over_fallibles::*;
use fundamental_functions::*;
use if_let_rescope::IfLetRescope;
use impl_trait_overcaptures::ImplTraitOvercaptures;
use internal::*;
Expand Down Expand Up @@ -240,6 +242,7 @@ late_lint_methods!(
AsyncClosureUsage: AsyncClosureUsage,
AsyncFnInTrait: AsyncFnInTrait,
NonLocalDefinitions: NonLocalDefinitions::default(),
FundamentalFunctions: FundamentalFunctions,
ImplTraitOvercaptures: ImplTraitOvercaptures,
IfLetRescope: IfLetRescope::default(),
StaticMutRefs: StaticMutRefs,
Expand Down
10 changes: 10 additions & 0 deletions compiler/rustc_lint/src/lints.rs
Original file line number Diff line number Diff line change
Expand Up @@ -705,6 +705,16 @@ pub(crate) enum UseLetUnderscoreIgnoreSuggestion {
},
}

// fundamental_functions.rs
#[derive(LintDiagnostic)]
#[diag(lint_clashing_function_names_with_fundamental_functions)]
#[note(lint_match_exactly)]
#[note(lint_learn_more)]
#[help]
pub(crate) struct ClashingFunctionNamesWithFundamentalFunctions {
pub symbol_name: String,
}

// drop_forget_useless.rs
#[derive(LintDiagnostic)]
#[diag(lint_dropping_references)]
Expand Down
47 changes: 30 additions & 17 deletions compiler/rustc_symbol_mangling/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ use rustc_hir::def_id::{CrateNum, LOCAL_CRATE};
use rustc_middle::middle::codegen_fn_attrs::{CodegenFnAttrFlags, CodegenFnAttrs};
use rustc_middle::mir::mono::{InstantiationMode, MonoItem};
use rustc_middle::query::Providers;
use rustc_middle::ty::{self, Instance, TyCtxt};
use rustc_middle::ty::{self, Instance, InstanceKind, TyCtxt};
use rustc_session::config::SymbolManglingVersion;
use tracing::debug;

Expand Down Expand Up @@ -158,29 +158,22 @@ pub fn typeid_for_trait_ref<'tcx>(
v0::mangle_typeid_for_trait_ref(tcx, trait_ref)
}

/// Computes the symbol name for the given instance. This function will call
/// `compute_instantiating_crate` if it needs to factor the instantiating crate
/// into the symbol name.
fn compute_symbol_name<'tcx>(
pub fn symbol_name_without_mangling<'tcx>(
tcx: TyCtxt<'tcx>,
instance: Instance<'tcx>,
compute_instantiating_crate: impl FnOnce() -> CrateNum,
) -> String {
let def_id = instance.def_id();
let args = instance.args;

debug!("symbol_name(def_id={:?}, args={:?})", def_id, args);
instance_kind: InstanceKind<'tcx>,
) -> Option<String> {
let def_id = instance_kind.def_id();

if let Some(def_id) = def_id.as_local() {
if tcx.proc_macro_decls_static(()) == Some(def_id) {
let stable_crate_id = tcx.stable_crate_id(LOCAL_CRATE);
return tcx.sess.generate_proc_macro_decls_symbol(stable_crate_id);
return Some(tcx.sess.generate_proc_macro_decls_symbol(stable_crate_id));
}
}

// FIXME(eddyb) Precompute a custom symbol name based on attributes.
let attrs = if tcx.def_kind(def_id).has_codegen_attrs() {
&tcx.codegen_instance_attrs(instance.def)
&tcx.codegen_instance_attrs(instance_kind)
} else {
CodegenFnAttrs::EMPTY
};
Expand All @@ -206,7 +199,7 @@ fn compute_symbol_name<'tcx>(
// legacy symbol mangling scheme.
let name = if let Some(name) = attrs.symbol_name { name } else { tcx.item_name(def_id) };

return v0::mangle_internal_symbol(tcx, name.as_str());
return Some(v0::mangle_internal_symbol(tcx, name.as_str()));
}

let wasm_import_module_exception_force_mangling = {
Expand Down Expand Up @@ -234,15 +227,35 @@ fn compute_symbol_name<'tcx>(
if !wasm_import_module_exception_force_mangling {
if let Some(name) = attrs.symbol_name {
// Use provided name
return name.to_string();
return Some(name.to_string());
}

if attrs.flags.contains(CodegenFnAttrFlags::NO_MANGLE) {
// Don't mangle
return tcx.item_name(def_id).to_string();
return Some(tcx.item_name(def_id).to_string());
}
}

None
}

/// Computes the symbol name for the given instance. This function will call
/// `compute_instantiating_crate` if it needs to factor the instantiating crate
/// into the symbol name.
fn compute_symbol_name<'tcx>(
tcx: TyCtxt<'tcx>,
instance: Instance<'tcx>,
compute_instantiating_crate: impl FnOnce() -> CrateNum,
) -> String {
let def_id = instance.def_id();
let args = instance.args;

debug!("symbol_name(def_id={:?}, args={:?})", def_id, args);

if let Some(symbol) = symbol_name_without_mangling(tcx, instance.def) {
return symbol;
}

// If we're dealing with an instance of a function that's inlined from
// another crate but we're marking it as globally shared to our
// compilation (aka we're not making an internal copy in each of our
Expand Down
59 changes: 59 additions & 0 deletions tests/ui/lint/clashing-fn-names-with-fundamental-functions.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
//@ check-pass
//@ edition: 2021

use std::ffi::c_void;

// From core

#[no_mangle]
pub extern "C" fn memcpy(
dest: *mut c_void,
src: *const c_void,
n: i64,
) -> *mut c_void { std::ptr::null_mut() }
//~^^^^^ WARN `memcpy` clashes

#[no_mangle]
pub fn memmove() {}
//~^ WARN `memmove` clashes

#[no_mangle]
pub fn memset() {}
//~^ WARN `memset` clashes

#[no_mangle]
pub fn memcmp() {}
//~^ WARN `memcmp` clashes

#[export_name = "bcmp"]
pub fn bcmp_() {}
//~^ WARN `bcmp` clashes

#[no_mangle]
pub fn strlen() {}
//~^ WARN `strlen` clashes

// From std

#[no_mangle]
pub fn open() {}
//~^ WARN `open` clashes

#[export_name = "read"]
pub async fn read1() {}
//~^ WARN `read` clashes

#[export_name = "write"]
pub fn write1() {}
//~^ WARN `write` clashes

#[export_name = "close"]
pub fn close_() {}
//~^ WARN `close` clashes

extern "C" {
// No warning, not a body.
pub fn close(a: i32) -> i32;
}

fn main() {}
Loading