Skip to main content

rustc_lint/
runtime_symbols.rs

1use rustc_hir::def_id::{DefId, LocalDefId};
2use rustc_hir::{self as hir, FnSig, ForeignItemKind, LanguageItems};
3use rustc_infer::infer::DefineOpaqueTypes;
4use rustc_middle::ty::{self, Instance, Ty};
5use rustc_session::{declare_lint, declare_lint_pass};
6use rustc_span::Span;
7use rustc_trait_selection::infer::TyCtxtInferExt;
8
9use crate::lints::RedefiningRuntimeSymbolsDiag;
10use crate::{LateContext, LateLintPass, LintContext};
11
12#[doc =
r" The `invalid_runtime_symbol_definitions` lint checks the signature of items whose"]
#[doc =
r" symbol name is a runtime symbol expected by `core` differs significantly from the"]
#[doc =
r" expected signature (like mismatch ABI, mismatch C variadics, mismatch argument count,"]
#[doc = r" missing return type, ...)."]
#[doc = r""]
#[doc = r" ### Example"]
#[doc = r""]
#[doc = "```rust,compile_fail"]
#[doc = "#[unsafe(no_mangle)]"]
#[doc = r" pub fn strlen() {} // invalid definition of the `strlen` function"]
#[doc = "```"]
#[doc = r""]
#[doc = r" {{produces}}"]
#[doc = r""]
#[doc = r" ### Explanation"]
#[doc = r""]
#[doc =
r" Up-most care is required when defining runtime symbols assumed and"]
#[doc =
r" used by the standard library. They must follow the C specification, not use any"]
#[doc = r" standard-library facility or undefined behavior may occur."]
#[doc = r""]
#[doc =
r" The symbols currently checked are `memcpy`, `memmove`, `memset`, `memcmp`,"]
#[doc = r" `bcmp` and `strlen`."]
#[doc = r""]
#[doc =
r" [^1]: https://doc.rust-lang.org/core/index.html#how-to-use-the-core-library"]
pub static INVALID_RUNTIME_SYMBOL_DEFINITIONS: &::rustc_lint_defs::Lint =
    &::rustc_lint_defs::Lint {
            name: "INVALID_RUNTIME_SYMBOL_DEFINITIONS",
            default_level: ::rustc_lint_defs::Deny,
            desc: "invalid definition of a symbol used by the standard library",
            is_externally_loaded: false,
            ..::rustc_lint_defs::Lint::default_fields_for_macro()
        };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` differs significantly from the
15    /// expected signature (like mismatch ABI, mismatch C variadics, mismatch argument count,
16    /// missing return type, ...).
17    ///
18    /// ### Example
19    ///
20    #[cfg_attr(bootstrap, doc = "```rust")]
21    #[cfg_attr(not(bootstrap), doc = "```rust,compile_fail")]
22    #[cfg_attr(not(bootstrap), doc = "#[unsafe(no_mangle)]")]
23    /// pub fn strlen() {} // invalid definition of the `strlen` function
24    #[doc = "```"]
25    ///
26    /// {{produces}}
27    ///
28    /// ### Explanation
29    ///
30    /// Up-most care is required when defining runtime symbols assumed and
31    /// used by the standard library. They must follow the C specification, not use any
32    /// standard-library facility or undefined behavior may occur.
33    ///
34    /// The symbols currently checked are `memcpy`, `memmove`, `memset`, `memcmp`,
35    /// `bcmp` and `strlen`.
36    ///
37    /// [^1]: https://doc.rust-lang.org/core/index.html#how-to-use-the-core-library
38    pub INVALID_RUNTIME_SYMBOL_DEFINITIONS,
39    Deny,
40    "invalid definition of a symbol used by the standard library"
41}
42
43#[doc =
r" The `suspicious_runtime_symbol_definitions` lint checks the signature of items whose"]
#[doc = r" symbol name is a runtime symbol expected by `core`."]
#[doc = r""]
#[doc = r" ### Example"]
#[doc = r""]
#[doc = r" ```rust,no_run,standalone_crate"]
#[doc = "#[unsafe(no_mangle)]"]
#[doc = r#" pub extern "C" fn strlen(ptr: *mut f32) -> usize { 0 }"#]
#[doc = r" // suspicious definition of the `strlen` function"]
#[doc = r" // `ptr` should be `*const std::ffi::c_char`"]
#[doc = r" ```"]
#[doc = r""]
#[doc = r" {{produces}}"]
#[doc = r""]
#[doc = r" ### Explanation"]
#[doc = r""]
#[doc =
r" Up-most care is required when defining runtime symbols assumed and"]
#[doc =
r" used by the standard library. They must follow the C specification, not use any"]
#[doc = r" standard-library facility or undefined behavior may occur."]
#[doc = r""]
#[doc =
r" The symbols currently checked are `memcpy`, `memmove`, `memset`, `memcmp`,"]
#[doc = r" `bcmp` and `strlen`."]
#[doc = r""]
#[doc =
r" [^1]: https://doc.rust-lang.org/core/index.html#how-to-use-the-core-library"]
pub static SUSPICIOUS_RUNTIME_SYMBOL_DEFINITIONS: &::rustc_lint_defs::Lint =
    &::rustc_lint_defs::Lint {
            name: "SUSPICIOUS_RUNTIME_SYMBOL_DEFINITIONS",
            default_level: ::rustc_lint_defs::Warn,
            desc: "suspicious definition of a symbol used by the standard library",
            is_externally_loaded: false,
            ..::rustc_lint_defs::Lint::default_fields_for_macro()
        };declare_lint! {
44    /// The `suspicious_runtime_symbol_definitions` lint checks the signature of items whose
45    /// symbol name is a runtime symbol expected by `core`.
46    ///
47    /// ### Example
48    ///
49    /// ```rust,no_run,standalone_crate
50    #[cfg_attr(not(bootstrap), doc = "#[unsafe(no_mangle)]")]
51    /// pub extern "C" fn strlen(ptr: *mut f32) -> usize { 0 }
52    /// // suspicious definition of the `strlen` function
53    /// // `ptr` should be `*const std::ffi::c_char`
54    /// ```
55    ///
56    /// {{produces}}
57    ///
58    /// ### Explanation
59    ///
60    /// Up-most care is required when defining runtime symbols assumed and
61    /// used by the standard library. They must follow the C specification, not use any
62    /// standard-library facility or undefined behavior may occur.
63    ///
64    /// The symbols currently checked are `memcpy`, `memmove`, `memset`, `memcmp`,
65    /// `bcmp` and `strlen`.
66    ///
67    /// [^1]: https://doc.rust-lang.org/core/index.html#how-to-use-the-core-library
68    pub SUSPICIOUS_RUNTIME_SYMBOL_DEFINITIONS,
69    Warn,
70    "suspicious definition of a symbol used by the standard library"
71}
72
73pub struct RuntimeSymbols;
#[automatically_derived]
impl ::core::marker::Copy for RuntimeSymbols { }
#[automatically_derived]
#[doc(hidden)]
unsafe impl ::core::clone::TrivialClone for RuntimeSymbols { }
#[automatically_derived]
impl ::core::clone::Clone for RuntimeSymbols {
    #[inline]
    fn clone(&self) -> RuntimeSymbols { *self }
}
impl ::rustc_lint_defs::LintPass for RuntimeSymbols {
    fn name(&self) -> &'static str { "RuntimeSymbols" }
    fn get_lints(&self) -> ::rustc_lint_defs::LintVec {
        ::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
                [INVALID_RUNTIME_SYMBOL_DEFINITIONS,
                        SUSPICIOUS_RUNTIME_SYMBOL_DEFINITIONS]))
    }
}
impl RuntimeSymbols {
    #[allow(unused)]
    pub fn lint_vec() -> ::rustc_lint_defs::LintVec {
        ::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
                [INVALID_RUNTIME_SYMBOL_DEFINITIONS,
                        SUSPICIOUS_RUNTIME_SYMBOL_DEFINITIONS]))
    }
}declare_lint_pass!(RuntimeSymbols => [INVALID_RUNTIME_SYMBOL_DEFINITIONS, SUSPICIOUS_RUNTIME_SYMBOL_DEFINITIONS]);
74
75static EXPECTED_SYMBOLS: &[ExpectedSymbol] = &[
76    ExpectedSymbol { symbol: "memcpy", lang: LanguageItems::memcpy_fn },
77    ExpectedSymbol { symbol: "memmove", lang: LanguageItems::memmove_fn },
78    ExpectedSymbol { symbol: "memset", lang: LanguageItems::memset_fn },
79    ExpectedSymbol { symbol: "memcmp", lang: LanguageItems::memcmp_fn },
80    ExpectedSymbol { symbol: "bcmp", lang: LanguageItems::bcmp_fn },
81    ExpectedSymbol { symbol: "strlen", lang: LanguageItems::strlen_fn },
82];
83
84#[derive(#[automatically_derived]
impl ::core::marker::Copy for ExpectedSymbol { }Copy, #[automatically_derived]
impl ::core::clone::Clone for ExpectedSymbol {
    #[inline]
    fn clone(&self) -> ExpectedSymbol {
        let _: ::core::clone::AssertParamIsClone<&'static str>;
        let _:
                ::core::clone::AssertParamIsClone<fn(&LanguageItems)
                    -> Option<DefId>>;
        *self
    }
}Clone, #[automatically_derived]
impl ::core::fmt::Debug for ExpectedSymbol {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        ::core::fmt::Formatter::debug_struct_field2_finish(f,
            "ExpectedSymbol", "symbol", &self.symbol, "lang", &&self.lang)
    }
}Debug)]
85struct ExpectedSymbol {
86    symbol: &'static str,
87    lang: fn(&LanguageItems) -> Option<DefId>,
88}
89
90impl<'tcx> LateLintPass<'tcx> for RuntimeSymbols {
91    fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::Item<'tcx>) {
92        // Bail-out if the item is not a function/method or static.
93        match item.kind {
94            hir::ItemKind::Fn { sig, ident: _, generics, body: _, has_body: _ } => {
95                // Generic functions cannot have the same runtime symbol as we do not allow
96                // any symbol attributes.
97                if !generics.params.is_empty() {
98                    return;
99                }
100
101                // Try to get the overridden symbol name of this function (our mangling
102                // cannot ever conflict with runtime symbols, so no need to check for those).
103                let Some(symbol_name) = rustc_symbol_mangling::symbol_name_from_attrs(
104                    cx.tcx,
105                    rustc_middle::ty::InstanceKind::Item(item.owner_id.to_def_id()),
106                ) else {
107                    return;
108                };
109
110                check_fn(cx, &symbol_name, sig, item.owner_id.def_id);
111            }
112            hir::ItemKind::Static(..) => {
113                // Compute the symbol name of this static (without mangling, as our mangling
114                // cannot ever conflict with runtime symbols).
115                let Some(symbol_name) = rustc_symbol_mangling::symbol_name_from_attrs(
116                    cx.tcx,
117                    rustc_middle::ty::InstanceKind::Item(item.owner_id.to_def_id()),
118                ) else {
119                    return;
120                };
121
122                let def_id = item.owner_id.def_id;
123
124                check_static(cx, &symbol_name, def_id, item.span);
125            }
126            hir::ItemKind::ForeignMod { abi: _, items } => {
127                for item in items {
128                    let item = cx.tcx.hir_foreign_item(*item);
129
130                    let did = item.owner_id.def_id;
131                    let instance = Instance::new_raw(
132                        did.to_def_id(),
133                        ty::List::identity_for_item(cx.tcx, did),
134                    );
135                    let symbol_name = cx.tcx.symbol_name(instance);
136
137                    match item.kind {
138                        ForeignItemKind::Fn(fn_sig, _idents, _generics) => {
139                            check_fn(cx, &symbol_name.name, fn_sig, did);
140                        }
141                        ForeignItemKind::Static(..) => {
142                            check_static(cx, &symbol_name.name, did, item.span);
143                        }
144                        ForeignItemKind::Type => return,
145                    }
146                }
147            }
148            _ => return,
149        }
150    }
151}
152
153fn check_fn(cx: &LateContext<'_>, symbol_name: &str, sig: FnSig<'_>, did: LocalDefId) {
154    let Some(expected_symbol) = EXPECTED_SYMBOLS.iter().find(|es| es.symbol == symbol_name) else {
155        // The symbol name does not correspond to a runtime symbols, bail out
156        return;
157    };
158
159    let Some(expected_def_id) = (expected_symbol.lang)(&cx.tcx.lang_items()) else {
160        // Can't find the corresponding language item, bail out
161        return;
162    };
163
164    // Get the two function signatures
165    let lang_sig = cx.tcx.normalize_erasing_regions(
166        cx.typing_env(),
167        cx.tcx.fn_sig(expected_def_id).instantiate_identity(),
168    );
169    let user_sig = cx
170        .tcx
171        .normalize_erasing_regions(cx.typing_env(), cx.tcx.fn_sig(did).instantiate_identity());
172
173    // Compare the two signatures with an inference context
174    let infcx = cx.tcx.infer_ctxt().build(cx.typing_mode());
175    let cause = rustc_middle::traits::ObligationCause::misc(sig.span, did);
176    let result = infcx.at(&cause, cx.param_env).eq(DefineOpaqueTypes::No, lang_sig, user_sig);
177
178    // If they don't match, emit our own mismatch signatures
179    if let Err(_terr) = result {
180        // Create fn pointers for diagnostics purpose
181        let expected = Ty::new_fn_ptr(cx.tcx, lang_sig);
182        let actual = Ty::new_fn_ptr(cx.tcx, user_sig);
183
184        if lang_sig.abi() != user_sig.abi()
185            || lang_sig.c_variadic() != user_sig.c_variadic()
186            || lang_sig.inputs().skip_binder().len() != user_sig.inputs().skip_binder().len()
187            || (!lang_sig.output().skip_binder().is_unit()
188                && user_sig.output().skip_binder().is_unit())
189        {
190            cx.emit_span_lint(
191                INVALID_RUNTIME_SYMBOL_DEFINITIONS,
192                sig.span,
193                RedefiningRuntimeSymbolsDiag::FnDefInvalid {
194                    symbol_name: symbol_name.to_string(),
195                    found_fn_sig: actual,
196                    expected_fn_sig: expected,
197                },
198            );
199        } else {
200            cx.emit_span_lint(
201                SUSPICIOUS_RUNTIME_SYMBOL_DEFINITIONS,
202                sig.span,
203                RedefiningRuntimeSymbolsDiag::FnDefSuspicious {
204                    symbol_name: symbol_name.to_string(),
205                    found_fn_sig: actual,
206                    expected_fn_sig: expected,
207                },
208            );
209        };
210    }
211}
212
213fn check_static<'tcx>(cx: &LateContext<'tcx>, symbol_name: &str, did: LocalDefId, sp: Span) {
214    let Some(expected_symbol) = EXPECTED_SYMBOLS.iter().find(|es| es.symbol == symbol_name) else {
215        // The symbol name does not correspond to a runtime symbols, bail out
216        return;
217    };
218
219    let Some(expected_def_id) = (expected_symbol.lang)(&cx.tcx.lang_items()) else {
220        // Can't find the corresponding language item, bail out
221        return;
222    };
223
224    // Get the static type
225    let static_ty = cx.tcx.type_of(did).instantiate_identity().skip_norm_wip();
226
227    // Peel Option<...> and get the inner type (see std weak! macro with #[linkage = "extern_weak"])
228    let inner_static_ty: Ty<'_> = match static_ty.kind() {
229        ty::Adt(def, args) if Some(def.did()) == cx.tcx.lang_items().option_type() => {
230            args.type_at(0)
231        }
232        _ => static_ty,
233    };
234
235    // Get the expected symbol function signature
236    let lang_sig = cx.tcx.normalize_erasing_regions(
237        cx.typing_env(),
238        cx.tcx.fn_sig(expected_def_id).instantiate_identity(),
239    );
240
241    let expected = Ty::new_fn_ptr(cx.tcx, lang_sig);
242
243    // Compare the expected function signature with the static type, report an error if they don't match
244    if expected != inner_static_ty {
245        cx.emit_span_lint(
246            INVALID_RUNTIME_SYMBOL_DEFINITIONS,
247            sp,
248            RedefiningRuntimeSymbolsDiag::Static {
249                static_ty,
250                symbol_name: symbol_name.to_string(),
251                expected_fn_sig: expected,
252            },
253        );
254    }
255}