From 95f9595811b56acf7c78d755d5cabba6d6250048 Mon Sep 17 00:00:00 2001 From: Andrew Zhogin Date: Mon, 18 Nov 2024 04:07:29 +0700 Subject: [PATCH] Target modifiers (special marked options) are recorded in metainfo and compared to be equal in different crates --- compiler/rustc_driver_impl/src/lib.rs | 7 +- compiler/rustc_interface/src/passes.rs | 1 + compiler/rustc_lint/messages.ftl | 7 + .../rustc_lint/src/context/diagnostics.rs | 16 ++ compiler/rustc_lint/src/lints.rs | 14 ++ compiler/rustc_lint_defs/src/builtin.rs | 38 ++++ compiler/rustc_lint_defs/src/lib.rs | 8 + compiler/rustc_metadata/src/creader.rs | 121 ++++++++++- compiler/rustc_metadata/src/rmeta/decoder.rs | 19 ++ compiler/rustc_metadata/src/rmeta/encoder.rs | 10 +- compiler/rustc_metadata/src/rmeta/mod.rs | 5 +- compiler/rustc_middle/src/ty/parameterized.rs | 1 + compiler/rustc_session/src/config.rs | 8 +- compiler/rustc_session/src/options.rs | 188 +++++++++++++++++- src/librustdoc/config.rs | 5 +- .../auxiliary/wrong_regparm.rs | 14 ++ ...ncompatible_regparm.error_generated.stderr | 13 ++ .../target_modifiers/incompatible_regparm.rs | 21 ++ 18 files changed, 475 insertions(+), 21 deletions(-) create mode 100644 tests/ui/target_modifiers/auxiliary/wrong_regparm.rs create mode 100644 tests/ui/target_modifiers/incompatible_regparm.error_generated.stderr create mode 100644 tests/ui/target_modifiers/incompatible_regparm.rs diff --git a/compiler/rustc_driver_impl/src/lib.rs b/compiler/rustc_driver_impl/src/lib.rs index a59dea557bb96..a2102c4c01449 100644 --- a/compiler/rustc_driver_impl/src/lib.rs +++ b/compiler/rustc_driver_impl/src/lib.rs @@ -1160,11 +1160,12 @@ fn describe_codegen_flags() { fn print_flag_list( cmdline_opt: &str, - flag_list: &[(&'static str, T, &'static str, &'static str)], + flag_list: &[(&'static str, T, &'static str, &'static str, bool)], ) { - let max_len = flag_list.iter().map(|&(name, _, _, _)| name.chars().count()).max().unwrap_or(0); + let max_len = + flag_list.iter().map(|&(name, _, _, _, _)| name.chars().count()).max().unwrap_or(0); - for &(name, _, _, desc) in flag_list { + for &(name, _, _, desc, _) in flag_list { safe_println!( " {} {:>width$}=val -- {}", cmdline_opt, 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..128f538fa9992 100644 --- a/compiler/rustc_lint/messages.ftl +++ b/compiler/rustc_lint/messages.ftl @@ -415,6 +415,13 @@ 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 = + mixing `{$flag_name_prefixed}` will cause an ABI mismatch + .note1 = `{$flag_name_prefixed}={$flag_local_value}` in this crate is incompatible with `{$flag_name_prefixed}={$flag_extern_value}` in dependency `{$extern_crate}` + .help = `{$flag_name_prefixed}` modifies the ABI and Rust crates compiled with different values of this flag cannot be used together safely + .suggestion = set `{$flag_name_prefixed}=${flag_extern_value}` in this crate or `{$flag_name_prefixed}=${flag_local_value}` in `{$extern_crate}` + .note2 = alternatively, use `-Cunsafe-allow-abi-mismatch={$flag_name}` to silence this error + 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..f77b3ba06950e 100644 --- a/compiler/rustc_lint/src/context/diagnostics.rs +++ b/compiler/rustc_lint/src/context/diagnostics.rs @@ -422,6 +422,22 @@ 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, + flag_name, + flag_name_prefixed, + flag_local_value, + flag_extern_value, + } => lints::IncompatibleTargetModifiers { + extern_crate, + local_crate, + flag_name, + flag_name_prefixed, + flag_local_value, + flag_extern_value, + } + .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..cd83128173c8c 100644 --- a/compiler/rustc_lint/src/lints.rs +++ b/compiler/rustc_lint/src/lints.rs @@ -2470,6 +2470,20 @@ pub(crate) struct UnusedCrateDependency { pub local_crate: Symbol, } +#[derive(LintDiagnostic)] +#[diag(lint_incompatible_target_modifiers)] +#[help] +#[note(lint_note1)] +#[note(lint_note2)] +pub(crate) struct IncompatibleTargetModifiers { + pub extern_crate: Symbol, + pub local_crate: Symbol, + pub flag_name: String, + pub flag_name_prefixed: String, + pub flag_local_value: String, + pub flag_extern_value: 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..fa847ee2fe2a1 100644 --- a/compiler/rustc_lint_defs/src/builtin.rs +++ b/compiler/rustc_lint_defs/src/builtin.rs @@ -49,6 +49,7 @@ declare_lint_pass! { FUZZY_PROVENANCE_CASTS, HIDDEN_GLOB_REEXPORTS, ILL_FORMED_ATTRIBUTE_INPUT, + INCOMPATIBLE_TARGET_MODIFIERS, INCOMPLETE_INCLUDE, INEFFECTIVE_UNSTABLE_TRAIT_IMPL, INLINE_NO_SANITIZE, @@ -578,6 +579,43 @@ 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 + /// + /// ```rust,ignore (needs extern crate) + /// #![deny(incompatible_target_modifiers)] + /// ``` + /// + /// 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 + /// ``` + /// + /// ### 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", + crate_level_only +} + 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..df420a1671912 100644 --- a/compiler/rustc_lint_defs/src/lib.rs +++ b/compiler/rustc_lint_defs/src/lib.rs @@ -719,6 +719,14 @@ pub enum BuiltinLintDiag { AvoidUsingIntelSyntax, AvoidUsingAttSyntax, IncompleteInclude, + IncompatibleTargetModifiers { + extern_crate: Symbol, + local_crate: Symbol, + flag_name: String, + flag_name_prefixed: String, + flag_local_value: String, + flag_extern_value: String, + }, UnnameableTestItems, DuplicateMacroAttribute, CfgAttrNoAttributes, diff --git a/compiler/rustc_metadata/src/creader.rs b/compiler/rustc_metadata/src/creader.rs index 8adec7554a834..3a9fe1596dfd1 100644 --- a/compiler/rustc_metadata/src/creader.rs +++ b/compiler/rustc_metadata/src/creader.rs @@ -22,9 +22,9 @@ use rustc_hir::definitions::Definitions; use rustc_index::IndexVec; use rustc_middle::bug; use rustc_middle::ty::{TyCtxt, TyCtxtFeed}; -use rustc_session::config::{self, CrateType, ExternLocation}; +use rustc_session::config::{self, CrateType, ExternLocation, TargetModifier}; 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,98 @@ 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 empty_vec = Vec::::new(); + let allowed_flag_mismatches = match sess.opts.cg.unsafe_allow_abi_mismatch.as_ref() { + Some(vec) => { + if vec.is_empty() { + // Setting `-Zunsafe-allow-abi-mismatch=` to an empty + // value allows all target modifier mismatches. + return; + } + vec + } + None => &empty_vec, + }; + let span = krate.spans.inner_span.shrink_to_lo(); + + 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 = |opt_name_hash: u64, + flag_local_value: &String, + flag_extern_value: &String| { + let name_info = sess.opts.target_modifier_info_by_hash(opt_name_hash); + let (prefix, opt_name) = name_info.expect("Target modifier not found by name hash"); + if allowed_flag_mismatches.contains(&opt_name) { + return; + } + lints.buffer_lint( + lint::builtin::INCOMPATIBLE_TARGET_MODIFIERS, + ast::CRATE_NODE_ID, + span, + BuiltinLintDiag::IncompatibleTargetModifiers { + extern_crate: data.name(), + local_crate: name, + flag_name: opt_name.clone(), + flag_name_prefixed: format!("-{}{}", prefix, opt_name), + flag_local_value: flag_local_value.to_string(), + flag_extern_value: flag_extern_value.to_string(), + }, + ); + }; + let mut it1 = mods.iter(); + let mut it2 = data.target_modifiers(); + let mut left_name_val: Option = None; + let mut right_name_val: Option = None; + let no_val = "*".to_string(); + loop { + left_name_val = left_name_val.or_else(|| it1.next().cloned()); + right_name_val = right_name_val.or_else(|| it2.next().cloned()); + match (&left_name_val, &right_name_val) { + (Some(l), Some(r)) => match l.name_hash.cmp(&r.name_hash) { + cmp::Ordering::Equal => { + if l.value_code != r.value_code { + report_diff(l.name_hash, &l.value_name, &r.value_name); + } + left_name_val = None; + right_name_val = None; + } + cmp::Ordering::Greater => { + report_diff(r.name_hash, &no_val, &r.value_name); + right_name_val = None; + } + cmp::Ordering::Less => { + report_diff(l.name_hash, &l.value_name, &no_val); + left_name_val = None; + } + }, + (Some(l), None) => { + report_diff(l.name_hash, &l.value_name, &no_val); + left_name_val = None; + } + (None, Some(r)) => { + report_diff(r.name_hash, &no_val, &r.value_name); + right_name_val = None; + } + (None, None) => break, + } + } + } + } + pub fn new(metadata_loader: Box) -> CStore { CStore { metadata_loader, @@ -432,6 +526,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 +551,7 @@ impl<'a, 'tcx> CrateLoader<'a, 'tcx> { raw_proc_macros, cnum, cnum_map, + target_modifiers, dep_kind, source, private_dep, @@ -689,6 +785,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..9bf5c917501d4 100644 --- a/compiler/rustc_metadata/src/rmeta/decoder.rs +++ b/compiler/rustc_metadata/src/rmeta/decoder.rs @@ -28,6 +28,7 @@ use rustc_middle::{bug, implement_ty_decoder}; use rustc_serialize::opaque::MemDecoder; use rustc_serialize::{Decodable, Decoder}; use rustc_session::Session; +use rustc_session::config::TargetModifier; use rustc_session::cstore::{CrateSource, ExternCrate}; use rustc_span::hygiene::HygieneDecodeContext; use rustc_span::symbol::kw; @@ -73,6 +74,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 +114,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 +966,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 +1828,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 +1860,7 @@ impl CrateMetadata { cnum, cnum_map, dependencies, + target_modifiers, dep_kind, source: Lrc::new(source), private_dep, @@ -1875,6 +1890,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..02333bf2ff293 100644 --- a/compiler/rustc_metadata/src/rmeta/encoder.rs +++ b/compiler/rustc_metadata/src/rmeta/encoder.rs @@ -25,7 +25,7 @@ use rustc_middle::ty::{AssocItemContainer, SymbolName}; use rustc_middle::util::common::to_readable_str; use rustc_middle::{bug, span_bug}; use rustc_serialize::{Decodable, Decoder, Encodable, Encoder, opaque}; -use rustc_session::config::{CrateType, OptLevel}; +use rustc_session::config::{CrateType, OptLevel, TargetModifier}; use rustc_span::hygiene::HygieneEncodeContext; use rustc_span::symbol::sym; use rustc_span::{ @@ -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..5426709a5a956 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; @@ -31,7 +31,7 @@ use rustc_middle::ty::{ use rustc_middle::util::Providers; use rustc_middle::{mir, trivially_parameterized_over_tcx}; use rustc_serialize::opaque::FileEncoder; -use rustc_session::config::SymbolManglingVersion; +use rustc_session::config::{SymbolManglingVersion, TargetModifier}; use rustc_session::cstore::{CrateDepKind, ForeignModule, LinkagePreference, NativeLib}; use rustc_span::edition::Edition; use rustc_span::hygiene::{ExpnIndex, MacroKind, SyntaxContextData}; @@ -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_middle/src/ty/parameterized.rs b/compiler/rustc_middle/src/ty/parameterized.rs index 7e1255f606c35..90a91f996ac0a 100644 --- a/compiler/rustc_middle/src/ty/parameterized.rs +++ b/compiler/rustc_middle/src/ty/parameterized.rs @@ -99,6 +99,7 @@ trivially_parameterized_over_tcx! { rustc_session::cstore::ForeignModule, rustc_session::cstore::LinkagePreference, rustc_session::cstore::NativeLib, + rustc_session::config::TargetModifier, rustc_span::ExpnData, rustc_span::ExpnHash, rustc_span::ExpnId, diff --git a/compiler/rustc_session/src/config.rs b/compiler/rustc_session/src/config.rs index d733e32f209db..77a58df33be5c 100644 --- a/compiler/rustc_session/src/config.rs +++ b/compiler/rustc_session/src/config.rs @@ -1188,6 +1188,7 @@ impl Default for Options { color: ColorConfig::Auto, logical_env: FxIndexMap::default(), verbose: false, + target_modifiers: BTreeMap::default(), } } } @@ -2445,14 +2446,16 @@ pub fn build_session_options(early_dcx: &mut EarlyDiagCtxt, matches: &getopts::M let crate_types = parse_crate_types_from_list(unparsed_crate_types) .unwrap_or_else(|e| early_dcx.early_fatal(e)); - let mut unstable_opts = UnstableOptions::build(early_dcx, matches); + let mut target_modifiers = BTreeMap::::new(); + + let mut unstable_opts = UnstableOptions::build(early_dcx, matches, &mut target_modifiers); let (lint_opts, describe_lints, lint_cap) = get_cmd_lint_options(early_dcx, matches); check_error_format_stability(early_dcx, &unstable_opts, error_format); let output_types = parse_output_types(early_dcx, &unstable_opts, matches); - let mut cg = CodegenOptions::build(early_dcx, matches); + let mut cg = CodegenOptions::build(early_dcx, matches, &mut target_modifiers); let (disable_local_thinlto, mut codegen_units) = should_override_cgus_and_disable_thinlto( early_dcx, &output_types, @@ -2738,6 +2741,7 @@ pub fn build_session_options(early_dcx: &mut EarlyDiagCtxt, matches: &getopts::M color, logical_env, verbose, + target_modifiers, } } diff --git a/compiler/rustc_session/src/options.rs b/compiler/rustc_session/src/options.rs index 54a4621db2462..8c7ead4a6b5d7 100644 --- a/compiler/rustc_session/src/options.rs +++ b/compiler/rustc_session/src/options.rs @@ -9,6 +9,7 @@ use rustc_data_structures::profiling::TimePassesFormat; use rustc_data_structures::stable_hasher::Hash64; use rustc_errors::{ColorConfig, LanguageIdentifier, TerminalUrl}; use rustc_feature::UnstableFeatures; +use rustc_macros::{Decodable, Encodable}; use rustc_span::edition::Edition; use rustc_span::{RealFileName, SourceFileHashAlgorithm}; use rustc_target::spec::{ @@ -58,10 +59,109 @@ macro_rules! hash_substruct { }; } +// A recorded -Zopt_name=opt_value (or -Copt_name=opt_value) +// for abi-changing or vulnerability-affecting options +#[derive(Debug, Clone, Encodable, Decodable)] +pub struct TargetModifier { + pub name_hash: u64, // option name hash + pub value_name: String, // User-provided option value (before parsing) for display + pub value_code: String, // Technical option value representation for comparison +} + +macro_rules! tmod_push { + ($opt_name:ident, $opt_expr:expr, $mods:expr, $tmod_vals:expr) => { + $mods.push(TargetModifier { + name_hash: calc_opt_name_hash(stringify!($opt_name)), + value_name: $tmod_vals.get(stringify!($opt_name)).cloned().unwrap_or_default(), + value_code: format!("{}={:?}", stringify!($opt_name), $opt_expr), + }); + }; +} + +macro_rules! gather_tmods { + ($_opt_name:ident, $init:expr, $opt_expr:expr, $mods:expr, $tmod_vals:expr, [SUBSTRUCT], [TMOD]) => { + compile_error!("SUBSTRUCT can't be target modifier"); + }; + ($opt_name:ident, $init:expr, $opt_expr:expr, $mods:expr, $tmod_vals:expr, [UNTRACKED], [TMOD]) => { + tmod_push!($opt_name, $opt_expr, $mods, $tmod_vals) + }; + ($opt_name:ident, $init:expr, $opt_expr:expr, $mods:expr, $tmod_vals:expr, [TRACKED], [TMOD]) => { + tmod_push!($opt_name, $opt_expr, $mods, $tmod_vals) + }; + ($opt_name:ident, $init:expr, $opt_expr:expr, $mods:expr, $tmod_vals:expr, [TRACKED_NO_CRATE_HASH], [TMOD]) => { + tmod_push!($opt_name, $opt_expr, $mods, $tmod_vals) + }; + ($_opt_name:ident, $init:expr, $opt_expr:expr, $mods:expr, $tmod_vals:expr, [SUBSTRUCT], []) => { + $opt_expr.gather_target_modifiers($mods, $tmod_vals); + }; + ($_opt_name:ident, $init:expr, $_opt_expr:expr, $_mods:expr, $tmod_vals:expr, [UNTRACKED], []) => {{}}; + ($_opt_name:ident, $init:expr, $_opt_expr:expr, $_mods:expr, $tmod_vals:expr, [TRACKED], []) => {{}}; + ($_opt_name:ident, $init:expr, $_opt_expr:expr, $_mods:expr, $tmod_vals:expr, [TRACKED_NO_CRATE_HASH], []) => {{}}; +} + +fn calc_opt_name_hash(opt_name: &str) -> u64 { + let mut hasher = DefaultHasher::new(); + use std::hash::Hash; + opt_name.hash(&mut hasher); + hasher.finish() +} + +macro_rules! tmod_info_ret { + ($prefix:expr, $opt_name:ident, $name_hash:expr) => { + if $name_hash == calc_opt_name_hash(stringify!($opt_name)) { + return Some(($prefix.to_string(), stringify!($opt_name).to_string())); + } + }; +} + +macro_rules! tmod_info { + ($opt_expr:expr, $prefix:expr, $_opt_name:ident, $_name_hash:expr, [SUBSTRUCT], [TMOD]) => { + compile_error!("SUBSTRUCT can't be target modifier"); + }; + ($opt_expr:expr, $prefix:expr, $opt_name:ident, $name_hash:expr, [UNTRACKED], [TMOD]) => { + tmod_info_ret!($prefix, $opt_name, $name_hash); + }; + ($opt_expr:expr, $prefix:expr, $opt_name:ident, $name_hash:expr, [TRACKED], [TMOD]) => { + tmod_info_ret!($prefix, $opt_name, $name_hash); + }; + ($opt_expr:expr, $prefix:expr, $opt_name:ident, $name_hash:expr, [TRACKED_NO_CRATE_HASH], [TMOD]) => { + tmod_info_ret!($prefix, $opt_name, $name_hash); + }; + ($opt_expr:expr, $prefix:expr, $_opt_name:ident, $name_hash:expr, [SUBSTRUCT], []) => { + if let Some(v) = $opt_expr.target_modifier_info_by_hash($name_hash) { + return Some(v); + } + }; + ($opt_expr:expr, $prefix:expr, $_opt_name:ident, $_name_hash:expr, [UNTRACKED], []) => {{}}; + ($opt_expr:expr, $prefix:expr, $_opt_name:ident, $_name_hash:expr, [TRACKED], []) => {{}}; + ($opt_expr:expr, $prefix:expr, $_opt_name:ident, $_name_hash:expr, [TRACKED_NO_CRATE_HASH], []) => {{}}; +} + +macro_rules! gather_tmods_top_level { + ($_opt_name:ident, $opt_expr:expr, $mods:expr, $tmod_vals:expr, [SUBSTRUCT], [TMOD]) => { + compile_error!("SUBSTRUCT can't be target modifier"); + }; + ($opt_name:ident, $opt_expr:expr, $mods:expr, $tmod_vals:expr, [UNTRACKED], [TMOD]) => { + compile_error!("Top level option can't be target modifier"); + }; + ($opt_name:ident, $opt_expr:expr, $mods:expr, $tmod_vals:expr, [TRACKED], [TMOD]) => { + compile_error!("Top level option can't be target modifier"); + }; + ($opt_name:ident, $opt_expr:expr, $mods:expr, $tmod_vals:expr, [TRACKED_NO_CRATE_HASH], [TMOD]) => { + compile_error!("Top level option can't be target modifier"); + }; + ($_opt_name:ident, $opt_expr:expr, $mods:expr, $tmod_vals:expr, [SUBSTRUCT], []) => { + $opt_expr.gather_target_modifiers($mods, $tmod_vals); + }; + ($_opt_name:ident, $_opt_expr:expr, $_mods:expr, $_tmod_vals:expr, [UNTRACKED], []) => {{}}; + ($_opt_name:ident, $_opt_expr:expr, $_mods:expr, $_tmod_vals:expr, [TRACKED], []) => {{}}; + ($_opt_name:ident, $_opt_expr:expr, $_mods:expr, $_tmod_vals: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] )* @@ -69,7 +169,8 @@ macro_rules! top_level_options { $( $( #[$attr] )* pub $opt: $t - ),* + ),*, + pub target_modifiers: BTreeMap, } impl Options { @@ -97,6 +198,25 @@ macro_rules! top_level_options { })* hasher.finish() } + + // Returns options prefix ("Z"/"C") and option name + pub fn target_modifier_info_by_hash(&self, opt_name_hash: u64) -> Option<(String, String)> { + $({ + tmod_info!(&self.$opt, "", $opt, opt_name_hash, [$dep_tracking_marker], [$($tmod),*]); + })* + None + } + + pub fn gather_target_modifiers(&self) -> Vec { + let mut mods = Vec::::new(); + $({ + gather_tmods_top_level!($opt, + &self.$opt, &mut mods, &self.target_modifiers, + [$dep_tracking_marker], [$($tmod),*]); + })* + mods.sort_by(|a, b| a.name_hash.cmp(&b.name_hash)); + mods + } } ); } @@ -225,6 +345,15 @@ top_level_options!( } ); +macro_rules! has_tmod { + ($v:ident) => { + true + }; + () => { + false + }; +} + /// Defines all `CodegenOptions`/`DebuggingOptions` fields and parsers all at once. The goal of this /// macro is to define an interface that can be programmatically used by the option parser /// to initialize the struct without hardcoding field names all over the place. @@ -238,7 +367,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) ),* ,) => ( @@ -256,8 +385,9 @@ macro_rules! options { pub fn build( early_dcx: &EarlyDiagCtxt, matches: &getopts::Matches, + target_modifiers: &mut BTreeMap, ) -> $struct_name { - build_options(early_dcx, matches, $stat, $prefix, $outputname) + build_options(early_dcx, matches, target_modifiers, $stat, $prefix, $outputname) } fn dep_tracking_hash(&self, for_crate_hash: bool, error_format: ErrorOutputType) -> u64 { @@ -277,10 +407,29 @@ macro_rules! options { ); hasher.finish() } + + // Returns option prefix ("Z"/"C") and name if found by hash + pub fn target_modifier_info_by_hash(&self, _opt_name_hash: u64) -> Option<(String, String)> { + $({ + tmod_info!(&self.$opt, $prefix, $opt, _opt_name_hash, [$dep_tracking_marker], [$($tmod),*]); + })* + None + } + + pub fn gather_target_modifiers( + &self, + _mods: &mut Vec, + _tmod_vals: &BTreeMap, + ) { + $({ + gather_tmods!($opt, $init, + &self.$opt, _mods, _tmod_vals, [$dep_tracking_marker], [$($tmod),*]); + })* + } } pub const $stat: OptionDescrs<$struct_name> = - &[ $( (stringify!($opt), $optmod::$opt, desc::$parse, $desc) ),* ]; + &[ $( (stringify!($opt), $optmod::$opt, desc::$parse, $desc, has_tmod!($($tmod),*)) ),* ]; mod $optmod { $( @@ -315,12 +464,14 @@ macro_rules! redirect_field { } type OptionSetter = fn(&mut O, v: Option<&str>) -> bool; -type OptionDescrs = &'static [(&'static str, OptionSetter, &'static str, &'static str)]; +type OptionDescrs = + &'static [(&'static str, OptionSetter, &'static str, &'static str, bool)]; #[allow(rustc::untranslatable_diagnostic)] // FIXME: make this translatable fn build_options( early_dcx: &EarlyDiagCtxt, matches: &getopts::Matches, + target_modifiers: &mut BTreeMap, descrs: OptionDescrs, prefix: &str, outputname: &str, @@ -334,7 +485,7 @@ fn build_options( let option_to_lookup = key.replace('-', "_"); match descrs.iter().find(|(name, ..)| *name == option_to_lookup) { - Some((_, setter, type_desc, _)) => { + Some((_, setter, type_desc, _, tmod)) => { if !setter(&mut op, value) { match value { None => early_dcx.early_fatal( @@ -349,6 +500,9 @@ fn build_options( ), } } + if *tmod && let Some(value) = value { + target_modifiers.insert(option_to_lookup, value.to_string()); + } } None => early_dcx.early_fatal(format!("unknown {outputname} option: `{key}`")), } @@ -372,6 +526,7 @@ mod desc { "a comma-separated list of strings, with elements beginning with + or -"; pub(crate) const parse_comma_list: &str = "a comma-separated list of strings"; pub(crate) const parse_opt_comma_list: &str = parse_comma_list; + pub(crate) const parse_opt_comma_list_or_empty: &str = parse_comma_list; pub(crate) const parse_number: &str = "a number"; pub(crate) const parse_opt_number: &str = parse_number; pub(crate) const parse_frame_pointer: &str = "one of `true`/`yes`/`on`, `false`/`no`/`off`, or (with -Zunstable-options) `non-leaf` or `always`"; @@ -656,6 +811,20 @@ mod parse { } } + // Allows both "-C(Z)flag=value1,value2,...,valueN" and "-C(Z)flag" (setting empty vector) + pub(crate) fn parse_opt_comma_list_or_empty( + slot: &mut Option>, + v: Option<&str>, + ) -> bool { + match v { + Some(_) => parse_opt_comma_list(slot, v), + None => { + *slot = Some(Vec::::new()); + true + } + } + } + pub(crate) fn parse_threads(slot: &mut usize, v: Option<&str>) -> bool { match v.and_then(|s| s.parse().ok()) { Some(0) => { @@ -1656,6 +1825,9 @@ options! { target_feature: String = (String::new(), parse_target_feature, [TRACKED], "target specific attributes. (`rustc --print target-features` for details). \ This feature is unsafe."), + unsafe_allow_abi_mismatch: Option> = (None, parse_opt_comma_list_or_empty, [UNTRACKED], + "Allow incompatible target modifiers in dependency crates \ + (comma separated list or empty to allow all)"), // tidy-alphabetical-end // If you add a new option, please update: @@ -2000,7 +2172,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/src/librustdoc/config.rs b/src/librustdoc/config.rs index 2cd69474b1cf0..a8a01097d9007 100644 --- a/src/librustdoc/config.rs +++ b/src/librustdoc/config.rs @@ -382,8 +382,9 @@ impl Options { config::parse_error_format(early_dcx, matches, color, json_color, json_rendered); let diagnostic_width = matches.opt_get("diagnostic-width").unwrap_or_default(); - let codegen_options = CodegenOptions::build(early_dcx, matches); - let unstable_opts = UnstableOptions::build(early_dcx, matches); + let mut target_modifiers = BTreeMap::::new(); + let codegen_options = CodegenOptions::build(early_dcx, matches, &mut target_modifiers); + let unstable_opts = UnstableOptions::build(early_dcx, matches, &mut target_modifiers); let remap_path_prefix = match parse_remap_path_prefix(matches) { Ok(prefix_mappings) => prefix_mappings, 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.error_generated.stderr b/tests/ui/target_modifiers/incompatible_regparm.error_generated.stderr new file mode 100644 index 0000000000000..e9e0368f1c1ed --- /dev/null +++ b/tests/ui/target_modifiers/incompatible_regparm.error_generated.stderr @@ -0,0 +1,13 @@ +error: mixing `-Zregparm` will cause an ABI mismatch + --> $DIR/incompatible_regparm.rs:12:1 + | +LL | #![crate_type = "lib"] + | ^ + | + = help: `-Zregparm` modifies the ABI and Rust crates compiled with different values of this flag cannot be used together safely + = note: `-Zregparm=1` in this crate is incompatible with `-Zregparm=2` in dependency `wrong_regparm` + = note: alternatively, use `-Cunsafe-allow-abi-mismatch=regparm` to silence this error + = note: `#[deny(incompatible_target_modifiers)]` on by default + +error: aborting due to 1 previous error + diff --git a/tests/ui/target_modifiers/incompatible_regparm.rs b/tests/ui/target_modifiers/incompatible_regparm.rs new file mode 100644 index 0000000000000..2c9fe5796816f --- /dev/null +++ b/tests/ui/target_modifiers/incompatible_regparm.rs @@ -0,0 +1,21 @@ +//@ aux-crate:wrong_regparm=wrong_regparm.rs +//@ compile-flags: --target i686-unknown-linux-gnu -Zregparm=1 -Cpanic=abort +//@ needs-llvm-components: x86 +//@ revisions:error_generated allow_regparm_mismatch allow_any_mismatch allow_attr + +//@[allow_regparm_mismatch] compile-flags: -Cunsafe-allow-abi-mismatch=regparm +//@[allow_any_mismatch] compile-flags: -Cunsafe-allow-abi-mismatch +//@[allow_regparm_mismatch] build-pass +//@[allow_any_mismatch] build-pass +//@[allow_attr] build-pass + +#![crate_type = "lib"] +//[error_generated]~^ ERROR 12:1: 12:1: mixing `-Zregparm` will cause an ABI mismatch [incompatible_target_modifiers] +#![no_core] +#![feature(no_core, lang_items, repr_simd)] + +#![cfg_attr(allow_attr, allow(incompatible_target_modifiers))] + +fn foo() { + wrong_regparm::somefun(); +}