Skip to content

Commit 46b7db0

Browse files
committed
Add lint against invalid runtime symbol definitions
1 parent 80048b4 commit 46b7db0

7 files changed

Lines changed: 336 additions & 0 deletions

File tree

Cargo.lock

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4222,6 +4222,7 @@ dependencies = [
42224222
"rustc_parse_format",
42234223
"rustc_session",
42244224
"rustc_span",
4225+
"rustc_symbol_mangling",
42254226
"rustc_target",
42264227
"rustc_trait_selection",
42274228
"smallvec",

compiler/rustc_lint/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ rustc_middle = { path = "../rustc_middle" }
2222
rustc_parse_format = { path = "../rustc_parse_format" }
2323
rustc_session = { path = "../rustc_session" }
2424
rustc_span = { path = "../rustc_span" }
25+
rustc_symbol_mangling = { path = "../rustc_symbol_mangling" }
2526
rustc_target = { path = "../rustc_target" }
2627
rustc_trait_selection = { path = "../rustc_trait_selection" }
2728
smallvec = { version = "1.8.1", features = ["union", "may_dangle"] }

compiler/rustc_lint/src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ mod precedence;
6969
mod ptr_nulls;
7070
mod redundant_semicolon;
7171
mod reference_casting;
72+
mod runtime_symbols;
7273
mod shadowed_into_iter;
7374
mod static_mut_refs;
7475
mod traits;
@@ -112,6 +113,7 @@ use precedence::*;
112113
use ptr_nulls::*;
113114
use redundant_semicolon::*;
114115
use reference_casting::*;
116+
use runtime_symbols::*;
115117
use rustc_hir::def_id::LocalModDefId;
116118
use rustc_middle::query::Providers;
117119
use rustc_middle::ty::TyCtxt;
@@ -241,6 +243,7 @@ late_lint_methods!(
241243
AsyncFnInTrait: AsyncFnInTrait,
242244
NonLocalDefinitions: NonLocalDefinitions::default(),
243245
InteriorMutableConsts: InteriorMutableConsts,
246+
RuntimeSymbols: RuntimeSymbols,
244247
ImplTraitOvercaptures: ImplTraitOvercaptures,
245248
IfLetRescope: IfLetRescope::default(),
246249
StaticMutRefs: StaticMutRefs,

compiler/rustc_lint/src/lints.rs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -848,6 +848,33 @@ pub(crate) enum UseLetUnderscoreIgnoreSuggestion {
848848
},
849849
}
850850

851+
// runtime_symbols.rs
852+
#[derive(Diagnostic)]
853+
pub(crate) enum RedefiningRuntimeSymbolsDiag<'tcx> {
854+
#[diag(
855+
"invalid definition of the runtime `{$symbol_name}` symbol used by the standard library"
856+
)]
857+
#[note(
858+
"expected `{$expected_fn_sig}`
859+
found `{$found_fn_sig}`"
860+
)]
861+
#[help(
862+
"either fix the signature or remove any attributes like `#[unsafe(no_mangle)]`, `#[unsafe(export_name = \"{$symbol_name}\")]`, or `#[link_name = \"{$symbol_name}\"]`"
863+
)]
864+
FnDef { symbol_name: String, expected_fn_sig: Ty<'tcx>, found_fn_sig: Ty<'tcx> },
865+
#[diag(
866+
"invalid definition of the runtime `{$symbol_name}` symbol used by the standard library"
867+
)]
868+
#[note(
869+
"expected `{$expected_fn_sig}`
870+
found `static {$symbol_name}: {$static_ty}`"
871+
)]
872+
#[help(
873+
"either fix the signature or remove any attributes `#[unsafe(no_mangle)]` or `#[unsafe(export_name = \"{$symbol_name}\")]`"
874+
)]
875+
Static { symbol_name: String, static_ty: Ty<'tcx>, expected_fn_sig: Ty<'tcx> },
876+
}
877+
851878
// drop_forget_useless.rs
852879
#[derive(Diagnostic)]
853880
#[diag("calls to `std::mem::drop` with a reference instead of an owned value does nothing")]
Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
use rustc_hir::def_id::{DefId, LocalDefId};
2+
use rustc_hir::{self as hir, FnSig, ForeignItemKind, LanguageItems};
3+
use rustc_infer::infer::DefineOpaqueTypes;
4+
use rustc_middle::ty::{self, Instance, Ty};
5+
use rustc_session::{declare_lint, declare_lint_pass};
6+
use rustc_span::Span;
7+
use rustc_trait_selection::infer::TyCtxtInferExt;
8+
9+
use crate::lints::RedefiningRuntimeSymbolsDiag;
10+
use crate::{LateContext, LateLintPass, LintContext};
11+
12+
declare_lint! {
13+
/// The `invalid_runtime_symbol_definitions` lint checks the signature of items whose
14+
/// symbol name is a runtime symbols expected by `core`.
15+
///
16+
/// ### Example
17+
///
18+
/// ```rust,compile_fail
19+
/// #[unsafe(no_mangle)]
20+
/// pub fn strlen() {} // invalid definition of the `strlen` function
21+
/// ```
22+
///
23+
/// {{produces}}
24+
///
25+
/// ### Explanation
26+
///
27+
/// Up-most care is required when defining runtime symbols assumed and
28+
/// used by the standard library. They must follow the C specification, not use any
29+
/// standard-library facility or undefined behavior may occur.
30+
///
31+
/// The symbols currently checked are `memcpy`, `memmove`, `memset`, `memcmp`,
32+
/// `bcmp` and `strlen`.
33+
///
34+
/// [^1]: https://doc.rust-lang.org/core/index.html#how-to-use-the-core-library
35+
pub INVALID_RUNTIME_SYMBOL_DEFINITIONS,
36+
Deny,
37+
"invalid definition of a symbol used by the standard library"
38+
}
39+
40+
declare_lint_pass!(RuntimeSymbols => [INVALID_RUNTIME_SYMBOL_DEFINITIONS]);
41+
42+
static EXPECTED_SYMBOLS: &[ExpectedSymbol] = &[
43+
ExpectedSymbol { symbol: "memcpy", lang: LanguageItems::memcpy_fn },
44+
ExpectedSymbol { symbol: "memmove", lang: LanguageItems::memmove_fn },
45+
ExpectedSymbol { symbol: "memset", lang: LanguageItems::memset_fn },
46+
ExpectedSymbol { symbol: "memcmp", lang: LanguageItems::memcmp_fn },
47+
ExpectedSymbol { symbol: "bcmp", lang: LanguageItems::bcmp_fn },
48+
ExpectedSymbol { symbol: "strlen", lang: LanguageItems::strlen_fn },
49+
];
50+
51+
#[derive(Copy, Clone, Debug)]
52+
struct ExpectedSymbol {
53+
symbol: &'static str,
54+
lang: fn(&LanguageItems) -> Option<DefId>,
55+
}
56+
57+
impl<'tcx> LateLintPass<'tcx> for RuntimeSymbols {
58+
fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::Item<'tcx>) {
59+
// Bail-out if the item is not a function/method or static.
60+
match item.kind {
61+
hir::ItemKind::Fn { sig, ident: _, generics, body: _, has_body: _ } => {
62+
// Generic functions cannot have the same runtime symbol as we do not allow
63+
// any symbol attributes.
64+
if !generics.params.is_empty() {
65+
return;
66+
}
67+
68+
// Try to the overridden symbol name of this function (our mangling
69+
// cannot ever conflict with runtime symbols, so no need to check for those).
70+
let Some(symbol_name) = rustc_symbol_mangling::symbol_name_from_attrs(
71+
cx.tcx,
72+
rustc_middle::ty::InstanceKind::Item(item.owner_id.to_def_id()),
73+
) else {
74+
return;
75+
};
76+
77+
check_fn(cx, &symbol_name, sig, item.owner_id.def_id);
78+
}
79+
hir::ItemKind::Static(..) => {
80+
// Compute the symbol name of this static (without mangling, as our mangling
81+
// cannot ever conflict with runtime symbols).
82+
let Some(symbol_name) = rustc_symbol_mangling::symbol_name_from_attrs(
83+
cx.tcx,
84+
rustc_middle::ty::InstanceKind::Item(item.owner_id.to_def_id()),
85+
) else {
86+
return;
87+
};
88+
89+
let def_id = item.owner_id.def_id;
90+
let static_ty = cx.tcx.type_of(def_id).instantiate_identity();
91+
92+
check_static(cx, &symbol_name, static_ty, item.span);
93+
}
94+
hir::ItemKind::ForeignMod { abi: _, items } => {
95+
for item in items {
96+
let item = cx.tcx.hir_foreign_item(*item);
97+
98+
let did = item.owner_id.def_id;
99+
let instance = Instance::new_raw(
100+
did.to_def_id(),
101+
ty::List::identity_for_item(cx.tcx, did),
102+
);
103+
let symbol_name = cx.tcx.symbol_name(instance);
104+
105+
match item.kind {
106+
ForeignItemKind::Fn(fn_sig, _idents, _generics) => {
107+
check_fn(cx, &symbol_name.name, fn_sig, did);
108+
}
109+
ForeignItemKind::Static(..) => {
110+
let def_id = item.owner_id.def_id;
111+
let static_ty = cx.tcx.type_of(def_id).instantiate_identity();
112+
check_static(cx, &symbol_name.name, static_ty, item.span);
113+
}
114+
ForeignItemKind::Type => return,
115+
}
116+
}
117+
}
118+
_ => return,
119+
}
120+
}
121+
}
122+
123+
fn check_fn(cx: &LateContext<'_>, symbol_name: &str, sig: FnSig<'_>, did: LocalDefId) {
124+
let Some(expected_symbol) = EXPECTED_SYMBOLS.iter().find(|es| es.symbol == symbol_name) else {
125+
// The symbol name does not correspond to a runtime symbols, bail out
126+
return;
127+
};
128+
129+
let Some(expected_def_id) = (expected_symbol.lang)(&cx.tcx.lang_items()) else {
130+
// Can't find the corresponding language item, bail out
131+
return;
132+
};
133+
134+
// Get the two function signatures
135+
let lang_sig =
136+
cx.tcx.erase_and_anonymize_regions(cx.tcx.fn_sig(expected_def_id).instantiate_identity());
137+
let user_sig = cx.tcx.erase_and_anonymize_regions(cx.tcx.fn_sig(did).instantiate_identity());
138+
139+
// Compare the two signatures with an inference context
140+
let infcx = cx.tcx.infer_ctxt().build(cx.typing_mode());
141+
let cause = rustc_middle::traits::ObligationCause::misc(sig.span, did);
142+
let result = infcx.at(&cause, cx.param_env).eq(DefineOpaqueTypes::No, lang_sig, user_sig);
143+
144+
// If they don't match, emit our own mismatch signatures
145+
if let Err(_terr) = result {
146+
// Create fn pointers for diagnostics purpose
147+
let expected = Ty::new_fn_ptr(cx.tcx, lang_sig);
148+
let actual = Ty::new_fn_ptr(cx.tcx, user_sig);
149+
150+
cx.emit_span_lint(
151+
INVALID_RUNTIME_SYMBOL_DEFINITIONS,
152+
sig.span,
153+
RedefiningRuntimeSymbolsDiag::FnDef {
154+
symbol_name: symbol_name.to_string(),
155+
found_fn_sig: actual,
156+
expected_fn_sig: expected,
157+
},
158+
);
159+
}
160+
}
161+
162+
fn check_static<'tcx>(cx: &LateContext<'tcx>, symbol_name: &str, static_ty: Ty<'tcx>, sp: Span) {
163+
let Some(expected_symbol) = EXPECTED_SYMBOLS.iter().find(|es| es.symbol == symbol_name) else {
164+
// The symbol name does not correspond to a runtime symbols, bail out
165+
return;
166+
};
167+
168+
let Some(expected_def_id) = (expected_symbol.lang)(&cx.tcx.lang_items()) else {
169+
// Can't find the corresponding language item, bail out
170+
return;
171+
};
172+
173+
// Unconditionally report a mismatch, a static cannot ever be a function definition
174+
175+
let lang_sig =
176+
cx.tcx.erase_and_anonymize_regions(cx.tcx.fn_sig(expected_def_id).instantiate_identity());
177+
let expected = Ty::new_fn_ptr(cx.tcx, lang_sig);
178+
179+
cx.emit_span_lint(
180+
INVALID_RUNTIME_SYMBOL_DEFINITIONS,
181+
sp,
182+
RedefiningRuntimeSymbolsDiag::Static {
183+
static_ty,
184+
symbol_name: symbol_name.to_string(),
185+
expected_fn_sig: expected,
186+
},
187+
);
188+
}

