|
| 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