Skip to main content

rustc_lint/
runtime_symbols.rs

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