tests/ui/lint/runtime-symbols.rs

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
// This test checks the runtime symbols lint.
2+
3+
//@ edition: 2021
4+
5+
#![allow(clashing_extern_declarations)] // we are volontary testing differents defs
6+
7+
use core::ffi::{c_char, c_int, c_void};
8+
9+
fn invalid() {
10+
#[no_mangle]
11+
pub extern "C" fn memcpy(dest: *mut c_void, src: *const c_void, n: i64) -> *mut c_void {
12+
std::ptr::null_mut()
13+
}
14+
//~^^^ ERROR invalid definition of the runtime `memcpy` symbol
15+
16+
#[no_mangle]
17+
pub fn memmove() {}
18+
//~^ ERROR invalid definition of the runtime `memmove` symbol
19+
20+
extern "C" {
21+
pub fn memset();
22+
//~^ ERROR invalid definition of the runtime `memset` symbol
23+
24+
pub fn memcmp();
25+
//~^ ERROR invalid definition of the runtime `memcmp` symbol
26+
}
27+
28+
#[export_name = "bcmp"]
29+
pub fn bcmp_() {}
30+
//~^ ERROR invalid definition of the runtime `bcmp` symbol
31+
32+
#[no_mangle]
33+
pub static strlen: () = ();
34+
//~^ ERROR invalid definition of the runtime `strlen` symbol
35+
}
36+
37+
fn valid() {
38+
extern "C" {
39+
fn memcpy(dest: *mut c_void, src: *const c_void, n: usize) -> *mut c_void;
40+
41+
fn memmove(dest: *mut c_void, src: *const c_void, n: usize) -> *mut c_void;
42+
43+
fn memset(s: *mut c_void, c: c_int, n: usize) -> *mut c_void;
44+
45+
fn memcmp(s1: *const c_void, s2: *const c_void, n: usize) -> c_int;
46+
47+
fn bcmp(s1: *const c_void, s2: *const c_void, n: usize) -> c_int;
48+
49+
fn strlen(s: *const c_char) -> usize;
50+
}
51+
}
52+
53+
fn main() {}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
error: invalid definition of the runtime `memcpy` symbol used by the standard library
2+
--> $DIR/runtime-symbols.rs:11:5
3+
|
4+
LL | pub extern "C" fn memcpy(dest: *mut c_void, src: *const c_void, n: i64) -> *mut c_void {
5+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
6+
|
7+
= note: expected `unsafe extern "C" fn(*mut c_void, *const c_void, usize) -> *mut c_void`
8+
found `extern "C" fn(*mut c_void, *const c_void, i64) -> *mut c_void`
9+
= help: either fix the signature or remove any attributes like `#[unsafe(no_mangle)]`, `#[unsafe(export_name = "memcpy")]`, or `#[link_name = "memcpy"]`
10+
= note: `#[deny(invalid_runtime_symbol_definitions)]` on by default
11+
12+
error: invalid definition of the runtime `memmove` symbol used by the standard library
13+
--> $DIR/runtime-symbols.rs:17:5
14+
|
15+
LL | pub fn memmove() {}
16+
| ^^^^^^^^^^^^^^^^
17+
|
18+
= note: expected `unsafe extern "C" fn(*mut c_void, *const c_void, usize) -> *mut c_void`
19+
found `fn()`
20+
= help: either fix the signature or remove any attributes like `#[unsafe(no_mangle)]`, `#[unsafe(export_name = "memmove")]`, or `#[link_name = "memmove"]`
21+
22+
error: invalid definition of the runtime `memset` symbol used by the standard library
23+
--> $DIR/runtime-symbols.rs:21:9
24+
|
25+
LL | pub fn memset();
26+
| ^^^^^^^^^^^^^^^^
27+
|
28+
= note: expected `unsafe extern "C" fn(*mut c_void, i32, usize) -> *mut c_void`
29+
found `unsafe extern "C" fn()`
30+
= help: either fix the signature or remove any attributes like `#[unsafe(no_mangle)]`, `#[unsafe(export_name = "memset")]`, or `#[link_name = "memset"]`
31+
32+
error: invalid definition of the runtime `memcmp` symbol used by the standard library
33+
--> $DIR/runtime-symbols.rs:24:9
34+
|
35+
LL | pub fn memcmp();
36+
| ^^^^^^^^^^^^^^^^
37+
|
38+
= note: expected `unsafe extern "C" fn(*const c_void, *const c_void, usize) -> i32`
39+
found `unsafe extern "C" fn()`
40+
= help: either fix the signature or remove any attributes like `#[unsafe(no_mangle)]`, `#[unsafe(export_name = "memcmp")]`, or `#[link_name = "memcmp"]`
41+
42+
error: invalid definition of the runtime `bcmp` symbol used by the standard library
43+
--> $DIR/runtime-symbols.rs:29:5
44+
|
45+
LL | pub fn bcmp_() {}
46+
| ^^^^^^^^^^^^^^
47+
|
48+
= note: expected `unsafe extern "C" fn(*const c_void, *const c_void, usize) -> i32`
49+
found `fn()`
50+
= help: either fix the signature or remove any attributes like `#[unsafe(no_mangle)]`, `#[unsafe(export_name = "bcmp")]`, or `#[link_name = "bcmp"]`
51+
52+
error: invalid definition of the runtime `strlen` symbol used by the standard library
53+
--> $DIR/runtime-symbols.rs:33:5
54+
|
55+
LL | pub static strlen: () = ();
56+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
57+
|
58+
= note: expected `unsafe extern "C" fn(*const i8) -> usize`
59+
found `static strlen: ()`
60+
= help: either fix the signature or remove any attributes `#[unsafe(no_mangle)]` or `#[unsafe(export_name = "strlen")]`
61+
62+
error: aborting due to 6 previous errors
63+

0 commit comments

Comments
 (0)