Skip to content

Commit d3bea8c

Browse files
committed
Auto merge of #155521 - Urgau:runtime-symbols, r=<try>
Add lint againts invalid runtime symbol definitions try-job: x86_64-rust-for-linux
2 parents ac6f3a3 + 56403c1 commit d3bea8c

23 files changed

Lines changed: 594 additions & 64 deletions

File tree

Cargo.lock

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4268,6 +4268,7 @@ dependencies = [
42684268
"rustc_parse_format",
42694269
"rustc_session",
42704270
"rustc_span",
4271+
"rustc_symbol_mangling",
42714272
"rustc_target",
42724273
"rustc_trait_selection",
42734274
"smallvec",

compiler/rustc_error_codes/src/error_codes/E0755.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ side effects or infinite loops:
2020
2121
extern "C" {
2222
#[unsafe(ffi_pure)] // ok!
23-
pub fn strlen(s: *const i8) -> isize;
23+
pub fn strlen(s: *const std::ffi::c_char) -> usize;
2424
}
2525
# fn main() {}
2626
```

compiler/rustc_error_codes/src/error_codes/E0756.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ which have no side effects except for their return value:
2121
2222
extern "C" {
2323
#[unsafe(ffi_const)] // ok!
24-
pub fn strlen(s: *const i8) -> i32;
24+
pub fn strlen(s: *const std::ffi::c_char) -> usize;
2525
}
2626
# fn main() {}
2727
```

compiler/rustc_hir/src/lang_items.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -447,6 +447,14 @@ language_item_table! {
447447

448448
// Used to fallback `{float}` to `f32` when `f32: From<{float}>`
449449
From, sym::From, from_trait, Target::Trait, GenericRequirement::Exact(1);
450+
451+
// Runtime symbols
452+
MemCpy, sym::memcpy_fn, memcpy_fn, Target::Fn, GenericRequirement::None;
453+
MemMove, sym::memmove_fn, memmove_fn, Target::Fn, GenericRequirement::None;
454+
MemSet, sym::memset_fn, memset_fn, Target::Fn, GenericRequirement::None;
455+
MemCmp, sym::memcmp_fn, memcmp_fn, Target::Fn, GenericRequirement::None;
456+
Bcmp, sym::bcmp_fn, bcmp_fn, Target::Fn, GenericRequirement::None;
457+
StrLen, sym::strlen_fn, strlen_fn, Target::Fn, GenericRequirement::None;
450458
}
451459

452460
/// The requirement imposed on the generics of a lang item

compiler/rustc_hir/src/weak_lang_items.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,28 @@ macro_rules! weak_lang_items {
2323
}
2424
}
2525

