diff --git a/compiler/rustc_interface/src/passes.rs b/compiler/rustc_interface/src/passes.rs index fd850d2f39a5f..7e9513428a14d 100644 --- a/compiler/rustc_interface/src/passes.rs +++ b/compiler/rustc_interface/src/passes.rs @@ -281,6 +281,7 @@ fn configure_and_expand( resolver.resolve_crate(&krate); + CStore::from_tcx(tcx).report_incompatible_target_modifiers(tcx, &krate, resolver.lint_buffer()); krate } diff --git a/compiler/rustc_lint/messages.ftl b/compiler/rustc_lint/messages.ftl index c4d709aa1f985..261b755e4b8da 100644 --- a/compiler/rustc_lint/messages.ftl +++ b/compiler/rustc_lint/messages.ftl @@ -415,6 +415,8 @@ lint_improper_ctypes_union_layout_help = consider adding a `#[repr(C)]` or `#[re lint_improper_ctypes_union_layout_reason = this union has unspecified layout lint_improper_ctypes_union_non_exhaustive = this union is non-exhaustive +lint_incompatible_target_modifiers = crate `{$local_crate}` has incompatible target modifier with extern crate `{$extern_crate}`: `{$tmod}` + lint_incomplete_include = include macro expected single expression in source diff --git a/compiler/rustc_lint/src/context/diagnostics.rs b/compiler/rustc_lint/src/context/diagnostics.rs index 565c3c0425256..77bd1b0ca90ef 100644 --- a/compiler/rustc_lint/src/context/diagnostics.rs +++ b/compiler/rustc_lint/src/context/diagnostics.rs @@ -422,6 +422,10 @@ pub(super) fn decorate_lint(sess: &Session, diagnostic: BuiltinLintDiag, diag: & lints::UnusedCrateDependency { extern_crate, local_crate }.decorate_lint(diag) } BuiltinLintDiag::WasmCAbi => lints::WasmCAbi.decorate_lint(diag), + BuiltinLintDiag::IncompatibleTargetModifiers { extern_crate, local_crate, tmod } => { + lints::IncompatibleTargetModifiers { extern_crate, local_crate, tmod } + .decorate_lint(diag) + } BuiltinLintDiag::IllFormedAttributeInput { suggestions } => { lints::IllFormedAttributeInput { num_suggestions: suggestions.len(), diff --git a/compiler/rustc_lint/src/lints.rs b/compiler/rustc_lint/src/lints.rs index 16cfae17d4020..4b991086610b7 100644 --- a/compiler/rustc_lint/src/lints.rs +++ b/compiler/rustc_lint/src/lints.rs @@ -2470,6 +2470,14 @@ pub(crate) struct UnusedCrateDependency { pub local_crate: Symbol, } +#[derive(LintDiagnostic)] +#[diag(lint_incompatible_target_modifiers)] +pub(crate) struct IncompatibleTargetModifiers { + pub extern_crate: Symbol, + pub local_crate: Symbol, + pub tmod: String, +} + #[derive(LintDiagnostic)] #[diag(lint_wasm_c_abi)] pub(crate) struct WasmCAbi; diff --git a/compiler/rustc_lint_defs/src/builtin.rs b/compiler/rustc_lint_defs/src/builtin.rs index a4c49a1590535..f711dcfe5c627 100644 --- a/compiler/rustc_lint_defs/src/builtin.rs +++ b/compiler/rustc_lint_defs/src/builtin.rs @@ -578,6 +578,40 @@ declare_lint! { crate_level_only } +declare_lint! { + /// The `incompatible_target_modifiers` lint detects crates with incompatible target modifiers + /// (abi-changing or vulnerability-affecting flags). + /// + /// ### Example + /// + /// When main and dependency crates are compiled with `-Zregparm=1` and `-Zregparm=2` correspondingly. + /// + /// This will produce: + /// + /// ```text + /// error: crate `incompatible_regparm` has incompatible target modifier with extern crate `wrong_regparm`: `regparm = ( Some(1) | Some(2) )` + /// --> $DIR/incompatible_regparm.rs:5:1 + /// | + /// 1 | #![no_core] + /// | ^ + /// | + /// = note: `#[deny(incompatible_target_modifiers)]` on by default + /// ``` + /// + /// {{produces}} + /// + /// ### Explanation + /// + /// `Target modifiers` are compilation flags that affects abi or vulnerability resistance. + /// Linking together crates with incompatible target modifiers would produce incorrect code + /// or degradation of vulnerability resistance. + /// So this lint should find such inconsistency. + /// + pub INCOMPATIBLE_TARGET_MODIFIERS, + Deny, + "Incompatible target modifiers" +} + declare_lint! { /// The `unused_qualifications` lint detects unnecessarily qualified /// names. diff --git a/compiler/rustc_lint_defs/src/lib.rs b/compiler/rustc_lint_defs/src/lib.rs index c01fa5c54d65e..44a45051a2dae 100644 --- a/compiler/rustc_lint_defs/src/lib.rs +++ b/compiler/rustc_lint_defs/src/lib.rs @@ -719,6 +719,11 @@ pub enum BuiltinLintDiag { AvoidUsingIntelSyntax, AvoidUsingAttSyntax, IncompleteInclude, + IncompatibleTargetModifiers { + extern_crate: Symbol, + local_crate: Symbol, + tmod: String, + }, UnnameableTestItems, DuplicateMacroAttribute, CfgAttrNoAttributes, diff --git a/compiler/rustc_metadata/src/creader.rs b/compiler/rustc_metadata/src/creader.rs index 8adec7554a834..239cdc59f3dc7 100644 --- a/compiler/rustc_metadata/src/creader.rs +++ b/compiler/rustc_metadata/src/creader.rs @@ -24,7 +24,7 @@ use rustc_middle::bug; use rustc_middle::ty::{TyCtxt, TyCtxtFeed}; use rustc_session::config::{self, CrateType, ExternLocation}; use rustc_session::cstore::{CrateDepKind, CrateSource, ExternCrate, ExternCrateSource}; -use rustc_session::lint::{self, BuiltinLintDiag}; +use rustc_session::lint::{self, BuiltinLintDiag, LintBuffer}; use rustc_session::output::validate_crate_name; use rustc_session::search_paths::PathKind; use rustc_span::edition::Edition; @@ -35,7 +35,9 @@ use tracing::{debug, info, trace}; use crate::errors; use crate::locator::{CrateError, CrateLocator, CratePaths}; -use crate::rmeta::{CrateDep, CrateMetadata, CrateNumMap, CrateRoot, MetadataBlob}; +use crate::rmeta::{ + CrateDep, CrateMetadata, CrateNumMap, CrateRoot, MetadataBlob, TargetModifiers, +}; /// The backend's way to give the crate store access to the metadata in a library. /// Note that it returns the raw metadata bytes stored in the library file, whether @@ -290,6 +292,79 @@ impl CStore { } } + pub fn report_incompatible_target_modifiers( + &self, + tcx: TyCtxt<'_>, + krate: &Crate, + lints: &mut LintBuffer, + ) { + if tcx.crate_types().contains(&CrateType::ProcMacro) { + return; + } + let sess = tcx.sess; + let span = krate.spans.inner_span.shrink_to_lo(); + + let splitter = |v: &String| { + let splitted: Vec<_> = v.split("=").collect(); + (splitted[0].to_string(), splitted[1].to_string()) + }; + let name = tcx.crate_name(LOCAL_CRATE); + let mods = sess.opts.gather_target_modifiers(); + for (_cnum, data) in self.iter_crate_data() { + if data.is_proc_macro_crate() { + continue; + } + let mut report_diff = |tmod: String| { + lints.buffer_lint( + lint::builtin::INCOMPATIBLE_TARGET_MODIFIERS, + ast::CRATE_NODE_ID, + span, + BuiltinLintDiag::IncompatibleTargetModifiers { + extern_crate: data.name(), + local_crate: name, + tmod, + }, + ); + }; + let mut it1 = mods.iter().map(splitter); + let mut it2 = data.target_modifiers().map(splitter); + let mut left_name_val: Option<(String, String)> = None; + let mut right_name_val: Option<(String, String)> = None; + loop { + left_name_val = left_name_val.or_else(|| it1.next()); + right_name_val = right_name_val.or_else(|| it2.next()); + match (&left_name_val, &right_name_val) { + (Some(l), Some(r)) => match l.0.cmp(&r.0) { + cmp::Ordering::Equal => { + if l.1 != r.1 { + report_diff(format!("{} = ( {} | {} )", l.0, l.1, r.1)); + } + left_name_val = None; + right_name_val = None; + } + cmp::Ordering::Greater => { + report_diff(format!("{} = ( * | {} )", r.0, r.1)); + right_name_val = None; + } + cmp::Ordering::Less => { + report_diff(format!("{} = ( {} | * )", l.0, l.1)); + left_name_val = None; + } + }, + (Some(l), None) => { + report_diff(format!("{} = ( {} | * )", l.0, l.1)); + left_name_val = None; + } + (None, Some(r)) => { + report_diff(format!("{} = ( * | {} )", r.0, r.1)); + right_name_val = None; + } + (None, None) => break, + } + } + } + } + pub fn new(metadata_loader: Box) -> CStore { CStore { metadata_loader, @@ -432,6 +507,7 @@ impl<'a, 'tcx> CrateLoader<'a, 'tcx> { }; let cnum_map = self.resolve_crate_deps(root, &crate_root, &metadata, cnum, dep_kind)?; + let target_modifiers = self.resolve_target_modifiers(&crate_root, &metadata, cnum)?; let raw_proc_macros = if crate_root.is_proc_macro_crate() { let temp_root; @@ -456,6 +532,7 @@ impl<'a, 'tcx> CrateLoader<'a, 'tcx> { raw_proc_macros, cnum, cnum_map, + target_modifiers, dep_kind, source, private_dep, @@ -689,6 +766,25 @@ impl<'a, 'tcx> CrateLoader<'a, 'tcx> { Ok(crate_num_map) } + fn resolve_target_modifiers( + &mut self, + crate_root: &CrateRoot, + metadata: &MetadataBlob, + krate: CrateNum, + ) -> Result { + debug!("resolving target modifiers of external crate"); + if crate_root.is_proc_macro_crate() { + return Ok(TargetModifiers::new()); + } + let mods = crate_root.decode_target_modifiers(metadata); + let mut target_modifiers = TargetModifiers::with_capacity(mods.len()); + for modifier in mods { + target_modifiers.push(modifier); + } + debug!("resolve_target_modifiers: target mods for {:?} is {:?}", krate, target_modifiers); + Ok(target_modifiers) + } + fn dlsym_proc_macros( &self, path: &Path, diff --git a/compiler/rustc_metadata/src/rmeta/decoder.rs b/compiler/rustc_metadata/src/rmeta/decoder.rs index 4b406496337a7..402a928e3ff6b 100644 --- a/compiler/rustc_metadata/src/rmeta/decoder.rs +++ b/compiler/rustc_metadata/src/rmeta/decoder.rs @@ -73,6 +73,9 @@ impl MetadataBlob { /// own crate numbers. pub(crate) type CrateNumMap = IndexVec; +/// Target modifiers - abi / vulnerability-resist affecting flags +pub(crate) type TargetModifiers = Vec; + pub(crate) struct CrateMetadata { /// The primary crate data - binary metadata blob. blob: MetadataBlob, @@ -110,6 +113,8 @@ pub(crate) struct CrateMetadata { cnum_map: CrateNumMap, /// Same ID set as `cnum_map` plus maybe some injected crates like panic runtime. dependencies: Vec, + /// Target modifiers - abi and vulnerability-resist affecting flags the crate was compiled with + target_modifiers: TargetModifiers, /// How to link (or not link) this crate to the currently compiled crate. dep_kind: CrateDepKind, /// Filesystem location of this crate. @@ -960,6 +965,13 @@ impl CrateRoot { ) -> impl ExactSizeIterator + Captures<'a> { self.crate_deps.decode(metadata) } + + pub(crate) fn decode_target_modifiers<'a>( + &self, + metadata: &'a MetadataBlob, + ) -> impl ExactSizeIterator + Captures<'a> { + self.target_modifiers.decode(metadata) + } } impl<'a> CrateMetadataRef<'a> { @@ -1815,6 +1827,7 @@ impl CrateMetadata { raw_proc_macros: Option<&'static [ProcMacro]>, cnum: CrateNum, cnum_map: CrateNumMap, + target_modifiers: TargetModifiers, dep_kind: CrateDepKind, source: CrateSource, private_dep: bool, @@ -1846,6 +1859,7 @@ impl CrateMetadata { cnum, cnum_map, dependencies, + target_modifiers, dep_kind, source: Lrc::new(source), private_dep, @@ -1875,6 +1889,10 @@ impl CrateMetadata { self.dependencies.push(cnum); } + pub(crate) fn target_modifiers(&self) -> impl Iterator + '_ { + self.target_modifiers.iter() + } + pub(crate) fn update_extern_crate(&mut self, new_extern_crate: ExternCrate) -> bool { let update = Some(new_extern_crate.rank()) > self.extern_crate.as_ref().map(ExternCrate::rank); diff --git a/compiler/rustc_metadata/src/rmeta/encoder.rs b/compiler/rustc_metadata/src/rmeta/encoder.rs index afe03531861c9..a7e279f64118f 100644 --- a/compiler/rustc_metadata/src/rmeta/encoder.rs +++ b/compiler/rustc_metadata/src/rmeta/encoder.rs @@ -692,6 +692,7 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> { // Encode source_map. This needs to be done last, because encoding `Span`s tells us which // `SourceFiles` we actually need to encode. let source_map = stat!("source-map", || self.encode_source_map()); + let target_modifiers = stat!("target-modifiers", || self.encode_target_modifiers()); let root = stat!("final", || { let attrs = tcx.hir().krate_attrs(); @@ -732,6 +733,7 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> { native_libraries, foreign_modules, source_map, + target_modifiers, traits, impls, incoherent_impls, @@ -1978,6 +1980,12 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> { self.lazy_array(deps.iter().map(|(_, dep)| dep)) } + fn encode_target_modifiers(&mut self) -> LazyArray { + empty_proc_macro!(self); + let tcx = self.tcx; + self.lazy_array(tcx.sess.opts.gather_target_modifiers()) + } + fn encode_lib_features(&mut self) -> LazyArray<(Symbol, FeatureStability)> { empty_proc_macro!(self); let tcx = self.tcx; diff --git a/compiler/rustc_metadata/src/rmeta/mod.rs b/compiler/rustc_metadata/src/rmeta/mod.rs index 79bd1c13b1216..c54062e813112 100644 --- a/compiler/rustc_metadata/src/rmeta/mod.rs +++ b/compiler/rustc_metadata/src/rmeta/mod.rs @@ -1,7 +1,7 @@ use std::marker::PhantomData; use std::num::NonZero; -pub(crate) use decoder::{CrateMetadata, CrateNumMap, MetadataBlob}; +pub(crate) use decoder::{CrateMetadata, CrateNumMap, MetadataBlob, TargetModifiers}; use decoder::{DecodeContext, Metadata}; use def_path_hash_map::DefPathHashMapRef; use encoder::EncodeContext; @@ -283,6 +283,7 @@ pub(crate) struct CrateRoot { def_path_hash_map: LazyValue>, source_map: LazyTable>>, + target_modifiers: LazyArray, compiler_builtins: bool, needs_allocator: bool, diff --git a/compiler/rustc_session/src/options.rs b/compiler/rustc_session/src/options.rs index 54a4621db2462..a01621a778db4 100644 --- a/compiler/rustc_session/src/options.rs +++ b/compiler/rustc_session/src/options.rs @@ -58,10 +58,58 @@ macro_rules! hash_substruct { }; } +macro_rules! gather_tmods { + ($_opt_name:ident, $init:expr, $opt_expr:expr, $mods:expr, [SUBSTRUCT], [TMOD]) => { + compile_error!("SUBSTRUCT can't be TMOD (target modifier)"); + }; + ($opt_name:ident, $init:expr, $opt_expr:expr, $mods:expr, [UNTRACKED], [TMOD]) => { + if *$opt_expr != $init { + $mods.push(format!("{}={:?}", stringify!($opt_name), $opt_expr)); + } + }; + ($opt_name:ident, $init:expr, $opt_expr:expr, $mods:expr, [TRACKED], [TMOD]) => { + if *$opt_expr != $init { + $mods.push(format!("{}={:?}", stringify!($opt_name), $opt_expr)); + } + }; + ($opt_name:ident, $init:expr, $opt_expr:expr, $mods:expr, [TRACKED_NO_CRATE_HASH], [TMOD]) => { + if *$opt_expr != $init { + $mods.push(format!("{}={:?}", stringify!($opt_name), $opt_expr)); + } + }; + ($_opt_name:ident, $init:expr, $opt_expr:expr, $mods:expr, [SUBSTRUCT], []) => { + $opt_expr.gather_target_modifiers($mods); + }; + ($_opt_name:ident, $init:expr, $_opt_expr:expr, $_mods:expr, [UNTRACKED], []) => {{}}; + ($_opt_name:ident, $init:expr, $_opt_expr:expr, $_mods:expr, [TRACKED], []) => {{}}; + ($_opt_name:ident, $init:expr, $_opt_expr:expr, $_mods:expr, [TRACKED_NO_CRATE_HASH], []) => {{}}; +} + +macro_rules! gather_tmods_top_level { + ($_opt_name:ident, $opt_expr:expr, $mods:expr, [SUBSTRUCT], [TMOD]) => { + compile_error!("SUBSTRUCT can't be TMOD (target modifier)"); + }; + ($opt_name:ident, $opt_expr:expr, $mods:expr, [UNTRACKED], [TMOD]) => { + compile_error!("Top level option can't be TMOD (target modifier)"); + }; + ($opt_name:ident, $opt_expr:expr, $mods:expr, [TRACKED], [TMOD]) => { + compile_error!("Top level option can't be TMOD (target modifier)"); + }; + ($opt_name:ident, $opt_expr:expr, $mods:expr, [TRACKED_NO_CRATE_HASH], [TMOD]) => { + compile_error!("Top level option can't be TMOD (target modifier)"); + }; + ($_opt_name:ident, $opt_expr:expr, $mods:expr, [SUBSTRUCT], []) => { + $opt_expr.gather_target_modifiers($mods); + }; + ($_opt_name:ident, $_opt_expr:expr, $_mods:expr, [UNTRACKED], []) => {{}}; + ($_opt_name:ident, $_opt_expr:expr, $_mods:expr, [TRACKED], []) => {{}}; + ($_opt_name:ident, $_opt_expr:expr, $_mods:expr, [TRACKED_NO_CRATE_HASH], []) => {{}}; +} + macro_rules! top_level_options { ( $( #[$top_level_attr:meta] )* pub struct Options { $( $( #[$attr:meta] )* - $opt:ident : $t:ty [$dep_tracking_marker:ident], + $opt:ident : $t:ty [$dep_tracking_marker:ident $( $tmod:ident )*], )* } ) => ( #[derive(Clone)] $( #[$top_level_attr] )* @@ -97,6 +145,16 @@ macro_rules! top_level_options { })* hasher.finish() } + + pub fn gather_target_modifiers(&self) -> Vec { + let mut mods = Vec::::new(); + $({ + gather_tmods_top_level!($opt, + &self.$opt, &mut mods, [$dep_tracking_marker], [$($tmod),*]); + })* + mods.sort_unstable(); + mods + } } ); } @@ -238,7 +296,7 @@ macro_rules! options { $($( #[$attr:meta] )* $opt:ident : $t:ty = ( $init:expr, $parse:ident, - [$dep_tracking_marker:ident], + [$dep_tracking_marker:ident $( $tmod:ident )*], $desc:expr) ),* ,) => ( @@ -277,6 +335,13 @@ macro_rules! options { ); hasher.finish() } + + pub fn gather_target_modifiers(&self, _mods: &mut Vec) { + $({ + gather_tmods!($opt, $init, + &self.$opt, _mods, [$dep_tracking_marker], [$($tmod),*]); + })* + } } pub const $stat: OptionDescrs<$struct_name> = @@ -2000,7 +2065,7 @@ options! { "enable queries of the dependency graph for regression testing (default: no)"), randomize_layout: bool = (false, parse_bool, [TRACKED], "randomize the layout of types (default: no)"), - regparm: Option = (None, parse_opt_number, [TRACKED], + regparm: Option = (None, parse_opt_number, [TRACKED TMOD], "On x86-32 targets, setting this to N causes the compiler to pass N arguments \ in registers EAX, EDX, and ECX instead of on the stack for\ \"C\", \"cdecl\", and \"stdcall\" fn.\ diff --git a/tests/ui/target_modifiers/auxiliary/wrong_regparm.rs b/tests/ui/target_modifiers/auxiliary/wrong_regparm.rs new file mode 100644 index 0000000000000..50cd64d2df4d1 --- /dev/null +++ b/tests/ui/target_modifiers/auxiliary/wrong_regparm.rs @@ -0,0 +1,14 @@ +//@ compile-flags: --target i686-unknown-linux-gnu -Zregparm=2 -Cpanic=abort +//@ needs-llvm-components: x86 +#![crate_type = "lib"] +#![no_core] +#![feature(no_core, lang_items, repr_simd)] + +#[lang = "sized"] +trait Sized {} +#[lang = "copy"] +trait Copy {} + +pub fn somefun() {} + +pub struct S; diff --git a/tests/ui/target_modifiers/incompatible_regparm.rs b/tests/ui/target_modifiers/incompatible_regparm.rs new file mode 100644 index 0000000000000..f99858cffbc2d --- /dev/null +++ b/tests/ui/target_modifiers/incompatible_regparm.rs @@ -0,0 +1,10 @@ +//@ aux-crate:wrong_regparm=wrong_regparm.rs +//@ compile-flags: --target i686-unknown-linux-gnu -Zregparm=1 -Cpanic=abort +//@ needs-llvm-components: x86 + +#![no_core] //~ERROR 5:1: 5:1: crate `incompatible_regparm` has incompatible target modifier with extern crate `wrong_regparm`: `regparm = ( Some(1) | Some(2) )` +#![feature(no_core, lang_items, repr_simd)] + +fn main() { + wrong_regparm::somefun(); +} diff --git a/tests/ui/target_modifiers/incompatible_regparm.stderr b/tests/ui/target_modifiers/incompatible_regparm.stderr new file mode 100644 index 0000000000000..580dbcc63d906 --- /dev/null +++ b/tests/ui/target_modifiers/incompatible_regparm.stderr @@ -0,0 +1,10 @@ +error: crate `incompatible_regparm` has incompatible target modifier with extern crate `wrong_regparm`: `regparm = ( Some(1) | Some(2) )` + --> $DIR/incompatible_regparm.rs:5:1 + | +LL | #![no_core] + | ^ + | + = note: `#[deny(incompatible_target_modifiers)]` on by default + +error: aborting due to 1 previous error +