Skip to content

Commit 856305b

Browse files
committed
Add lint against invalid runtime symbol definitions
1 parent b0d413b commit 856305b

7 files changed

Lines changed: 344 additions & 0 deletions

File tree

Cargo.lock

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4175,6 +4175,7 @@ dependencies = [
41754175
"rustc_parse_format",
41764176
"rustc_session",
41774177
"rustc_span",
4178+
"rustc_symbol_mangling",
41784179
"rustc_target",
41794180
"rustc_trait_selection",
41804181
"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: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
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 = cx.tcx.normalize_erasing_regions(
136+
cx.typing_env(),
137+
cx.tcx.fn_sig(expected_def_id).instantiate_identity(),
138+
);
139+
let user_sig = cx
140+
.tcx
141+
.normalize_erasing_regions(cx.typing_env(), cx.tcx.fn_sig(did).instantiate_identity());
142+
143+
// Compare the two signatures with an inference context
144+
let infcx = cx.tcx.infer_ctxt().build(cx.typing_mode());
145+
let cause = rustc_middle::traits::ObligationCause::misc(sig.span, did);
146+
let result = infcx.at(&cause, cx.param_env).eq(DefineOpaqueTypes::No, lang_sig, user_sig);
147+
148+
// If they don't match, emit our own mismatch signatures
149+
if let Err(_terr) = result {
150+
// Create fn pointers for diagnostics purpose
151+
let expected = Ty::new_fn_ptr(cx.tcx, lang_sig);
152+
let actual = Ty::new_fn_ptr(cx.tcx, user_sig);
153+
154+
cx.emit_span_lint(
155+
INVALID_RUNTIME_SYMBOL_DEFINITIONS,
156+
sig.span,
157+
RedefiningRuntimeSymbolsDiag::FnDef {
158+
symbol_name: symbol_name.to_string(),
159+
found_fn_sig: actual,
160+
expected_fn_sig: expected,
161+
},
162+
);
163+
}
164+
}
165+
166+
fn check_static<'tcx>(cx: &LateContext<'tcx>, symbol_name: &str, static_ty: Ty<'tcx>, sp: Span) {
167+
let Some(expected_symbol) = EXPECTED_SYMBOLS.iter().find(|es| es.symbol == symbol_name) else {
168+
// The symbol name does not correspond to a runtime symbols, bail out
169+
return;
170+
};
171+
172+
let Some(expected_def_id) = (expected_symbol.lang)(&cx.tcx.lang_items()) else {
173+
// Can't find the corresponding language item, bail out
174+
return;
175+
};
176+
177+
// Unconditionally report a mismatch, a static cannot ever be a function definition
178+
179+
let lang_sig = cx.tcx.normalize_erasing_regions(
180+
cx.typing_env(),
181+
cx.tcx.fn_sig(expected_def_id).instantiate_identity(),
182+
);
183+
184+
let expected = Ty::new_fn_ptr(cx.tcx, lang_sig);
185+
186+
cx.emit_span_lint(
187+
INVALID_RUNTIME_SYMBOL_DEFINITIONS,
188+
sp,
189+
RedefiningRuntimeSymbolsDiag::Static {
190+
static_ty,
191+
symbol_name: symbol_name.to_string(),
192+
expected_fn_sig: expected,
193+
},
194+
);
195+
}

tests/ui/lint/runtime-symbols.rs

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
// This test checks the runtime symbols lint.
2+
3+
//@ edition: 2021
4+
//@ normalize-stderr: "\*const [iu]8" -> "*const U8"
5+
6+
#![allow(clashing_extern_declarations)] // we are volontary testing differents defs
7+
8+
use core::ffi::{c_char, c_int, c_void};
9+
10+
fn invalid() {
11+
#[no_mangle]
12+
pub extern "C" fn memcpy(dest: *mut c_void, src: *const c_void, n: i64) -> *mut c_void {
13+
std::ptr::null_mut()
14+
}
15+
//~^^^ ERROR invalid definition of the runtime `memcpy` symbol
16+
17+
#[no_mangle]
18+
pub fn memmove() {}
19+
//~^ ERROR invalid definition of the runtime `memmove` symbol
20+
21+
extern "C" {
22+
pub fn memset();
23+
//~^ ERROR invalid definition of the runtime `memset` symbol
24+
25+
pub fn memcmp();
26+
//~^ ERROR invalid definition of the runtime `memcmp` symbol
27+
}
28+
29+
#[export_name = "bcmp"]
30+
pub fn bcmp_() {}
31+
//~^ ERROR invalid definition of the runtime `bcmp` symbol
32+
33+
#[no_mangle]
34+
pub static strlen: () = ();
35+
//~^ ERROR invalid definition of the runtime `strlen` symbol
36+
}
37+
38+
fn valid() {
39+
extern "C" {
40+
fn memcpy(dest: *mut c_void, src: *const c_void, n: usize) -> *mut c_void;
41+
42+
fn memmove(dest: *mut c_void, src: *const c_void, n: usize) -> *mut c_void;
43+
44+
fn memset(s: *mut c_void, c: c_int, n: usize) -> *mut c_void;
45+
46+
fn memcmp(s1: *const c_void, s2: *const c_void, n: usize) -> c_int;
47+
48+
fn bcmp(s1: *const c_void, s2: *const c_void, n: usize) -> c_int;
49+
50+
fn strlen(s: *const c_char) -> usize;
51+
}
52+
}
53+
54+
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:12: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:18: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:22: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:25: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:30: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:34:5
54+
|
55+
LL | pub static strlen: () = ();
56+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
57+
|
58+
= note: expected `unsafe extern "C" fn(*const U8) -> 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)