26+
macro_rules! weak_only_lang_items {
27+
($($item:ident,)*) => {
28+
pub static WEAK_ONLY_LANG_ITEMS: &[LangItem] = &[$(LangItem::$item,)*];
29+
30+
impl LangItem {
31+
pub fn is_weak_only(self) -> bool {
32+
matches!(self, $(LangItem::$item)|*)
33+
}
34+
}
35+
}
36+
}
37+
2638
weak_lang_items! {
2739
PanicImpl, rust_begin_unwind;
2840
EhPersonality, rust_eh_personality;
2941
}
42+
43+
weak_only_lang_items! {
44+
MemCpy,
45+
MemMove,
46+
MemSet,
47+
MemCmp,
48+
Bcmp,
49+
StrLen,
50+
}

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
@@ -72,6 +72,7 @@ mod precedence;
7272
mod ptr_nulls;
7373
mod redundant_semicolon;
7474
mod reference_casting;
75+
mod runtime_symbols;
7576
mod shadowed_into_iter;
7677
mod static_mut_refs;
7778
mod traits;
@@ -117,6 +118,7 @@ use precedence::*;
117118
use ptr_nulls::*;
118119
use redundant_semicolon::*;
119120
use reference_casting::*;
121+
use runtime_symbols::*;
120122
use rustc_hir::def_id::LocalModDefId;
121123
use rustc_middle::query::Providers;
122124
use rustc_middle::ty::TyCtxt;
@@ -246,6 +248,7 @@ late_lint_methods!(
246248
AsyncFnInTrait: AsyncFnInTrait,
247249
NonLocalDefinitions: NonLocalDefinitions::default(),
248250
InteriorMutableConsts: InteriorMutableConsts,
251+
RuntimeSymbols: RuntimeSymbols,
249252
ImplTraitOvercaptures: ImplTraitOvercaptures,
250253
IfLetRescope: IfLetRescope::default(),
251254
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: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
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 symbol expected by `core`.
15+
///
16+
/// ### Example
17+
///
18+
#[cfg_attr(bootstrap, doc = "```rust")]
19+
#[cfg_attr(not(bootstrap), doc = "```rust,compile_fail")]
20+
#[cfg_attr(not(bootstrap), doc = "#[unsafe(no_mangle)]")]
21+
/// pub fn strlen() {} // invalid definition of the `strlen` function
22+
#[doc = "```"]
23+
///
24+
/// {{produces}}
25+
///
26+
/// ### Explanation
27+
///
28+
/// Up-most care is required when defining runtime symbols assumed and
29+
/// used by the standard library. They must follow the C specification, not use any
30+
/// standard-library facility or undefined behavior may occur.
31+
///
32+
/// The symbols currently checked are `memcpy`, `memmove`, `memset`, `memcmp`,
33+
/// `bcmp` and `strlen`.
34+
///
35+
/// [^1]: https://doc.rust-lang.org/core/index.html#how-to-use-the-core-library
36+
pub INVALID_RUNTIME_SYMBOL_DEFINITIONS,
37+
Deny,
38+
"invalid definition of a symbol used by the standard library"
39+
}
40+
41+
declare_lint_pass!(RuntimeSymbols => [INVALID_RUNTIME_SYMBOL_DEFINITIONS]);
42+
43+
static EXPECTED_SYMBOLS: &[ExpectedSymbol] = &[
44+
ExpectedSymbol { symbol: "memcpy", lang: LanguageItems::memcpy_fn },
45+
ExpectedSymbol { symbol: "memmove", lang: LanguageItems::memmove_fn },
46+
ExpectedSymbol { symbol: "memset", lang: LanguageItems::memset_fn },
47+
ExpectedSymbol { symbol: "memcmp", lang: LanguageItems::memcmp_fn },
48+
ExpectedSymbol { symbol: "bcmp", lang: LanguageItems::bcmp_fn },
49+
ExpectedSymbol { symbol: "strlen", lang: LanguageItems::strlen_fn },
50+
];
51+
52+
#[derive(Copy, Clone, Debug)]
53+
struct ExpectedSymbol {
54+
symbol: &'static str,
55+
lang: fn(&LanguageItems) -> Option<DefId>,
56+
}
57+
58+
impl<'tcx> LateLintPass<'tcx> for RuntimeSymbols {
59+
fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::Item<'tcx>) {
60+
// Bail-out if the item is not a function/method or static.
61+
match item.kind {
62+
hir::ItemKind::Fn { sig, ident: _, generics, body: _, has_body: _ } => {
63+
// Generic functions cannot have the same runtime symbol as we do not allow
64+
// any symbol attributes.
65+
if !generics.params.is_empty() {
66+
return;
67+
}
68+
69+
// Try to get the overridden symbol name of this function (our mangling
70+
// cannot ever conflict with runtime symbols, so no need to check for those).
71+
let Some(symbol_name) = rustc_symbol_mangling::symbol_name_from_attrs(
72+
cx.tcx,
73+
rustc_middle::ty::InstanceKind::Item(item.owner_id.to_def_id()),
74+
) else {
75+
return;
76+
};
77+
78+
check_fn(cx, &symbol_name, sig, item.owner_id.def_id);
79+
}
80+
hir::ItemKind::Static(..) => {
81+
// Compute the symbol name of this static (without mangling, as our mangling
82+
// cannot ever conflict with runtime symbols).
83+
let Some(symbol_name) = rustc_symbol_mangling::symbol_name_from_attrs(
84+
cx.tcx,
85+
rustc_middle::ty::InstanceKind::Item(item.owner_id.to_def_id()),
86+
) else {
87+
return;
88+
};
89+
90+
let def_id = item.owner_id.def_id;
91+
92+
check_static(cx, &symbol_name, def_id, 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+
check_static(cx, &symbol_name.name, did, item.span);
111+
}
112+
ForeignItemKind::Type => return,
113+
}
114+
}
115+
}
116+
_ => return,
117+
}
118+
}
119+
}
120+
121+
fn check_fn(cx: &LateContext<'_>, symbol_name: &str, sig: FnSig<'_>, did: LocalDefId) {
122+
let Some(expected_symbol) = EXPECTED_SYMBOLS.iter().find(|es| es.symbol == symbol_name) else {
123+
// The symbol name does not correspond to a runtime symbols, bail out
124+
return;
125+
};
126+
127+
let Some(expected_def_id) = (expected_symbol.lang)(&cx.tcx.lang_items()) else {
128+
// Can't find the corresponding language item, bail out
129+
return;
130+
};
131+
132+
// Get the two function signatures
133+
let lang_sig = cx.tcx.normalize_erasing_regions(
134+
cx.typing_env(),
135+
cx.tcx.fn_sig(expected_def_id).instantiate_identity(),
136+
);
137+
let user_sig = cx
138+
.tcx
139+
.normalize_erasing_regions(cx.typing_env(), cx.tcx.fn_sig(did).instantiate_identity());
140+
141+
// Compare the two signatures with an inference context
142+
let infcx = cx.tcx.infer_ctxt().build(cx.typing_mode());
143+
let cause = rustc_middle::traits::ObligationCause::misc(sig.span, did);
144+
let result = infcx.at(&cause, cx.param_env).eq(DefineOpaqueTypes::No, lang_sig, user_sig);
145+
146+
// If they don't match, emit our own mismatch signatures
147+
if let Err(_terr) = result {
148+
// Create fn pointers for diagnostics purpose
149+
let expected = Ty::new_fn_ptr(cx.tcx, lang_sig);
150+
let actual = Ty::new_fn_ptr(cx.tcx, user_sig);
151+
152+
cx.emit_span_lint(
153+
INVALID_RUNTIME_SYMBOL_DEFINITIONS,
154+
sig.span,
155+
RedefiningRuntimeSymbolsDiag::FnDef {
156+
symbol_name: symbol_name.to_string(),
157+
found_fn_sig: actual,
158+
expected_fn_sig: expected,
159+
},
160+
);
161+
}
162+
}
163+
164+
fn check_static<'tcx>(cx: &LateContext<'tcx>, symbol_name: &str, did: LocalDefId, sp: Span) {
165+
let Some(expected_symbol) = EXPECTED_SYMBOLS.iter().find(|es| es.symbol == symbol_name) else {
166+
// The symbol name does not correspond to a runtime symbols, bail out
167+
return;
168+
};
169+
170+
let Some(expected_def_id) = (expected_symbol.lang)(&cx.tcx.lang_items()) else {
171+
// Can't find the corresponding language item, bail out
172+
return;
173+
};
174+
175+
// Get the static type
176+
let static_ty = cx.tcx.type_of(did).instantiate_identity().skip_norm_wip();
177+
178+
// Peel Option<...> and get the inner type (see std weak! macro with #[linkage = "extern_weak"])
179+
let inner_static_ty: Ty<'_> = match static_ty.kind() {
180+
ty::Adt(def, args) if Some(def.did()) == cx.tcx.lang_items().option_type() => {
181+
args.type_at(0)
182+
}
183+
_ => static_ty,
184+
};
185+
186+
// Get the expected symbol function signature
187+
let lang_sig = cx.tcx.normalize_erasing_regions(
188+
cx.typing_env(),
189+
cx.tcx.fn_sig(expected_def_id).instantiate_identity(),
190+
);
191+
192+
let expected = Ty::new_fn_ptr(cx.tcx, lang_sig);
193+
194+
// Compare the expected function signature with the static type, report an error if they don't match
195+
if expected != inner_static_ty {
196+
cx.emit_span_lint(
197+
INVALID_RUNTIME_SYMBOL_DEFINITIONS,
198+
sp,
199+
RedefiningRuntimeSymbolsDiag::Static {
200+
static_ty,
201+
symbol_name: symbol_name.to_string(),
202+
expected_fn_sig: expected,
203+
},
204+
);
205+
}
206+
}

0 commit comments

Comments
 (0)