diff options
Diffstat (limited to 'compiler/rustc_session/src')
| -rw-r--r-- | compiler/rustc_session/src/code_stats.rs | 270 | ||||
| -rw-r--r-- | compiler/rustc_session/src/config.rs | 3422 | ||||
| -rw-r--r-- | compiler/rustc_session/src/config/cfg.rs | 463 | ||||
| -rw-r--r-- | compiler/rustc_session/src/config/sigpipe.rs | 25 | ||||
| -rw-r--r-- | compiler/rustc_session/src/cstore.rs | 240 | ||||
| -rw-r--r-- | compiler/rustc_session/src/errors.rs | 513 | ||||
| -rw-r--r-- | compiler/rustc_session/src/filesearch.rs | 277 | ||||
| -rw-r--r-- | compiler/rustc_session/src/lib.rs | 40 | ||||
| -rw-r--r-- | compiler/rustc_session/src/options.rs | 2201 | ||||
| -rw-r--r-- | compiler/rustc_session/src/output.rs | 266 | ||||
| -rw-r--r-- | compiler/rustc_session/src/parse.rs | 348 | ||||
| -rw-r--r-- | compiler/rustc_session/src/search_paths.rs | 117 | ||||
| -rw-r--r-- | compiler/rustc_session/src/session.rs | 1543 | ||||
| -rw-r--r-- | compiler/rustc_session/src/utils.rs | 179 | ||||
| -rw-r--r-- | compiler/rustc_session/src/version.rs | 29 |
15 files changed, 9933 insertions, 0 deletions
diff --git a/compiler/rustc_session/src/code_stats.rs b/compiler/rustc_session/src/code_stats.rs new file mode 100644 index 00000000000..f3c21992784 --- /dev/null +++ b/compiler/rustc_session/src/code_stats.rs @@ -0,0 +1,270 @@ +use std::cmp; + +use rustc_abi::{Align, Size}; +use rustc_data_structures::fx::{FxHashMap, FxHashSet}; +use rustc_data_structures::sync::Lock; +use rustc_span::Symbol; +use rustc_span::def_id::DefId; + +#[derive(Clone, PartialEq, Eq, Hash, Debug)] +pub struct VariantInfo { + pub name: Option<Symbol>, + pub kind: SizeKind, + pub size: u64, + pub align: u64, + pub fields: Vec<FieldInfo>, +} + +#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] +pub enum SizeKind { + Exact, + Min, +} + +#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] +pub enum FieldKind { + AdtField, + Upvar, + CoroutineLocal, +} + +impl std::fmt::Display for FieldKind { + fn fmt(&self, w: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + FieldKind::AdtField => write!(w, "field"), + FieldKind::Upvar => write!(w, "upvar"), + FieldKind::CoroutineLocal => write!(w, "local"), + } + } +} + +#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] +pub struct FieldInfo { + pub kind: FieldKind, + pub name: Symbol, + pub offset: u64, + pub size: u64, + pub align: u64, + /// Name of the type of this field. + /// Present only if the creator thought that this would be important for identifying the field, + /// typically because the field name is uninformative. + pub type_name: Option<Symbol>, +} + +#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] +pub enum DataTypeKind { + Struct, + Union, + Enum, + Closure, + Coroutine, +} + +#[derive(PartialEq, Eq, Hash, Debug)] +pub struct TypeSizeInfo { + pub kind: DataTypeKind, + pub type_description: String, + pub align: u64, + pub overall_size: u64, + pub packed: bool, + pub opt_discr_size: Option<u64>, + pub variants: Vec<VariantInfo>, +} + +pub struct VTableSizeInfo { + pub trait_name: String, + + /// Number of entries in a vtable with the current algorithm + /// (i.e. with upcasting). + pub entries: usize, + + /// Number of entries in a vtable, as-if we did not have trait upcasting. + pub entries_ignoring_upcasting: usize, + + /// Number of entries in a vtable needed solely for upcasting + /// (i.e. `entries - entries_ignoring_upcasting`). + pub entries_for_upcasting: usize, + + /// Cost of having upcasting in % relative to the number of entries without + /// upcasting (i.e. `entries_for_upcasting / entries_ignoring_upcasting * 100%`). + pub upcasting_cost_percent: f64, +} + +#[derive(Default)] +pub struct CodeStats { + type_sizes: Lock<FxHashSet<TypeSizeInfo>>, + vtable_sizes: Lock<FxHashMap<DefId, VTableSizeInfo>>, +} + +impl CodeStats { + pub fn record_type_size<S: ToString>( + &self, + kind: DataTypeKind, + type_desc: S, + align: Align, + overall_size: Size, + packed: bool, + opt_discr_size: Option<Size>, + mut variants: Vec<VariantInfo>, + ) { + // Sort variants so the largest ones are shown first. A stable sort is + // used here so that source code order is preserved for all variants + // that have the same size. + // Except for Coroutines, whose variants are already sorted according to + // their yield points in `variant_info_for_coroutine`. + if kind != DataTypeKind::Coroutine { + variants.sort_by_key(|info| cmp::Reverse(info.size)); + } + let info = TypeSizeInfo { + kind, + type_description: type_desc.to_string(), + align: align.bytes(), + overall_size: overall_size.bytes(), + packed, + opt_discr_size: opt_discr_size.map(|s| s.bytes()), + variants, + }; + self.type_sizes.borrow_mut().insert(info); + } + + pub fn record_vtable_size(&self, trait_did: DefId, trait_name: &str, info: VTableSizeInfo) { + let prev = self.vtable_sizes.lock().insert(trait_did, info); + assert!( + prev.is_none(), + "size of vtable for `{trait_name}` ({trait_did:?}) is already recorded" + ); + } + + pub fn print_type_sizes(&self) { + let type_sizes = self.type_sizes.borrow(); + // We will soon sort, so the initial order does not matter. + #[allow(rustc::potential_query_instability)] + let mut sorted: Vec<_> = type_sizes.iter().collect(); + + // Primary sort: large-to-small. + // Secondary sort: description (dictionary order) + sorted.sort_by_key(|info| (cmp::Reverse(info.overall_size), &info.type_description)); + + for info in sorted { + let TypeSizeInfo { type_description, overall_size, align, kind, variants, .. } = info; + println!( + "print-type-size type: `{type_description}`: {overall_size} bytes, alignment: {align} bytes" + ); + let indent = " "; + + let discr_size = if let Some(discr_size) = info.opt_discr_size { + println!("print-type-size {indent}discriminant: {discr_size} bytes"); + discr_size + } else { + 0 + }; + + // We start this at discr_size (rather than 0) because + // things like C-enums do not have variants but we still + // want the max_variant_size at the end of the loop below + // to reflect the presence of the discriminant. + let mut max_variant_size = discr_size; + + let struct_like = match kind { + DataTypeKind::Struct | DataTypeKind::Closure => true, + DataTypeKind::Enum | DataTypeKind::Union | DataTypeKind::Coroutine => false, + }; + for (i, variant_info) in variants.into_iter().enumerate() { + let VariantInfo { ref name, kind: _, align: _, size, ref fields } = *variant_info; + let indent = if !struct_like { + let name = match name.as_ref() { + Some(name) => name.to_string(), + None => i.to_string(), + }; + println!( + "print-type-size {indent}variant `{name}`: {diff} bytes", + diff = size - discr_size + ); + " " + } else { + assert!(i < 1); + " " + }; + max_variant_size = cmp::max(max_variant_size, size); + + let mut min_offset = discr_size; + + // We want to print fields by increasing offset. We also want + // zero-sized fields before non-zero-sized fields, otherwise + // the loop below goes wrong; hence the `f.size` in the sort + // key. + let mut fields = fields.clone(); + fields.sort_by_key(|f| (f.offset, f.size)); + + for field in fields { + let FieldInfo { kind, ref name, offset, size, align, type_name } = field; + + if offset > min_offset { + let pad = offset - min_offset; + println!("print-type-size {indent}padding: {pad} bytes"); + } + + if offset < min_offset { + // If this happens it's probably a union. + print!( + "print-type-size {indent}{kind} `.{name}`: {size} bytes, \ + offset: {offset} bytes, \ + alignment: {align} bytes" + ); + } else if info.packed || offset == min_offset { + print!("print-type-size {indent}{kind} `.{name}`: {size} bytes"); + } else { + // Include field alignment in output only if it caused padding injection + print!( + "print-type-size {indent}{kind} `.{name}`: {size} bytes, \ + alignment: {align} bytes" + ); + } + + if let Some(type_name) = type_name { + println!(", type: {type_name}"); + } else { + println!(); + } + + min_offset = offset + size; + } + } + + match overall_size.checked_sub(max_variant_size) { + None => panic!("max_variant_size {max_variant_size} > {overall_size} overall_size"), + Some(diff @ 1..) => println!("print-type-size {indent}end padding: {diff} bytes"), + Some(0) => {} + } + } + } + + pub fn print_vtable_sizes(&self, crate_name: Symbol) { + // We will soon sort, so the initial order does not matter. + #[allow(rustc::potential_query_instability)] + let mut infos = + std::mem::take(&mut *self.vtable_sizes.lock()).into_values().collect::<Vec<_>>(); + + // Primary sort: cost % in reverse order (from largest to smallest) + // Secondary sort: trait_name + infos.sort_by(|a, b| { + a.upcasting_cost_percent + .total_cmp(&b.upcasting_cost_percent) + .reverse() + .then_with(|| a.trait_name.cmp(&b.trait_name)) + }); + + for VTableSizeInfo { + trait_name, + entries, + entries_ignoring_upcasting, + entries_for_upcasting, + upcasting_cost_percent, + } in infos + { + println!( + r#"print-vtable-sizes {{ "crate_name": "{crate_name}", "trait_name": "{trait_name}", "entries": "{entries}", "entries_ignoring_upcasting": "{entries_ignoring_upcasting}", "entries_for_upcasting": "{entries_for_upcasting}", "upcasting_cost_percent": "{upcasting_cost_percent}" }}"# + ); + } + } +} diff --git a/compiler/rustc_session/src/config.rs b/compiler/rustc_session/src/config.rs new file mode 100644 index 00000000000..44721bd889a --- /dev/null +++ b/compiler/rustc_session/src/config.rs @@ -0,0 +1,3422 @@ +//! Contains infrastructure for configuring the compiler, including parsing +//! command-line options. + +#![allow(rustc::untranslatable_diagnostic)] // FIXME: make this translatable + +use std::collections::btree_map::{ + Iter as BTreeMapIter, Keys as BTreeMapKeysIter, Values as BTreeMapValuesIter, +}; +use std::collections::{BTreeMap, BTreeSet}; +use std::ffi::OsStr; +use std::hash::Hash; +use std::path::{Path, PathBuf}; +use std::str::{self, FromStr}; +use std::sync::LazyLock; +use std::{cmp, fmt, fs, iter}; + +use rustc_data_structures::fx::{FxHashSet, FxIndexMap}; +use rustc_data_structures::stable_hasher::{StableOrd, ToStableHashKey}; +use rustc_errors::emitter::HumanReadableErrorType; +use rustc_errors::{ColorConfig, DiagArgValue, DiagCtxtFlags, IntoDiagArg}; +use rustc_feature::UnstableFeatures; +use rustc_macros::{Decodable, Encodable, HashStable_Generic}; +use rustc_span::edition::{DEFAULT_EDITION, EDITION_NAME_LIST, Edition, LATEST_STABLE_EDITION}; +use rustc_span::source_map::FilePathMapping; +use rustc_span::{ + FileName, FileNameDisplayPreference, RealFileName, SourceFileHashAlgorithm, Symbol, sym, +}; +use rustc_target::spec::{ + FramePointer, LinkSelfContainedComponents, LinkerFeatures, SplitDebuginfo, Target, TargetTuple, +}; +use tracing::debug; + +use crate::errors::FileWriteFail; +pub use crate::options::*; +use crate::search_paths::SearchPath; +use crate::utils::{CanonicalizedPath, NativeLib, NativeLibKind}; +use crate::{EarlyDiagCtxt, HashStableContext, Session, filesearch, lint}; + +mod cfg; +pub mod sigpipe; + +pub use cfg::{Cfg, CheckCfg, ExpectedValues}; + +/// The different settings that the `-C strip` flag can have. +#[derive(Clone, Copy, PartialEq, Hash, Debug)] +pub enum Strip { + /// Do not strip at all. + None, + + /// Strip debuginfo. + Debuginfo, + + /// Strip all symbols. + Symbols, +} + +/// The different settings that the `-C control-flow-guard` flag can have. +#[derive(Clone, Copy, PartialEq, Hash, Debug)] +pub enum CFGuard { + /// Do not emit Control Flow Guard metadata or checks. + Disabled, + + /// Emit Control Flow Guard metadata but no checks. + NoChecks, + + /// Emit Control Flow Guard metadata and checks. + Checks, +} + +/// The different settings that the `-Z cf-protection` flag can have. +#[derive(Clone, Copy, PartialEq, Hash, Debug)] +pub enum CFProtection { + /// Do not enable control-flow protection + None, + + /// Emit control-flow protection for branches (enables indirect branch tracking). + Branch, + + /// Emit control-flow protection for returns. + Return, + + /// Emit control-flow protection for both branches and returns. + Full, +} + +#[derive(Clone, Copy, Debug, PartialEq, Hash, HashStable_Generic)] +pub enum OptLevel { + No, // -O0 + Less, // -O1 + Default, // -O2 + Aggressive, // -O3 + Size, // -Os + SizeMin, // -Oz +} + +/// This is what the `LtoCli` values get mapped to after resolving defaults and +/// and taking other command line options into account. +/// +/// Note that linker plugin-based LTO is a different mechanism entirely. +#[derive(Clone, PartialEq)] +pub enum Lto { + /// Don't do any LTO whatsoever. + No, + + /// Do a full-crate-graph (inter-crate) LTO with ThinLTO. + Thin, + + /// Do a local ThinLTO (intra-crate, over the CodeGen Units of the local crate only). This is + /// only relevant if multiple CGUs are used. + ThinLocal, + + /// Do a full-crate-graph (inter-crate) LTO with "fat" LTO. + Fat, +} + +/// The different settings that the `-C lto` flag can have. +#[derive(Clone, Copy, PartialEq, Hash, Debug)] +pub enum LtoCli { + /// `-C lto=no` + No, + /// `-C lto=yes` + Yes, + /// `-C lto` + NoParam, + /// `-C lto=thin` + Thin, + /// `-C lto=fat` + Fat, + /// No `-C lto` flag passed + Unspecified, +} + +/// The different settings that the `-C instrument-coverage` flag can have. +/// +/// Coverage instrumentation now supports combining `-C instrument-coverage` +/// with compiler and linker optimization (enabled with `-O` or `-C opt-level=1` +/// and higher). Nevertheless, there are many variables, depending on options +/// selected, code structure, and enabled attributes. If errors are encountered, +/// either while compiling or when generating `llvm-cov show` reports, consider +/// lowering the optimization level, or including/excluding `-C link-dead-code`. +#[derive(Clone, Copy, PartialEq, Hash, Debug)] +pub enum InstrumentCoverage { + /// `-C instrument-coverage=no` (or `off`, `false` etc.) + No, + /// `-C instrument-coverage` or `-C instrument-coverage=yes` + Yes, +} + +/// Individual flag values controlled by `-Z coverage-options`. +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default)] +pub struct CoverageOptions { + pub level: CoverageLevel, + + /// `-Z coverage-options=no-mir-spans`: Don't extract block coverage spans + /// from MIR statements/terminators, making it easier to inspect/debug + /// branch and MC/DC coverage mappings. + /// + /// For internal debugging only. If other code changes would make it hard + /// to keep supporting this flag, remove it. + pub no_mir_spans: bool, +} + +/// Controls whether branch coverage or MC/DC coverage is enabled. +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] +pub enum CoverageLevel { + /// Instrument for coverage at the MIR block level. + Block, + /// Also instrument branch points (includes block coverage). + Branch, + /// Same as branch coverage, but also adds branch instrumentation for + /// certain boolean expressions that are not directly used for branching. + /// + /// For example, in the following code, `b` does not directly participate + /// in a branch, but condition coverage will instrument it as its own + /// artificial branch: + /// ``` + /// # let (a, b) = (false, true); + /// let x = a && b; + /// // ^ last operand + /// ``` + /// + /// This level is mainly intended to be a stepping-stone towards full MC/DC + /// instrumentation, so it might be removed in the future when MC/DC is + /// sufficiently complete, or if it is making MC/DC changes difficult. + Condition, + /// Instrument for MC/DC. Mostly a superset of condition coverage, but might + /// differ in some corner cases. + Mcdc, +} + +impl Default for CoverageLevel { + fn default() -> Self { + Self::Block + } +} + +/// Settings for `-Z instrument-xray` flag. +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)] +pub struct InstrumentXRay { + /// `-Z instrument-xray=always`, force instrumentation + pub always: bool, + /// `-Z instrument-xray=never`, disable instrumentation + pub never: bool, + /// `-Z instrument-xray=ignore-loops`, ignore presence of loops, + /// instrument functions based only on instruction count + pub ignore_loops: bool, + /// `-Z instrument-xray=instruction-threshold=N`, explicitly set instruction threshold + /// for instrumentation, or `None` to use compiler's default + pub instruction_threshold: Option<usize>, + /// `-Z instrument-xray=skip-entry`, do not instrument function entry + pub skip_entry: bool, + /// `-Z instrument-xray=skip-exit`, do not instrument function exit + pub skip_exit: bool, +} + +#[derive(Clone, PartialEq, Hash, Debug)] +pub enum LinkerPluginLto { + LinkerPlugin(PathBuf), + LinkerPluginAuto, + Disabled, +} + +impl LinkerPluginLto { + pub fn enabled(&self) -> bool { + match *self { + LinkerPluginLto::LinkerPlugin(_) | LinkerPluginLto::LinkerPluginAuto => true, + LinkerPluginLto::Disabled => false, + } + } +} + +/// The different values `-C link-self-contained` can take: a list of individually enabled or +/// disabled components used during linking, coming from the rustc distribution, instead of being +/// found somewhere on the host system. +/// +/// They can be set in bulk via `-C link-self-contained=yes|y|on` or `-C +/// link-self-contained=no|n|off`, and those boolean values are the historical defaults. +/// +/// But each component is fine-grained, and can be unstably targeted, to use: +/// - some CRT objects +/// - the libc static library +/// - libgcc/libunwind libraries +/// - a linker we distribute +/// - some sanitizer runtime libraries +/// - all other MinGW libraries and Windows import libs +/// +#[derive(Default, Clone, PartialEq, Debug)] +pub struct LinkSelfContained { + /// Whether the user explicitly set `-C link-self-contained` on or off, the historical values. + /// Used for compatibility with the existing opt-in and target inference. + pub explicitly_set: Option<bool>, + + /// The components that are enabled on the CLI, using the `+component` syntax or one of the + /// `true` shortcuts. + enabled_components: LinkSelfContainedComponents, + + /// The components that are disabled on the CLI, using the `-component` syntax or one of the + /// `false` shortcuts. + disabled_components: LinkSelfContainedComponents, +} + +impl LinkSelfContained { + /// Incorporates an enabled or disabled component as specified on the CLI, if possible. + /// For example: `+linker`, and `-crto`. + pub(crate) fn handle_cli_component(&mut self, component: &str) -> Option<()> { + // Note that for example `-Cself-contained=y -Cself-contained=-linker` is not an explicit + // set of all values like `y` or `n` used to be. Therefore, if this flag had previously been + // set in bulk with its historical values, then manually setting a component clears that + // `explicitly_set` state. + if let Some(component_to_enable) = component.strip_prefix('+') { + self.explicitly_set = None; + self.enabled_components + .insert(LinkSelfContainedComponents::from_str(component_to_enable)?); + Some(()) + } else if let Some(component_to_disable) = component.strip_prefix('-') { + self.explicitly_set = None; + self.disabled_components + .insert(LinkSelfContainedComponents::from_str(component_to_disable)?); + Some(()) + } else { + None + } + } + + /// Turns all components on or off and records that this was done explicitly for compatibility + /// purposes. + pub(crate) fn set_all_explicitly(&mut self, enabled: bool) { + self.explicitly_set = Some(enabled); + + if enabled { + self.enabled_components = LinkSelfContainedComponents::all(); + self.disabled_components = LinkSelfContainedComponents::empty(); + } else { + self.enabled_components = LinkSelfContainedComponents::empty(); + self.disabled_components = LinkSelfContainedComponents::all(); + } + } + + /// Helper creating a fully enabled `LinkSelfContained` instance. Used in tests. + pub fn on() -> Self { + let mut on = LinkSelfContained::default(); + on.set_all_explicitly(true); + on + } + + /// To help checking CLI usage while some of the values are unstable: returns whether one of the + /// components was set individually. This would also require the `-Zunstable-options` flag, to + /// be allowed. + fn are_unstable_variants_set(&self) -> bool { + let any_component_set = + !self.enabled_components.is_empty() || !self.disabled_components.is_empty(); + self.explicitly_set.is_none() && any_component_set + } + + /// Returns whether the self-contained linker component was enabled on the CLI, using the + /// `-C link-self-contained=+linker` syntax, or one of the `true` shortcuts. + pub fn is_linker_enabled(&self) -> bool { + self.enabled_components.contains(LinkSelfContainedComponents::LINKER) + } + + /// Returns whether the self-contained linker component was disabled on the CLI, using the + /// `-C link-self-contained=-linker` syntax, or one of the `false` shortcuts. + pub fn is_linker_disabled(&self) -> bool { + self.disabled_components.contains(LinkSelfContainedComponents::LINKER) + } + + /// Returns CLI inconsistencies to emit errors: individual components were both enabled and + /// disabled. + fn check_consistency(&self) -> Option<LinkSelfContainedComponents> { + if self.explicitly_set.is_some() { + None + } else { + let common = self.enabled_components.intersection(self.disabled_components); + if common.is_empty() { None } else { Some(common) } + } + } +} + +/// The different values that `-Z linker-features` can take on the CLI: a list of individually +/// enabled or disabled features used during linking. +/// +/// There is no need to enable or disable them in bulk. Each feature is fine-grained, and can be +/// used to turn `LinkerFeatures` on or off, without needing to change the linker flavor: +/// - using the system lld, or the self-contained `rust-lld` linker +/// - using a C/C++ compiler to drive the linker (not yet exposed on the CLI) +/// - etc. +#[derive(Default, Copy, Clone, PartialEq, Debug)] +pub struct LinkerFeaturesCli { + /// The linker features that are enabled on the CLI, using the `+feature` syntax. + pub enabled: LinkerFeatures, + + /// The linker features that are disabled on the CLI, using the `-feature` syntax. + pub disabled: LinkerFeatures, +} + +impl LinkerFeaturesCli { + /// Accumulates an enabled or disabled feature as specified on the CLI, if possible. + /// For example: `+lld`, and `-lld`. + pub(crate) fn handle_cli_feature(&mut self, feature: &str) -> Option<()> { + // Duplicate flags are reduced as we go, the last occurrence wins: + // `+feature,-feature,+feature` only enables the feature, and does not record it as both + // enabled and disabled on the CLI. + // We also only expose `+/-lld` at the moment, as it's currently the only implemented linker + // feature and toggling `LinkerFeatures::CC` would be a noop. + match feature { + "+lld" => { + self.enabled.insert(LinkerFeatures::LLD); + self.disabled.remove(LinkerFeatures::LLD); + Some(()) + } + "-lld" => { + self.disabled.insert(LinkerFeatures::LLD); + self.enabled.remove(LinkerFeatures::LLD); + Some(()) + } + _ => None, + } + } +} + +/// Used with `-Z assert-incr-state`. +#[derive(Clone, Copy, PartialEq, Hash, Debug)] +pub enum IncrementalStateAssertion { + /// Found and loaded an existing session directory. + /// + /// Note that this says nothing about whether any particular query + /// will be found to be red or green. + Loaded, + /// Did not load an existing session directory. + NotLoaded, +} + +/// The different settings that can be enabled via the `-Z location-detail` flag. +#[derive(Copy, Clone, PartialEq, Hash, Debug)] +pub struct LocationDetail { + pub file: bool, + pub line: bool, + pub column: bool, +} + +impl LocationDetail { + pub(crate) fn all() -> Self { + Self { file: true, line: true, column: true } + } +} + +/// Values for the `-Z fmt-debug` flag. +#[derive(Copy, Clone, PartialEq, Hash, Debug)] +pub enum FmtDebug { + /// Derive fully-featured implementation + Full, + /// Print only type name, without fields + Shallow, + /// `#[derive(Debug)]` and `{:?}` are no-ops + None, +} + +impl FmtDebug { + pub(crate) fn all() -> [Symbol; 3] { + [sym::full, sym::none, sym::shallow] + } +} + +#[derive(Clone, PartialEq, Hash, Debug)] +pub enum SwitchWithOptPath { + Enabled(Option<PathBuf>), + Disabled, +} + +impl SwitchWithOptPath { + pub fn enabled(&self) -> bool { + match *self { + SwitchWithOptPath::Enabled(_) => true, + SwitchWithOptPath::Disabled => false, + } + } +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, HashStable_Generic)] +#[derive(Encodable, Decodable)] +pub enum SymbolManglingVersion { + Legacy, + V0, + Hashed, +} + +#[derive(Clone, Copy, Debug, PartialEq, Hash)] +pub enum DebugInfo { + None, + LineDirectivesOnly, + LineTablesOnly, + Limited, + Full, +} + +#[derive(Clone, Copy, Debug, PartialEq, Hash)] +pub enum DebugInfoCompression { + None, + Zlib, + Zstd, +} + +impl ToString for DebugInfoCompression { + fn to_string(&self) -> String { + match self { + DebugInfoCompression::None => "none", + DebugInfoCompression::Zlib => "zlib", + DebugInfoCompression::Zstd => "zstd", + } + .to_owned() + } +} + +/// Split debug-information is enabled by `-C split-debuginfo`, this enum is only used if split +/// debug-information is enabled (in either `Packed` or `Unpacked` modes), and the platform +/// uses DWARF for debug-information. +/// +/// Some debug-information requires link-time relocation and some does not. LLVM can partition +/// the debuginfo into sections depending on whether or not it requires link-time relocation. Split +/// DWARF provides a mechanism which allows the linker to skip the sections which don't require +/// link-time relocation - either by putting those sections in DWARF object files, or by keeping +/// them in the object file in such a way that the linker will skip them. +#[derive(Clone, Copy, Debug, PartialEq, Hash)] +pub enum SplitDwarfKind { + /// Sections which do not require relocation are written into object file but ignored by the + /// linker. + Single, + /// Sections which do not require relocation are written into a DWARF object (`.dwo`) file + /// which is ignored by the linker. + Split, +} + +impl FromStr for SplitDwarfKind { + type Err = (); + + fn from_str(s: &str) -> Result<Self, ()> { + Ok(match s { + "single" => SplitDwarfKind::Single, + "split" => SplitDwarfKind::Split, + _ => return Err(()), + }) + } +} + +#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, PartialOrd, Ord, HashStable_Generic)] +#[derive(Encodable, Decodable)] +pub enum OutputType { + Bitcode, + ThinLinkBitcode, + Assembly, + LlvmAssembly, + Mir, + Metadata, + Object, + Exe, + DepInfo, +} + +impl StableOrd for OutputType { + const CAN_USE_UNSTABLE_SORT: bool = true; + + // Trivial C-Style enums have a stable sort order across compilation sessions. + const THIS_IMPLEMENTATION_HAS_BEEN_TRIPLE_CHECKED: () = (); +} + +impl<HCX: HashStableContext> ToStableHashKey<HCX> for OutputType { + type KeyType = Self; + + fn to_stable_hash_key(&self, _: &HCX) -> Self::KeyType { + *self + } +} + +impl OutputType { + fn is_compatible_with_codegen_units_and_single_output_file(&self) -> bool { + match *self { + OutputType::Exe | OutputType::DepInfo | OutputType::Metadata => true, + OutputType::Bitcode + | OutputType::ThinLinkBitcode + | OutputType::Assembly + | OutputType::LlvmAssembly + | OutputType::Mir + | OutputType::Object => false, + } + } + + pub fn shorthand(&self) -> &'static str { + match *self { + OutputType::Bitcode => "llvm-bc", + OutputType::ThinLinkBitcode => "thin-link-bitcode", + OutputType::Assembly => "asm", + OutputType::LlvmAssembly => "llvm-ir", + OutputType::Mir => "mir", + OutputType::Object => "obj", + OutputType::Metadata => "metadata", + OutputType::Exe => "link", + OutputType::DepInfo => "dep-info", + } + } + + fn from_shorthand(shorthand: &str) -> Option<Self> { + Some(match shorthand { + "asm" => OutputType::Assembly, + "llvm-ir" => OutputType::LlvmAssembly, + "mir" => OutputType::Mir, + "llvm-bc" => OutputType::Bitcode, + "thin-link-bitcode" => OutputType::ThinLinkBitcode, + "obj" => OutputType::Object, + "metadata" => OutputType::Metadata, + "link" => OutputType::Exe, + "dep-info" => OutputType::DepInfo, + _ => return None, + }) + } + + fn shorthands_display() -> String { + format!( + "`{}`, `{}`, `{}`, `{}`, `{}`, `{}`, `{}`, `{}`, `{}`", + OutputType::Bitcode.shorthand(), + OutputType::ThinLinkBitcode.shorthand(), + OutputType::Assembly.shorthand(), + OutputType::LlvmAssembly.shorthand(), + OutputType::Mir.shorthand(), + OutputType::Object.shorthand(), + OutputType::Metadata.shorthand(), + OutputType::Exe.shorthand(), + OutputType::DepInfo.shorthand(), + ) + } + + pub fn extension(&self) -> &'static str { + match *self { + OutputType::Bitcode => "bc", + OutputType::ThinLinkBitcode => "indexing.o", + OutputType::Assembly => "s", + OutputType::LlvmAssembly => "ll", + OutputType::Mir => "mir", + OutputType::Object => "o", + OutputType::Metadata => "rmeta", + OutputType::DepInfo => "d", + OutputType::Exe => "", + } + } + + pub fn is_text_output(&self) -> bool { + match *self { + OutputType::Assembly + | OutputType::LlvmAssembly + | OutputType::Mir + | OutputType::DepInfo => true, + OutputType::Bitcode + | OutputType::ThinLinkBitcode + | OutputType::Object + | OutputType::Metadata + | OutputType::Exe => false, + } + } +} + +/// The type of diagnostics output to generate. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum ErrorOutputType { + /// Output meant for the consumption of humans. + HumanReadable(HumanReadableErrorType, ColorConfig), + /// Output that's consumed by other tools such as `rustfix` or the `RLS`. + Json { + /// Render the JSON in a human readable way (with indents and newlines). + pretty: bool, + /// The JSON output includes a `rendered` field that includes the rendered + /// human output. + json_rendered: HumanReadableErrorType, + color_config: ColorConfig, + }, +} + +impl Default for ErrorOutputType { + fn default() -> Self { + Self::HumanReadable(HumanReadableErrorType::Default, ColorConfig::Auto) + } +} + +#[derive(Clone, Hash, Debug)] +pub enum ResolveDocLinks { + /// Do not resolve doc links. + None, + /// Resolve doc links on exported items only for crate types that have metadata. + ExportedMetadata, + /// Resolve doc links on exported items. + Exported, + /// Resolve doc links on all items. + All, +} + +/// Use tree-based collections to cheaply get a deterministic `Hash` implementation. +/// *Do not* switch `BTreeMap` out for an unsorted container type! That would break +/// dependency tracking for command-line arguments. Also only hash keys, since tracking +/// should only depend on the output types, not the paths they're written to. +#[derive(Clone, Debug, Hash, HashStable_Generic, Encodable, Decodable)] +pub struct OutputTypes(BTreeMap<OutputType, Option<OutFileName>>); + +impl OutputTypes { + pub fn new(entries: &[(OutputType, Option<OutFileName>)]) -> OutputTypes { + OutputTypes(BTreeMap::from_iter(entries.iter().map(|&(k, ref v)| (k, v.clone())))) + } + + pub(crate) fn get(&self, key: &OutputType) -> Option<&Option<OutFileName>> { + self.0.get(key) + } + + pub fn contains_key(&self, key: &OutputType) -> bool { + self.0.contains_key(key) + } + + /// Returns `true` if user specified a name and not just produced type + pub fn contains_explicit_name(&self, key: &OutputType) -> bool { + self.0.get(key).map_or(false, |f| f.is_some()) + } + + pub fn iter(&self) -> BTreeMapIter<'_, OutputType, Option<OutFileName>> { + self.0.iter() + } + + pub fn keys(&self) -> BTreeMapKeysIter<'_, OutputType, Option<OutFileName>> { + self.0.keys() + } + + pub fn values(&self) -> BTreeMapValuesIter<'_, OutputType, Option<OutFileName>> { + self.0.values() + } + + pub fn len(&self) -> usize { + self.0.len() + } + + /// Returns `true` if any of the output types require codegen or linking. + pub fn should_codegen(&self) -> bool { + self.0.keys().any(|k| match *k { + OutputType::Bitcode + | OutputType::ThinLinkBitcode + | OutputType::Assembly + | OutputType::LlvmAssembly + | OutputType::Mir + | OutputType::Object + | OutputType::Exe => true, + OutputType::Metadata | OutputType::DepInfo => false, + }) + } + + /// Returns `true` if any of the output types require linking. + pub fn should_link(&self) -> bool { + self.0.keys().any(|k| match *k { + OutputType::Bitcode + | OutputType::ThinLinkBitcode + | OutputType::Assembly + | OutputType::LlvmAssembly + | OutputType::Mir + | OutputType::Metadata + | OutputType::Object + | OutputType::DepInfo => false, + OutputType::Exe => true, + }) + } +} + +/// Use tree-based collections to cheaply get a deterministic `Hash` implementation. +/// *Do not* switch `BTreeMap` or `BTreeSet` out for an unsorted container type! That +/// would break dependency tracking for command-line arguments. +#[derive(Clone)] +pub struct Externs(BTreeMap<String, ExternEntry>); + +#[derive(Clone, Debug)] +pub struct ExternEntry { + pub location: ExternLocation, + /// Indicates this is a "private" dependency for the + /// `exported_private_dependencies` lint. + /// + /// This can be set with the `priv` option like + /// `--extern priv:name=foo.rlib`. + pub is_private_dep: bool, + /// Add the extern entry to the extern prelude. + /// + /// This can be disabled with the `noprelude` option like + /// `--extern noprelude:name`. + pub add_prelude: bool, + /// The extern entry shouldn't be considered for unused dependency warnings. + /// + /// `--extern nounused:std=/path/to/lib/libstd.rlib`. This is used to + /// suppress `unused-crate-dependencies` warnings. + pub nounused_dep: bool, + /// If the extern entry is not referenced in the crate, force it to be resolved anyway. + /// + /// Allows a dependency satisfying, for instance, a missing panic handler to be injected + /// without modifying source: + /// `--extern force:extras=/path/to/lib/libstd.rlib` + pub force: bool, +} + +#[derive(Clone, Debug)] +pub enum ExternLocation { + /// Indicates to look for the library in the search paths. + /// + /// Added via `--extern name`. + FoundInLibrarySearchDirectories, + /// The locations where this extern entry must be found. + /// + /// The `CrateLoader` is responsible for loading these and figuring out + /// which one to use. + /// + /// Added via `--extern prelude_name=some_file.rlib` + ExactPaths(BTreeSet<CanonicalizedPath>), +} + +impl Externs { + /// Used for testing. + pub fn new(data: BTreeMap<String, ExternEntry>) -> Externs { + Externs(data) + } + + pub fn get(&self, key: &str) -> Option<&ExternEntry> { + self.0.get(key) + } + + pub fn iter(&self) -> BTreeMapIter<'_, String, ExternEntry> { + self.0.iter() + } +} + +impl ExternEntry { + fn new(location: ExternLocation) -> ExternEntry { + ExternEntry { + location, + is_private_dep: false, + add_prelude: false, + nounused_dep: false, + force: false, + } + } + + pub fn files(&self) -> Option<impl Iterator<Item = &CanonicalizedPath>> { + match &self.location { + ExternLocation::ExactPaths(set) => Some(set.iter()), + _ => None, + } + } +} + +#[derive(Clone, PartialEq, Debug)] +pub struct PrintRequest { + pub kind: PrintKind, + pub out: OutFileName, +} + +#[derive(Copy, Clone, PartialEq, Eq, Debug)] +pub enum PrintKind { + FileNames, + HostTuple, + Sysroot, + TargetLibdir, + CrateName, + Cfg, + CheckCfg, + CallingConventions, + TargetList, + TargetCPUs, + TargetFeatures, + RelocationModels, + CodeModels, + TlsModels, + TargetSpec, + AllTargetSpecs, + NativeStaticLibs, + StackProtectorStrategies, + LinkArgs, + SplitDebuginfo, + DeploymentTarget, +} + +#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] +pub struct NextSolverConfig { + /// Whether the new trait solver should be enabled in coherence. + pub coherence: bool, + /// Whether the new trait solver should be enabled everywhere. + /// This is only `true` if `coherence` is also enabled. + pub globally: bool, +} +impl Default for NextSolverConfig { + fn default() -> Self { + NextSolverConfig { coherence: true, globally: false } + } +} + +#[derive(Clone)] +pub enum Input { + /// Load source code from a file. + File(PathBuf), + /// Load source code from a string. + Str { + /// A string that is shown in place of a filename. + name: FileName, + /// An anonymous string containing the source code. + input: String, + }, +} + +impl Input { + pub fn filestem(&self) -> &str { + if let Input::File(ifile) = self { + // If for some reason getting the file stem as a UTF-8 string fails, + // then fallback to a fixed name. + if let Some(name) = ifile.file_stem().and_then(OsStr::to_str) { + return name; + } + } + "rust_out" + } + + pub fn source_name(&self) -> FileName { + match *self { + Input::File(ref ifile) => ifile.clone().into(), + Input::Str { ref name, .. } => name.clone(), + } + } + + pub fn opt_path(&self) -> Option<&Path> { + match self { + Input::File(file) => Some(file), + Input::Str { name, .. } => match name { + FileName::Real(real) => real.local_path(), + FileName::QuoteExpansion(_) => None, + FileName::Anon(_) => None, + FileName::MacroExpansion(_) => None, + FileName::ProcMacroSourceCode(_) => None, + FileName::CliCrateAttr(_) => None, + FileName::Custom(_) => None, + FileName::DocTest(path, _) => Some(path), + FileName::InlineAsm(_) => None, + }, + } + } +} + +#[derive(Clone, Hash, Debug, HashStable_Generic, PartialEq, Encodable, Decodable)] +pub enum OutFileName { + Real(PathBuf), + Stdout, +} + +impl OutFileName { + pub fn parent(&self) -> Option<&Path> { + match *self { + OutFileName::Real(ref path) => path.parent(), + OutFileName::Stdout => None, + } + } + + pub fn filestem(&self) -> Option<&OsStr> { + match *self { + OutFileName::Real(ref path) => path.file_stem(), + OutFileName::Stdout => Some(OsStr::new("stdout")), + } + } + + pub fn is_stdout(&self) -> bool { + match *self { + OutFileName::Real(_) => false, + OutFileName::Stdout => true, + } + } + + pub fn is_tty(&self) -> bool { + use std::io::IsTerminal; + match *self { + OutFileName::Real(_) => false, + OutFileName::Stdout => std::io::stdout().is_terminal(), + } + } + + pub fn as_path(&self) -> &Path { + match *self { + OutFileName::Real(ref path) => path.as_ref(), + OutFileName::Stdout => Path::new("stdout"), + } + } + + /// For a given output filename, return the actual name of the file that + /// can be used to write codegen data of type `flavor`. For real-path + /// output filenames, this would be trivial as we can just use the path. + /// Otherwise for stdout, return a temporary path so that the codegen data + /// may be later copied to stdout. + pub fn file_for_writing( + &self, + outputs: &OutputFilenames, + flavor: OutputType, + codegen_unit_name: Option<&str>, + ) -> PathBuf { + match *self { + OutFileName::Real(ref path) => path.clone(), + OutFileName::Stdout => outputs.temp_path(flavor, codegen_unit_name), + } + } + + pub fn overwrite(&self, content: &str, sess: &Session) { + match self { + OutFileName::Stdout => print!("{content}"), + OutFileName::Real(path) => { + if let Err(e) = fs::write(path, content) { + sess.dcx().emit_fatal(FileWriteFail { path, err: e.to_string() }); + } + } + } + } +} + +#[derive(Clone, Hash, Debug, HashStable_Generic, Encodable, Decodable)] +pub struct OutputFilenames { + pub(crate) out_directory: PathBuf, + /// Crate name. Never contains '-'. + crate_stem: String, + /// Typically based on `.rs` input file name. Any '-' is preserved. + filestem: String, + pub single_output_file: Option<OutFileName>, + temps_directory: Option<PathBuf>, + pub outputs: OutputTypes, +} + +pub const RLINK_EXT: &str = "rlink"; +pub const RUST_CGU_EXT: &str = "rcgu"; +pub const DWARF_OBJECT_EXT: &str = "dwo"; + +impl OutputFilenames { + pub fn new( + out_directory: PathBuf, + out_crate_name: String, + out_filestem: String, + single_output_file: Option<OutFileName>, + temps_directory: Option<PathBuf>, + extra: String, + outputs: OutputTypes, + ) -> Self { + OutputFilenames { + out_directory, + single_output_file, + temps_directory, + outputs, + crate_stem: format!("{out_crate_name}{extra}"), + filestem: format!("{out_filestem}{extra}"), + } + } + + pub fn path(&self, flavor: OutputType) -> OutFileName { + self.outputs + .get(&flavor) + .and_then(|p| p.to_owned()) + .or_else(|| self.single_output_file.clone()) + .unwrap_or_else(|| OutFileName::Real(self.output_path(flavor))) + } + + /// Gets the output path where a compilation artifact of the given type + /// should be placed on disk. + fn output_path(&self, flavor: OutputType) -> PathBuf { + let extension = flavor.extension(); + match flavor { + OutputType::Metadata => { + self.out_directory.join(format!("lib{}.{}", self.crate_stem, extension)) + } + _ => self.with_directory_and_extension(&self.out_directory, extension), + } + } + + /// Gets the path where a compilation artifact of the given type for the + /// given codegen unit should be placed on disk. If codegen_unit_name is + /// None, a path distinct from those of any codegen unit will be generated. + pub fn temp_path(&self, flavor: OutputType, codegen_unit_name: Option<&str>) -> PathBuf { + let extension = flavor.extension(); + self.temp_path_ext(extension, codegen_unit_name) + } + + /// Like `temp_path`, but specifically for dwarf objects. + pub fn temp_path_dwo(&self, codegen_unit_name: Option<&str>) -> PathBuf { + self.temp_path_ext(DWARF_OBJECT_EXT, codegen_unit_name) + } + + /// Like `temp_path`, but also supports things where there is no corresponding + /// OutputType, like noopt-bitcode or lto-bitcode. + pub fn temp_path_ext(&self, ext: &str, codegen_unit_name: Option<&str>) -> PathBuf { + let mut extension = String::new(); + + if let Some(codegen_unit_name) = codegen_unit_name { + extension.push_str(codegen_unit_name); + } + + if !ext.is_empty() { + if !extension.is_empty() { + extension.push('.'); + extension.push_str(RUST_CGU_EXT); + extension.push('.'); + } + + extension.push_str(ext); + } + + let temps_directory = self.temps_directory.as_ref().unwrap_or(&self.out_directory); + + self.with_directory_and_extension(temps_directory, &extension) + } + + pub fn with_extension(&self, extension: &str) -> PathBuf { + self.with_directory_and_extension(&self.out_directory, extension) + } + + fn with_directory_and_extension(&self, directory: &PathBuf, extension: &str) -> PathBuf { + let mut path = directory.join(&self.filestem); + path.set_extension(extension); + path + } + + /// Returns the path for the Split DWARF file - this can differ depending on which Split DWARF + /// mode is being used, which is the logic that this function is intended to encapsulate. + pub fn split_dwarf_path( + &self, + split_debuginfo_kind: SplitDebuginfo, + split_dwarf_kind: SplitDwarfKind, + cgu_name: Option<&str>, + ) -> Option<PathBuf> { + let obj_out = self.temp_path(OutputType::Object, cgu_name); + let dwo_out = self.temp_path_dwo(cgu_name); + match (split_debuginfo_kind, split_dwarf_kind) { + (SplitDebuginfo::Off, SplitDwarfKind::Single | SplitDwarfKind::Split) => None, + // Single mode doesn't change how DWARF is emitted, but does add Split DWARF attributes + // (pointing at the path which is being determined here). Use the path to the current + // object file. + (SplitDebuginfo::Packed | SplitDebuginfo::Unpacked, SplitDwarfKind::Single) => { + Some(obj_out) + } + // Split mode emits the DWARF into a different file, use that path. + (SplitDebuginfo::Packed | SplitDebuginfo::Unpacked, SplitDwarfKind::Split) => { + Some(dwo_out) + } + } + } +} + +bitflags::bitflags! { + /// Scopes used to determined if it need to apply to --remap-path-prefix + #[derive(Clone, Copy, PartialEq, Eq, Hash)] + pub struct RemapPathScopeComponents: u8 { + /// Apply remappings to the expansion of std::file!() macro + const MACRO = 1 << 0; + /// Apply remappings to printed compiler diagnostics + const DIAGNOSTICS = 1 << 1; + /// Apply remappings to debug information + const DEBUGINFO = 1 << 3; + + /// An alias for `macro` and `debuginfo`. This ensures all paths in compiled + /// executables or libraries are remapped but not elsewhere. + const OBJECT = Self::MACRO.bits() | Self::DEBUGINFO.bits(); + } +} + +pub fn host_tuple() -> &'static str { + // Get the host triple out of the build environment. This ensures that our + // idea of the host triple is the same as for the set of libraries we've + // actually built. We can't just take LLVM's host triple because they + // normalize all ix86 architectures to i386. + // + // Instead of grabbing the host triple (for the current host), we grab (at + // compile time) the target triple that this rustc is built with and + // calling that (at runtime) the host triple. + (option_env!("CFG_COMPILER_HOST_TRIPLE")).expect("CFG_COMPILER_HOST_TRIPLE") +} + +fn file_path_mapping( + remap_path_prefix: Vec<(PathBuf, PathBuf)>, + unstable_opts: &UnstableOptions, +) -> FilePathMapping { + FilePathMapping::new( + remap_path_prefix.clone(), + if unstable_opts.remap_path_scope.contains(RemapPathScopeComponents::DIAGNOSTICS) + && !remap_path_prefix.is_empty() + { + FileNameDisplayPreference::Remapped + } else { + FileNameDisplayPreference::Local + }, + ) +} + +impl Default for Options { + fn default() -> Options { + Options { + assert_incr_state: None, + crate_types: Vec::new(), + optimize: OptLevel::No, + debuginfo: DebugInfo::None, + debuginfo_compression: DebugInfoCompression::None, + lint_opts: Vec::new(), + lint_cap: None, + describe_lints: false, + output_types: OutputTypes(BTreeMap::new()), + search_paths: vec![], + maybe_sysroot: None, + target_triple: TargetTuple::from_tuple(host_tuple()), + test: false, + incremental: None, + untracked_state_hash: Default::default(), + unstable_opts: Default::default(), + prints: Vec::new(), + cg: Default::default(), + error_format: ErrorOutputType::default(), + diagnostic_width: None, + externs: Externs(BTreeMap::new()), + crate_name: None, + libs: Vec::new(), + unstable_features: UnstableFeatures::Disallow, + debug_assertions: true, + actually_rustdoc: false, + resolve_doc_links: ResolveDocLinks::None, + trimmed_def_paths: false, + cli_forced_codegen_units: None, + cli_forced_local_thinlto_off: false, + remap_path_prefix: Vec::new(), + real_rust_source_base_dir: None, + edition: DEFAULT_EDITION, + json_artifact_notifications: false, + json_unused_externs: JsonUnusedExterns::No, + json_future_incompat: false, + pretty: None, + working_dir: RealFileName::LocalPath(std::env::current_dir().unwrap()), + color: ColorConfig::Auto, + logical_env: FxIndexMap::default(), + verbose: false, + } + } +} + +impl Options { + /// Returns `true` if there is a reason to build the dep graph. + pub fn build_dep_graph(&self) -> bool { + self.incremental.is_some() + || self.unstable_opts.dump_dep_graph + || self.unstable_opts.query_dep_graph + } + + pub fn file_path_mapping(&self) -> FilePathMapping { + file_path_mapping(self.remap_path_prefix.clone(), &self.unstable_opts) + } + + /// Returns `true` if there will be an output file generated. + pub fn will_create_output_file(&self) -> bool { + !self.unstable_opts.parse_only && // The file is just being parsed + self.unstable_opts.ls.is_empty() // The file is just being queried + } + + #[inline] + pub fn share_generics(&self) -> bool { + match self.unstable_opts.share_generics { + Some(setting) => setting, + None => match self.optimize { + OptLevel::No | OptLevel::Less | OptLevel::Size | OptLevel::SizeMin => true, + OptLevel::Default | OptLevel::Aggressive => false, + }, + } + } + + pub fn get_symbol_mangling_version(&self) -> SymbolManglingVersion { + self.cg.symbol_mangling_version.unwrap_or(SymbolManglingVersion::Legacy) + } +} + +impl UnstableOptions { + pub fn dcx_flags(&self, can_emit_warnings: bool) -> DiagCtxtFlags { + DiagCtxtFlags { + can_emit_warnings, + treat_err_as_bug: self.treat_err_as_bug, + eagerly_emit_delayed_bugs: self.eagerly_emit_delayed_bugs, + macro_backtrace: self.macro_backtrace, + deduplicate_diagnostics: self.deduplicate_diagnostics, + track_diagnostics: self.track_diagnostics, + } + } + + pub fn src_hash_algorithm(&self, target: &Target) -> SourceFileHashAlgorithm { + self.src_hash_algorithm.unwrap_or_else(|| { + if target.is_like_msvc { + SourceFileHashAlgorithm::Sha256 + } else { + SourceFileHashAlgorithm::Md5 + } + }) + } + + pub fn checksum_hash_algorithm(&self) -> Option<SourceFileHashAlgorithm> { + self.checksum_hash_algorithm + } +} + +// The type of entry function, so users can have their own entry functions +#[derive(Copy, Clone, PartialEq, Hash, Debug, HashStable_Generic)] +pub enum EntryFnType { + Main { + /// Specifies what to do with `SIGPIPE` before calling `fn main()`. + /// + /// What values that are valid and what they mean must be in sync + /// across rustc and libstd, but we don't want it public in libstd, + /// so we take a bit of an unusual approach with simple constants + /// and an `include!()`. + sigpipe: u8, + }, + Start, +} + +#[derive(Copy, PartialEq, PartialOrd, Clone, Ord, Eq, Hash, Debug, Encodable, Decodable)] +#[derive(HashStable_Generic)] +pub enum CrateType { + Executable, + Dylib, + Rlib, + Staticlib, + Cdylib, + ProcMacro, +} + +impl CrateType { + pub fn has_metadata(self) -> bool { + match self { + CrateType::Rlib | CrateType::Dylib | CrateType::ProcMacro => true, + CrateType::Executable | CrateType::Cdylib | CrateType::Staticlib => false, + } + } +} + +#[derive(Clone, Hash, Debug, PartialEq, Eq)] +pub enum Passes { + Some(Vec<String>), + All, +} + +impl Passes { + fn is_empty(&self) -> bool { + match *self { + Passes::Some(ref v) => v.is_empty(), + Passes::All => false, + } + } + + pub(crate) fn extend(&mut self, passes: impl IntoIterator<Item = String>) { + match *self { + Passes::Some(ref mut v) => v.extend(passes), + Passes::All => {} + } + } +} + +#[derive(Clone, Copy, Hash, Debug, PartialEq)] +pub enum PAuthKey { + A, + B, +} + +#[derive(Clone, Copy, Hash, Debug, PartialEq)] +pub struct PacRet { + pub leaf: bool, + pub pc: bool, + pub key: PAuthKey, +} + +#[derive(Clone, Copy, Hash, Debug, PartialEq, Default)] +pub struct BranchProtection { + pub bti: bool, + pub pac_ret: Option<PacRet>, +} + +pub(crate) const fn default_lib_output() -> CrateType { + CrateType::Rlib +} + +pub fn build_configuration(sess: &Session, mut user_cfg: Cfg) -> Cfg { + // First disallow some configuration given on the command line + cfg::disallow_cfgs(sess, &user_cfg); + + // Then combine the configuration requested by the session (command line) with + // some default and generated configuration items. + user_cfg.extend(cfg::default_configuration(sess)); + user_cfg +} + +pub fn build_target_config(early_dcx: &EarlyDiagCtxt, opts: &Options, sysroot: &Path) -> Target { + match Target::search(&opts.target_triple, sysroot) { + Ok((target, warnings)) => { + for warning in warnings.warning_messages() { + early_dcx.early_warn(warning) + } + + if !matches!(target.pointer_width, 16 | 32 | 64) { + early_dcx.early_fatal(format!( + "target specification was invalid: unrecognized target-pointer-width {}", + target.pointer_width + )) + } + target + } + Err(e) => early_dcx.early_fatal(format!( + "Error loading target specification: {e}. \ + Run `rustc --print target-list` for a list of built-in targets" + )), + } +} + +#[derive(Copy, Clone, PartialEq, Eq, Debug)] +pub enum OptionStability { + Stable, + Unstable, +} + +#[derive(Copy, Clone, PartialEq, Eq, Debug)] +pub enum OptionKind { + /// An option that takes a value, and cannot appear more than once (e.g. `--out-dir`). + /// + /// Corresponds to [`getopts::Options::optopt`]. + Opt, + + /// An option that takes a value, and can appear multiple times (e.g. `--emit`). + /// + /// Corresponds to [`getopts::Options::optmulti`]. + Multi, + + /// An option that does not take a value, and cannot appear more than once (e.g. `--help`). + /// + /// Corresponds to [`getopts::Options::optflag`]. + /// The `hint` string must be empty. + Flag, + + /// An option that does not take a value, and can appear multiple times (e.g. `-O`). + /// + /// Corresponds to [`getopts::Options::optflagmulti`]. + /// The `hint` string must be empty. + FlagMulti, +} + +pub struct RustcOptGroup { + /// The "primary" name for this option. Normally equal to `long_name`, + /// except for options that don't have a long name, in which case + /// `short_name` is used. + /// + /// This is needed when interacting with `getopts` in some situations, + /// because if an option has both forms, that library treats the long name + /// as primary and the short name as an alias. + pub name: &'static str, + stability: OptionStability, + kind: OptionKind, + + short_name: &'static str, + long_name: &'static str, + desc: &'static str, + value_hint: &'static str, + + /// If true, this option should not be printed by `rustc --help`, but + /// should still be printed by `rustc --help -v`. + pub is_verbose_help_only: bool, +} + +impl RustcOptGroup { + pub fn is_stable(&self) -> bool { + self.stability == OptionStability::Stable + } + + pub fn apply(&self, options: &mut getopts::Options) { + let &Self { short_name, long_name, desc, value_hint, .. } = self; + match self.kind { + OptionKind::Opt => options.optopt(short_name, long_name, desc, value_hint), + OptionKind::Multi => options.optmulti(short_name, long_name, desc, value_hint), + OptionKind::Flag => options.optflag(short_name, long_name, desc), + OptionKind::FlagMulti => options.optflagmulti(short_name, long_name, desc), + }; + } +} + +pub fn make_opt( + stability: OptionStability, + kind: OptionKind, + short_name: &'static str, + long_name: &'static str, + desc: &'static str, + value_hint: &'static str, +) -> RustcOptGroup { + // "Flag" options don't have a value, and therefore don't have a value hint. + match kind { + OptionKind::Opt | OptionKind::Multi => {} + OptionKind::Flag | OptionKind::FlagMulti => assert_eq!(value_hint, ""), + } + RustcOptGroup { + name: cmp::max_by_key(short_name, long_name, |s| s.len()), + stability, + kind, + short_name, + long_name, + desc, + value_hint, + is_verbose_help_only: false, + } +} + +static EDITION_STRING: LazyLock<String> = LazyLock::new(|| { + format!( + "Specify which edition of the compiler to use when compiling code. \ +The default is {DEFAULT_EDITION} and the latest stable edition is {LATEST_STABLE_EDITION}." + ) +}); + +/// Returns all rustc command line options, including metadata for +/// each option, such as whether the option is stable. +pub fn rustc_optgroups() -> Vec<RustcOptGroup> { + use OptionKind::{Flag, FlagMulti, Multi, Opt}; + use OptionStability::{Stable, Unstable}; + + use self::make_opt as opt; + + let mut options = vec![ + opt(Stable, Flag, "h", "help", "Display this message", ""), + opt( + Stable, + Multi, + "", + "cfg", + "Configure the compilation environment.\n\ + SPEC supports the syntax `NAME[=\"VALUE\"]`.", + "SPEC", + ), + opt(Stable, Multi, "", "check-cfg", "Provide list of expected cfgs for checking", "SPEC"), + opt( + Stable, + Multi, + "L", + "", + "Add a directory to the library search path. \ + The optional KIND can be one of dependency, crate, native, framework, or all (the default).", + "[KIND=]PATH", + ), + opt( + Stable, + Multi, + "l", + "", + "Link the generated crate(s) to the specified native\n\ + library NAME. The optional KIND can be one of\n\ + static, framework, or dylib (the default).\n\ + Optional comma separated MODIFIERS\n\ + (bundle|verbatim|whole-archive|as-needed)\n\ + may be specified each with a prefix of either '+' to\n\ + enable or '-' to disable.", + "[KIND[:MODIFIERS]=]NAME[:RENAME]", + ), + make_crate_type_option(), + opt(Stable, Opt, "", "crate-name", "Specify the name of the crate being built", "NAME"), + opt(Stable, Opt, "", "edition", &EDITION_STRING, EDITION_NAME_LIST), + opt( + Stable, + Multi, + "", + "emit", + "Comma separated list of types of output for the compiler to emit", + "[asm|llvm-bc|llvm-ir|obj|metadata|link|dep-info|mir]", + ), + opt( + Stable, + Multi, + "", + "print", + "Compiler information to print on stdout", + "[crate-name|file-names|sysroot|target-libdir|cfg|check-cfg|calling-conventions|\ + target-list|target-cpus|target-features|relocation-models|code-models|\ + tls-models|target-spec-json|all-target-specs-json|native-static-libs|\ + stack-protector-strategies|link-args|deployment-target]", + ), + opt(Stable, FlagMulti, "g", "", "Equivalent to -C debuginfo=2", ""), + opt(Stable, FlagMulti, "O", "", "Equivalent to -C opt-level=2", ""), + opt(Stable, Opt, "o", "", "Write output to <filename>", "FILENAME"), + opt(Stable, Opt, "", "out-dir", "Write output to compiler-chosen filename in <dir>", "DIR"), + opt( + Stable, + Opt, + "", + "explain", + "Provide a detailed explanation of an error message", + "OPT", + ), + opt(Stable, Flag, "", "test", "Build a test harness", ""), + opt(Stable, Opt, "", "target", "Target triple for which the code is compiled", "TARGET"), + opt(Stable, Multi, "A", "allow", "Set lint allowed", "LINT"), + opt(Stable, Multi, "W", "warn", "Set lint warnings", "LINT"), + opt(Stable, Multi, "", "force-warn", "Set lint force-warn", "LINT"), + opt(Stable, Multi, "D", "deny", "Set lint denied", "LINT"), + opt(Stable, Multi, "F", "forbid", "Set lint forbidden", "LINT"), + opt( + Stable, + Multi, + "", + "cap-lints", + "Set the most restrictive lint level. More restrictive lints are capped at this level", + "LEVEL", + ), + opt(Stable, Multi, "C", "codegen", "Set a codegen option", "OPT[=VALUE]"), + opt(Stable, Flag, "V", "version", "Print version info and exit", ""), + opt(Stable, Flag, "v", "verbose", "Use verbose output", ""), + ]; + + // Options in this list are hidden from `rustc --help` by default, but are + // shown by `rustc --help -v`. + let verbose_only = [ + opt( + Stable, + Multi, + "", + "extern", + "Specify where an external rust library is located", + "NAME[=PATH]", + ), + opt(Stable, Opt, "", "sysroot", "Override the system root", "PATH"), + opt(Unstable, Multi, "Z", "", "Set unstable / perma-unstable options", "FLAG"), + opt( + Stable, + Opt, + "", + "error-format", + "How errors and other messages are produced", + "human|json|short", + ), + opt(Stable, Multi, "", "json", "Configure the JSON output of the compiler", "CONFIG"), + opt( + Stable, + Opt, + "", + "color", + "Configure coloring of output: + auto = colorize, if output goes to a tty (default); + always = always colorize output; + never = never colorize output", + "auto|always|never", + ), + opt( + Stable, + Opt, + "", + "diagnostic-width", + "Inform rustc of the width of the output so that diagnostics can be truncated to fit", + "WIDTH", + ), + opt( + Stable, + Multi, + "", + "remap-path-prefix", + "Remap source names in all output (compiler messages and output files)", + "FROM=TO", + ), + opt(Unstable, Multi, "", "env-set", "Inject an environment variable", "VAR=VALUE"), + ]; + options.extend(verbose_only.into_iter().map(|mut opt| { + opt.is_verbose_help_only = true; + opt + })); + + options +} + +pub fn get_cmd_lint_options( + early_dcx: &EarlyDiagCtxt, + matches: &getopts::Matches, +) -> (Vec<(String, lint::Level)>, bool, Option<lint::Level>) { + let mut lint_opts_with_position = vec![]; + let mut describe_lints = false; + + for level in [lint::Allow, lint::Warn, lint::ForceWarn(None), lint::Deny, lint::Forbid] { + for (arg_pos, lint_name) in matches.opt_strs_pos(level.as_str()) { + if lint_name == "help" { + describe_lints = true; + } else { + lint_opts_with_position.push((arg_pos, lint_name.replace('-', "_"), level)); + } + } + } + + lint_opts_with_position.sort_by_key(|x| x.0); + let lint_opts = lint_opts_with_position + .iter() + .cloned() + .map(|(_, lint_name, level)| (lint_name, level)) + .collect(); + + let lint_cap = matches.opt_str("cap-lints").map(|cap| { + lint::Level::from_str(&cap) + .unwrap_or_else(|| early_dcx.early_fatal(format!("unknown lint level: `{cap}`"))) + }); + + (lint_opts, describe_lints, lint_cap) +} + +/// Parses the `--color` flag. +pub fn parse_color(early_dcx: &EarlyDiagCtxt, matches: &getopts::Matches) -> ColorConfig { + match matches.opt_str("color").as_deref() { + Some("auto") => ColorConfig::Auto, + Some("always") => ColorConfig::Always, + Some("never") => ColorConfig::Never, + + None => ColorConfig::Auto, + + Some(arg) => early_dcx.early_fatal(format!( + "argument for `--color` must be auto, \ + always or never (instead was `{arg}`)" + )), + } +} + +/// Possible json config files +pub struct JsonConfig { + pub json_rendered: HumanReadableErrorType, + pub json_color: ColorConfig, + json_artifact_notifications: bool, + pub json_unused_externs: JsonUnusedExterns, + json_future_incompat: bool, +} + +/// Report unused externs in event stream +#[derive(Copy, Clone)] +pub enum JsonUnusedExterns { + /// Do not + No, + /// Report, but do not exit with failure status for deny/forbid + Silent, + /// Report, and also exit with failure status for deny/forbid + Loud, +} + +impl JsonUnusedExterns { + pub fn is_enabled(&self) -> bool { + match self { + JsonUnusedExterns::No => false, + JsonUnusedExterns::Loud | JsonUnusedExterns::Silent => true, + } + } + + pub fn is_loud(&self) -> bool { + match self { + JsonUnusedExterns::No | JsonUnusedExterns::Silent => false, + JsonUnusedExterns::Loud => true, + } + } +} + +/// Parse the `--json` flag. +/// +/// The first value returned is how to render JSON diagnostics, and the second +/// is whether or not artifact notifications are enabled. +pub fn parse_json(early_dcx: &EarlyDiagCtxt, matches: &getopts::Matches) -> JsonConfig { + let mut json_rendered = HumanReadableErrorType::Default; + let mut json_color = ColorConfig::Never; + let mut json_artifact_notifications = false; + let mut json_unused_externs = JsonUnusedExterns::No; + let mut json_future_incompat = false; + for option in matches.opt_strs("json") { + // For now conservatively forbid `--color` with `--json` since `--json` + // won't actually be emitting any colors and anything colorized is + // embedded in a diagnostic message anyway. + if matches.opt_str("color").is_some() { + early_dcx.early_fatal("cannot specify the `--color` option with `--json`"); + } + + for sub_option in option.split(',') { + match sub_option { + "diagnostic-short" => json_rendered = HumanReadableErrorType::Short, + "diagnostic-unicode" => { + json_rendered = HumanReadableErrorType::Unicode; + } + "diagnostic-rendered-ansi" => json_color = ColorConfig::Always, + "artifacts" => json_artifact_notifications = true, + "unused-externs" => json_unused_externs = JsonUnusedExterns::Loud, + "unused-externs-silent" => json_unused_externs = JsonUnusedExterns::Silent, + "future-incompat" => json_future_incompat = true, + s => early_dcx.early_fatal(format!("unknown `--json` option `{s}`")), + } + } + } + + JsonConfig { + json_rendered, + json_color, + json_artifact_notifications, + json_unused_externs, + json_future_incompat, + } +} + +/// Parses the `--error-format` flag. +pub fn parse_error_format( + early_dcx: &mut EarlyDiagCtxt, + matches: &getopts::Matches, + color: ColorConfig, + json_color: ColorConfig, + json_rendered: HumanReadableErrorType, +) -> ErrorOutputType { + // We need the `opts_present` check because the driver will send us Matches + // with only stable options if no unstable options are used. Since error-format + // is unstable, it will not be present. We have to use `opts_present` not + // `opt_present` because the latter will panic. + let error_format = if matches.opts_present(&["error-format".to_owned()]) { + match matches.opt_str("error-format").as_deref() { + None | Some("human") => { + ErrorOutputType::HumanReadable(HumanReadableErrorType::Default, color) + } + Some("human-annotate-rs") => { + ErrorOutputType::HumanReadable(HumanReadableErrorType::AnnotateSnippet, color) + } + Some("json") => { + ErrorOutputType::Json { pretty: false, json_rendered, color_config: json_color } + } + Some("pretty-json") => { + ErrorOutputType::Json { pretty: true, json_rendered, color_config: json_color } + } + Some("short") => ErrorOutputType::HumanReadable(HumanReadableErrorType::Short, color), + Some("human-unicode") => { + ErrorOutputType::HumanReadable(HumanReadableErrorType::Unicode, color) + } + Some(arg) => { + early_dcx.abort_if_error_and_set_error_format(ErrorOutputType::HumanReadable( + HumanReadableErrorType::Default, + color, + )); + early_dcx.early_fatal(format!( + "argument for `--error-format` must be `human`, `human-annotate-rs`, \ + `human-unicode`, `json`, `pretty-json` or `short` (instead was `{arg}`)" + )) + } + } + } else { + ErrorOutputType::HumanReadable(HumanReadableErrorType::Default, color) + }; + + match error_format { + ErrorOutputType::Json { .. } => {} + + // Conservatively require that the `--json` argument is coupled with + // `--error-format=json`. This means that `--json` is specified we + // should actually be emitting JSON blobs. + _ if !matches.opt_strs("json").is_empty() => { + early_dcx.early_fatal("using `--json` requires also using `--error-format=json`"); + } + + _ => {} + } + + error_format +} + +pub fn parse_crate_edition(early_dcx: &EarlyDiagCtxt, matches: &getopts::Matches) -> Edition { + let edition = match matches.opt_str("edition") { + Some(arg) => Edition::from_str(&arg).unwrap_or_else(|_| { + early_dcx.early_fatal(format!( + "argument for `--edition` must be one of: \ + {EDITION_NAME_LIST}. (instead was `{arg}`)" + )) + }), + None => DEFAULT_EDITION, + }; + + if !edition.is_stable() && !nightly_options::is_unstable_enabled(matches) { + let is_nightly = nightly_options::match_is_nightly_build(matches); + let msg = if !is_nightly { + format!( + "the crate requires edition {edition}, but the latest edition supported by this Rust version is {LATEST_STABLE_EDITION}" + ) + } else { + format!("edition {edition} is unstable and only available with -Z unstable-options") + }; + early_dcx.early_fatal(msg) + } + + edition +} + +fn check_error_format_stability( + early_dcx: &EarlyDiagCtxt, + unstable_opts: &UnstableOptions, + format: ErrorOutputType, +) { + if unstable_opts.unstable_options { + return; + } + let format = match format { + ErrorOutputType::Json { pretty: true, .. } => "pretty-json", + ErrorOutputType::HumanReadable(format, _) => match format { + HumanReadableErrorType::AnnotateSnippet => "human-annotate-rs", + HumanReadableErrorType::Unicode => "human-unicode", + _ => return, + }, + _ => return, + }; + early_dcx.early_fatal(format!("`--error-format={format}` is unstable")) +} + +fn parse_output_types( + early_dcx: &EarlyDiagCtxt, + unstable_opts: &UnstableOptions, + matches: &getopts::Matches, +) -> OutputTypes { + let mut output_types = BTreeMap::new(); + if !unstable_opts.parse_only { + for list in matches.opt_strs("emit") { + for output_type in list.split(',') { + let (shorthand, path) = split_out_file_name(output_type); + let output_type = OutputType::from_shorthand(shorthand).unwrap_or_else(|| { + early_dcx.early_fatal(format!( + "unknown emission type: `{shorthand}` - expected one of: {display}", + display = OutputType::shorthands_display(), + )) + }); + if output_type == OutputType::ThinLinkBitcode && !unstable_opts.unstable_options { + early_dcx.early_fatal(format!( + "{} requested but -Zunstable-options not specified", + OutputType::ThinLinkBitcode.shorthand() + )); + } + output_types.insert(output_type, path); + } + } + }; + if output_types.is_empty() { + output_types.insert(OutputType::Exe, None); + } + OutputTypes(output_types) +} + +fn split_out_file_name(arg: &str) -> (&str, Option<OutFileName>) { + match arg.split_once('=') { + None => (arg, None), + Some((kind, "-")) => (kind, Some(OutFileName::Stdout)), + Some((kind, path)) => (kind, Some(OutFileName::Real(PathBuf::from(path)))), + } +} + +fn should_override_cgus_and_disable_thinlto( + early_dcx: &EarlyDiagCtxt, + output_types: &OutputTypes, + matches: &getopts::Matches, + mut codegen_units: Option<usize>, +) -> (bool, Option<usize>) { + let mut disable_local_thinlto = false; + // Issue #30063: if user requests LLVM-related output to one + // particular path, disable codegen-units. + let incompatible: Vec<_> = output_types + .0 + .iter() + .map(|ot_path| ot_path.0) + .filter(|ot| !ot.is_compatible_with_codegen_units_and_single_output_file()) + .map(|ot| ot.shorthand()) + .collect(); + if !incompatible.is_empty() { + match codegen_units { + Some(n) if n > 1 => { + if matches.opt_present("o") { + for ot in &incompatible { + early_dcx.early_warn(format!( + "`--emit={ot}` with `-o` incompatible with \ + `-C codegen-units=N` for N > 1", + )); + } + early_dcx.early_warn("resetting to default -C codegen-units=1"); + codegen_units = Some(1); + disable_local_thinlto = true; + } + } + _ => { + codegen_units = Some(1); + disable_local_thinlto = true; + } + } + } + + if codegen_units == Some(0) { + early_dcx.early_fatal("value for codegen units must be a positive non-zero integer"); + } + + (disable_local_thinlto, codegen_units) +} + +fn collect_print_requests( + early_dcx: &EarlyDiagCtxt, + cg: &mut CodegenOptions, + unstable_opts: &UnstableOptions, + matches: &getopts::Matches, +) -> Vec<PrintRequest> { + let mut prints = Vec::<PrintRequest>::new(); + if cg.target_cpu.as_ref().is_some_and(|s| s == "help") { + prints.push(PrintRequest { kind: PrintKind::TargetCPUs, out: OutFileName::Stdout }); + cg.target_cpu = None; + }; + if cg.target_feature == "help" { + prints.push(PrintRequest { kind: PrintKind::TargetFeatures, out: OutFileName::Stdout }); + cg.target_feature = String::new(); + } + + const PRINT_KINDS: &[(&str, PrintKind)] = &[ + // tidy-alphabetical-start + ("all-target-specs-json", PrintKind::AllTargetSpecs), + ("calling-conventions", PrintKind::CallingConventions), + ("cfg", PrintKind::Cfg), + ("check-cfg", PrintKind::CheckCfg), + ("code-models", PrintKind::CodeModels), + ("crate-name", PrintKind::CrateName), + ("deployment-target", PrintKind::DeploymentTarget), + ("file-names", PrintKind::FileNames), + ("host-tuple", PrintKind::HostTuple), + ("link-args", PrintKind::LinkArgs), + ("native-static-libs", PrintKind::NativeStaticLibs), + ("relocation-models", PrintKind::RelocationModels), + ("split-debuginfo", PrintKind::SplitDebuginfo), + ("stack-protector-strategies", PrintKind::StackProtectorStrategies), + ("sysroot", PrintKind::Sysroot), + ("target-cpus", PrintKind::TargetCPUs), + ("target-features", PrintKind::TargetFeatures), + ("target-libdir", PrintKind::TargetLibdir), + ("target-list", PrintKind::TargetList), + ("target-spec-json", PrintKind::TargetSpec), + ("tls-models", PrintKind::TlsModels), + // tidy-alphabetical-end + ]; + + // We disallow reusing the same path in multiple prints, such as `--print + // cfg=output.txt --print link-args=output.txt`, because outputs are printed + // by disparate pieces of the compiler, and keeping track of which files + // need to be overwritten vs appended to is annoying. + let mut printed_paths = FxHashSet::default(); + + prints.extend(matches.opt_strs("print").into_iter().map(|req| { + let (req, out) = split_out_file_name(&req); + + let kind = match PRINT_KINDS.iter().find(|&&(name, _)| name == req) { + Some((_, PrintKind::TargetSpec)) => { + if unstable_opts.unstable_options { + PrintKind::TargetSpec + } else { + early_dcx.early_fatal( + "the `-Z unstable-options` flag must also be passed to \ + enable the target-spec-json print option", + ); + } + } + Some((_, PrintKind::AllTargetSpecs)) => { + if unstable_opts.unstable_options { + PrintKind::AllTargetSpecs + } else { + early_dcx.early_fatal( + "the `-Z unstable-options` flag must also be passed to \ + enable the all-target-specs-json print option", + ); + } + } + Some((_, PrintKind::CheckCfg)) => { + if unstable_opts.unstable_options { + PrintKind::CheckCfg + } else { + early_dcx.early_fatal( + "the `-Z unstable-options` flag must also be passed to \ + enable the check-cfg print option", + ); + } + } + Some(&(_, print_kind)) => print_kind, + None => { + let prints = + PRINT_KINDS.iter().map(|(name, _)| format!("`{name}`")).collect::<Vec<_>>(); + let prints = prints.join(", "); + + let mut diag = + early_dcx.early_struct_fatal(format!("unknown print request: `{req}`")); + #[allow(rustc::diagnostic_outside_of_impl)] + diag.help(format!("valid print requests are: {prints}")); + diag.emit() + } + }; + + let out = out.unwrap_or(OutFileName::Stdout); + if let OutFileName::Real(path) = &out { + if !printed_paths.insert(path.clone()) { + early_dcx.early_fatal(format!( + "cannot print multiple outputs to the same path: {}", + path.display(), + )); + } + } + + PrintRequest { kind, out } + })); + + prints +} + +pub fn parse_target_triple(early_dcx: &EarlyDiagCtxt, matches: &getopts::Matches) -> TargetTuple { + match matches.opt_str("target") { + Some(target) if target.ends_with(".json") => { + let path = Path::new(&target); + TargetTuple::from_path(path).unwrap_or_else(|_| { + early_dcx.early_fatal(format!("target file {path:?} does not exist")) + }) + } + Some(target) => TargetTuple::TargetTuple(target), + _ => TargetTuple::from_tuple(host_tuple()), + } +} + +fn parse_opt_level( + early_dcx: &EarlyDiagCtxt, + matches: &getopts::Matches, + cg: &CodegenOptions, +) -> OptLevel { + // The `-O` and `-C opt-level` flags specify the same setting, so we want to be able + // to use them interchangeably. However, because they're technically different flags, + // we need to work out manually which should take precedence if both are supplied (i.e. + // the rightmost flag). We do this by finding the (rightmost) position of both flags and + // comparing them. Note that if a flag is not found, its position will be `None`, which + // always compared less than `Some(_)`. + let max_o = matches.opt_positions("O").into_iter().max(); + let max_c = matches + .opt_strs_pos("C") + .into_iter() + .flat_map(|(i, s)| { + // NB: This can match a string without `=`. + if let Some("opt-level") = s.split('=').next() { Some(i) } else { None } + }) + .max(); + if max_o > max_c { + OptLevel::Default + } else { + match cg.opt_level.as_ref() { + "0" => OptLevel::No, + "1" => OptLevel::Less, + "2" => OptLevel::Default, + "3" => OptLevel::Aggressive, + "s" => OptLevel::Size, + "z" => OptLevel::SizeMin, + arg => { + early_dcx.early_fatal(format!( + "optimization level needs to be \ + between 0-3, s or z (instead was `{arg}`)" + )); + } + } + } +} + +fn select_debuginfo(matches: &getopts::Matches, cg: &CodegenOptions) -> DebugInfo { + let max_g = matches.opt_positions("g").into_iter().max(); + let max_c = matches + .opt_strs_pos("C") + .into_iter() + .flat_map(|(i, s)| { + // NB: This can match a string without `=`. + if let Some("debuginfo") = s.split('=').next() { Some(i) } else { None } + }) + .max(); + if max_g > max_c { DebugInfo::Full } else { cg.debuginfo } +} + +fn parse_assert_incr_state( + early_dcx: &EarlyDiagCtxt, + opt_assertion: &Option<String>, +) -> Option<IncrementalStateAssertion> { + match opt_assertion { + Some(s) if s.as_str() == "loaded" => Some(IncrementalStateAssertion::Loaded), + Some(s) if s.as_str() == "not-loaded" => Some(IncrementalStateAssertion::NotLoaded), + Some(s) => { + early_dcx.early_fatal(format!("unexpected incremental state assertion value: {s}")) + } + None => None, + } +} + +fn parse_native_lib_kind( + early_dcx: &EarlyDiagCtxt, + matches: &getopts::Matches, + kind: &str, +) -> (NativeLibKind, Option<bool>) { + let (kind, modifiers) = match kind.split_once(':') { + None => (kind, None), + Some((kind, modifiers)) => (kind, Some(modifiers)), + }; + + let kind = match kind { + "static" => NativeLibKind::Static { bundle: None, whole_archive: None }, + "dylib" => NativeLibKind::Dylib { as_needed: None }, + "framework" => NativeLibKind::Framework { as_needed: None }, + "link-arg" => { + if !nightly_options::is_unstable_enabled(matches) { + let why = if nightly_options::match_is_nightly_build(matches) { + " and only accepted on the nightly compiler" + } else { + ", the `-Z unstable-options` flag must also be passed to use it" + }; + early_dcx.early_fatal(format!("library kind `link-arg` is unstable{why}")) + } + NativeLibKind::LinkArg + } + _ => early_dcx.early_fatal(format!( + "unknown library kind `{kind}`, expected one of: static, dylib, framework, link-arg" + )), + }; + match modifiers { + None => (kind, None), + Some(modifiers) => parse_native_lib_modifiers(early_dcx, kind, modifiers, matches), + } +} + +fn parse_native_lib_modifiers( + early_dcx: &EarlyDiagCtxt, + mut kind: NativeLibKind, + modifiers: &str, + matches: &getopts::Matches, +) -> (NativeLibKind, Option<bool>) { + let mut verbatim = None; + for modifier in modifiers.split(',') { + let (modifier, value) = match modifier.strip_prefix(['+', '-']) { + Some(m) => (m, modifier.starts_with('+')), + None => early_dcx.early_fatal( + "invalid linking modifier syntax, expected '+' or '-' prefix \ + before one of: bundle, verbatim, whole-archive, as-needed", + ), + }; + + let report_unstable_modifier = || { + if !nightly_options::is_unstable_enabled(matches) { + let why = if nightly_options::match_is_nightly_build(matches) { + " and only accepted on the nightly compiler" + } else { + ", the `-Z unstable-options` flag must also be passed to use it" + }; + early_dcx.early_fatal(format!("linking modifier `{modifier}` is unstable{why}")) + } + }; + let assign_modifier = |dst: &mut Option<bool>| { + if dst.is_some() { + let msg = format!("multiple `{modifier}` modifiers in a single `-l` option"); + early_dcx.early_fatal(msg) + } else { + *dst = Some(value); + } + }; + match (modifier, &mut kind) { + ("bundle", NativeLibKind::Static { bundle, .. }) => assign_modifier(bundle), + ("bundle", _) => early_dcx.early_fatal( + "linking modifier `bundle` is only compatible with `static` linking kind", + ), + + ("verbatim", _) => assign_modifier(&mut verbatim), + + ("whole-archive", NativeLibKind::Static { whole_archive, .. }) => { + assign_modifier(whole_archive) + } + ("whole-archive", _) => early_dcx.early_fatal( + "linking modifier `whole-archive` is only compatible with `static` linking kind", + ), + + ("as-needed", NativeLibKind::Dylib { as_needed }) + | ("as-needed", NativeLibKind::Framework { as_needed }) => { + report_unstable_modifier(); + assign_modifier(as_needed) + } + ("as-needed", _) => early_dcx.early_fatal( + "linking modifier `as-needed` is only compatible with \ + `dylib` and `framework` linking kinds", + ), + + // Note: this error also excludes the case with empty modifier + // string, like `modifiers = ""`. + _ => early_dcx.early_fatal(format!( + "unknown linking modifier `{modifier}`, expected one \ + of: bundle, verbatim, whole-archive, as-needed" + )), + } + } + + (kind, verbatim) +} + +fn parse_libs(early_dcx: &EarlyDiagCtxt, matches: &getopts::Matches) -> Vec<NativeLib> { + matches + .opt_strs("l") + .into_iter() + .map(|s| { + // Parse string of the form "[KIND[:MODIFIERS]=]lib[:new_name]", + // where KIND is one of "dylib", "framework", "static", "link-arg" and + // where MODIFIERS are a comma separated list of supported modifiers + // (bundle, verbatim, whole-archive, as-needed). Each modifier is prefixed + // with either + or - to indicate whether it is enabled or disabled. + // The last value specified for a given modifier wins. + let (name, kind, verbatim) = match s.split_once('=') { + None => (s, NativeLibKind::Unspecified, None), + Some((kind, name)) => { + let (kind, verbatim) = parse_native_lib_kind(early_dcx, matches, kind); + (name.to_string(), kind, verbatim) + } + }; + + let (name, new_name) = match name.split_once(':') { + None => (name, None), + Some((name, new_name)) => (name.to_string(), Some(new_name.to_owned())), + }; + if name.is_empty() { + early_dcx.early_fatal("library name must not be empty"); + } + NativeLib { name, new_name, kind, verbatim } + }) + .collect() +} + +pub fn parse_externs( + early_dcx: &EarlyDiagCtxt, + matches: &getopts::Matches, + unstable_opts: &UnstableOptions, +) -> Externs { + fn is_ascii_ident(string: &str) -> bool { + let mut chars = string.chars(); + if let Some(start) = chars.next() + && (start.is_ascii_alphabetic() || start == '_') + { + chars.all(|char| char.is_ascii_alphanumeric() || char == '_') + } else { + false + } + } + + let is_unstable_enabled = unstable_opts.unstable_options; + let mut externs: BTreeMap<String, ExternEntry> = BTreeMap::new(); + for arg in matches.opt_strs("extern") { + let (name, path) = match arg.split_once('=') { + None => (arg, None), + Some((name, path)) => (name.to_string(), Some(Path::new(path))), + }; + let (options, name) = match name.split_once(':') { + None => (None, name), + Some((opts, name)) => (Some(opts), name.to_string()), + }; + + if !is_ascii_ident(&name) { + let mut error = early_dcx.early_struct_fatal(format!( + "crate name `{name}` passed to `--extern` is not a valid ASCII identifier" + )); + let adjusted_name = name.replace('-', "_"); + if is_ascii_ident(&adjusted_name) { + #[allow(rustc::diagnostic_outside_of_impl)] // FIXME + error.help(format!( + "consider replacing the dashes with underscores: `{adjusted_name}`" + )); + } + error.emit(); + } + + let path = path.map(|p| CanonicalizedPath::new(p)); + + let entry = externs.entry(name.to_owned()); + + use std::collections::btree_map::Entry; + + let entry = if let Some(path) = path { + // --extern prelude_name=some_file.rlib + match entry { + Entry::Vacant(vacant) => { + let files = BTreeSet::from_iter(iter::once(path)); + vacant.insert(ExternEntry::new(ExternLocation::ExactPaths(files))) + } + Entry::Occupied(occupied) => { + let ext_ent = occupied.into_mut(); + match ext_ent { + ExternEntry { location: ExternLocation::ExactPaths(files), .. } => { + files.insert(path); + } + ExternEntry { + location: location @ ExternLocation::FoundInLibrarySearchDirectories, + .. + } => { + // Exact paths take precedence over search directories. + let files = BTreeSet::from_iter(iter::once(path)); + *location = ExternLocation::ExactPaths(files); + } + } + ext_ent + } + } + } else { + // --extern prelude_name + match entry { + Entry::Vacant(vacant) => { + vacant.insert(ExternEntry::new(ExternLocation::FoundInLibrarySearchDirectories)) + } + Entry::Occupied(occupied) => { + // Ignore if already specified. + occupied.into_mut() + } + } + }; + + let mut is_private_dep = false; + let mut add_prelude = true; + let mut nounused_dep = false; + let mut force = false; + if let Some(opts) = options { + if !is_unstable_enabled { + early_dcx.early_fatal( + "the `-Z unstable-options` flag must also be passed to \ + enable `--extern` options", + ); + } + for opt in opts.split(',') { + match opt { + "priv" => is_private_dep = true, + "noprelude" => { + if let ExternLocation::ExactPaths(_) = &entry.location { + add_prelude = false; + } else { + early_dcx.early_fatal( + "the `noprelude` --extern option requires a file path", + ); + } + } + "nounused" => nounused_dep = true, + "force" => force = true, + _ => early_dcx.early_fatal(format!("unknown --extern option `{opt}`")), + } + } + } + + // Crates start out being not private, and go to being private `priv` + // is specified. + entry.is_private_dep |= is_private_dep; + // likewise `nounused` + entry.nounused_dep |= nounused_dep; + // and `force` + entry.force |= force; + // If any flag is missing `noprelude`, then add to the prelude. + entry.add_prelude |= add_prelude; + } + Externs(externs) +} + +fn parse_remap_path_prefix( + early_dcx: &EarlyDiagCtxt, + matches: &getopts::Matches, + unstable_opts: &UnstableOptions, +) -> Vec<(PathBuf, PathBuf)> { + let mut mapping: Vec<(PathBuf, PathBuf)> = matches + .opt_strs("remap-path-prefix") + .into_iter() + .map(|remap| match remap.rsplit_once('=') { + None => { + early_dcx.early_fatal("--remap-path-prefix must contain '=' between FROM and TO") + } + Some((from, to)) => (PathBuf::from(from), PathBuf::from(to)), + }) + .collect(); + match &unstable_opts.remap_cwd_prefix { + Some(to) => match std::env::current_dir() { + Ok(cwd) => mapping.push((cwd, to.clone())), + Err(_) => (), + }, + None => (), + }; + mapping +} + +fn parse_logical_env( + early_dcx: &EarlyDiagCtxt, + matches: &getopts::Matches, +) -> FxIndexMap<String, String> { + let mut vars = FxIndexMap::default(); + + for arg in matches.opt_strs("env-set") { + if let Some((name, val)) = arg.split_once('=') { + vars.insert(name.to_string(), val.to_string()); + } else { + early_dcx.early_fatal(format!("`--env-set`: specify value for variable `{arg}`")); + } + } + + vars +} + +// JUSTIFICATION: before wrapper fn is available +#[allow(rustc::bad_opt_access)] +pub fn build_session_options(early_dcx: &mut EarlyDiagCtxt, matches: &getopts::Matches) -> Options { + let color = parse_color(early_dcx, matches); + + let edition = parse_crate_edition(early_dcx, matches); + + let JsonConfig { + json_rendered, + json_color, + json_artifact_notifications, + json_unused_externs, + json_future_incompat, + } = parse_json(early_dcx, matches); + + let error_format = parse_error_format(early_dcx, matches, color, json_color, json_rendered); + + early_dcx.abort_if_error_and_set_error_format(error_format); + + let diagnostic_width = matches.opt_get("diagnostic-width").unwrap_or_else(|_| { + early_dcx.early_fatal("`--diagnostic-width` must be an positive integer"); + }); + + let unparsed_crate_types = matches.opt_strs("crate-type"); + 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 (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 (disable_local_thinlto, codegen_units) = should_override_cgus_and_disable_thinlto( + early_dcx, + &output_types, + matches, + cg.codegen_units, + ); + + if unstable_opts.threads == 0 { + early_dcx.early_fatal("value for threads must be a positive non-zero integer"); + } + + if unstable_opts.threads == parse::MAX_THREADS_CAP { + early_dcx.early_warn(format!("number of threads was capped at {}", parse::MAX_THREADS_CAP)); + } + + let fuel = unstable_opts.fuel.is_some() || unstable_opts.print_fuel.is_some(); + if fuel && unstable_opts.threads > 1 { + early_dcx.early_fatal("optimization fuel is incompatible with multiple threads"); + } + if fuel && cg.incremental.is_some() { + early_dcx.early_fatal("optimization fuel is incompatible with incremental compilation"); + } + + let incremental = cg.incremental.as_ref().map(PathBuf::from); + + let assert_incr_state = parse_assert_incr_state(early_dcx, &unstable_opts.assert_incr_state); + + if cg.profile_generate.enabled() && cg.profile_use.is_some() { + early_dcx.early_fatal("options `-C profile-generate` and `-C profile-use` are exclusive"); + } + + if unstable_opts.profile_sample_use.is_some() + && (cg.profile_generate.enabled() || cg.profile_use.is_some()) + { + early_dcx.early_fatal( + "option `-Z profile-sample-use` cannot be used with `-C profile-generate` or `-C profile-use`", + ); + } + + // Check for unstable values of `-C symbol-mangling-version`. + // This is what prevents them from being used on stable compilers. + match cg.symbol_mangling_version { + // Stable values: + None | Some(SymbolManglingVersion::V0) => {} + + // Unstable values: + Some(SymbolManglingVersion::Legacy) => { + if !unstable_opts.unstable_options { + early_dcx.early_fatal( + "`-C symbol-mangling-version=legacy` requires `-Z unstable-options`", + ); + } + } + Some(SymbolManglingVersion::Hashed) => { + if !unstable_opts.unstable_options { + early_dcx.early_fatal( + "`-C symbol-mangling-version=hashed` requires `-Z unstable-options`", + ); + } + } + } + + if cg.instrument_coverage != InstrumentCoverage::No { + if cg.profile_generate.enabled() || cg.profile_use.is_some() { + early_dcx.early_fatal( + "option `-C instrument-coverage` is not compatible with either `-C profile-use` \ + or `-C profile-generate`", + ); + } + + // `-C instrument-coverage` implies `-C symbol-mangling-version=v0` - to ensure consistent + // and reversible name mangling. Note, LLVM coverage tools can analyze coverage over + // multiple runs, including some changes to source code; so mangled names must be consistent + // across compilations. + match cg.symbol_mangling_version { + None => cg.symbol_mangling_version = Some(SymbolManglingVersion::V0), + Some(SymbolManglingVersion::Legacy) => { + early_dcx.early_warn( + "-C instrument-coverage requires symbol mangling version `v0`, \ + but `-C symbol-mangling-version=legacy` was specified", + ); + } + Some(SymbolManglingVersion::V0) => {} + Some(SymbolManglingVersion::Hashed) => { + early_dcx.early_warn( + "-C instrument-coverage requires symbol mangling version `v0`, \ + but `-C symbol-mangling-version=hashed` was specified", + ); + } + } + } + + if let Ok(graphviz_font) = std::env::var("RUSTC_GRAPHVIZ_FONT") { + // FIXME: this is only mutation of UnstableOptions here, move into + // UnstableOptions::build? + unstable_opts.graphviz_font = graphviz_font; + } + + if !cg.embed_bitcode { + match cg.lto { + LtoCli::No | LtoCli::Unspecified => {} + LtoCli::Yes | LtoCli::NoParam | LtoCli::Thin | LtoCli::Fat => { + early_dcx.early_fatal("options `-C embed-bitcode=no` and `-C lto` are incompatible") + } + } + } + + if !nightly_options::is_unstable_enabled(matches) + && cg.force_frame_pointers == FramePointer::NonLeaf + { + early_dcx.early_fatal( + "`-Cforce-frame-pointers=non-leaf` or `always` also requires `-Zunstable-options` \ + and a nightly compiler", + ) + } + + // For testing purposes, until we have more feedback about these options: ensure `-Z + // unstable-options` is required when using the unstable `-C link-self-contained` and `-C + // linker-flavor` options. + if !nightly_options::is_unstable_enabled(matches) { + let uses_unstable_self_contained_option = + cg.link_self_contained.are_unstable_variants_set(); + if uses_unstable_self_contained_option { + early_dcx.early_fatal( + "only `-C link-self-contained` values `y`/`yes`/`on`/`n`/`no`/`off` are stable, \ + the `-Z unstable-options` flag must also be passed to use the unstable values", + ); + } + + if let Some(flavor) = cg.linker_flavor { + if flavor.is_unstable() { + early_dcx.early_fatal(format!( + "the linker flavor `{}` is unstable, the `-Z unstable-options` \ + flag must also be passed to use the unstable values", + flavor.desc() + )); + } + } + } + + // Check `-C link-self-contained` for consistency: individual components cannot be both enabled + // and disabled at the same time. + if let Some(erroneous_components) = cg.link_self_contained.check_consistency() { + let names: String = erroneous_components + .into_iter() + .map(|c| c.as_str().unwrap()) + .intersperse(", ") + .collect(); + early_dcx.early_fatal(format!( + "some `-C link-self-contained` components were both enabled and disabled: {names}" + )); + } + + let prints = collect_print_requests(early_dcx, &mut cg, &unstable_opts, matches); + + let cg = cg; + + let sysroot_opt = matches.opt_str("sysroot").map(|m| PathBuf::from(&m)); + let target_triple = parse_target_triple(early_dcx, matches); + let opt_level = parse_opt_level(early_dcx, matches, &cg); + // The `-g` and `-C debuginfo` flags specify the same setting, so we want to be able + // to use them interchangeably. See the note above (regarding `-O` and `-C opt-level`) + // for more details. + let debug_assertions = cg.debug_assertions.unwrap_or(opt_level == OptLevel::No); + let debuginfo = select_debuginfo(matches, &cg); + let debuginfo_compression = unstable_opts.debuginfo_compression; + + let libs = parse_libs(early_dcx, matches); + + let test = matches.opt_present("test"); + + if !cg.remark.is_empty() && debuginfo == DebugInfo::None { + early_dcx.early_warn("-C remark requires \"-C debuginfo=n\" to show source locations"); + } + + if cg.remark.is_empty() && unstable_opts.remark_dir.is_some() { + early_dcx + .early_warn("using -Z remark-dir without enabling remarks using e.g. -C remark=all"); + } + + let externs = parse_externs(early_dcx, matches, &unstable_opts); + + let crate_name = matches.opt_str("crate-name"); + + let remap_path_prefix = parse_remap_path_prefix(early_dcx, matches, &unstable_opts); + + let pretty = parse_pretty(early_dcx, &unstable_opts); + + // query-dep-graph is required if dump-dep-graph is given #106736 + if unstable_opts.dump_dep_graph && !unstable_opts.query_dep_graph { + early_dcx.early_fatal("can't dump dependency graph without `-Z query-dep-graph`"); + } + + let logical_env = parse_logical_env(early_dcx, matches); + + let sysroot = filesearch::materialize_sysroot(sysroot_opt); + + let real_rust_source_base_dir = { + // This is the location used by the `rust-src` `rustup` component. + let mut candidate = sysroot.join("lib/rustlib/src/rust"); + if let Ok(metadata) = candidate.symlink_metadata() { + // Replace the symlink bootstrap creates, with its destination. + // We could try to use `fs::canonicalize` instead, but that might + // produce unnecessarily verbose path. + if metadata.file_type().is_symlink() { + if let Ok(symlink_dest) = std::fs::read_link(&candidate) { + candidate = symlink_dest; + } + } + } + + // Only use this directory if it has a file we can expect to always find. + candidate.join("library/std/src/lib.rs").is_file().then_some(candidate) + }; + + let mut search_paths = vec![]; + for s in &matches.opt_strs("L") { + search_paths.push(SearchPath::from_cli_opt( + &sysroot, + &target_triple, + early_dcx, + s, + unstable_opts.unstable_options, + )); + } + + let working_dir = std::env::current_dir().unwrap_or_else(|e| { + early_dcx.early_fatal(format!("Current directory is invalid: {e}")); + }); + + let file_mapping = file_path_mapping(remap_path_prefix.clone(), &unstable_opts); + let working_dir = file_mapping.to_real_filename(&working_dir); + + let verbose = matches.opt_present("verbose") || unstable_opts.verbose_internals; + + Options { + assert_incr_state, + crate_types, + optimize: opt_level, + debuginfo, + debuginfo_compression, + lint_opts, + lint_cap, + describe_lints, + output_types, + search_paths, + maybe_sysroot: Some(sysroot), + target_triple, + test, + incremental, + untracked_state_hash: Default::default(), + unstable_opts, + prints, + cg, + error_format, + diagnostic_width, + externs, + unstable_features: UnstableFeatures::from_environment(crate_name.as_deref()), + crate_name, + libs, + debug_assertions, + actually_rustdoc: false, + resolve_doc_links: ResolveDocLinks::ExportedMetadata, + trimmed_def_paths: false, + cli_forced_codegen_units: codegen_units, + cli_forced_local_thinlto_off: disable_local_thinlto, + remap_path_prefix, + real_rust_source_base_dir, + edition, + json_artifact_notifications, + json_unused_externs, + json_future_incompat, + pretty, + working_dir, + color, + logical_env, + verbose, + } +} + +fn parse_pretty(early_dcx: &EarlyDiagCtxt, unstable_opts: &UnstableOptions) -> Option<PpMode> { + use PpMode::*; + + let first = match unstable_opts.unpretty.as_deref()? { + "normal" => Source(PpSourceMode::Normal), + "identified" => Source(PpSourceMode::Identified), + "expanded" => Source(PpSourceMode::Expanded), + "expanded,identified" => Source(PpSourceMode::ExpandedIdentified), + "expanded,hygiene" => Source(PpSourceMode::ExpandedHygiene), + "ast-tree" => AstTree, + "ast-tree,expanded" => AstTreeExpanded, + "hir" => Hir(PpHirMode::Normal), + "hir,identified" => Hir(PpHirMode::Identified), + "hir,typed" => Hir(PpHirMode::Typed), + "hir-tree" => HirTree, + "thir-tree" => ThirTree, + "thir-flat" => ThirFlat, + "mir" => Mir, + "stable-mir" => StableMir, + "mir-cfg" => MirCFG, + name => early_dcx.early_fatal(format!( + "argument to `unpretty` must be one of `normal`, `identified`, \ + `expanded`, `expanded,identified`, `expanded,hygiene`, \ + `ast-tree`, `ast-tree,expanded`, `hir`, `hir,identified`, \ + `hir,typed`, `hir-tree`, `thir-tree`, `thir-flat`, `mir`, `stable-mir`, or \ + `mir-cfg`; got {name}" + )), + }; + debug!("got unpretty option: {first:?}"); + Some(first) +} + +pub fn make_crate_type_option() -> RustcOptGroup { + make_opt( + OptionStability::Stable, + OptionKind::Multi, + "", + "crate-type", + "Comma separated list of types of crates + for the compiler to emit", + "[bin|lib|rlib|dylib|cdylib|staticlib|proc-macro]", + ) +} + +pub fn parse_crate_types_from_list(list_list: Vec<String>) -> Result<Vec<CrateType>, String> { + let mut crate_types: Vec<CrateType> = Vec::new(); + for unparsed_crate_type in &list_list { + for part in unparsed_crate_type.split(',') { + let new_part = match part { + "lib" => default_lib_output(), + "rlib" => CrateType::Rlib, + "staticlib" => CrateType::Staticlib, + "dylib" => CrateType::Dylib, + "cdylib" => CrateType::Cdylib, + "bin" => CrateType::Executable, + "proc-macro" => CrateType::ProcMacro, + _ => return Err(format!("unknown crate type: `{part}`")), + }; + if !crate_types.contains(&new_part) { + crate_types.push(new_part) + } + } + } + + Ok(crate_types) +} + +pub mod nightly_options { + use rustc_feature::UnstableFeatures; + + use super::{OptionStability, RustcOptGroup}; + use crate::EarlyDiagCtxt; + + pub fn is_unstable_enabled(matches: &getopts::Matches) -> bool { + match_is_nightly_build(matches) + && matches.opt_strs("Z").iter().any(|x| *x == "unstable-options") + } + + pub fn match_is_nightly_build(matches: &getopts::Matches) -> bool { + is_nightly_build(matches.opt_str("crate-name").as_deref()) + } + + fn is_nightly_build(krate: Option<&str>) -> bool { + UnstableFeatures::from_environment(krate).is_nightly_build() + } + + pub fn check_nightly_options( + early_dcx: &EarlyDiagCtxt, + matches: &getopts::Matches, + flags: &[RustcOptGroup], + ) { + let has_z_unstable_option = matches.opt_strs("Z").iter().any(|x| *x == "unstable-options"); + let really_allows_unstable_options = match_is_nightly_build(matches); + let mut nightly_options_on_stable = 0; + + for opt in flags.iter() { + if opt.stability == OptionStability::Stable { + continue; + } + if !matches.opt_present(opt.name) { + continue; + } + if opt.name != "Z" && !has_z_unstable_option { + early_dcx.early_fatal(format!( + "the `-Z unstable-options` flag must also be passed to enable \ + the flag `{}`", + opt.name + )); + } + if really_allows_unstable_options { + continue; + } + match opt.stability { + OptionStability::Unstable => { + nightly_options_on_stable += 1; + let msg = format!( + "the option `{}` is only accepted on the nightly compiler", + opt.name + ); + let _ = early_dcx.early_err(msg); + } + OptionStability::Stable => {} + } + } + if nightly_options_on_stable > 0 { + early_dcx + .early_help("consider switching to a nightly toolchain: `rustup default nightly`"); + early_dcx.early_note("selecting a toolchain with `+toolchain` arguments require a rustup proxy; see <https://rust-lang.github.io/rustup/concepts/index.html>"); + early_dcx.early_note("for more information about Rust's stability policy, see <https://doc.rust-lang.org/book/appendix-07-nightly-rust.html#unstable-features>"); + early_dcx.early_fatal(format!( + "{} nightly option{} were parsed", + nightly_options_on_stable, + if nightly_options_on_stable > 1 { "s" } else { "" } + )); + } + } +} + +impl fmt::Display for CrateType { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match *self { + CrateType::Executable => "bin".fmt(f), + CrateType::Dylib => "dylib".fmt(f), + CrateType::Rlib => "rlib".fmt(f), + CrateType::Staticlib => "staticlib".fmt(f), + CrateType::Cdylib => "cdylib".fmt(f), + CrateType::ProcMacro => "proc-macro".fmt(f), + } + } +} + +impl IntoDiagArg for CrateType { + fn into_diag_arg(self) -> DiagArgValue { + self.to_string().into_diag_arg() + } +} + +#[derive(Copy, Clone, PartialEq, Debug)] +pub enum PpSourceMode { + /// `-Zunpretty=normal` + Normal, + /// `-Zunpretty=expanded` + Expanded, + /// `-Zunpretty=identified` + Identified, + /// `-Zunpretty=expanded,identified` + ExpandedIdentified, + /// `-Zunpretty=expanded,hygiene` + ExpandedHygiene, +} + +#[derive(Copy, Clone, PartialEq, Debug)] +pub enum PpHirMode { + /// `-Zunpretty=hir` + Normal, + /// `-Zunpretty=hir,identified` + Identified, + /// `-Zunpretty=hir,typed` + Typed, +} + +#[derive(Copy, Clone, PartialEq, Debug)] +/// Pretty print mode +pub enum PpMode { + /// Options that print the source code, i.e. + /// `-Zunpretty=normal` and `-Zunpretty=expanded` + Source(PpSourceMode), + /// `-Zunpretty=ast-tree` + AstTree, + /// `-Zunpretty=ast-tree,expanded` + AstTreeExpanded, + /// Options that print the HIR, i.e. `-Zunpretty=hir` + Hir(PpHirMode), + /// `-Zunpretty=hir-tree` + HirTree, + /// `-Zunpretty=thir-tree` + ThirTree, + /// `-Zunpretty=thir-flat` + ThirFlat, + /// `-Zunpretty=mir` + Mir, + /// `-Zunpretty=mir-cfg` + MirCFG, + /// `-Zunpretty=stable-mir` + StableMir, +} + +impl PpMode { + pub fn needs_ast_map(&self) -> bool { + use PpMode::*; + use PpSourceMode::*; + match *self { + Source(Normal | Identified) | AstTree => false, + + Source(Expanded | ExpandedIdentified | ExpandedHygiene) + | AstTreeExpanded + | Hir(_) + | HirTree + | ThirTree + | ThirFlat + | Mir + | MirCFG + | StableMir => true, + } + } + pub fn needs_hir(&self) -> bool { + use PpMode::*; + match *self { + Source(_) | AstTree | AstTreeExpanded => false, + + Hir(_) | HirTree | ThirTree | ThirFlat | Mir | MirCFG | StableMir => true, + } + } + + pub fn needs_analysis(&self) -> bool { + use PpMode::*; + matches!(*self, Hir(PpHirMode::Typed) | Mir | StableMir | MirCFG | ThirTree | ThirFlat) + } +} + +#[derive(Clone, Hash, PartialEq, Eq, Debug)] +pub enum WasiExecModel { + Command, + Reactor, +} + +/// Command-line arguments passed to the compiler have to be incorporated with +/// the dependency tracking system for incremental compilation. This module +/// provides some utilities to make this more convenient. +/// +/// The values of all command-line arguments that are relevant for dependency +/// tracking are hashed into a single value that determines whether the +/// incremental compilation cache can be re-used or not. This hashing is done +/// via the `DepTrackingHash` trait defined below, since the standard `Hash` +/// implementation might not be suitable (e.g., arguments are stored in a `Vec`, +/// the hash of which is order dependent, but we might not want the order of +/// arguments to make a difference for the hash). +/// +/// However, since the value provided by `Hash::hash` often *is* suitable, +/// especially for primitive types, there is the +/// `impl_dep_tracking_hash_via_hash!()` macro that allows to simply reuse the +/// `Hash` implementation for `DepTrackingHash`. It's important though that +/// we have an opt-in scheme here, so one is hopefully forced to think about +/// how the hash should be calculated when adding a new command-line argument. +pub(crate) mod dep_tracking { + use std::collections::BTreeMap; + use std::hash::{DefaultHasher, Hash}; + use std::num::NonZero; + use std::path::PathBuf; + + use rustc_data_structures::fx::FxIndexMap; + use rustc_data_structures::stable_hasher::Hash64; + use rustc_errors::LanguageIdentifier; + use rustc_feature::UnstableFeatures; + use rustc_span::RealFileName; + use rustc_span::edition::Edition; + use rustc_target::spec::{ + CodeModel, FramePointer, MergeFunctions, OnBrokenPipe, PanicStrategy, RelocModel, + RelroLevel, SanitizerSet, SplitDebuginfo, StackProtector, SymbolVisibility, TargetTuple, + TlsModel, WasmCAbi, + }; + + use super::{ + BranchProtection, CFGuard, CFProtection, CollapseMacroDebuginfo, CoverageOptions, + CrateType, DebugInfo, DebugInfoCompression, ErrorOutputType, FmtDebug, FunctionReturn, + InliningThreshold, InstrumentCoverage, InstrumentXRay, LinkerPluginLto, LocationDetail, + LtoCli, NextSolverConfig, OomStrategy, OptLevel, OutFileName, OutputType, OutputTypes, + PatchableFunctionEntry, Polonius, RemapPathScopeComponents, ResolveDocLinks, + SourceFileHashAlgorithm, SplitDwarfKind, SwitchWithOptPath, SymbolManglingVersion, + WasiExecModel, + }; + use crate::lint; + use crate::utils::NativeLib; + + pub(crate) trait DepTrackingHash { + fn hash( + &self, + hasher: &mut DefaultHasher, + error_format: ErrorOutputType, + for_crate_hash: bool, + ); + } + + macro_rules! impl_dep_tracking_hash_via_hash { + ($($t:ty),+ $(,)?) => {$( + impl DepTrackingHash for $t { + fn hash(&self, hasher: &mut DefaultHasher, _: ErrorOutputType, _for_crate_hash: bool) { + Hash::hash(self, hasher); + } + } + )+}; + } + + impl<T: DepTrackingHash> DepTrackingHash for Option<T> { + fn hash( + &self, + hasher: &mut DefaultHasher, + error_format: ErrorOutputType, + for_crate_hash: bool, + ) { + match self { + Some(x) => { + Hash::hash(&1, hasher); + DepTrackingHash::hash(x, hasher, error_format, for_crate_hash); + } + None => Hash::hash(&0, hasher), + } + } + } + + impl_dep_tracking_hash_via_hash!( + bool, + usize, + NonZero<usize>, + u64, + Hash64, + String, + PathBuf, + lint::Level, + WasiExecModel, + u32, + FramePointer, + RelocModel, + CodeModel, + TlsModel, + InstrumentCoverage, + CoverageOptions, + InstrumentXRay, + CrateType, + MergeFunctions, + OnBrokenPipe, + PanicStrategy, + RelroLevel, + OptLevel, + LtoCli, + DebugInfo, + DebugInfoCompression, + CollapseMacroDebuginfo, + UnstableFeatures, + NativeLib, + SanitizerSet, + CFGuard, + CFProtection, + TargetTuple, + Edition, + LinkerPluginLto, + ResolveDocLinks, + SplitDebuginfo, + SplitDwarfKind, + StackProtector, + SwitchWithOptPath, + SymbolManglingVersion, + SymbolVisibility, + RemapPathScopeComponents, + SourceFileHashAlgorithm, + OutFileName, + OutputType, + RealFileName, + LocationDetail, + FmtDebug, + BranchProtection, + OomStrategy, + LanguageIdentifier, + NextSolverConfig, + PatchableFunctionEntry, + Polonius, + InliningThreshold, + FunctionReturn, + WasmCAbi, + ); + + impl<T1, T2> DepTrackingHash for (T1, T2) + where + T1: DepTrackingHash, + T2: DepTrackingHash, + { + fn hash( + &self, + hasher: &mut DefaultHasher, + error_format: ErrorOutputType, + for_crate_hash: bool, + ) { + Hash::hash(&0, hasher); + DepTrackingHash::hash(&self.0, hasher, error_format, for_crate_hash); + Hash::hash(&1, hasher); + DepTrackingHash::hash(&self.1, hasher, error_format, for_crate_hash); + } + } + + impl<T1, T2, T3> DepTrackingHash for (T1, T2, T3) + where + T1: DepTrackingHash, + T2: DepTrackingHash, + T3: DepTrackingHash, + { + fn hash( + &self, + hasher: &mut DefaultHasher, + error_format: ErrorOutputType, + for_crate_hash: bool, + ) { + Hash::hash(&0, hasher); + DepTrackingHash::hash(&self.0, hasher, error_format, for_crate_hash); + Hash::hash(&1, hasher); + DepTrackingHash::hash(&self.1, hasher, error_format, for_crate_hash); + Hash::hash(&2, hasher); + DepTrackingHash::hash(&self.2, hasher, error_format, for_crate_hash); + } + } + + impl<T: DepTrackingHash> DepTrackingHash for Vec<T> { + fn hash( + &self, + hasher: &mut DefaultHasher, + error_format: ErrorOutputType, + for_crate_hash: bool, + ) { + Hash::hash(&self.len(), hasher); + for (index, elem) in self.iter().enumerate() { + Hash::hash(&index, hasher); + DepTrackingHash::hash(elem, hasher, error_format, for_crate_hash); + } + } + } + + impl<T: DepTrackingHash, V: DepTrackingHash> DepTrackingHash for FxIndexMap<T, V> { + fn hash( + &self, + hasher: &mut DefaultHasher, + error_format: ErrorOutputType, + for_crate_hash: bool, + ) { + Hash::hash(&self.len(), hasher); + for (key, value) in self.iter() { + DepTrackingHash::hash(key, hasher, error_format, for_crate_hash); + DepTrackingHash::hash(value, hasher, error_format, for_crate_hash); + } + } + } + + impl DepTrackingHash for OutputTypes { + fn hash( + &self, + hasher: &mut DefaultHasher, + error_format: ErrorOutputType, + for_crate_hash: bool, + ) { + Hash::hash(&self.0.len(), hasher); + for (key, val) in &self.0 { + DepTrackingHash::hash(key, hasher, error_format, for_crate_hash); + if !for_crate_hash { + DepTrackingHash::hash(val, hasher, error_format, for_crate_hash); + } + } + } + } + + // This is a stable hash because BTreeMap is a sorted container + pub(crate) fn stable_hash( + sub_hashes: BTreeMap<&'static str, &dyn DepTrackingHash>, + hasher: &mut DefaultHasher, + error_format: ErrorOutputType, + for_crate_hash: bool, + ) { + for (key, sub_hash) in sub_hashes { + // Using Hash::hash() instead of DepTrackingHash::hash() is fine for + // the keys, as they are just plain strings + Hash::hash(&key.len(), hasher); + Hash::hash(key, hasher); + sub_hash.hash(hasher, error_format, for_crate_hash); + } + } +} + +/// Default behavior to use in out-of-memory situations. +#[derive(Clone, Copy, PartialEq, Hash, Debug, Encodable, Decodable, HashStable_Generic)] +pub enum OomStrategy { + /// Generate a panic that can be caught by `catch_unwind`. + Panic, + + /// Abort the process immediately. + Abort, +} + +impl OomStrategy { + pub const SYMBOL: &'static str = "__rust_alloc_error_handler_should_panic"; + + pub fn should_panic(self) -> u8 { + match self { + OomStrategy::Panic => 1, + OomStrategy::Abort => 0, + } + } +} + +/// How to run proc-macro code when building this crate +#[derive(Clone, Copy, PartialEq, Hash, Debug)] +pub enum ProcMacroExecutionStrategy { + /// Run the proc-macro code on the same thread as the server. + SameThread, + + /// Run the proc-macro code on a different thread. + CrossThread, +} + +/// How to perform collapse macros debug info +/// if-ext - if macro from different crate (related to callsite code) +/// | cmd \ attr | no | (unspecified) | external | yes | +/// | no | no | no | no | no | +/// | (unspecified) | no | no | if-ext | yes | +/// | external | no | if-ext | if-ext | yes | +/// | yes | yes | yes | yes | yes | +#[derive(Clone, Copy, PartialEq, Hash, Debug)] +pub enum CollapseMacroDebuginfo { + /// Don't collapse debuginfo for the macro + No = 0, + /// Unspecified value + Unspecified = 1, + /// Collapse debuginfo if the macro comes from a different crate + External = 2, + /// Collapse debuginfo for the macro + Yes = 3, +} + +/// Which format to use for `-Z dump-mono-stats` +#[derive(Clone, Copy, PartialEq, Hash, Debug)] +pub enum DumpMonoStatsFormat { + /// Pretty-print a markdown table + Markdown, + /// Emit structured JSON + Json, +} + +impl DumpMonoStatsFormat { + pub fn extension(self) -> &'static str { + match self { + Self::Markdown => "md", + Self::Json => "json", + } + } +} + +/// `-Z patchable-function-entry` representation - how many nops to put before and after function +/// entry. +#[derive(Clone, Copy, PartialEq, Hash, Debug, Default)] +pub struct PatchableFunctionEntry { + /// Nops before the entry + prefix: u8, + /// Nops after the entry + entry: u8, +} + +impl PatchableFunctionEntry { + pub fn from_total_and_prefix_nops( + total_nops: u8, + prefix_nops: u8, + ) -> Option<PatchableFunctionEntry> { + if total_nops < prefix_nops { + None + } else { + Some(Self { prefix: prefix_nops, entry: total_nops - prefix_nops }) + } + } + pub fn prefix(&self) -> u8 { + self.prefix + } + pub fn entry(&self) -> u8 { + self.entry + } +} + +/// `-Zpolonius` values, enabling the borrow checker polonius analysis, and which version: legacy, +/// or future prototype. +#[derive(Clone, Copy, PartialEq, Hash, Debug, Default)] +pub enum Polonius { + /// The default value: disabled. + #[default] + Off, + + /// Legacy version, using datalog and the `polonius-engine` crate. Historical value for `-Zpolonius`. + Legacy, + + /// In-tree prototype, extending the NLL infrastructure. + Next, +} + +impl Polonius { + /// Returns whether the legacy version of polonius is enabled + pub fn is_legacy_enabled(&self) -> bool { + matches!(self, Polonius::Legacy) + } + + /// Returns whether the "next" version of polonius is enabled + pub fn is_next_enabled(&self) -> bool { + matches!(self, Polonius::Next) + } +} + +#[derive(Clone, Copy, PartialEq, Hash, Debug)] +pub enum InliningThreshold { + Always, + Sometimes(usize), + Never, +} + +impl Default for InliningThreshold { + fn default() -> Self { + Self::Sometimes(100) + } +} + +/// The different settings that the `-Zfunction-return` flag can have. +#[derive(Clone, Copy, PartialEq, Hash, Debug, Default)] +pub enum FunctionReturn { + /// Keep the function return unmodified. + #[default] + Keep, + + /// Replace returns with jumps to thunk, without emitting the thunk. + ThunkExtern, +} + +/// Whether extra span comments are included when dumping MIR, via the `-Z mir-include-spans` flag. +/// By default, only enabled in the NLL MIR dumps, and disabled in all other passes. +#[derive(Clone, Copy, Default, PartialEq, Debug)] +pub enum MirIncludeSpans { + Off, + On, + /// Default: include extra comments in NLL MIR dumps only. Can be ignored and considered as + /// `Off` in all other cases. + #[default] + Nll, +} + +impl MirIncludeSpans { + /// Unless opting into extra comments for all passes, they can be considered disabled. + /// The cases where a distinction between on/off and a per-pass value can exist will be handled + /// in the passes themselves: i.e. the `Nll` value is considered off for all intents and + /// purposes, except for the NLL MIR dump pass. + pub fn is_enabled(self) -> bool { + self == MirIncludeSpans::On + } +} diff --git a/compiler/rustc_session/src/config/cfg.rs b/compiler/rustc_session/src/config/cfg.rs new file mode 100644 index 00000000000..99d9d5b7665 --- /dev/null +++ b/compiler/rustc_session/src/config/cfg.rs @@ -0,0 +1,463 @@ +//! cfg and check-cfg configuration +//! +//! This module contains the definition of [`Cfg`] and [`CheckCfg`] +//! as well as the logic for creating the default configuration for a +//! given [`Session`]. +//! +//! It also contains the filling of the well known configs, which should +//! ALWAYS be in sync with the default_configuration. +//! +//! ## Adding a new cfg +//! +//! Adding a new feature requires two new symbols one for the cfg it-self +//! and the second one for the unstable feature gate, those are defined in +//! `rustc_span::symbol`. +//! +//! As well as the following points, +//! - Add the activation logic in [`default_configuration`] +//! - Add the cfg to [`CheckCfg::fill_well_known`] (and related files), +//! so that the compiler can know the cfg is expected +//! - Add the cfg in [`disallow_cfgs`] to disallow users from setting it via `--cfg` +//! - Add the feature gating in `compiler/rustc_feature/src/builtin_attrs.rs` + +use std::hash::Hash; +use std::iter; + +use rustc_abi::Align; +use rustc_ast::ast; +use rustc_data_structures::fx::{FxHashMap, FxHashSet, FxIndexSet}; +use rustc_lint_defs::BuiltinLintDiag; +use rustc_lint_defs::builtin::EXPLICIT_BUILTIN_CFGS_IN_FLAGS; +use rustc_span::symbol::{Symbol, sym}; +use rustc_target::spec::{PanicStrategy, RelocModel, SanitizerSet, TARGETS, Target, TargetTuple}; + +use crate::Session; +use crate::config::{CrateType, FmtDebug}; + +/// The parsed `--cfg` options that define the compilation environment of the +/// crate, used to drive conditional compilation. +/// +/// An `FxIndexSet` is used to ensure deterministic ordering of error messages +/// relating to `--cfg`. +pub type Cfg = FxIndexSet<(Symbol, Option<Symbol>)>; + +/// The parsed `--check-cfg` options. +#[derive(Default)] +pub struct CheckCfg { + /// Is well known names activated + pub exhaustive_names: bool, + /// Is well known values activated + pub exhaustive_values: bool, + /// All the expected values for a config name + pub expecteds: FxHashMap<Symbol, ExpectedValues<Symbol>>, + /// Well known names (only used for diagnostics purposes) + pub well_known_names: FxHashSet<Symbol>, +} + +pub enum ExpectedValues<T> { + Some(FxHashSet<Option<T>>), + Any, +} + +impl<T: Eq + Hash> ExpectedValues<T> { + fn insert(&mut self, value: T) -> bool { + match self { + ExpectedValues::Some(expecteds) => expecteds.insert(Some(value)), + ExpectedValues::Any => false, + } + } +} + +impl<T: Eq + Hash> Extend<T> for ExpectedValues<T> { + fn extend<I: IntoIterator<Item = T>>(&mut self, iter: I) { + match self { + ExpectedValues::Some(expecteds) => expecteds.extend(iter.into_iter().map(Some)), + ExpectedValues::Any => {} + } + } +} + +impl<'a, T: Eq + Hash + Copy + 'a> Extend<&'a T> for ExpectedValues<T> { + fn extend<I: IntoIterator<Item = &'a T>>(&mut self, iter: I) { + match self { + ExpectedValues::Some(expecteds) => expecteds.extend(iter.into_iter().map(|a| Some(*a))), + ExpectedValues::Any => {} + } + } +} + +/// Disallow builtin cfgs from the CLI. +pub(crate) fn disallow_cfgs(sess: &Session, user_cfgs: &Cfg) { + let disallow = |cfg: &(Symbol, Option<Symbol>), controlled_by| { + let cfg_name = cfg.0; + let cfg = if let Some(value) = cfg.1 { + format!(r#"{}="{}""#, cfg_name, value) + } else { + format!("{}", cfg_name) + }; + sess.psess.opt_span_buffer_lint( + EXPLICIT_BUILTIN_CFGS_IN_FLAGS, + None, + ast::CRATE_NODE_ID, + BuiltinLintDiag::UnexpectedBuiltinCfg { cfg, cfg_name, controlled_by }, + ) + }; + + // We want to restrict setting builtin cfgs that will produce incoherent behavior + // between the cfg and the rustc cli flag that sets it. + // + // The tests are in tests/ui/cfg/disallowed-cli-cfgs.rs. + + // By-default all builtin cfgs are disallowed, only those are allowed: + // - test: as it makes sense to the have the `test` cfg active without the builtin + // test harness. See Cargo `harness = false` config. + // + // Cargo `--cfg test`: https://github.com/rust-lang/cargo/blob/bc89bffa5987d4af8f71011c7557119b39e44a65/src/cargo/core/compiler/mod.rs#L1124 + + for cfg in user_cfgs { + match cfg { + (sym::overflow_checks, None) => disallow(cfg, "-C overflow-checks"), + (sym::debug_assertions, None) => disallow(cfg, "-C debug-assertions"), + (sym::ub_checks, None) => disallow(cfg, "-Z ub-checks"), + (sym::sanitize, None | Some(_)) => disallow(cfg, "-Z sanitizer"), + ( + sym::sanitizer_cfi_generalize_pointers | sym::sanitizer_cfi_normalize_integers, + None | Some(_), + ) => disallow(cfg, "-Z sanitizer=cfi"), + (sym::proc_macro, None) => disallow(cfg, "--crate-type proc-macro"), + (sym::panic, Some(sym::abort | sym::unwind)) => disallow(cfg, "-C panic"), + (sym::target_feature, Some(_)) => disallow(cfg, "-C target-feature"), + (sym::unix, None) + | (sym::windows, None) + | (sym::relocation_model, Some(_)) + | (sym::target_abi, None | Some(_)) + | (sym::target_arch, Some(_)) + | (sym::target_endian, Some(_)) + | (sym::target_env, None | Some(_)) + | (sym::target_family, Some(_)) + | (sym::target_os, Some(_)) + | (sym::target_pointer_width, Some(_)) + | (sym::target_vendor, None | Some(_)) + | (sym::target_has_atomic, Some(_)) + | (sym::target_has_atomic_equal_alignment, Some(_)) + | (sym::target_has_atomic_load_store, Some(_)) + | (sym::target_thread_local, None) => disallow(cfg, "--target"), + (sym::fmt_debug, None | Some(_)) => disallow(cfg, "-Z fmt-debug"), + _ => {} + } + } +} + +/// Generate the default configs for a given session +pub(crate) fn default_configuration(sess: &Session) -> Cfg { + let mut ret = Cfg::default(); + + macro_rules! ins_none { + ($key:expr) => { + ret.insert(($key, None)); + }; + } + macro_rules! ins_str { + ($key:expr, $val_str:expr) => { + ret.insert(($key, Some(Symbol::intern($val_str)))); + }; + } + macro_rules! ins_sym { + ($key:expr, $val_sym:expr) => { + ret.insert(($key, Some($val_sym))); + }; + } + + // Symbols are inserted in alphabetical order as much as possible. + // The exceptions are where control flow forces things out of order. + // + // Run `rustc --print cfg` to see the configuration in practice. + // + // NOTE: These insertions should be kept in sync with + // `CheckCfg::fill_well_known` below. + + if sess.opts.debug_assertions { + ins_none!(sym::debug_assertions); + } + + if sess.is_nightly_build() { + match sess.opts.unstable_opts.fmt_debug { + FmtDebug::Full => { + ins_sym!(sym::fmt_debug, sym::full); + } + FmtDebug::Shallow => { + ins_sym!(sym::fmt_debug, sym::shallow); + } + FmtDebug::None => { + ins_sym!(sym::fmt_debug, sym::none); + } + } + } + + if sess.overflow_checks() { + ins_none!(sym::overflow_checks); + } + + ins_sym!(sym::panic, sess.panic_strategy().desc_symbol()); + + // JUSTIFICATION: before wrapper fn is available + #[allow(rustc::bad_opt_access)] + if sess.opts.crate_types.contains(&CrateType::ProcMacro) { + ins_none!(sym::proc_macro); + } + + if sess.is_nightly_build() { + ins_sym!(sym::relocation_model, sess.target.relocation_model.desc_symbol()); + } + + for mut s in sess.opts.unstable_opts.sanitizer { + // KASAN is still ASAN under the hood, so it uses the same attribute. + if s == SanitizerSet::KERNELADDRESS { + s = SanitizerSet::ADDRESS; + } + ins_str!(sym::sanitize, &s.to_string()); + } + + if sess.is_sanitizer_cfi_generalize_pointers_enabled() { + ins_none!(sym::sanitizer_cfi_generalize_pointers); + } + if sess.is_sanitizer_cfi_normalize_integers_enabled() { + ins_none!(sym::sanitizer_cfi_normalize_integers); + } + + ins_str!(sym::target_abi, &sess.target.abi); + ins_str!(sym::target_arch, &sess.target.arch); + ins_str!(sym::target_endian, sess.target.endian.as_str()); + ins_str!(sym::target_env, &sess.target.env); + + for family in sess.target.families.as_ref() { + ins_str!(sym::target_family, family); + if family == "windows" { + ins_none!(sym::windows); + } else if family == "unix" { + ins_none!(sym::unix); + } + } + + // `target_has_atomic*` + let layout = sess.target.parse_data_layout().unwrap_or_else(|err| { + sess.dcx().emit_fatal(err); + }); + let mut has_atomic = false; + for (i, align) in [ + (8, layout.i8_align.abi), + (16, layout.i16_align.abi), + (32, layout.i32_align.abi), + (64, layout.i64_align.abi), + (128, layout.i128_align.abi), + ] { + if i >= sess.target.min_atomic_width() && i <= sess.target.max_atomic_width() { + if !has_atomic { + has_atomic = true; + if sess.is_nightly_build() { + if sess.target.atomic_cas { + ins_none!(sym::target_has_atomic); + } + ins_none!(sym::target_has_atomic_load_store); + } + } + let mut insert_atomic = |sym, align: Align| { + if sess.target.atomic_cas { + ins_sym!(sym::target_has_atomic, sym); + } + if align.bits() == i { + ins_sym!(sym::target_has_atomic_equal_alignment, sym); + } + ins_sym!(sym::target_has_atomic_load_store, sym); + }; + insert_atomic(sym::integer(i), align); + if sess.target.pointer_width as u64 == i { + insert_atomic(sym::ptr, layout.pointer_align.abi); + } + } + } + + ins_str!(sym::target_os, &sess.target.os); + ins_sym!(sym::target_pointer_width, sym::integer(sess.target.pointer_width)); + + if sess.opts.unstable_opts.has_thread_local.unwrap_or(sess.target.has_thread_local) { + ins_none!(sym::target_thread_local); + } + + ins_str!(sym::target_vendor, &sess.target.vendor); + + // If the user wants a test runner, then add the test cfg. + if sess.is_test_crate() { + ins_none!(sym::test); + } + + if sess.ub_checks() { + ins_none!(sym::ub_checks); + } + + ret +} + +impl CheckCfg { + /// Fill the current [`CheckCfg`] with all the well known cfgs + pub fn fill_well_known(&mut self, current_target: &Target) { + if !self.exhaustive_values && !self.exhaustive_names { + return; + } + + // for `#[cfg(foo)]` (ie. cfg value is none) + let no_values = || { + let mut values = FxHashSet::default(); + values.insert(None); + ExpectedValues::Some(values) + }; + + // preparation for inserting some values + let empty_values = || { + let values = FxHashSet::default(); + ExpectedValues::Some(values) + }; + + macro_rules! ins { + ($name:expr, $values:expr) => {{ + self.well_known_names.insert($name); + self.expecteds.entry($name).or_insert_with($values) + }}; + } + + // Symbols are inserted in alphabetical order as much as possible. + // The exceptions are where control flow forces things out of order. + // + // NOTE: This should be kept in sync with `default_configuration`. + // Note that symbols inserted conditionally in `default_configuration` + // are inserted unconditionally here. + // + // When adding a new config here you should also update + // `tests/ui/check-cfg/well-known-values.rs` (in order to test the + // expected values of the new config) and bless the all directory. + // + // Don't forget to update `src/doc/rustc/src/check-cfg.md` + // in the unstable book as well! + + ins!(sym::debug_assertions, no_values); + + ins!(sym::fmt_debug, empty_values).extend(FmtDebug::all()); + + // These four are never set by rustc, but we set them anyway; they + // should not trigger the lint because `cargo clippy`, `cargo doc`, + // `cargo test`, `cargo miri run` and `cargo fmt` (respectively) + // can set them. + ins!(sym::clippy, no_values); + ins!(sym::doc, no_values); + ins!(sym::doctest, no_values); + ins!(sym::miri, no_values); + ins!(sym::rustfmt, no_values); + + ins!(sym::overflow_checks, no_values); + + ins!(sym::panic, empty_values).extend(&PanicStrategy::all()); + + ins!(sym::proc_macro, no_values); + + ins!(sym::relocation_model, empty_values).extend(RelocModel::all()); + + let sanitize_values = SanitizerSet::all() + .into_iter() + .map(|sanitizer| Symbol::intern(sanitizer.as_str().unwrap())); + ins!(sym::sanitize, empty_values).extend(sanitize_values); + + ins!(sym::sanitizer_cfi_generalize_pointers, no_values); + ins!(sym::sanitizer_cfi_normalize_integers, no_values); + + ins!(sym::target_feature, empty_values).extend( + rustc_target::target_features::all_rust_features() + .filter(|(_, s)| s.is_supported()) + .map(|(f, _s)| f) + .chain(rustc_target::target_features::RUSTC_SPECIFIC_FEATURES.iter().cloned()) + .map(Symbol::intern), + ); + + // sym::target_* + { + const VALUES: [&Symbol; 8] = [ + &sym::target_abi, + &sym::target_arch, + &sym::target_endian, + &sym::target_env, + &sym::target_family, + &sym::target_os, + &sym::target_pointer_width, + &sym::target_vendor, + ]; + + // Initialize (if not already initialized) + for &e in VALUES { + if !self.exhaustive_values { + ins!(e, || ExpectedValues::Any); + } else { + ins!(e, empty_values); + } + } + + if self.exhaustive_values { + // Get all values map at once otherwise it would be costly. + // (8 values * 220 targets ~= 1760 times, at the time of writing this comment). + let [ + Some(values_target_abi), + Some(values_target_arch), + Some(values_target_endian), + Some(values_target_env), + Some(values_target_family), + Some(values_target_os), + Some(values_target_pointer_width), + Some(values_target_vendor), + ] = self.expecteds.get_many_mut(VALUES) + else { + panic!("unable to get all the check-cfg values buckets"); + }; + + for target in TARGETS + .iter() + .map(|target| Target::expect_builtin(&TargetTuple::from_tuple(target))) + .chain(iter::once(current_target.clone())) + { + values_target_abi.insert(Symbol::intern(&target.options.abi)); + values_target_arch.insert(Symbol::intern(&target.arch)); + values_target_endian.insert(Symbol::intern(target.options.endian.as_str())); + values_target_env.insert(Symbol::intern(&target.options.env)); + values_target_family.extend( + target.options.families.iter().map(|family| Symbol::intern(family)), + ); + values_target_os.insert(Symbol::intern(&target.options.os)); + values_target_pointer_width.insert(sym::integer(target.pointer_width)); + values_target_vendor.insert(Symbol::intern(&target.options.vendor)); + } + } + } + + let atomic_values = &[ + sym::ptr, + sym::integer(8usize), + sym::integer(16usize), + sym::integer(32usize), + sym::integer(64usize), + sym::integer(128usize), + ]; + for sym in [ + sym::target_has_atomic, + sym::target_has_atomic_equal_alignment, + sym::target_has_atomic_load_store, + ] { + ins!(sym, no_values).extend(atomic_values); + } + + ins!(sym::target_thread_local, no_values); + + ins!(sym::test, no_values); + + ins!(sym::ub_checks, no_values); + + ins!(sym::unix, no_values); + ins!(sym::windows, no_values); + } +} diff --git a/compiler/rustc_session/src/config/sigpipe.rs b/compiler/rustc_session/src/config/sigpipe.rs new file mode 100644 index 00000000000..1830ee03485 --- /dev/null +++ b/compiler/rustc_session/src/config/sigpipe.rs @@ -0,0 +1,25 @@ +//! NOTE: Keep these constants in sync with `library/std/src/sys/pal/unix/mod.rs`! + +/// The default value if `-Zon-broken-pipe=...` is not specified. This resolves +/// to `SIG_IGN` in `library/std/src/sys/pal/unix/mod.rs`. +/// +/// Note that `SIG_IGN` has been the Rust default since 2014. See +/// <https://github.com/rust-lang/rust/issues/62569>. +#[allow(dead_code)] +pub const DEFAULT: u8 = 0; + +/// Do not touch `SIGPIPE`. Use whatever the parent process uses. +#[allow(dead_code)] +pub const INHERIT: u8 = 1; + +/// Change `SIGPIPE` to `SIG_IGN` so that failed writes results in `EPIPE` +/// that are eventually converted to `ErrorKind::BrokenPipe`. +#[allow(dead_code)] +pub const SIG_IGN: u8 = 2; + +/// Change `SIGPIPE` to `SIG_DFL` so that the process is killed when trying +/// to write to a closed pipe. This is usually the desired behavior for CLI +/// apps that produce textual output that you want to pipe to other programs +/// such as `head -n 1`. +#[allow(dead_code)] +pub const SIG_DFL: u8 = 3; diff --git a/compiler/rustc_session/src/cstore.rs b/compiler/rustc_session/src/cstore.rs new file mode 100644 index 00000000000..3e2cbc18933 --- /dev/null +++ b/compiler/rustc_session/src/cstore.rs @@ -0,0 +1,240 @@ +//! the rustc crate store interface. This also includes types that +//! are *mostly* used as a part of that interface, but these should +//! probably get a better home if someone can find one. + +use std::any::Any; +use std::path::PathBuf; + +use rustc_abi::ExternAbi; +use rustc_ast as ast; +use rustc_data_structures::sync::{self, AppendOnlyIndexVec, FreezeLock}; +use rustc_hir::def_id::{ + CrateNum, DefId, LOCAL_CRATE, LocalDefId, StableCrateId, StableCrateIdMap, +}; +use rustc_hir::definitions::{DefKey, DefPath, DefPathHash, Definitions}; +use rustc_macros::{Decodable, Encodable, HashStable_Generic}; +use rustc_span::Span; +use rustc_span::symbol::Symbol; + +use crate::search_paths::PathKind; +use crate::utils::NativeLibKind; + +// lonely orphan structs and enums looking for a better home + +/// Where a crate came from on the local filesystem. One of these three options +/// must be non-None. +#[derive(PartialEq, Clone, Debug, HashStable_Generic, Encodable, Decodable)] +pub struct CrateSource { + pub dylib: Option<(PathBuf, PathKind)>, + pub rlib: Option<(PathBuf, PathKind)>, + pub rmeta: Option<(PathBuf, PathKind)>, +} + +impl CrateSource { + #[inline] + pub fn paths(&self) -> impl Iterator<Item = &PathBuf> { + self.dylib.iter().chain(self.rlib.iter()).chain(self.rmeta.iter()).map(|p| &p.0) + } +} + +#[derive(Encodable, Decodable, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Debug)] +#[derive(HashStable_Generic)] +pub enum CrateDepKind { + /// A dependency that is only used for its macros. + MacrosOnly, + /// A dependency that is always injected into the dependency list and so + /// doesn't need to be linked to an rlib, e.g., the injected allocator. + Implicit, + /// A dependency that is required by an rlib version of this crate. + /// Ordinary `extern crate`s result in `Explicit` dependencies. + Explicit, +} + +impl CrateDepKind { + #[inline] + pub fn macros_only(self) -> bool { + match self { + CrateDepKind::MacrosOnly => true, + CrateDepKind::Implicit | CrateDepKind::Explicit => false, + } + } +} + +#[derive(Copy, Debug, PartialEq, Clone, Encodable, Decodable, HashStable_Generic)] +pub enum LinkagePreference { + RequireDynamic, + RequireStatic, +} + +#[derive(Debug, Encodable, Decodable, HashStable_Generic)] +pub struct NativeLib { + pub kind: NativeLibKind, + pub name: Symbol, + /// If packed_bundled_libs enabled, actual filename of library is stored. + pub filename: Option<Symbol>, + pub cfg: Option<ast::MetaItemInner>, + pub foreign_module: Option<DefId>, + pub verbatim: Option<bool>, + pub dll_imports: Vec<DllImport>, +} + +impl NativeLib { + pub fn has_modifiers(&self) -> bool { + self.verbatim.is_some() || self.kind.has_modifiers() + } + + pub fn wasm_import_module(&self) -> Option<Symbol> { + if self.kind == NativeLibKind::WasmImportModule { Some(self.name) } else { None } + } +} + +/// Different ways that the PE Format can decorate a symbol name. +/// From <https://docs.microsoft.com/en-us/windows/win32/debug/pe-format#import-name-type> +#[derive(Copy, Clone, Debug, Encodable, Decodable, HashStable_Generic, PartialEq, Eq)] +pub enum PeImportNameType { + /// IMPORT_ORDINAL + /// Uses the ordinal (i.e., a number) rather than the name. + Ordinal(u16), + /// Same as IMPORT_NAME + /// Name is decorated with all prefixes and suffixes. + Decorated, + /// Same as IMPORT_NAME_NOPREFIX + /// Prefix (e.g., the leading `_` or `@`) is skipped, but suffix is kept. + NoPrefix, + /// Same as IMPORT_NAME_UNDECORATE + /// Prefix (e.g., the leading `_` or `@`) and suffix (the first `@` and all + /// trailing characters) are skipped. + Undecorated, +} + +#[derive(Clone, Debug, Encodable, Decodable, HashStable_Generic)] +pub struct DllImport { + pub name: Symbol, + pub import_name_type: Option<PeImportNameType>, + /// Calling convention for the function. + /// + /// On x86_64, this is always `DllCallingConvention::C`; on i686, it can be any + /// of the values, and we use `DllCallingConvention::C` to represent `"cdecl"`. + pub calling_convention: DllCallingConvention, + /// Span of import's "extern" declaration; used for diagnostics. + pub span: Span, + /// Is this for a function (rather than a static variable). + pub is_fn: bool, +} + +impl DllImport { + pub fn ordinal(&self) -> Option<u16> { + if let Some(PeImportNameType::Ordinal(ordinal)) = self.import_name_type { + Some(ordinal) + } else { + None + } + } + + pub fn is_missing_decorations(&self) -> bool { + self.import_name_type == Some(PeImportNameType::Undecorated) + || self.import_name_type == Some(PeImportNameType::NoPrefix) + } +} + +/// Calling convention for a function defined in an external library. +/// +/// The usize value, where present, indicates the size of the function's argument list +/// in bytes. +#[derive(Clone, PartialEq, Debug, Encodable, Decodable, HashStable_Generic)] +pub enum DllCallingConvention { + C, + Stdcall(usize), + Fastcall(usize), + Vectorcall(usize), +} + +#[derive(Clone, Encodable, Decodable, HashStable_Generic, Debug)] +pub struct ForeignModule { + pub foreign_items: Vec<DefId>, + pub def_id: DefId, + pub abi: ExternAbi, +} + +#[derive(Copy, Clone, Debug, HashStable_Generic)] +pub struct ExternCrate { + pub src: ExternCrateSource, + + /// span of the extern crate that caused this to be loaded + pub span: Span, + + /// Number of links to reach the extern; + /// used to select the extern with the shortest path + pub path_len: usize, + + /// Crate that depends on this crate + pub dependency_of: CrateNum, +} + +impl ExternCrate { + /// If true, then this crate is the crate named by the extern + /// crate referenced above. If false, then this crate is a dep + /// of the crate. + #[inline] + pub fn is_direct(&self) -> bool { + self.dependency_of == LOCAL_CRATE + } + + #[inline] + pub fn rank(&self) -> impl PartialOrd { + // Prefer: + // - direct extern crate to indirect + // - shorter paths to longer + (self.is_direct(), !self.path_len) + } +} + +#[derive(Copy, Clone, Debug, HashStable_Generic)] +pub enum ExternCrateSource { + /// Crate is loaded by `extern crate`. + Extern( + /// def_id of the item in the current crate that caused + /// this crate to be loaded; note that there could be multiple + /// such ids + DefId, + ), + /// Crate is implicitly loaded by a path resolving through extern prelude. + Path, +} + +/// A store of Rust crates, through which their metadata can be accessed. +/// +/// Note that this trait should probably not be expanding today. All new +/// functionality should be driven through queries instead! +/// +/// If you find a method on this trait named `{name}_untracked` it signifies +/// that it's *not* tracked for dependency information throughout compilation +/// (it'd break incremental compilation) and should only be called pre-HIR (e.g. +/// during resolve) +pub trait CrateStore: std::fmt::Debug { + fn as_any(&self) -> &dyn Any; + fn untracked_as_any(&mut self) -> &mut dyn Any; + + // Foreign definitions. + // This information is safe to access, since it's hashed as part of the DefPathHash, which incr. + // comp. uses to identify a DefId. + fn def_key(&self, def: DefId) -> DefKey; + fn def_path(&self, def: DefId) -> DefPath; + fn def_path_hash(&self, def: DefId) -> DefPathHash; + + // This information is safe to access, since it's hashed as part of the StableCrateId, which + // incr. comp. uses to identify a CrateNum. + fn crate_name(&self, cnum: CrateNum) -> Symbol; + fn stable_crate_id(&self, cnum: CrateNum) -> StableCrateId; +} + +pub type CrateStoreDyn = dyn CrateStore + sync::DynSync + sync::DynSend; + +pub struct Untracked { + pub cstore: FreezeLock<Box<CrateStoreDyn>>, + /// Reference span for definitions. + pub source_span: AppendOnlyIndexVec<LocalDefId, Span>, + pub definitions: FreezeLock<Definitions>, + /// The interned [StableCrateId]s. + pub stable_crate_ids: FreezeLock<StableCrateIdMap>, +} diff --git a/compiler/rustc_session/src/errors.rs b/compiler/rustc_session/src/errors.rs new file mode 100644 index 00000000000..33f84f10447 --- /dev/null +++ b/compiler/rustc_session/src/errors.rs @@ -0,0 +1,513 @@ +use std::num::NonZero; + +use rustc_ast::token; +use rustc_ast::util::literal::LitError; +use rustc_errors::codes::*; +use rustc_errors::{ + Diag, DiagCtxtHandle, DiagMessage, Diagnostic, EmissionGuarantee, ErrorGuaranteed, Level, + MultiSpan, +}; +use rustc_macros::{Diagnostic, Subdiagnostic}; +use rustc_span::{Span, Symbol}; +use rustc_target::spec::{SplitDebuginfo, StackProtector, TargetTuple}; + +use crate::config::CrateType; +use crate::parse::ParseSess; + +pub(crate) struct FeatureGateError { + pub(crate) span: MultiSpan, + pub(crate) explain: DiagMessage, +} + +impl<'a, G: EmissionGuarantee> Diagnostic<'a, G> for FeatureGateError { + #[track_caller] + fn into_diag(self, dcx: DiagCtxtHandle<'a>, level: Level) -> Diag<'a, G> { + Diag::new(dcx, level, self.explain).with_span(self.span).with_code(E0658) + } +} + +#[derive(Subdiagnostic)] +#[note(session_feature_diagnostic_for_issue)] +pub(crate) struct FeatureDiagnosticForIssue { + pub(crate) n: NonZero<u32>, +} + +#[derive(Subdiagnostic)] +#[note(session_feature_suggest_upgrade_compiler)] +pub(crate) struct SuggestUpgradeCompiler { + date: &'static str, +} + +impl SuggestUpgradeCompiler { + pub(crate) fn ui_testing() -> Self { + Self { date: "YYYY-MM-DD" } + } + + pub(crate) fn new() -> Option<Self> { + let date = option_env!("CFG_VER_DATE")?; + + Some(Self { date }) + } +} + +#[derive(Subdiagnostic)] +#[help(session_feature_diagnostic_help)] +pub(crate) struct FeatureDiagnosticHelp { + pub(crate) feature: Symbol, +} + +#[derive(Subdiagnostic)] +#[suggestion( + session_feature_diagnostic_suggestion, + applicability = "maybe-incorrect", + code = "#![feature({feature})]\n" +)] +pub struct FeatureDiagnosticSuggestion { + pub feature: Symbol, + #[primary_span] + pub span: Span, +} + +#[derive(Subdiagnostic)] +#[help(session_cli_feature_diagnostic_help)] +pub(crate) struct CliFeatureDiagnosticHelp { + pub(crate) feature: Symbol, +} + +#[derive(Diagnostic)] +#[diag(session_not_circumvent_feature)] +pub(crate) struct NotCircumventFeature; + +#[derive(Diagnostic)] +#[diag(session_linker_plugin_lto_windows_not_supported)] +pub(crate) struct LinkerPluginToWindowsNotSupported; + +#[derive(Diagnostic)] +#[diag(session_profile_use_file_does_not_exist)] +pub(crate) struct ProfileUseFileDoesNotExist<'a> { + pub(crate) path: &'a std::path::Path, +} + +#[derive(Diagnostic)] +#[diag(session_profile_sample_use_file_does_not_exist)] +pub(crate) struct ProfileSampleUseFileDoesNotExist<'a> { + pub(crate) path: &'a std::path::Path, +} + +#[derive(Diagnostic)] +#[diag(session_target_requires_unwind_tables)] +pub(crate) struct TargetRequiresUnwindTables; + +#[derive(Diagnostic)] +#[diag(session_instrumentation_not_supported)] +pub(crate) struct InstrumentationNotSupported { + pub(crate) us: String, +} + +#[derive(Diagnostic)] +#[diag(session_sanitizer_not_supported)] +pub(crate) struct SanitizerNotSupported { + pub(crate) us: String, +} + +#[derive(Diagnostic)] +#[diag(session_sanitizers_not_supported)] +pub(crate) struct SanitizersNotSupported { + pub(crate) us: String, +} + +#[derive(Diagnostic)] +#[diag(session_cannot_mix_and_match_sanitizers)] +pub(crate) struct CannotMixAndMatchSanitizers { + pub(crate) first: String, + pub(crate) second: String, +} + +#[derive(Diagnostic)] +#[diag(session_cannot_enable_crt_static_linux)] +pub(crate) struct CannotEnableCrtStaticLinux; + +#[derive(Diagnostic)] +#[diag(session_sanitizer_cfi_requires_lto)] +pub(crate) struct SanitizerCfiRequiresLto; + +#[derive(Diagnostic)] +#[diag(session_sanitizer_cfi_requires_single_codegen_unit)] +pub(crate) struct SanitizerCfiRequiresSingleCodegenUnit; + +#[derive(Diagnostic)] +#[diag(session_sanitizer_cfi_canonical_jump_tables_requires_cfi)] +pub(crate) struct SanitizerCfiCanonicalJumpTablesRequiresCfi; + +#[derive(Diagnostic)] +#[diag(session_sanitizer_cfi_generalize_pointers_requires_cfi)] +pub(crate) struct SanitizerCfiGeneralizePointersRequiresCfi; + +#[derive(Diagnostic)] +#[diag(session_sanitizer_cfi_normalize_integers_requires_cfi)] +pub(crate) struct SanitizerCfiNormalizeIntegersRequiresCfi; + +#[derive(Diagnostic)] +#[diag(session_sanitizer_kcfi_requires_panic_abort)] +pub(crate) struct SanitizerKcfiRequiresPanicAbort; + +#[derive(Diagnostic)] +#[diag(session_split_lto_unit_requires_lto)] +pub(crate) struct SplitLtoUnitRequiresLto; + +#[derive(Diagnostic)] +#[diag(session_unstable_virtual_function_elimination)] +pub(crate) struct UnstableVirtualFunctionElimination; + +#[derive(Diagnostic)] +#[diag(session_unsupported_dwarf_version)] +pub(crate) struct UnsupportedDwarfVersion { + pub(crate) dwarf_version: u32, +} + +#[derive(Diagnostic)] +#[diag(session_embed_source_insufficient_dwarf_version)] +pub(crate) struct EmbedSourceInsufficientDwarfVersion { + pub(crate) dwarf_version: u32, +} + +#[derive(Diagnostic)] +#[diag(session_embed_source_requires_debug_info)] +pub(crate) struct EmbedSourceRequiresDebugInfo; + +#[derive(Diagnostic)] +#[diag(session_target_stack_protector_not_supported)] +pub(crate) struct StackProtectorNotSupportedForTarget<'a> { + pub(crate) stack_protector: StackProtector, + pub(crate) target_triple: &'a TargetTuple, +} + +#[derive(Diagnostic)] +#[diag(session_target_small_data_threshold_not_supported)] +pub(crate) struct SmallDataThresholdNotSupportedForTarget<'a> { + pub(crate) target_triple: &'a TargetTuple, +} + +#[derive(Diagnostic)] +#[diag(session_branch_protection_requires_aarch64)] +pub(crate) struct BranchProtectionRequiresAArch64; + +#[derive(Diagnostic)] +#[diag(session_split_debuginfo_unstable_platform)] +pub(crate) struct SplitDebugInfoUnstablePlatform { + pub(crate) debuginfo: SplitDebuginfo, +} + +#[derive(Diagnostic)] +#[diag(session_file_is_not_writeable)] +pub(crate) struct FileIsNotWriteable<'a> { + pub(crate) file: &'a std::path::Path, +} + +#[derive(Diagnostic)] +#[diag(session_file_write_fail)] +pub(crate) struct FileWriteFail<'a> { + pub(crate) path: &'a std::path::Path, + pub(crate) err: String, +} + +#[derive(Diagnostic)] +#[diag(session_crate_name_does_not_match)] +pub(crate) struct CrateNameDoesNotMatch { + #[primary_span] + pub(crate) span: Span, + pub(crate) s: Symbol, + pub(crate) name: Symbol, +} + +#[derive(Diagnostic)] +#[diag(session_crate_name_invalid)] +pub(crate) struct CrateNameInvalid<'a> { + pub(crate) s: &'a str, +} + +#[derive(Diagnostic)] +#[diag(session_crate_name_empty)] +pub(crate) struct CrateNameEmpty { + #[primary_span] + pub(crate) span: Option<Span>, +} + +#[derive(Diagnostic)] +#[diag(session_invalid_character_in_create_name)] +pub(crate) struct InvalidCharacterInCrateName { + #[primary_span] + pub(crate) span: Option<Span>, + pub(crate) character: char, + pub(crate) crate_name: Symbol, + #[subdiagnostic] + pub(crate) crate_name_help: Option<InvalidCrateNameHelp>, +} + +#[derive(Subdiagnostic)] +pub(crate) enum InvalidCrateNameHelp { + #[help(session_invalid_character_in_create_name_help)] + AddCrateName, +} + +#[derive(Subdiagnostic)] +#[multipart_suggestion(session_expr_parentheses_needed, applicability = "machine-applicable")] +pub struct ExprParenthesesNeeded { + #[suggestion_part(code = "(")] + left: Span, + #[suggestion_part(code = ")")] + right: Span, +} + +impl ExprParenthesesNeeded { + pub fn surrounding(s: Span) -> Self { + ExprParenthesesNeeded { left: s.shrink_to_lo(), right: s.shrink_to_hi() } + } +} + +#[derive(Diagnostic)] +#[diag(session_skipping_const_checks)] +pub(crate) struct SkippingConstChecks { + #[subdiagnostic] + pub(crate) unleashed_features: Vec<UnleashedFeatureHelp>, +} + +#[derive(Subdiagnostic)] +pub(crate) enum UnleashedFeatureHelp { + #[help(session_unleashed_feature_help_named)] + Named { + #[primary_span] + span: Span, + gate: Symbol, + }, + #[help(session_unleashed_feature_help_unnamed)] + Unnamed { + #[primary_span] + span: Span, + }, +} + +#[derive(Diagnostic)] +#[diag(session_invalid_literal_suffix)] +struct InvalidLiteralSuffix<'a> { + #[primary_span] + #[label] + span: Span, + // FIXME(#100717) + kind: &'a str, + suffix: Symbol, +} + +#[derive(Diagnostic)] +#[diag(session_invalid_int_literal_width)] +#[help] +struct InvalidIntLiteralWidth { + #[primary_span] + span: Span, + width: String, +} + +#[derive(Diagnostic)] +#[diag(session_invalid_num_literal_base_prefix)] +#[note] +struct InvalidNumLiteralBasePrefix { + #[primary_span] + #[suggestion(applicability = "maybe-incorrect", code = "{fixed}")] + span: Span, + fixed: String, +} + +#[derive(Diagnostic)] +#[diag(session_invalid_num_literal_suffix)] +#[help] +struct InvalidNumLiteralSuffix { + #[primary_span] + #[label] + span: Span, + suffix: String, +} + +#[derive(Diagnostic)] +#[diag(session_invalid_float_literal_width)] +#[help] +struct InvalidFloatLiteralWidth { + #[primary_span] + span: Span, + width: String, +} + +#[derive(Diagnostic)] +#[diag(session_invalid_float_literal_suffix)] +#[help] +struct InvalidFloatLiteralSuffix { + #[primary_span] + #[label] + span: Span, + suffix: String, +} + +#[derive(Diagnostic)] +#[diag(session_int_literal_too_large)] +#[note] +struct IntLiteralTooLarge { + #[primary_span] + span: Span, + limit: String, +} + +#[derive(Diagnostic)] +#[diag(session_hexadecimal_float_literal_not_supported)] +struct HexadecimalFloatLiteralNotSupported { + #[primary_span] + #[label(session_not_supported)] + span: Span, +} + +#[derive(Diagnostic)] +#[diag(session_octal_float_literal_not_supported)] +struct OctalFloatLiteralNotSupported { + #[primary_span] + #[label(session_not_supported)] + span: Span, +} + +#[derive(Diagnostic)] +#[diag(session_binary_float_literal_not_supported)] +struct BinaryFloatLiteralNotSupported { + #[primary_span] + #[label(session_not_supported)] + span: Span, +} + +#[derive(Diagnostic)] +#[diag(session_unsupported_crate_type_for_target)] +pub(crate) struct UnsupportedCrateTypeForTarget<'a> { + pub(crate) crate_type: CrateType, + pub(crate) target_triple: &'a TargetTuple, +} + +pub fn report_lit_error( + psess: &ParseSess, + err: LitError, + lit: token::Lit, + span: Span, +) -> ErrorGuaranteed { + // Checks if `s` looks like i32 or u1234 etc. + fn looks_like_width_suffix(first_chars: &[char], s: &str) -> bool { + s.len() > 1 && s.starts_with(first_chars) && s[1..].chars().all(|c| c.is_ascii_digit()) + } + + // Try to lowercase the prefix if the prefix and suffix are valid. + fn fix_base_capitalisation(prefix: &str, suffix: &str) -> Option<String> { + let mut chars = suffix.chars(); + + let base_char = chars.next().unwrap(); + let base = match base_char { + 'B' => 2, + 'O' => 8, + 'X' => 16, + _ => return None, + }; + + // check that the suffix contains only base-appropriate characters + let valid = prefix == "0" + && chars + .filter(|c| *c != '_') + .take_while(|c| *c != 'i' && *c != 'u') + .all(|c| c.to_digit(base).is_some()); + + valid.then(|| format!("0{}{}", base_char.to_ascii_lowercase(), &suffix[1..])) + } + + let dcx = psess.dcx(); + match err { + LitError::InvalidSuffix(suffix) => { + dcx.emit_err(InvalidLiteralSuffix { span, kind: lit.kind.descr(), suffix }) + } + LitError::InvalidIntSuffix(suffix) => { + let suf = suffix.as_str(); + if looks_like_width_suffix(&['i', 'u'], suf) { + // If it looks like a width, try to be helpful. + dcx.emit_err(InvalidIntLiteralWidth { span, width: suf[1..].into() }) + } else if let Some(fixed) = fix_base_capitalisation(lit.symbol.as_str(), suf) { + dcx.emit_err(InvalidNumLiteralBasePrefix { span, fixed }) + } else { + dcx.emit_err(InvalidNumLiteralSuffix { span, suffix: suf.to_string() }) + } + } + LitError::InvalidFloatSuffix(suffix) => { + let suf = suffix.as_str(); + if looks_like_width_suffix(&['f'], suf) { + // If it looks like a width, try to be helpful. + dcx.emit_err(InvalidFloatLiteralWidth { span, width: suf[1..].to_string() }) + } else { + dcx.emit_err(InvalidFloatLiteralSuffix { span, suffix: suf.to_string() }) + } + } + LitError::NonDecimalFloat(base) => match base { + 16 => dcx.emit_err(HexadecimalFloatLiteralNotSupported { span }), + 8 => dcx.emit_err(OctalFloatLiteralNotSupported { span }), + 2 => dcx.emit_err(BinaryFloatLiteralNotSupported { span }), + _ => unreachable!(), + }, + LitError::IntTooLarge(base) => { + let max = u128::MAX; + let limit = match base { + 2 => format!("{max:#b}"), + 8 => format!("{max:#o}"), + 16 => format!("{max:#x}"), + _ => format!("{max}"), + }; + dcx.emit_err(IntLiteralTooLarge { span, limit }) + } + } +} + +#[derive(Diagnostic)] +#[diag(session_optimization_fuel_exhausted)] +pub(crate) struct OptimisationFuelExhausted { + pub(crate) msg: String, +} + +#[derive(Diagnostic)] +#[diag(session_incompatible_linker_flavor)] +#[note] +pub(crate) struct IncompatibleLinkerFlavor { + pub(crate) flavor: &'static str, + pub(crate) compatible_list: String, +} + +#[derive(Diagnostic)] +#[diag(session_function_return_requires_x86_or_x86_64)] +pub(crate) struct FunctionReturnRequiresX86OrX8664; + +#[derive(Diagnostic)] +#[diag(session_function_return_thunk_extern_requires_non_large_code_model)] +pub(crate) struct FunctionReturnThunkExternRequiresNonLargeCodeModel; + +#[derive(Diagnostic)] +#[diag(session_unsupported_regparm)] +pub(crate) struct UnsupportedRegparm { + pub(crate) regparm: u32, +} + +#[derive(Diagnostic)] +#[diag(session_unsupported_regparm_arch)] +pub(crate) struct UnsupportedRegparmArch; + +#[derive(Diagnostic)] +#[diag(session_failed_to_create_profiler)] +pub(crate) struct FailedToCreateProfiler { + pub(crate) err: String, +} + +#[derive(Diagnostic)] +#[diag(session_soft_float_ignored)] +#[note] +pub(crate) struct SoftFloatIgnored; + +#[derive(Diagnostic)] +#[diag(session_soft_float_deprecated)] +#[note] +#[note(session_soft_float_deprecated_issue)] +pub(crate) struct SoftFloatDeprecated; diff --git a/compiler/rustc_session/src/filesearch.rs b/compiler/rustc_session/src/filesearch.rs new file mode 100644 index 00000000000..213a94ab880 --- /dev/null +++ b/compiler/rustc_session/src/filesearch.rs @@ -0,0 +1,277 @@ +//! A module for searching for libraries + +use std::path::{Path, PathBuf}; +use std::{env, fs}; + +use rustc_fs_util::{fix_windows_verbatim_for_gcc, try_canonicalize}; +use smallvec::{SmallVec, smallvec}; + +use crate::search_paths::{PathKind, SearchPath}; + +#[derive(Clone)] +pub struct FileSearch<'a> { + cli_search_paths: &'a [SearchPath], + tlib_path: &'a SearchPath, + kind: PathKind, +} + +impl<'a> FileSearch<'a> { + pub fn cli_search_paths(&self) -> impl Iterator<Item = &'a SearchPath> { + let kind = self.kind; + self.cli_search_paths.iter().filter(move |sp| sp.kind.matches(kind)) + } + + pub fn search_paths(&self) -> impl Iterator<Item = &'a SearchPath> { + let kind = self.kind; + self.cli_search_paths + .iter() + .filter(move |sp| sp.kind.matches(kind)) + .chain(std::iter::once(self.tlib_path)) + } + + pub fn new( + cli_search_paths: &'a [SearchPath], + tlib_path: &'a SearchPath, + kind: PathKind, + ) -> FileSearch<'a> { + FileSearch { cli_search_paths, tlib_path, kind } + } +} + +pub fn make_target_lib_path(sysroot: &Path, target_triple: &str) -> PathBuf { + let rustlib_path = rustc_target::relative_target_rustlib_path(sysroot, target_triple); + sysroot.join(rustlib_path).join("lib") +} + +/// Returns a path to the target's `bin` folder within its `rustlib` path in the sysroot. This is +/// where binaries are usually installed, e.g. the self-contained linkers, lld-wrappers, LLVM tools, +/// etc. +pub fn make_target_bin_path(sysroot: &Path, target_triple: &str) -> PathBuf { + let rustlib_path = rustc_target::relative_target_rustlib_path(sysroot, target_triple); + sysroot.join(rustlib_path).join("bin") +} + +#[cfg(unix)] +fn current_dll_path() -> Result<PathBuf, String> { + use std::ffi::{CStr, OsStr}; + use std::os::unix::prelude::*; + + #[cfg(not(target_os = "aix"))] + unsafe { + let addr = current_dll_path as usize as *mut _; + let mut info = std::mem::zeroed(); + if libc::dladdr(addr, &mut info) == 0 { + return Err("dladdr failed".into()); + } + if info.dli_fname.is_null() { + return Err("dladdr returned null pointer".into()); + } + let bytes = CStr::from_ptr(info.dli_fname).to_bytes(); + let os = OsStr::from_bytes(bytes); + Ok(PathBuf::from(os)) + } + + #[cfg(target_os = "aix")] + unsafe { + // On AIX, the symbol `current_dll_path` references a function descriptor. + // A function descriptor is consisted of (See https://reviews.llvm.org/D62532) + // * The address of the entry point of the function. + // * The TOC base address for the function. + // * The environment pointer. + // The function descriptor is in the data section. + let addr = current_dll_path as u64; + let mut buffer = vec![std::mem::zeroed::<libc::ld_info>(); 64]; + loop { + if libc::loadquery( + libc::L_GETINFO, + buffer.as_mut_ptr() as *mut u8, + (std::mem::size_of::<libc::ld_info>() * buffer.len()) as u32, + ) >= 0 + { + break; + } else { + if std::io::Error::last_os_error().raw_os_error().unwrap() != libc::ENOMEM { + return Err("loadquery failed".into()); + } + buffer.resize(buffer.len() * 2, std::mem::zeroed::<libc::ld_info>()); + } + } + let mut current = buffer.as_mut_ptr() as *mut libc::ld_info; + loop { + let data_base = (*current).ldinfo_dataorg as u64; + let data_end = data_base + (*current).ldinfo_datasize; + if (data_base..data_end).contains(&addr) { + let bytes = CStr::from_ptr(&(*current).ldinfo_filename[0]).to_bytes(); + let os = OsStr::from_bytes(bytes); + return Ok(PathBuf::from(os)); + } + if (*current).ldinfo_next == 0 { + break; + } + current = + (current as *mut i8).offset((*current).ldinfo_next as isize) as *mut libc::ld_info; + } + return Err(format!("current dll's address {} is not in the load map", addr)); + } +} + +#[cfg(windows)] +fn current_dll_path() -> Result<PathBuf, String> { + use std::ffi::OsString; + use std::io; + use std::os::windows::prelude::*; + + use windows::Win32::Foundation::HMODULE; + use windows::Win32::System::LibraryLoader::{ + GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, GetModuleFileNameW, GetModuleHandleExW, + }; + use windows::core::PCWSTR; + + let mut module = HMODULE::default(); + unsafe { + GetModuleHandleExW( + GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, + PCWSTR(current_dll_path as *mut u16), + &mut module, + ) + } + .map_err(|e| e.to_string())?; + + let mut filename = vec![0; 1024]; + let n = unsafe { GetModuleFileNameW(module, &mut filename) } as usize; + if n == 0 { + return Err(format!("GetModuleFileNameW failed: {}", io::Error::last_os_error())); + } + if n >= filename.capacity() { + return Err(format!("our buffer was too small? {}", io::Error::last_os_error())); + } + + filename.truncate(n); + + Ok(OsString::from_wide(&filename).into()) +} + +pub fn sysroot_candidates() -> SmallVec<[PathBuf; 2]> { + let target = crate::config::host_tuple(); + let mut sysroot_candidates: SmallVec<[PathBuf; 2]> = + smallvec![get_or_default_sysroot().expect("Failed finding sysroot")]; + let path = current_dll_path().and_then(|s| try_canonicalize(s).map_err(|e| e.to_string())); + if let Ok(dll) = path { + // use `parent` twice to chop off the file name and then also the + // directory containing the dll which should be either `lib` or `bin`. + if let Some(path) = dll.parent().and_then(|p| p.parent()) { + // The original `path` pointed at the `rustc_driver` crate's dll. + // Now that dll should only be in one of two locations. The first is + // in the compiler's libdir, for example `$sysroot/lib/*.dll`. The + // other is the target's libdir, for example + // `$sysroot/lib/rustlib/$target/lib/*.dll`. + // + // We don't know which, so let's assume that if our `path` above + // ends in `$target` we *could* be in the target libdir, and always + // assume that we may be in the main libdir. + sysroot_candidates.push(path.to_owned()); + + if path.ends_with(target) { + sysroot_candidates.extend( + path.parent() // chop off `$target` + .and_then(|p| p.parent()) // chop off `rustlib` + .and_then(|p| p.parent()) // chop off `lib` + .map(|s| s.to_owned()), + ); + } + } + } + + sysroot_candidates +} + +/// Returns the provided sysroot or calls [`get_or_default_sysroot`] if it's none. +/// Panics if [`get_or_default_sysroot`] returns an error. +pub fn materialize_sysroot(maybe_sysroot: Option<PathBuf>) -> PathBuf { + maybe_sysroot.unwrap_or_else(|| get_or_default_sysroot().expect("Failed finding sysroot")) +} + +/// This function checks if sysroot is found using env::args().next(), and if it +/// is not found, finds sysroot from current rustc_driver dll. +pub fn get_or_default_sysroot() -> Result<PathBuf, String> { + // Follow symlinks. If the resolved path is relative, make it absolute. + fn canonicalize(path: PathBuf) -> PathBuf { + let path = try_canonicalize(&path).unwrap_or(path); + // See comments on this target function, but the gist is that + // gcc chokes on verbatim paths which fs::canonicalize generates + // so we try to avoid those kinds of paths. + fix_windows_verbatim_for_gcc(&path) + } + + fn default_from_rustc_driver_dll() -> Result<PathBuf, String> { + let dll = current_dll_path().map(|s| canonicalize(s))?; + + // `dll` will be in one of the following two: + // - compiler's libdir: $sysroot/lib/*.dll + // - target's libdir: $sysroot/lib/rustlib/$target/lib/*.dll + // + // use `parent` twice to chop off the file name and then also the + // directory containing the dll + let dir = dll.parent().and_then(|p| p.parent()).ok_or(format!( + "Could not move 2 levels upper using `parent()` on {}", + dll.display() + ))?; + + // if `dir` points target's dir, move up to the sysroot + let mut sysroot_dir = if dir.ends_with(crate::config::host_tuple()) { + dir.parent() // chop off `$target` + .and_then(|p| p.parent()) // chop off `rustlib` + .and_then(|p| p.parent()) // chop off `lib` + .map(|s| s.to_owned()) + .ok_or_else(|| { + format!("Could not move 3 levels upper using `parent()` on {}", dir.display()) + })? + } else { + dir.to_owned() + }; + + // On multiarch linux systems, there will be multiarch directory named + // with the architecture(e.g `x86_64-linux-gnu`) under the `lib` directory. + // Which cause us to mistakenly end up in the lib directory instead of the sysroot directory. + if sysroot_dir.ends_with("lib") { + sysroot_dir = + sysroot_dir.parent().map(|real_sysroot| real_sysroot.to_owned()).ok_or_else( + || format!("Could not move to parent path of {}", sysroot_dir.display()), + )? + } + + Ok(sysroot_dir) + } + + // Use env::args().next() to get the path of the executable without + // following symlinks/canonicalizing any component. This makes the rustc + // binary able to locate Rust libraries in systems using content-addressable + // storage (CAS). + fn from_env_args_next() -> Option<PathBuf> { + match env::args_os().next() { + Some(first_arg) => { + let mut p = PathBuf::from(first_arg); + + // Check if sysroot is found using env::args().next() only if the rustc in argv[0] + // is a symlink (see #79253). We might want to change/remove it to conform with + // https://www.gnu.org/prep/standards/standards.html#Finding-Program-Files in the + // future. + if fs::read_link(&p).is_err() { + // Path is not a symbolic link or does not exist. + return None; + } + + // Pop off `bin/rustc`, obtaining the suspected sysroot. + p.pop(); + p.pop(); + // Look for the target rustlib directory in the suspected sysroot. + let mut rustlib_path = rustc_target::relative_target_rustlib_path(&p, "dummy"); + rustlib_path.pop(); // pop off the dummy target. + rustlib_path.exists().then_some(p) + } + None => None, + } + } + + Ok(from_env_args_next().unwrap_or(default_from_rustc_driver_dll()?)) +} diff --git a/compiler/rustc_session/src/lib.rs b/compiler/rustc_session/src/lib.rs new file mode 100644 index 00000000000..0b4470b2b0f --- /dev/null +++ b/compiler/rustc_session/src/lib.rs @@ -0,0 +1,40 @@ +// tidy-alphabetical-start +#![allow(internal_features)] +#![feature(iter_intersperse)] +#![feature(let_chains)] +#![feature(map_many_mut)] +#![feature(rustc_attrs)] +#![warn(unreachable_pub)] +// tidy-alphabetical-end + +pub mod errors; + +pub mod utils; +pub use lint::{declare_lint, declare_lint_pass, declare_tool_lint, impl_lint_pass}; +pub use rustc_lint_defs as lint; +pub mod parse; + +pub mod code_stats; +#[macro_use] +pub mod config; +pub mod cstore; +pub mod filesearch; +mod options; +pub mod search_paths; + +mod session; +pub use session::*; + +pub mod output; + +pub use getopts; + +mod version; +pub use version::RustcVersion; + +rustc_fluent_macro::fluent_messages! { "../messages.ftl" } + +/// Requirements for a `StableHashingContext` to be used in this crate. +/// This is a hack to allow using the `HashStable_Generic` derive macro +/// instead of implementing everything in `rustc_middle`. +pub trait HashStableContext: rustc_ast::HashStableContext + rustc_hir::HashStableContext {} diff --git a/compiler/rustc_session/src/options.rs b/compiler/rustc_session/src/options.rs new file mode 100644 index 00000000000..f485e8cace5 --- /dev/null +++ b/compiler/rustc_session/src/options.rs @@ -0,0 +1,2201 @@ +use std::collections::BTreeMap; +use std::hash::{DefaultHasher, Hasher}; +use std::num::{IntErrorKind, NonZero}; +use std::path::PathBuf; +use std::str; + +use rustc_data_structures::fx::FxIndexMap; +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_span::edition::Edition; +use rustc_span::{RealFileName, SourceFileHashAlgorithm}; +use rustc_target::spec::{ + CodeModel, FramePointer, LinkerFlavorCli, MergeFunctions, OnBrokenPipe, PanicStrategy, + RelocModel, RelroLevel, SanitizerSet, SplitDebuginfo, StackProtector, SymbolVisibility, + TargetTuple, TlsModel, WasmCAbi, +}; + +use crate::config::*; +use crate::search_paths::SearchPath; +use crate::utils::NativeLib; +use crate::{EarlyDiagCtxt, lint}; + +macro_rules! insert { + ($opt_name:ident, $opt_expr:expr, $sub_hashes:expr) => { + if $sub_hashes + .insert(stringify!($opt_name), $opt_expr as &dyn dep_tracking::DepTrackingHash) + .is_some() + { + panic!("duplicate key in CLI DepTrackingHash: {}", stringify!($opt_name)) + } + }; +} + +macro_rules! hash_opt { + ($opt_name:ident, $opt_expr:expr, $sub_hashes:expr, $_for_crate_hash: ident, [UNTRACKED]) => {{}}; + ($opt_name:ident, $opt_expr:expr, $sub_hashes:expr, $_for_crate_hash: ident, [TRACKED]) => {{ insert!($opt_name, $opt_expr, $sub_hashes) }}; + ($opt_name:ident, $opt_expr:expr, $sub_hashes:expr, $for_crate_hash: ident, [TRACKED_NO_CRATE_HASH]) => {{ + if !$for_crate_hash { + insert!($opt_name, $opt_expr, $sub_hashes) + } + }}; + ($opt_name:ident, $opt_expr:expr, $sub_hashes:expr, $_for_crate_hash: ident, [SUBSTRUCT]) => {{}}; +} + +macro_rules! hash_substruct { + ($opt_name:ident, $opt_expr:expr, $error_format:expr, $for_crate_hash:expr, $hasher:expr, [UNTRACKED]) => {{}}; + ($opt_name:ident, $opt_expr:expr, $error_format:expr, $for_crate_hash:expr, $hasher:expr, [TRACKED]) => {{}}; + ($opt_name:ident, $opt_expr:expr, $error_format:expr, $for_crate_hash:expr, $hasher:expr, [TRACKED_NO_CRATE_HASH]) => {{}}; + ($opt_name:ident, $opt_expr:expr, $error_format:expr, $for_crate_hash:expr, $hasher:expr, [SUBSTRUCT]) => { + use crate::config::dep_tracking::DepTrackingHash; + $opt_expr.dep_tracking_hash($for_crate_hash, $error_format).hash( + $hasher, + $error_format, + $for_crate_hash, + ); + }; +} + +macro_rules! top_level_options { + ( $( #[$top_level_attr:meta] )* pub struct Options { $( + $( #[$attr:meta] )* + $opt:ident : $t:ty [$dep_tracking_marker:ident], + )* } ) => ( + #[derive(Clone)] + $( #[$top_level_attr] )* + pub struct Options { + $( + $( #[$attr] )* + pub $opt: $t + ),* + } + + impl Options { + pub fn dep_tracking_hash(&self, for_crate_hash: bool) -> u64 { + let mut sub_hashes = BTreeMap::new(); + $({ + hash_opt!($opt, + &self.$opt, + &mut sub_hashes, + for_crate_hash, + [$dep_tracking_marker]); + })* + let mut hasher = DefaultHasher::new(); + dep_tracking::stable_hash(sub_hashes, + &mut hasher, + self.error_format, + for_crate_hash); + $({ + hash_substruct!($opt, + &self.$opt, + self.error_format, + for_crate_hash, + &mut hasher, + [$dep_tracking_marker]); + })* + hasher.finish() + } + } + ); +} + +top_level_options!( + /// The top-level command-line options struct. + /// + /// For each option, one has to specify how it behaves with regard to the + /// dependency tracking system of incremental compilation. This is done via the + /// square-bracketed directive after the field type. The options are: + /// + /// - `[TRACKED]` + /// A change in the given field will cause the compiler to completely clear the + /// incremental compilation cache before proceeding. + /// + /// - `[TRACKED_NO_CRATE_HASH]` + /// Same as `[TRACKED]`, but will not affect the crate hash. This is useful for options that + /// only affect the incremental cache. + /// + /// - `[UNTRACKED]` + /// Incremental compilation is not influenced by this option. + /// + /// - `[SUBSTRUCT]` + /// Second-level sub-structs containing more options. + /// + /// If you add a new option to this struct or one of the sub-structs like + /// `CodegenOptions`, think about how it influences incremental compilation. If in + /// doubt, specify `[TRACKED]`, which is always "correct" but might lead to + /// unnecessary re-compilation. + #[rustc_lint_opt_ty] + pub struct Options { + /// The crate config requested for the session, which may be combined + /// with additional crate configurations during the compile process. + #[rustc_lint_opt_deny_field_access("use `TyCtxt::crate_types` instead of this field")] + crate_types: Vec<CrateType> [TRACKED], + optimize: OptLevel [TRACKED], + /// Include the `debug_assertions` flag in dependency tracking, since it + /// can influence whether overflow checks are done or not. + debug_assertions: bool [TRACKED], + debuginfo: DebugInfo [TRACKED], + debuginfo_compression: DebugInfoCompression [TRACKED], + lint_opts: Vec<(String, lint::Level)> [TRACKED_NO_CRATE_HASH], + lint_cap: Option<lint::Level> [TRACKED_NO_CRATE_HASH], + describe_lints: bool [UNTRACKED], + output_types: OutputTypes [TRACKED], + search_paths: Vec<SearchPath> [UNTRACKED], + libs: Vec<NativeLib> [TRACKED], + maybe_sysroot: Option<PathBuf> [UNTRACKED], + + target_triple: TargetTuple [TRACKED], + + /// Effective logical environment used by `env!`/`option_env!` macros + logical_env: FxIndexMap<String, String> [TRACKED], + + test: bool [TRACKED], + error_format: ErrorOutputType [UNTRACKED], + diagnostic_width: Option<usize> [UNTRACKED], + + /// If `Some`, enable incremental compilation, using the given + /// directory to store intermediate results. + incremental: Option<PathBuf> [UNTRACKED], + assert_incr_state: Option<IncrementalStateAssertion> [UNTRACKED], + /// Set by the `Config::hash_untracked_state` callback for custom + /// drivers to invalidate the incremental cache + #[rustc_lint_opt_deny_field_access("should only be used via `Config::hash_untracked_state`")] + untracked_state_hash: Hash64 [TRACKED_NO_CRATE_HASH], + + unstable_opts: UnstableOptions [SUBSTRUCT], + prints: Vec<PrintRequest> [UNTRACKED], + cg: CodegenOptions [SUBSTRUCT], + externs: Externs [UNTRACKED], + crate_name: Option<String> [TRACKED], + /// Indicates how the compiler should treat unstable features. + unstable_features: UnstableFeatures [TRACKED], + + /// Indicates whether this run of the compiler is actually rustdoc. This + /// is currently just a hack and will be removed eventually, so please + /// try to not rely on this too much. + actually_rustdoc: bool [TRACKED], + /// Whether name resolver should resolve documentation links. + resolve_doc_links: ResolveDocLinks [TRACKED], + + /// Control path trimming. + trimmed_def_paths: bool [TRACKED], + + /// Specifications of codegen units / ThinLTO which are forced as a + /// result of parsing command line options. These are not necessarily + /// what rustc was invoked with, but massaged a bit to agree with + /// commands like `--emit llvm-ir` which they're often incompatible with + /// if we otherwise use the defaults of rustc. + #[rustc_lint_opt_deny_field_access("use `Session::codegen_units` instead of this field")] + cli_forced_codegen_units: Option<usize> [UNTRACKED], + #[rustc_lint_opt_deny_field_access("use `Session::lto` instead of this field")] + cli_forced_local_thinlto_off: bool [UNTRACKED], + + /// Remap source path prefixes in all output (messages, object files, debug, etc.). + remap_path_prefix: Vec<(PathBuf, PathBuf)> [TRACKED_NO_CRATE_HASH], + /// Base directory containing the `src/` for the Rust standard library, and + /// potentially `rustc` as well, if we can find it. Right now it's always + /// `$sysroot/lib/rustlib/src/rust` (i.e. the `rustup` `rust-src` component). + /// + /// This directory is what the virtual `/rustc/$hash` is translated back to, + /// if Rust was built with path remapping to `/rustc/$hash` enabled + /// (the `rust.remap-debuginfo` option in `config.toml`). + real_rust_source_base_dir: Option<PathBuf> [TRACKED_NO_CRATE_HASH], + + edition: Edition [TRACKED], + + /// `true` if we're emitting JSON blobs about each artifact produced + /// by the compiler. + json_artifact_notifications: bool [TRACKED], + + /// `true` if we're emitting a JSON blob containing the unused externs + json_unused_externs: JsonUnusedExterns [UNTRACKED], + + /// `true` if we're emitting a JSON job containing a future-incompat report for lints + json_future_incompat: bool [TRACKED], + + pretty: Option<PpMode> [UNTRACKED], + + /// The (potentially remapped) working directory + working_dir: RealFileName [TRACKED], + color: ColorConfig [UNTRACKED], + + verbose: bool [TRACKED_NO_CRATE_HASH], + } +); + +/// 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. +/// +/// The goal is to invoke this macro once with the correct fields, and then this macro generates all +/// necessary code. The main gotcha of this macro is the `cgsetters` module which is a bunch of +/// generated code to parse an option into its respective field in the struct. There are a few +/// hand-written parsers for parsing specific types of values in this module. +macro_rules! options { + ($struct_name:ident, $stat:ident, $optmod:ident, $prefix:expr, $outputname:expr, + $($( #[$attr:meta] )* $opt:ident : $t:ty = ( + $init:expr, + $parse:ident, + [$dep_tracking_marker:ident], + $desc:expr) + ),* ,) => +( + #[derive(Clone)] + #[rustc_lint_opt_ty] + pub struct $struct_name { $( $( #[$attr] )* pub $opt: $t),* } + + impl Default for $struct_name { + fn default() -> $struct_name { + $struct_name { $($opt: $init),* } + } + } + + impl $struct_name { + pub fn build( + early_dcx: &EarlyDiagCtxt, + matches: &getopts::Matches, + ) -> $struct_name { + build_options(early_dcx, matches, $stat, $prefix, $outputname) + } + + fn dep_tracking_hash(&self, for_crate_hash: bool, error_format: ErrorOutputType) -> u64 { + let mut sub_hashes = BTreeMap::new(); + $({ + hash_opt!($opt, + &self.$opt, + &mut sub_hashes, + for_crate_hash, + [$dep_tracking_marker]); + })* + let mut hasher = DefaultHasher::new(); + dep_tracking::stable_hash(sub_hashes, + &mut hasher, + error_format, + for_crate_hash + ); + hasher.finish() + } + } + + pub const $stat: OptionDescrs<$struct_name> = + &[ $( (stringify!($opt), $optmod::$opt, desc::$parse, $desc) ),* ]; + + mod $optmod { + $( + pub(super) fn $opt(cg: &mut super::$struct_name, v: Option<&str>) -> bool { + super::parse::$parse(&mut redirect_field!(cg.$opt), v) + } + )* + } + +) } + +impl CodegenOptions { + // JUSTIFICATION: defn of the suggested wrapper fn + #[allow(rustc::bad_opt_access)] + pub fn instrument_coverage(&self) -> InstrumentCoverage { + self.instrument_coverage + } +} + +// Sometimes different options need to build a common structure. +// That structure can be kept in one of the options' fields, the others become dummy. +macro_rules! redirect_field { + ($cg:ident.link_arg) => { + $cg.link_args + }; + ($cg:ident.pre_link_arg) => { + $cg.pre_link_args + }; + ($cg:ident.$field:ident) => { + $cg.$field + }; +} + +type OptionSetter<O> = fn(&mut O, v: Option<&str>) -> bool; +type OptionDescrs<O> = &'static [(&'static str, OptionSetter<O>, &'static str, &'static str)]; + +#[allow(rustc::untranslatable_diagnostic)] // FIXME: make this translatable +fn build_options<O: Default>( + early_dcx: &EarlyDiagCtxt, + matches: &getopts::Matches, + descrs: OptionDescrs<O>, + prefix: &str, + outputname: &str, +) -> O { + let mut op = O::default(); + for option in matches.opt_strs(prefix) { + let (key, value) = match option.split_once('=') { + None => (option, None), + Some((k, v)) => (k.to_string(), Some(v)), + }; + + let option_to_lookup = key.replace('-', "_"); + match descrs.iter().find(|(name, ..)| *name == option_to_lookup) { + Some((_, setter, type_desc, _)) => { + if !setter(&mut op, value) { + match value { + None => early_dcx.early_fatal( + format!( + "{outputname} option `{key}` requires {type_desc} ({prefix} {key}=<value>)" + ), + ), + Some(value) => early_dcx.early_fatal( + format!( + "incorrect value `{value}` for {outputname} option `{key}` - {type_desc} was expected" + ), + ), + } + } + } + None => early_dcx.early_fatal(format!("unknown {outputname} option: `{key}`")), + } + } + op +} + +#[allow(non_upper_case_globals)] +mod desc { + pub(crate) const parse_no_flag: &str = "no value"; + pub(crate) const parse_bool: &str = + "one of: `y`, `yes`, `on`, `true`, `n`, `no`, `off` or `false`"; + pub(crate) const parse_opt_bool: &str = parse_bool; + pub(crate) const parse_string: &str = "a string"; + pub(crate) const parse_opt_string: &str = parse_string; + pub(crate) const parse_string_push: &str = parse_string; + pub(crate) const parse_opt_langid: &str = "a language identifier"; + pub(crate) const parse_opt_pathbuf: &str = "a path"; + pub(crate) const parse_list: &str = "a space-separated list of strings"; + pub(crate) const parse_list_with_polarity: &str = + "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_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`"; + pub(crate) const parse_threads: &str = parse_number; + pub(crate) const parse_time_passes_format: &str = "`text` (default) or `json`"; + pub(crate) const parse_passes: &str = "a space-separated list of passes, or `all`"; + pub(crate) const parse_panic_strategy: &str = "either `unwind` or `abort`"; + pub(crate) const parse_on_broken_pipe: &str = "either `kill`, `error`, or `inherit`"; + pub(crate) const parse_patchable_function_entry: &str = "either two comma separated integers (total_nops,prefix_nops), with prefix_nops <= total_nops, or one integer (total_nops)"; + pub(crate) const parse_opt_panic_strategy: &str = parse_panic_strategy; + pub(crate) const parse_oom_strategy: &str = "either `panic` or `abort`"; + pub(crate) const parse_relro_level: &str = "one of: `full`, `partial`, or `off`"; + pub(crate) const parse_sanitizers: &str = "comma separated list of sanitizers: `address`, `cfi`, `dataflow`, `hwaddress`, `kcfi`, `kernel-address`, `leak`, `memory`, `memtag`, `safestack`, `shadow-call-stack`, or `thread`"; + pub(crate) const parse_sanitizer_memory_track_origins: &str = "0, 1, or 2"; + pub(crate) const parse_cfguard: &str = + "either a boolean (`yes`, `no`, `on`, `off`, etc), `checks`, or `nochecks`"; + pub(crate) const parse_cfprotection: &str = "`none`|`no`|`n` (default), `branch`, `return`, or `full`|`yes`|`y` (equivalent to `branch` and `return`)"; + pub(crate) const parse_debuginfo: &str = "either an integer (0, 1, 2), `none`, `line-directives-only`, `line-tables-only`, `limited`, or `full`"; + pub(crate) const parse_debuginfo_compression: &str = "one of `none`, `zlib`, or `zstd`"; + pub(crate) const parse_collapse_macro_debuginfo: &str = "one of `no`, `external`, or `yes`"; + pub(crate) const parse_strip: &str = "either `none`, `debuginfo`, or `symbols`"; + pub(crate) const parse_linker_flavor: &str = ::rustc_target::spec::LinkerFlavorCli::one_of(); + pub(crate) const parse_optimization_fuel: &str = "crate=integer"; + pub(crate) const parse_dump_mono_stats: &str = "`markdown` (default) or `json`"; + pub(crate) const parse_instrument_coverage: &str = parse_bool; + pub(crate) const parse_coverage_options: &str = + "`block` | `branch` | `condition` | `mcdc` | `no-mir-spans`"; + pub(crate) const parse_instrument_xray: &str = "either a boolean (`yes`, `no`, `on`, `off`, etc), or a comma separated list of settings: `always` or `never` (mutually exclusive), `ignore-loops`, `instruction-threshold=N`, `skip-entry`, `skip-exit`"; + pub(crate) const parse_unpretty: &str = "`string` or `string=string`"; + pub(crate) const parse_treat_err_as_bug: &str = "either no value or a non-negative number"; + pub(crate) const parse_next_solver_config: &str = + "either `globally` (when used without an argument), `coherence` (default) or `no`"; + pub(crate) const parse_lto: &str = + "either a boolean (`yes`, `no`, `on`, `off`, etc), `thin`, `fat`, or omitted"; + pub(crate) const parse_linker_plugin_lto: &str = + "either a boolean (`yes`, `no`, `on`, `off`, etc), or the path to the linker plugin"; + pub(crate) const parse_location_detail: &str = "either `none`, or a comma separated list of location details to track: `file`, `line`, or `column`"; + pub(crate) const parse_fmt_debug: &str = "either `full`, `shallow`, or `none`"; + pub(crate) const parse_switch_with_opt_path: &str = + "an optional path to the profiling data output directory"; + pub(crate) const parse_merge_functions: &str = + "one of: `disabled`, `trampolines`, or `aliases`"; + pub(crate) const parse_symbol_mangling_version: &str = + "one of: `legacy`, `v0` (RFC 2603), or `hashed`"; + pub(crate) const parse_opt_symbol_visibility: &str = + "one of: `hidden`, `protected`, or `interposable`"; + pub(crate) const parse_cargo_src_file_hash: &str = + "one of `blake3`, `md5`, `sha1`, or `sha256`"; + pub(crate) const parse_src_file_hash: &str = "one of `md5`, `sha1`, or `sha256`"; + pub(crate) const parse_relocation_model: &str = + "one of supported relocation models (`rustc --print relocation-models`)"; + pub(crate) const parse_code_model: &str = + "one of supported code models (`rustc --print code-models`)"; + pub(crate) const parse_tls_model: &str = + "one of supported TLS models (`rustc --print tls-models`)"; + pub(crate) const parse_target_feature: &str = parse_string; + pub(crate) const parse_terminal_url: &str = + "either a boolean (`yes`, `no`, `on`, `off`, etc), or `auto`"; + pub(crate) const parse_wasi_exec_model: &str = "either `command` or `reactor`"; + pub(crate) const parse_split_debuginfo: &str = + "one of supported split-debuginfo modes (`off`, `packed`, or `unpacked`)"; + pub(crate) const parse_split_dwarf_kind: &str = + "one of supported split dwarf modes (`split` or `single`)"; + pub(crate) const parse_link_self_contained: &str = "one of: `y`, `yes`, `on`, `n`, `no`, `off`, or a list of enabled (`+` prefix) and disabled (`-` prefix) \ + components: `crto`, `libc`, `unwind`, `linker`, `sanitizers`, `mingw`"; + pub(crate) const parse_linker_features: &str = + "a list of enabled (`+` prefix) and disabled (`-` prefix) features: `lld`"; + pub(crate) const parse_polonius: &str = "either no value or `legacy` (the default), or `next`"; + pub(crate) const parse_stack_protector: &str = + "one of (`none` (default), `basic`, `strong`, or `all`)"; + pub(crate) const parse_branch_protection: &str = "a `,` separated combination of `bti`, `pac-ret`, followed by a combination of `pc`, `b-key`, or `leaf`"; + pub(crate) const parse_proc_macro_execution_strategy: &str = + "one of supported execution strategies (`same-thread`, or `cross-thread`)"; + pub(crate) const parse_remap_path_scope: &str = + "comma separated list of scopes: `macro`, `diagnostics`, `debuginfo`, `object`, `all`"; + pub(crate) const parse_inlining_threshold: &str = + "either a boolean (`yes`, `no`, `on`, `off`, etc), or a non-negative number"; + pub(crate) const parse_llvm_module_flag: &str = "<key>:<type>:<value>:<behavior>. Type must currently be `u32`. Behavior should be one of (`error`, `warning`, `require`, `override`, `append`, `appendunique`, `max`, `min`)"; + pub(crate) const parse_function_return: &str = "`keep` or `thunk-extern`"; + pub(crate) const parse_wasm_c_abi: &str = "`legacy` or `spec`"; + pub(crate) const parse_mir_include_spans: &str = + "either a boolean (`yes`, `no`, `on`, `off`, etc), or `nll` (default: `nll`)"; +} + +pub mod parse { + use std::str::FromStr; + + pub(crate) use super::*; + pub(crate) const MAX_THREADS_CAP: usize = 256; + + /// This is for boolean options that don't take a value and start with + /// `no-`. This style of option is deprecated. + pub(crate) fn parse_no_flag(slot: &mut bool, v: Option<&str>) -> bool { + match v { + None => { + *slot = true; + true + } + Some(_) => false, + } + } + + /// Use this for any boolean option that has a static default. + pub(crate) fn parse_bool(slot: &mut bool, v: Option<&str>) -> bool { + match v { + Some("y") | Some("yes") | Some("on") | Some("true") | None => { + *slot = true; + true + } + Some("n") | Some("no") | Some("off") | Some("false") => { + *slot = false; + true + } + _ => false, + } + } + + /// Use this for any boolean option that lacks a static default. (The + /// actions taken when such an option is not specified will depend on + /// other factors, such as other options, or target options.) + pub(crate) fn parse_opt_bool(slot: &mut Option<bool>, v: Option<&str>) -> bool { + match v { + Some("y") | Some("yes") | Some("on") | Some("true") | None => { + *slot = Some(true); + true + } + Some("n") | Some("no") | Some("off") | Some("false") => { + *slot = Some(false); + true + } + _ => false, + } + } + + /// Parses whether polonius is enabled, and if so, which version. + pub(crate) fn parse_polonius(slot: &mut Polonius, v: Option<&str>) -> bool { + match v { + Some("legacy") | None => { + *slot = Polonius::Legacy; + true + } + Some("next") => { + *slot = Polonius::Next; + true + } + _ => false, + } + } + + /// Use this for any string option that has a static default. + pub(crate) fn parse_string(slot: &mut String, v: Option<&str>) -> bool { + match v { + Some(s) => { + *slot = s.to_string(); + true + } + None => false, + } + } + + /// Use this for any string option that lacks a static default. + pub(crate) fn parse_opt_string(slot: &mut Option<String>, v: Option<&str>) -> bool { + match v { + Some(s) => { + *slot = Some(s.to_string()); + true + } + None => false, + } + } + + /// Parse an optional language identifier, e.g. `en-US` or `zh-CN`. + pub(crate) fn parse_opt_langid(slot: &mut Option<LanguageIdentifier>, v: Option<&str>) -> bool { + match v { + Some(s) => { + *slot = rustc_errors::LanguageIdentifier::from_str(s).ok(); + true + } + None => false, + } + } + + pub(crate) fn parse_opt_pathbuf(slot: &mut Option<PathBuf>, v: Option<&str>) -> bool { + match v { + Some(s) => { + *slot = Some(PathBuf::from(s)); + true + } + None => false, + } + } + + pub(crate) fn parse_string_push(slot: &mut Vec<String>, v: Option<&str>) -> bool { + match v { + Some(s) => { + slot.push(s.to_string()); + true + } + None => false, + } + } + + pub(crate) fn parse_list(slot: &mut Vec<String>, v: Option<&str>) -> bool { + match v { + Some(s) => { + slot.extend(s.split_whitespace().map(|s| s.to_string())); + true + } + None => false, + } + } + + pub(crate) fn parse_list_with_polarity( + slot: &mut Vec<(String, bool)>, + v: Option<&str>, + ) -> bool { + match v { + Some(s) => { + for s in s.split(',') { + let Some(pass_name) = s.strip_prefix(&['+', '-'][..]) else { return false }; + slot.push((pass_name.to_string(), &s[..1] == "+")); + } + true + } + None => false, + } + } + + pub(crate) fn parse_fmt_debug(opt: &mut FmtDebug, v: Option<&str>) -> bool { + *opt = match v { + Some("full") => FmtDebug::Full, + Some("shallow") => FmtDebug::Shallow, + Some("none") => FmtDebug::None, + _ => return false, + }; + true + } + + pub(crate) fn parse_location_detail(ld: &mut LocationDetail, v: Option<&str>) -> bool { + if let Some(v) = v { + ld.line = false; + ld.file = false; + ld.column = false; + if v == "none" { + return true; + } + for s in v.split(',') { + match s { + "file" => ld.file = true, + "line" => ld.line = true, + "column" => ld.column = true, + _ => return false, + } + } + true + } else { + false + } + } + + pub(crate) fn parse_comma_list(slot: &mut Vec<String>, v: Option<&str>) -> bool { + match v { + Some(s) => { + let mut v: Vec<_> = s.split(',').map(|s| s.to_string()).collect(); + v.sort_unstable(); + *slot = v; + true + } + None => false, + } + } + + pub(crate) fn parse_opt_comma_list(slot: &mut Option<Vec<String>>, v: Option<&str>) -> bool { + match v { + Some(s) => { + let mut v: Vec<_> = s.split(',').map(|s| s.to_string()).collect(); + v.sort_unstable(); + *slot = Some(v); + true + } + None => false, + } + } + + pub(crate) fn parse_threads(slot: &mut usize, v: Option<&str>) -> bool { + let ret = match v.and_then(|s| s.parse().ok()) { + Some(0) => { + *slot = std::thread::available_parallelism().map_or(1, NonZero::<usize>::get); + true + } + Some(i) => { + *slot = i; + true + } + None => false, + }; + // We want to cap the number of threads here to avoid large numbers like 999999 and compiler panics. + // This solution was suggested here https://github.com/rust-lang/rust/issues/117638#issuecomment-1800925067 + *slot = slot.clone().min(MAX_THREADS_CAP); + ret + } + + /// Use this for any numeric option that has a static default. + pub(crate) fn parse_number<T: Copy + FromStr>(slot: &mut T, v: Option<&str>) -> bool { + match v.and_then(|s| s.parse().ok()) { + Some(i) => { + *slot = i; + true + } + None => false, + } + } + + /// Use this for any numeric option that lacks a static default. + pub(crate) fn parse_opt_number<T: Copy + FromStr>( + slot: &mut Option<T>, + v: Option<&str>, + ) -> bool { + match v { + Some(s) => { + *slot = s.parse().ok(); + slot.is_some() + } + None => false, + } + } + + pub(crate) fn parse_frame_pointer(slot: &mut FramePointer, v: Option<&str>) -> bool { + let mut yes = false; + match v { + _ if parse_bool(&mut yes, v) && yes => slot.ratchet(FramePointer::Always), + _ if parse_bool(&mut yes, v) => slot.ratchet(FramePointer::MayOmit), + Some("always") => slot.ratchet(FramePointer::Always), + Some("non-leaf") => slot.ratchet(FramePointer::NonLeaf), + _ => return false, + }; + true + } + + pub(crate) fn parse_passes(slot: &mut Passes, v: Option<&str>) -> bool { + match v { + Some("all") => { + *slot = Passes::All; + true + } + v => { + let mut passes = vec![]; + if parse_list(&mut passes, v) { + slot.extend(passes); + true + } else { + false + } + } + } + } + + pub(crate) fn parse_opt_panic_strategy( + slot: &mut Option<PanicStrategy>, + v: Option<&str>, + ) -> bool { + match v { + Some("unwind") => *slot = Some(PanicStrategy::Unwind), + Some("abort") => *slot = Some(PanicStrategy::Abort), + _ => return false, + } + true + } + + pub(crate) fn parse_panic_strategy(slot: &mut PanicStrategy, v: Option<&str>) -> bool { + match v { + Some("unwind") => *slot = PanicStrategy::Unwind, + Some("abort") => *slot = PanicStrategy::Abort, + _ => return false, + } + true + } + + pub(crate) fn parse_on_broken_pipe(slot: &mut OnBrokenPipe, v: Option<&str>) -> bool { + match v { + // OnBrokenPipe::Default can't be explicitly specified + Some("kill") => *slot = OnBrokenPipe::Kill, + Some("error") => *slot = OnBrokenPipe::Error, + Some("inherit") => *slot = OnBrokenPipe::Inherit, + _ => return false, + } + true + } + + pub(crate) fn parse_patchable_function_entry( + slot: &mut PatchableFunctionEntry, + v: Option<&str>, + ) -> bool { + let mut total_nops = 0; + let mut prefix_nops = 0; + + if !parse_number(&mut total_nops, v) { + let parts = v.and_then(|v| v.split_once(',')).unzip(); + if !parse_number(&mut total_nops, parts.0) { + return false; + } + if !parse_number(&mut prefix_nops, parts.1) { + return false; + } + } + + if let Some(pfe) = + PatchableFunctionEntry::from_total_and_prefix_nops(total_nops, prefix_nops) + { + *slot = pfe; + return true; + } + false + } + + pub(crate) fn parse_oom_strategy(slot: &mut OomStrategy, v: Option<&str>) -> bool { + match v { + Some("panic") => *slot = OomStrategy::Panic, + Some("abort") => *slot = OomStrategy::Abort, + _ => return false, + } + true + } + + pub(crate) fn parse_relro_level(slot: &mut Option<RelroLevel>, v: Option<&str>) -> bool { + match v { + Some(s) => match s.parse::<RelroLevel>() { + Ok(level) => *slot = Some(level), + _ => return false, + }, + _ => return false, + } + true + } + + pub(crate) fn parse_sanitizers(slot: &mut SanitizerSet, v: Option<&str>) -> bool { + if let Some(v) = v { + for s in v.split(',') { + *slot |= match s { + "address" => SanitizerSet::ADDRESS, + "cfi" => SanitizerSet::CFI, + "dataflow" => SanitizerSet::DATAFLOW, + "kcfi" => SanitizerSet::KCFI, + "kernel-address" => SanitizerSet::KERNELADDRESS, + "leak" => SanitizerSet::LEAK, + "memory" => SanitizerSet::MEMORY, + "memtag" => SanitizerSet::MEMTAG, + "shadow-call-stack" => SanitizerSet::SHADOWCALLSTACK, + "thread" => SanitizerSet::THREAD, + "hwaddress" => SanitizerSet::HWADDRESS, + "safestack" => SanitizerSet::SAFESTACK, + _ => return false, + } + } + true + } else { + false + } + } + + pub(crate) fn parse_sanitizer_memory_track_origins(slot: &mut usize, v: Option<&str>) -> bool { + match v { + Some("2") | None => { + *slot = 2; + true + } + Some("1") => { + *slot = 1; + true + } + Some("0") => { + *slot = 0; + true + } + Some(_) => false, + } + } + + pub(crate) fn parse_strip(slot: &mut Strip, v: Option<&str>) -> bool { + match v { + Some("none") => *slot = Strip::None, + Some("debuginfo") => *slot = Strip::Debuginfo, + Some("symbols") => *slot = Strip::Symbols, + _ => return false, + } + true + } + + pub(crate) fn parse_cfguard(slot: &mut CFGuard, v: Option<&str>) -> bool { + if v.is_some() { + let mut bool_arg = None; + if parse_opt_bool(&mut bool_arg, v) { + *slot = if bool_arg.unwrap() { CFGuard::Checks } else { CFGuard::Disabled }; + return true; + } + } + + *slot = match v { + None => CFGuard::Checks, + Some("checks") => CFGuard::Checks, + Some("nochecks") => CFGuard::NoChecks, + Some(_) => return false, + }; + true + } + + pub(crate) fn parse_cfprotection(slot: &mut CFProtection, v: Option<&str>) -> bool { + if v.is_some() { + let mut bool_arg = None; + if parse_opt_bool(&mut bool_arg, v) { + *slot = if bool_arg.unwrap() { CFProtection::Full } else { CFProtection::None }; + return true; + } + } + + *slot = match v { + None | Some("none") => CFProtection::None, + Some("branch") => CFProtection::Branch, + Some("return") => CFProtection::Return, + Some("full") => CFProtection::Full, + Some(_) => return false, + }; + true + } + + pub(crate) fn parse_debuginfo(slot: &mut DebugInfo, v: Option<&str>) -> bool { + match v { + Some("0") | Some("none") => *slot = DebugInfo::None, + Some("line-directives-only") => *slot = DebugInfo::LineDirectivesOnly, + Some("line-tables-only") => *slot = DebugInfo::LineTablesOnly, + Some("1") | Some("limited") => *slot = DebugInfo::Limited, + Some("2") | Some("full") => *slot = DebugInfo::Full, + _ => return false, + } + true + } + + pub(crate) fn parse_debuginfo_compression( + slot: &mut DebugInfoCompression, + v: Option<&str>, + ) -> bool { + match v { + Some("none") => *slot = DebugInfoCompression::None, + Some("zlib") => *slot = DebugInfoCompression::Zlib, + Some("zstd") => *slot = DebugInfoCompression::Zstd, + _ => return false, + }; + true + } + + pub(crate) fn parse_linker_flavor(slot: &mut Option<LinkerFlavorCli>, v: Option<&str>) -> bool { + match v.and_then(LinkerFlavorCli::from_str) { + Some(lf) => *slot = Some(lf), + _ => return false, + } + true + } + + pub(crate) fn parse_opt_symbol_visibility( + slot: &mut Option<SymbolVisibility>, + v: Option<&str>, + ) -> bool { + if let Some(v) = v { + if let Ok(vis) = SymbolVisibility::from_str(v) { + *slot = Some(vis); + } else { + return false; + } + } + true + } + + pub(crate) fn parse_optimization_fuel( + slot: &mut Option<(String, u64)>, + v: Option<&str>, + ) -> bool { + match v { + None => false, + Some(s) => { + let [crate_name, fuel] = *s.split('=').collect::<Vec<_>>() else { return false }; + let Ok(fuel) = fuel.parse::<u64>() else { return false }; + *slot = Some((crate_name.to_string(), fuel)); + true + } + } + } + + pub(crate) fn parse_unpretty(slot: &mut Option<String>, v: Option<&str>) -> bool { + match v { + None => false, + Some(s) if s.split('=').count() <= 2 => { + *slot = Some(s.to_string()); + true + } + _ => false, + } + } + + pub(crate) fn parse_time_passes_format(slot: &mut TimePassesFormat, v: Option<&str>) -> bool { + match v { + None => true, + Some("json") => { + *slot = TimePassesFormat::Json; + true + } + Some("text") => { + *slot = TimePassesFormat::Text; + true + } + Some(_) => false, + } + } + + pub(crate) fn parse_dump_mono_stats(slot: &mut DumpMonoStatsFormat, v: Option<&str>) -> bool { + match v { + None => true, + Some("json") => { + *slot = DumpMonoStatsFormat::Json; + true + } + Some("markdown") => { + *slot = DumpMonoStatsFormat::Markdown; + true + } + Some(_) => false, + } + } + + pub(crate) fn parse_instrument_coverage( + slot: &mut InstrumentCoverage, + v: Option<&str>, + ) -> bool { + if v.is_some() { + let mut bool_arg = false; + if parse_bool(&mut bool_arg, v) { + *slot = if bool_arg { InstrumentCoverage::Yes } else { InstrumentCoverage::No }; + return true; + } + } + + let Some(v) = v else { + *slot = InstrumentCoverage::Yes; + return true; + }; + + // Parse values that have historically been accepted by stable compilers, + // even though they're currently just aliases for boolean values. + *slot = match v { + "all" => InstrumentCoverage::Yes, + "0" => InstrumentCoverage::No, + _ => return false, + }; + true + } + + pub(crate) fn parse_coverage_options(slot: &mut CoverageOptions, v: Option<&str>) -> bool { + let Some(v) = v else { return true }; + + for option in v.split(',') { + match option { + "block" => slot.level = CoverageLevel::Block, + "branch" => slot.level = CoverageLevel::Branch, + "condition" => slot.level = CoverageLevel::Condition, + "mcdc" => slot.level = CoverageLevel::Mcdc, + "no-mir-spans" => slot.no_mir_spans = true, + _ => return false, + } + } + true + } + + pub(crate) fn parse_instrument_xray( + slot: &mut Option<InstrumentXRay>, + v: Option<&str>, + ) -> bool { + if v.is_some() { + let mut bool_arg = None; + if parse_opt_bool(&mut bool_arg, v) { + *slot = if bool_arg.unwrap() { Some(InstrumentXRay::default()) } else { None }; + return true; + } + } + + let options = slot.get_or_insert_default(); + let mut seen_always = false; + let mut seen_never = false; + let mut seen_ignore_loops = false; + let mut seen_instruction_threshold = false; + let mut seen_skip_entry = false; + let mut seen_skip_exit = false; + for option in v.into_iter().flat_map(|v| v.split(',')) { + match option { + "always" if !seen_always && !seen_never => { + options.always = true; + options.never = false; + seen_always = true; + } + "never" if !seen_never && !seen_always => { + options.never = true; + options.always = false; + seen_never = true; + } + "ignore-loops" if !seen_ignore_loops => { + options.ignore_loops = true; + seen_ignore_loops = true; + } + option + if option.starts_with("instruction-threshold") + && !seen_instruction_threshold => + { + let Some(("instruction-threshold", n)) = option.split_once('=') else { + return false; + }; + match n.parse() { + Ok(n) => options.instruction_threshold = Some(n), + Err(_) => return false, + } + seen_instruction_threshold = true; + } + "skip-entry" if !seen_skip_entry => { + options.skip_entry = true; + seen_skip_entry = true; + } + "skip-exit" if !seen_skip_exit => { + options.skip_exit = true; + seen_skip_exit = true; + } + _ => return false, + } + } + true + } + + pub(crate) fn parse_treat_err_as_bug( + slot: &mut Option<NonZero<usize>>, + v: Option<&str>, + ) -> bool { + match v { + Some(s) => match s.parse() { + Ok(val) => { + *slot = Some(val); + true + } + Err(e) => { + *slot = None; + e.kind() == &IntErrorKind::Zero + } + }, + None => { + *slot = NonZero::new(1); + true + } + } + } + + pub(crate) fn parse_next_solver_config(slot: &mut NextSolverConfig, v: Option<&str>) -> bool { + if let Some(config) = v { + *slot = match config { + "no" => NextSolverConfig { coherence: false, globally: false }, + "coherence" => NextSolverConfig { coherence: true, globally: false }, + "globally" => NextSolverConfig { coherence: true, globally: true }, + _ => return false, + }; + } else { + *slot = NextSolverConfig { coherence: true, globally: true }; + } + + true + } + + pub(crate) fn parse_lto(slot: &mut LtoCli, v: Option<&str>) -> bool { + if v.is_some() { + let mut bool_arg = None; + if parse_opt_bool(&mut bool_arg, v) { + *slot = if bool_arg.unwrap() { LtoCli::Yes } else { LtoCli::No }; + return true; + } + } + + *slot = match v { + None => LtoCli::NoParam, + Some("thin") => LtoCli::Thin, + Some("fat") => LtoCli::Fat, + Some(_) => return false, + }; + true + } + + pub(crate) fn parse_linker_plugin_lto(slot: &mut LinkerPluginLto, v: Option<&str>) -> bool { + if v.is_some() { + let mut bool_arg = None; + if parse_opt_bool(&mut bool_arg, v) { + *slot = if bool_arg.unwrap() { + LinkerPluginLto::LinkerPluginAuto + } else { + LinkerPluginLto::Disabled + }; + return true; + } + } + + *slot = match v { + None => LinkerPluginLto::LinkerPluginAuto, + Some(path) => LinkerPluginLto::LinkerPlugin(PathBuf::from(path)), + }; + true + } + + pub(crate) fn parse_switch_with_opt_path( + slot: &mut SwitchWithOptPath, + v: Option<&str>, + ) -> bool { + *slot = match v { + None => SwitchWithOptPath::Enabled(None), + Some(path) => SwitchWithOptPath::Enabled(Some(PathBuf::from(path))), + }; + true + } + + pub(crate) fn parse_merge_functions( + slot: &mut Option<MergeFunctions>, + v: Option<&str>, + ) -> bool { + match v.and_then(|s| MergeFunctions::from_str(s).ok()) { + Some(mergefunc) => *slot = Some(mergefunc), + _ => return false, + } + true + } + + pub(crate) fn parse_remap_path_scope( + slot: &mut RemapPathScopeComponents, + v: Option<&str>, + ) -> bool { + if let Some(v) = v { + *slot = RemapPathScopeComponents::empty(); + for s in v.split(',') { + *slot |= match s { + "macro" => RemapPathScopeComponents::MACRO, + "diagnostics" => RemapPathScopeComponents::DIAGNOSTICS, + "debuginfo" => RemapPathScopeComponents::DEBUGINFO, + "object" => RemapPathScopeComponents::OBJECT, + "all" => RemapPathScopeComponents::all(), + _ => return false, + } + } + true + } else { + false + } + } + + pub(crate) fn parse_relocation_model(slot: &mut Option<RelocModel>, v: Option<&str>) -> bool { + match v.and_then(|s| RelocModel::from_str(s).ok()) { + Some(relocation_model) => *slot = Some(relocation_model), + None if v == Some("default") => *slot = None, + _ => return false, + } + true + } + + pub(crate) fn parse_code_model(slot: &mut Option<CodeModel>, v: Option<&str>) -> bool { + match v.and_then(|s| CodeModel::from_str(s).ok()) { + Some(code_model) => *slot = Some(code_model), + _ => return false, + } + true + } + + pub(crate) fn parse_tls_model(slot: &mut Option<TlsModel>, v: Option<&str>) -> bool { + match v.and_then(|s| TlsModel::from_str(s).ok()) { + Some(tls_model) => *slot = Some(tls_model), + _ => return false, + } + true + } + + pub(crate) fn parse_terminal_url(slot: &mut TerminalUrl, v: Option<&str>) -> bool { + *slot = match v { + Some("on" | "" | "yes" | "y") | None => TerminalUrl::Yes, + Some("off" | "no" | "n") => TerminalUrl::No, + Some("auto") => TerminalUrl::Auto, + _ => return false, + }; + true + } + + pub(crate) fn parse_symbol_mangling_version( + slot: &mut Option<SymbolManglingVersion>, + v: Option<&str>, + ) -> bool { + *slot = match v { + Some("legacy") => Some(SymbolManglingVersion::Legacy), + Some("v0") => Some(SymbolManglingVersion::V0), + Some("hashed") => Some(SymbolManglingVersion::Hashed), + _ => return false, + }; + true + } + + pub(crate) fn parse_src_file_hash( + slot: &mut Option<SourceFileHashAlgorithm>, + v: Option<&str>, + ) -> bool { + match v.and_then(|s| SourceFileHashAlgorithm::from_str(s).ok()) { + Some(hash_kind) => *slot = Some(hash_kind), + _ => return false, + } + true + } + + pub(crate) fn parse_cargo_src_file_hash( + slot: &mut Option<SourceFileHashAlgorithm>, + v: Option<&str>, + ) -> bool { + match v.and_then(|s| SourceFileHashAlgorithm::from_str(s).ok()) { + Some(hash_kind) => { + *slot = Some(hash_kind); + } + _ => return false, + } + true + } + + pub(crate) fn parse_target_feature(slot: &mut String, v: Option<&str>) -> bool { + match v { + Some(s) => { + if !slot.is_empty() { + slot.push(','); + } + slot.push_str(s); + true + } + None => false, + } + } + + pub(crate) fn parse_link_self_contained(slot: &mut LinkSelfContained, v: Option<&str>) -> bool { + // Whenever `-C link-self-contained` is passed without a value, it's an opt-in + // just like `parse_opt_bool`, the historical value of this flag. + // + // 1. Parse historical single bool values + let s = v.unwrap_or("y"); + match s { + "y" | "yes" | "on" => { + slot.set_all_explicitly(true); + return true; + } + "n" | "no" | "off" => { + slot.set_all_explicitly(false); + return true; + } + _ => {} + } + + // 2. Parse a list of enabled and disabled components. + for comp in s.split(',') { + if slot.handle_cli_component(comp).is_none() { + return false; + } + } + + true + } + + /// Parse a comma-separated list of enabled and disabled linker features. + pub(crate) fn parse_linker_features(slot: &mut LinkerFeaturesCli, v: Option<&str>) -> bool { + match v { + Some(s) => { + for feature in s.split(',') { + if slot.handle_cli_feature(feature).is_none() { + return false; + } + } + + true + } + None => false, + } + } + + pub(crate) fn parse_wasi_exec_model(slot: &mut Option<WasiExecModel>, v: Option<&str>) -> bool { + match v { + Some("command") => *slot = Some(WasiExecModel::Command), + Some("reactor") => *slot = Some(WasiExecModel::Reactor), + _ => return false, + } + true + } + + pub(crate) fn parse_split_debuginfo( + slot: &mut Option<SplitDebuginfo>, + v: Option<&str>, + ) -> bool { + match v.and_then(|s| SplitDebuginfo::from_str(s).ok()) { + Some(e) => *slot = Some(e), + _ => return false, + } + true + } + + pub(crate) fn parse_split_dwarf_kind(slot: &mut SplitDwarfKind, v: Option<&str>) -> bool { + match v.and_then(|s| SplitDwarfKind::from_str(s).ok()) { + Some(e) => *slot = e, + _ => return false, + } + true + } + + pub(crate) fn parse_stack_protector(slot: &mut StackProtector, v: Option<&str>) -> bool { + match v.and_then(|s| StackProtector::from_str(s).ok()) { + Some(ssp) => *slot = ssp, + _ => return false, + } + true + } + + pub(crate) fn parse_branch_protection( + slot: &mut Option<BranchProtection>, + v: Option<&str>, + ) -> bool { + match v { + Some(s) => { + let slot = slot.get_or_insert_default(); + for opt in s.split(',') { + match opt { + "bti" => slot.bti = true, + "pac-ret" if slot.pac_ret.is_none() => { + slot.pac_ret = Some(PacRet { leaf: false, pc: false, key: PAuthKey::A }) + } + "leaf" => match slot.pac_ret.as_mut() { + Some(pac) => pac.leaf = true, + _ => return false, + }, + "b-key" => match slot.pac_ret.as_mut() { + Some(pac) => pac.key = PAuthKey::B, + _ => return false, + }, + "pc" => match slot.pac_ret.as_mut() { + Some(pac) => pac.pc = true, + _ => return false, + }, + _ => return false, + }; + } + } + _ => return false, + } + true + } + + pub(crate) fn parse_collapse_macro_debuginfo( + slot: &mut CollapseMacroDebuginfo, + v: Option<&str>, + ) -> bool { + if v.is_some() { + let mut bool_arg = None; + if parse_opt_bool(&mut bool_arg, v) { + *slot = if bool_arg.unwrap() { + CollapseMacroDebuginfo::Yes + } else { + CollapseMacroDebuginfo::No + }; + return true; + } + } + + *slot = match v { + Some("external") => CollapseMacroDebuginfo::External, + _ => return false, + }; + true + } + + pub(crate) fn parse_proc_macro_execution_strategy( + slot: &mut ProcMacroExecutionStrategy, + v: Option<&str>, + ) -> bool { + *slot = match v { + Some("same-thread") => ProcMacroExecutionStrategy::SameThread, + Some("cross-thread") => ProcMacroExecutionStrategy::CrossThread, + _ => return false, + }; + true + } + + pub(crate) fn parse_inlining_threshold(slot: &mut InliningThreshold, v: Option<&str>) -> bool { + match v { + Some("always" | "yes") => { + *slot = InliningThreshold::Always; + } + Some("never") => { + *slot = InliningThreshold::Never; + } + Some(v) => { + if let Ok(threshold) = v.parse() { + *slot = InliningThreshold::Sometimes(threshold); + } else { + return false; + } + } + None => return false, + } + true + } + + pub(crate) fn parse_llvm_module_flag( + slot: &mut Vec<(String, u32, String)>, + v: Option<&str>, + ) -> bool { + let elements = v.unwrap_or_default().split(':').collect::<Vec<_>>(); + let [key, md_type, value, behavior] = elements.as_slice() else { + return false; + }; + if *md_type != "u32" { + // Currently we only support u32 metadata flags, but require the + // type for forward-compatibility. + return false; + } + let Ok(value) = value.parse::<u32>() else { + return false; + }; + let behavior = behavior.to_lowercase(); + let all_behaviors = + ["error", "warning", "require", "override", "append", "appendunique", "max", "min"]; + if !all_behaviors.contains(&behavior.as_str()) { + return false; + } + + slot.push((key.to_string(), value, behavior)); + true + } + + pub(crate) fn parse_function_return(slot: &mut FunctionReturn, v: Option<&str>) -> bool { + match v { + Some("keep") => *slot = FunctionReturn::Keep, + Some("thunk-extern") => *slot = FunctionReturn::ThunkExtern, + _ => return false, + } + true + } + + pub(crate) fn parse_wasm_c_abi(slot: &mut WasmCAbi, v: Option<&str>) -> bool { + match v { + Some("spec") => *slot = WasmCAbi::Spec, + Some("legacy") => *slot = WasmCAbi::Legacy, + _ => return false, + } + true + } + + pub(crate) fn parse_mir_include_spans(slot: &mut MirIncludeSpans, v: Option<&str>) -> bool { + *slot = match v { + Some("on" | "yes" | "y" | "true") | None => MirIncludeSpans::On, + Some("off" | "no" | "n" | "false") => MirIncludeSpans::Off, + Some("nll") => MirIncludeSpans::Nll, + _ => return false, + }; + + true + } +} + +options! { + CodegenOptions, CG_OPTIONS, cgopts, "C", "codegen", + + // If you add a new option, please update: + // - compiler/rustc_interface/src/tests.rs + // - src/doc/rustc/src/codegen-options/index.md + + // tidy-alphabetical-start + #[rustc_lint_opt_deny_field_access("documented to do nothing")] + ar: String = (String::new(), parse_string, [UNTRACKED], + "this option is deprecated and does nothing"), + #[rustc_lint_opt_deny_field_access("use `Session::code_model` instead of this field")] + code_model: Option<CodeModel> = (None, parse_code_model, [TRACKED], + "choose the code model to use (`rustc --print code-models` for details)"), + codegen_units: Option<usize> = (None, parse_opt_number, [UNTRACKED], + "divide crate into N units to optimize in parallel"), + collapse_macro_debuginfo: CollapseMacroDebuginfo = (CollapseMacroDebuginfo::Unspecified, + parse_collapse_macro_debuginfo, [TRACKED], + "set option to collapse debuginfo for macros"), + control_flow_guard: CFGuard = (CFGuard::Disabled, parse_cfguard, [TRACKED], + "use Windows Control Flow Guard (default: no)"), + debug_assertions: Option<bool> = (None, parse_opt_bool, [TRACKED], + "explicitly enable the `cfg(debug_assertions)` directive"), + debuginfo: DebugInfo = (DebugInfo::None, parse_debuginfo, [TRACKED], + "debug info emission level (0-2, none, line-directives-only, \ + line-tables-only, limited, or full; default: 0)"), + default_linker_libraries: bool = (false, parse_bool, [UNTRACKED], + "allow the linker to link its default libraries (default: no)"), + dlltool: Option<PathBuf> = (None, parse_opt_pathbuf, [UNTRACKED], + "import library generation tool (ignored except when targeting windows-gnu)"), + embed_bitcode: bool = (true, parse_bool, [TRACKED], + "emit bitcode in rlibs (default: yes)"), + extra_filename: String = (String::new(), parse_string, [UNTRACKED], + "extra data to put in each output filename"), + force_frame_pointers: FramePointer = (FramePointer::MayOmit, parse_frame_pointer, [TRACKED], + "force use of the frame pointers"), + #[rustc_lint_opt_deny_field_access("use `Session::must_emit_unwind_tables` instead of this field")] + force_unwind_tables: Option<bool> = (None, parse_opt_bool, [TRACKED], + "force use of unwind tables"), + incremental: Option<String> = (None, parse_opt_string, [UNTRACKED], + "enable incremental compilation"), + #[rustc_lint_opt_deny_field_access("documented to do nothing")] + inline_threshold: Option<u32> = (None, parse_opt_number, [TRACKED], + "this option is deprecated and does nothing \ + (consider using `-Cllvm-args=--inline-threshold=...`)"), + #[rustc_lint_opt_deny_field_access("use `Session::instrument_coverage` instead of this field")] + instrument_coverage: InstrumentCoverage = (InstrumentCoverage::No, parse_instrument_coverage, [TRACKED], + "instrument the generated code to support LLVM source-based code coverage reports \ + (note, the compiler build config must include `profiler = true`); \ + implies `-C symbol-mangling-version=v0`"), + link_arg: (/* redirected to link_args */) = ((), parse_string_push, [UNTRACKED], + "a single extra argument to append to the linker invocation (can be used several times)"), + link_args: Vec<String> = (Vec::new(), parse_list, [UNTRACKED], + "extra arguments to append to the linker invocation (space separated)"), + #[rustc_lint_opt_deny_field_access("use `Session::link_dead_code` instead of this field")] + link_dead_code: Option<bool> = (None, parse_opt_bool, [TRACKED], + "keep dead code at link time (useful for code coverage) (default: no)"), + link_self_contained: LinkSelfContained = (LinkSelfContained::default(), parse_link_self_contained, [UNTRACKED], + "control whether to link Rust provided C objects/libraries or rely \ + on a C toolchain or linker installed in the system"), + linker: Option<PathBuf> = (None, parse_opt_pathbuf, [UNTRACKED], + "system linker to link outputs with"), + linker_flavor: Option<LinkerFlavorCli> = (None, parse_linker_flavor, [UNTRACKED], + "linker flavor"), + linker_plugin_lto: LinkerPluginLto = (LinkerPluginLto::Disabled, + parse_linker_plugin_lto, [TRACKED], + "generate build artifacts that are compatible with linker-based LTO"), + llvm_args: Vec<String> = (Vec::new(), parse_list, [TRACKED], + "a list of arguments to pass to LLVM (space separated)"), + #[rustc_lint_opt_deny_field_access("use `Session::lto` instead of this field")] + lto: LtoCli = (LtoCli::Unspecified, parse_lto, [TRACKED], + "perform LLVM link-time optimizations"), + metadata: Vec<String> = (Vec::new(), parse_list, [TRACKED], + "metadata to mangle symbol names with"), + no_prepopulate_passes: bool = (false, parse_no_flag, [TRACKED], + "give an empty list of passes to the pass manager"), + no_redzone: Option<bool> = (None, parse_opt_bool, [TRACKED], + "disable the use of the redzone"), + #[rustc_lint_opt_deny_field_access("documented to do nothing")] + no_stack_check: bool = (false, parse_no_flag, [UNTRACKED], + "this option is deprecated and does nothing"), + no_vectorize_loops: bool = (false, parse_no_flag, [TRACKED], + "disable loop vectorization optimization passes"), + no_vectorize_slp: bool = (false, parse_no_flag, [TRACKED], + "disable LLVM's SLP vectorization pass"), + opt_level: String = ("0".to_string(), parse_string, [TRACKED], + "optimization level (0-3, s, or z; default: 0)"), + #[rustc_lint_opt_deny_field_access("use `Session::overflow_checks` instead of this field")] + overflow_checks: Option<bool> = (None, parse_opt_bool, [TRACKED], + "use overflow checks for integer arithmetic"), + #[rustc_lint_opt_deny_field_access("use `Session::panic_strategy` instead of this field")] + panic: Option<PanicStrategy> = (None, parse_opt_panic_strategy, [TRACKED], + "panic strategy to compile crate with"), + passes: Vec<String> = (Vec::new(), parse_list, [TRACKED], + "a list of extra LLVM passes to run (space separated)"), + prefer_dynamic: bool = (false, parse_bool, [TRACKED], + "prefer dynamic linking to static linking (default: no)"), + profile_generate: SwitchWithOptPath = (SwitchWithOptPath::Disabled, + parse_switch_with_opt_path, [TRACKED], + "compile the program with profiling instrumentation"), + profile_use: Option<PathBuf> = (None, parse_opt_pathbuf, [TRACKED], + "use the given `.profdata` file for profile-guided optimization"), + #[rustc_lint_opt_deny_field_access("use `Session::relocation_model` instead of this field")] + relocation_model: Option<RelocModel> = (None, parse_relocation_model, [TRACKED], + "control generation of position-independent code (PIC) \ + (`rustc --print relocation-models` for details)"), + relro_level: Option<RelroLevel> = (None, parse_relro_level, [TRACKED], + "choose which RELRO level to use"), + remark: Passes = (Passes::Some(Vec::new()), parse_passes, [UNTRACKED], + "output remarks for these optimization passes (space separated, or \"all\")"), + rpath: bool = (false, parse_bool, [UNTRACKED], + "set rpath values in libs/exes (default: no)"), + save_temps: bool = (false, parse_bool, [UNTRACKED], + "save all temporary output files during compilation (default: no)"), + soft_float: bool = (false, parse_bool, [TRACKED], + "deprecated option: use soft float ABI (*eabihf targets only) (default: no)"), + #[rustc_lint_opt_deny_field_access("use `Session::split_debuginfo` instead of this field")] + split_debuginfo: Option<SplitDebuginfo> = (None, parse_split_debuginfo, [TRACKED], + "how to handle split-debuginfo, a platform-specific option"), + strip: Strip = (Strip::None, parse_strip, [UNTRACKED], + "tell the linker which information to strip (`none` (default), `debuginfo` or `symbols`)"), + symbol_mangling_version: Option<SymbolManglingVersion> = (None, + parse_symbol_mangling_version, [TRACKED], + "which mangling version to use for symbol names ('legacy' (default), 'v0', or 'hashed')"), + target_cpu: Option<String> = (None, parse_opt_string, [TRACKED], + "select target processor (`rustc --print target-cpus` for details)"), + target_feature: String = (String::new(), parse_target_feature, [TRACKED], + "target specific attributes. (`rustc --print target-features` for details). \ + This feature is unsafe."), + // tidy-alphabetical-end + + // If you add a new option, please update: + // - compiler/rustc_interface/src/tests.rs + // - src/doc/rustc/src/codegen-options/index.md +} + +options! { + UnstableOptions, Z_OPTIONS, dbopts, "Z", "unstable", + + // If you add a new option, please update: + // - compiler/rustc_interface/src/tests.rs + // - src/doc/unstable-book/src/compiler-flags + + // tidy-alphabetical-start + allow_features: Option<Vec<String>> = (None, parse_opt_comma_list, [TRACKED], + "only allow the listed language features to be enabled in code (comma separated)"), + always_encode_mir: bool = (false, parse_bool, [TRACKED], + "encode MIR of all functions into the crate metadata (default: no)"), + assert_incr_state: Option<String> = (None, parse_opt_string, [UNTRACKED], + "assert that the incremental cache is in given state: \ + either `loaded` or `not-loaded`."), + assume_incomplete_release: bool = (false, parse_bool, [TRACKED], + "make cfg(version) treat the current version as incomplete (default: no)"), + #[rustc_lint_opt_deny_field_access("use `Session::binary_dep_depinfo` instead of this field")] + binary_dep_depinfo: bool = (false, parse_bool, [TRACKED], + "include artifacts (sysroot, crate dependencies) used during compilation in dep-info \ + (default: no)"), + box_noalias: bool = (true, parse_bool, [TRACKED], + "emit noalias metadata for box (default: yes)"), + branch_protection: Option<BranchProtection> = (None, parse_branch_protection, [TRACKED], + "set options for branch target identification and pointer authentication on AArch64"), + cf_protection: CFProtection = (CFProtection::None, parse_cfprotection, [TRACKED], + "instrument control-flow architecture protection"), + check_cfg_all_expected: bool = (false, parse_bool, [UNTRACKED], + "show all expected values in check-cfg diagnostics (default: no)"), + checksum_hash_algorithm: Option<SourceFileHashAlgorithm> = (None, parse_cargo_src_file_hash, [TRACKED], + "hash algorithm of source files used to check freshness in cargo (`blake3` or `sha256`)"), + codegen_backend: Option<String> = (None, parse_opt_string, [TRACKED], + "the backend to use"), + combine_cgu: bool = (false, parse_bool, [TRACKED], + "combine CGUs into a single one"), + coverage_options: CoverageOptions = (CoverageOptions::default(), parse_coverage_options, [TRACKED], + "control details of coverage instrumentation"), + crate_attr: Vec<String> = (Vec::new(), parse_string_push, [TRACKED], + "inject the given attribute in the crate"), + cross_crate_inline_threshold: InliningThreshold = (InliningThreshold::Sometimes(100), parse_inlining_threshold, [TRACKED], + "threshold to allow cross crate inlining of functions"), + debug_info_for_profiling: bool = (false, parse_bool, [TRACKED], + "emit discriminators and other data necessary for AutoFDO"), + debuginfo_compression: DebugInfoCompression = (DebugInfoCompression::None, parse_debuginfo_compression, [TRACKED], + "compress debug info sections (none, zlib, zstd, default: none)"), + deduplicate_diagnostics: bool = (true, parse_bool, [UNTRACKED], + "deduplicate identical diagnostics (default: yes)"), + default_visibility: Option<SymbolVisibility> = (None, parse_opt_symbol_visibility, [TRACKED], + "overrides the `default_visibility` setting of the target"), + dep_info_omit_d_target: bool = (false, parse_bool, [TRACKED], + "in dep-info output, omit targets for tracking dependencies of the dep-info files \ + themselves (default: no)"), + direct_access_external_data: Option<bool> = (None, parse_opt_bool, [TRACKED], + "Direct or use GOT indirect to reference external data symbols"), + dual_proc_macros: bool = (false, parse_bool, [TRACKED], + "load proc macros for both target and host, but only link to the target (default: no)"), + dump_dep_graph: bool = (false, parse_bool, [UNTRACKED], + "dump the dependency graph to $RUST_DEP_GRAPH (default: /tmp/dep_graph.gv) \ + (default: no)"), + dump_mir: Option<String> = (None, parse_opt_string, [UNTRACKED], + "dump MIR state to file. + `val` is used to select which passes and functions to dump. For example: + `all` matches all passes and functions, + `foo` matches all passes for functions whose name contains 'foo', + `foo & ConstProp` only the 'ConstProp' pass for function names containing 'foo', + `foo | bar` all passes for function names containing 'foo' or 'bar'."), + dump_mir_dataflow: bool = (false, parse_bool, [UNTRACKED], + "in addition to `.mir` files, create graphviz `.dot` files with dataflow results \ + (default: no)"), + dump_mir_dir: String = ("mir_dump".to_string(), parse_string, [UNTRACKED], + "the directory the MIR is dumped into (default: `mir_dump`)"), + dump_mir_exclude_alloc_bytes: bool = (false, parse_bool, [UNTRACKED], + "exclude the raw bytes of allocations when dumping MIR (used in tests) (default: no)"), + dump_mir_exclude_pass_number: bool = (false, parse_bool, [UNTRACKED], + "exclude the pass number when dumping MIR (used in tests) (default: no)"), + dump_mir_graphviz: bool = (false, parse_bool, [UNTRACKED], + "in addition to `.mir` files, create graphviz `.dot` files (default: no)"), + dump_mono_stats: SwitchWithOptPath = (SwitchWithOptPath::Disabled, + parse_switch_with_opt_path, [UNTRACKED], + "output statistics about monomorphization collection"), + dump_mono_stats_format: DumpMonoStatsFormat = (DumpMonoStatsFormat::Markdown, parse_dump_mono_stats, [UNTRACKED], + "the format to use for -Z dump-mono-stats (`markdown` (default) or `json`)"), + dwarf_version: Option<u32> = (None, parse_opt_number, [TRACKED], + "version of DWARF debug information to emit (default: 2 or 4, depending on platform)"), + dylib_lto: bool = (false, parse_bool, [UNTRACKED], + "enables LTO for dylib crate type"), + eagerly_emit_delayed_bugs: bool = (false, parse_bool, [UNTRACKED], + "emit delayed bugs eagerly as errors instead of stashing them and emitting \ + them only if an error has not been emitted"), + ehcont_guard: bool = (false, parse_bool, [TRACKED], + "generate Windows EHCont Guard tables"), + embed_source: bool = (false, parse_bool, [TRACKED], + "embed source text in DWARF debug sections (default: no)"), + emit_stack_sizes: bool = (false, parse_bool, [UNTRACKED], + "emit a section containing stack size metadata (default: no)"), + emit_thin_lto: bool = (true, parse_bool, [TRACKED], + "emit the bc module with thin LTO info (default: yes)"), + enforce_type_length_limit: bool = (false, parse_bool, [TRACKED], + "enforce the type length limit when monomorphizing instances in codegen"), + export_executable_symbols: bool = (false, parse_bool, [TRACKED], + "export symbols from executables, as if they were dynamic libraries"), + external_clangrt: bool = (false, parse_bool, [UNTRACKED], + "rely on user specified linker commands to find clangrt"), + extra_const_ub_checks: bool = (false, parse_bool, [TRACKED], + "turns on more checks to detect const UB, which can be slow (default: no)"), + #[rustc_lint_opt_deny_field_access("use `Session::fewer_names` instead of this field")] + fewer_names: Option<bool> = (None, parse_opt_bool, [TRACKED], + "reduce memory use by retaining fewer names within compilation artifacts (LLVM-IR) \ + (default: no)"), + fixed_x18: bool = (false, parse_bool, [TRACKED], + "make the x18 register reserved on AArch64 (default: no)"), + flatten_format_args: bool = (true, parse_bool, [TRACKED], + "flatten nested format_args!() and literals into a simplified format_args!() call \ + (default: yes)"), + fmt_debug: FmtDebug = (FmtDebug::Full, parse_fmt_debug, [TRACKED], + "how detailed `#[derive(Debug)]` should be. `full` prints types recursively, \ + `shallow` prints only type names, `none` prints nothing and disables `{:?}`. (default: `full`)"), + force_unstable_if_unmarked: bool = (false, parse_bool, [TRACKED], + "force all crates to be `rustc_private` unstable (default: no)"), + fuel: Option<(String, u64)> = (None, parse_optimization_fuel, [TRACKED], + "set the optimization fuel quota for a crate"), + function_return: FunctionReturn = (FunctionReturn::default(), parse_function_return, [TRACKED], + "replace returns with jumps to `__x86_return_thunk` (default: `keep`)"), + function_sections: Option<bool> = (None, parse_opt_bool, [TRACKED], + "whether each function should go in its own section"), + future_incompat_test: bool = (false, parse_bool, [UNTRACKED], + "forces all lints to be future incompatible, used for internal testing (default: no)"), + graphviz_dark_mode: bool = (false, parse_bool, [UNTRACKED], + "use dark-themed colors in graphviz output (default: no)"), + graphviz_font: String = ("Courier, monospace".to_string(), parse_string, [UNTRACKED], + "use the given `fontname` in graphviz output; can be overridden by setting \ + environment variable `RUSTC_GRAPHVIZ_FONT` (default: `Courier, monospace`)"), + has_thread_local: Option<bool> = (None, parse_opt_bool, [TRACKED], + "explicitly enable the `cfg(target_thread_local)` directive"), + hir_stats: bool = (false, parse_bool, [UNTRACKED], + "print some statistics about AST and HIR (default: no)"), + human_readable_cgu_names: bool = (false, parse_bool, [TRACKED], + "generate human-readable, predictable names for codegen units (default: no)"), + identify_regions: bool = (false, parse_bool, [UNTRACKED], + "display unnamed regions as `'<id>`, using a non-ident unique id (default: no)"), + ignore_directory_in_diagnostics_source_blocks: Vec<String> = (Vec::new(), parse_string_push, [UNTRACKED], + "do not display the source code block in diagnostics for files in the directory"), + incremental_ignore_spans: bool = (false, parse_bool, [TRACKED], + "ignore spans during ICH computation -- used for testing (default: no)"), + incremental_info: bool = (false, parse_bool, [UNTRACKED], + "print high-level information about incremental reuse (or the lack thereof) \ + (default: no)"), + incremental_verify_ich: bool = (false, parse_bool, [UNTRACKED], + "verify extended properties for incr. comp. (default: no): + - hashes of green query instances + - hash collisions of query keys"), + inline_in_all_cgus: Option<bool> = (None, parse_opt_bool, [TRACKED], + "control whether `#[inline]` functions are in all CGUs"), + inline_llvm: bool = (true, parse_bool, [TRACKED], + "enable LLVM inlining (default: yes)"), + inline_mir: Option<bool> = (None, parse_opt_bool, [TRACKED], + "enable MIR inlining (default: no)"), + inline_mir_forwarder_threshold: Option<usize> = (None, parse_opt_number, [TRACKED], + "inlining threshold when the caller is a simple forwarding function (default: 30)"), + inline_mir_hint_threshold: Option<usize> = (None, parse_opt_number, [TRACKED], + "inlining threshold for functions with inline hint (default: 100)"), + inline_mir_preserve_debug: Option<bool> = (None, parse_opt_bool, [TRACKED], + "when MIR inlining, whether to preserve debug info for callee variables \ + (default: preserve for debuginfo != None, otherwise remove)"), + inline_mir_threshold: Option<usize> = (None, parse_opt_number, [TRACKED], + "a default MIR inlining threshold (default: 50)"), + input_stats: bool = (false, parse_bool, [UNTRACKED], + "gather statistics about the input (default: no)"), + instrument_mcount: bool = (false, parse_bool, [TRACKED], + "insert function instrument code for mcount-based tracing (default: no)"), + instrument_xray: Option<InstrumentXRay> = (None, parse_instrument_xray, [TRACKED], + "insert function instrument code for XRay-based tracing (default: no) + Optional extra settings: + `=always` + `=never` + `=ignore-loops` + `=instruction-threshold=N` + `=skip-entry` + `=skip-exit` + Multiple options can be combined with commas."), + layout_seed: Option<u64> = (None, parse_opt_number, [TRACKED], + "seed layout randomization"), + link_directives: bool = (true, parse_bool, [TRACKED], + "honor #[link] directives in the compiled crate (default: yes)"), + link_native_libraries: bool = (true, parse_bool, [UNTRACKED], + "link native libraries in the linker invocation (default: yes)"), + link_only: bool = (false, parse_bool, [TRACKED], + "link the `.rlink` file generated by `-Z no-link` (default: no)"), + linker_features: LinkerFeaturesCli = (LinkerFeaturesCli::default(), parse_linker_features, [UNTRACKED], + "a comma-separated list of linker features to enable (+) or disable (-): `lld`"), + lint_llvm_ir: bool = (false, parse_bool, [TRACKED], + "lint LLVM IR (default: no)"), + lint_mir: bool = (false, parse_bool, [UNTRACKED], + "lint MIR before and after each transformation"), + llvm_module_flag: Vec<(String, u32, String)> = (Vec::new(), parse_llvm_module_flag, [TRACKED], + "a list of module flags to pass to LLVM (space separated)"), + llvm_plugins: Vec<String> = (Vec::new(), parse_list, [TRACKED], + "a list LLVM plugins to enable (space separated)"), + llvm_time_trace: bool = (false, parse_bool, [UNTRACKED], + "generate JSON tracing data file from LLVM data (default: no)"), + location_detail: LocationDetail = (LocationDetail::all(), parse_location_detail, [TRACKED], + "what location details should be tracked when using caller_location, either \ + `none`, or a comma separated list of location details, for which \ + valid options are `file`, `line`, and `column` (default: `file,line,column`)"), + ls: Vec<String> = (Vec::new(), parse_list, [UNTRACKED], + "decode and print various parts of the crate metadata for a library crate \ + (space separated)"), + macro_backtrace: bool = (false, parse_bool, [UNTRACKED], + "show macro backtraces (default: no)"), + maximal_hir_to_mir_coverage: bool = (false, parse_bool, [TRACKED], + "save as much information as possible about the correspondence between MIR and HIR \ + as source scopes (default: no)"), + merge_functions: Option<MergeFunctions> = (None, parse_merge_functions, [TRACKED], + "control the operation of the MergeFunctions LLVM pass, taking \ + the same values as the target option of the same name"), + meta_stats: bool = (false, parse_bool, [UNTRACKED], + "gather metadata statistics (default: no)"), + metrics_dir: Option<PathBuf> = (None, parse_opt_pathbuf, [UNTRACKED], + "stores metrics about the errors being emitted by rustc to disk"), + mir_emit_retag: bool = (false, parse_bool, [TRACKED], + "emit Retagging MIR statements, interpreted e.g., by miri; implies -Zmir-opt-level=0 \ + (default: no)"), + mir_enable_passes: Vec<(String, bool)> = (Vec::new(), parse_list_with_polarity, [TRACKED], + "use like `-Zmir-enable-passes=+DestinationPropagation,-InstSimplify`. Forces the \ + specified passes to be enabled, overriding all other checks. In particular, this will \ + enable unsound (known-buggy and hence usually disabled) passes without further warning! \ + Passes that are not specified are enabled or disabled by other flags as usual."), + mir_include_spans: MirIncludeSpans = (MirIncludeSpans::default(), parse_mir_include_spans, [UNTRACKED], + "include extra comments in mir pretty printing, like line numbers and statement indices, \ + details about types, etc. (boolean for all passes, 'nll' to enable in NLL MIR only, default: 'nll')"), + mir_keep_place_mention: bool = (false, parse_bool, [TRACKED], + "keep place mention MIR statements, interpreted e.g., by miri; implies -Zmir-opt-level=0 \ + (default: no)"), + #[rustc_lint_opt_deny_field_access("use `Session::mir_opt_level` instead of this field")] + mir_opt_level: Option<usize> = (None, parse_opt_number, [TRACKED], + "MIR optimization level (0-4; default: 1 in non optimized builds and 2 in optimized builds)"), + move_size_limit: Option<usize> = (None, parse_opt_number, [TRACKED], + "the size at which the `large_assignments` lint starts to be emitted"), + mutable_noalias: bool = (true, parse_bool, [TRACKED], + "emit noalias metadata for mutable references (default: yes)"), + next_solver: NextSolverConfig = (NextSolverConfig::default(), parse_next_solver_config, [TRACKED], + "enable and configure the next generation trait solver used by rustc"), + nll_facts: bool = (false, parse_bool, [UNTRACKED], + "dump facts from NLL analysis into side files (default: no)"), + nll_facts_dir: String = ("nll-facts".to_string(), parse_string, [UNTRACKED], + "the directory the NLL facts are dumped into (default: `nll-facts`)"), + no_analysis: bool = (false, parse_no_flag, [UNTRACKED], + "parse and expand the source, but run no analysis"), + no_codegen: bool = (false, parse_no_flag, [TRACKED_NO_CRATE_HASH], + "run all passes except codegen; no output"), + no_generate_arange_section: bool = (false, parse_no_flag, [TRACKED], + "omit DWARF address ranges that give faster lookups"), + no_implied_bounds_compat: bool = (false, parse_bool, [TRACKED], + "disable the compatibility version of the `implied_bounds_ty` query"), + no_jump_tables: bool = (false, parse_no_flag, [TRACKED], + "disable the jump tables and lookup tables that can be generated from a switch case lowering"), + no_leak_check: bool = (false, parse_no_flag, [UNTRACKED], + "disable the 'leak check' for subtyping; unsound, but useful for tests"), + no_link: bool = (false, parse_no_flag, [TRACKED], + "compile without linking"), + no_parallel_backend: bool = (false, parse_no_flag, [UNTRACKED], + "run LLVM in non-parallel mode (while keeping codegen-units and ThinLTO)"), + no_profiler_runtime: bool = (false, parse_no_flag, [TRACKED], + "prevent automatic injection of the profiler_builtins crate"), + no_trait_vptr: bool = (false, parse_no_flag, [TRACKED], + "disable generation of trait vptr in vtable for upcasting"), + no_unique_section_names: bool = (false, parse_bool, [TRACKED], + "do not use unique names for text and data sections when -Z function-sections is used"), + normalize_docs: bool = (false, parse_bool, [TRACKED], + "normalize associated items in rustdoc when generating documentation"), + on_broken_pipe: OnBrokenPipe = (OnBrokenPipe::Default, parse_on_broken_pipe, [TRACKED], + "behavior of std::io::ErrorKind::BrokenPipe (SIGPIPE)"), + oom: OomStrategy = (OomStrategy::Abort, parse_oom_strategy, [TRACKED], + "panic strategy for out-of-memory handling"), + osx_rpath_install_name: bool = (false, parse_bool, [TRACKED], + "pass `-install_name @rpath/...` to the macOS linker (default: no)"), + packed_bundled_libs: bool = (false, parse_bool, [TRACKED], + "change rlib format to store native libraries as archives"), + panic_abort_tests: bool = (false, parse_bool, [TRACKED], + "support compiling tests with panic=abort (default: no)"), + panic_in_drop: PanicStrategy = (PanicStrategy::Unwind, parse_panic_strategy, [TRACKED], + "panic strategy for panics in drops"), + parse_only: bool = (false, parse_bool, [UNTRACKED], + "parse only; do not compile, assemble, or link (default: no)"), + patchable_function_entry: PatchableFunctionEntry = (PatchableFunctionEntry::default(), parse_patchable_function_entry, [TRACKED], + "nop padding at function entry"), + plt: Option<bool> = (None, parse_opt_bool, [TRACKED], + "whether to use the PLT when calling into shared libraries; + only has effect for PIC code on systems with ELF binaries + (default: PLT is disabled if full relro is enabled on x86_64)"), + polonius: Polonius = (Polonius::default(), parse_polonius, [TRACKED], + "enable polonius-based borrow-checker (default: no)"), + polymorphize: bool = (false, parse_bool, [TRACKED], + "perform polymorphization analysis"), + pre_link_arg: (/* redirected to pre_link_args */) = ((), parse_string_push, [UNTRACKED], + "a single extra argument to prepend the linker invocation (can be used several times)"), + pre_link_args: Vec<String> = (Vec::new(), parse_list, [UNTRACKED], + "extra arguments to prepend to the linker invocation (space separated)"), + precise_enum_drop_elaboration: bool = (true, parse_bool, [TRACKED], + "use a more precise version of drop elaboration for matches on enums (default: yes). \ + This results in better codegen, but has caused miscompilations on some tier 2 platforms. \ + See #77382 and #74551."), + #[rustc_lint_opt_deny_field_access("use `Session::print_codegen_stats` instead of this field")] + print_codegen_stats: bool = (false, parse_bool, [UNTRACKED], + "print codegen statistics (default: no)"), + print_fuel: Option<String> = (None, parse_opt_string, [TRACKED], + "make rustc print the total optimization fuel used by a crate"), + print_llvm_passes: bool = (false, parse_bool, [UNTRACKED], + "print the LLVM optimization passes being run (default: no)"), + print_mono_items: Option<String> = (None, parse_opt_string, [UNTRACKED], + "print the result of the monomorphization collection pass. \ + Value `lazy` means to use normal collection; `eager` means to collect all items. + Note that this overwrites the effect `-Clink-dead-code` has on collection!"), + print_type_sizes: bool = (false, parse_bool, [UNTRACKED], + "print layout information for each type encountered (default: no)"), + print_vtable_sizes: bool = (false, parse_bool, [UNTRACKED], + "print size comparison between old and new vtable layouts (default: no)"), + proc_macro_backtrace: bool = (false, parse_bool, [UNTRACKED], + "show backtraces for panics during proc-macro execution (default: no)"), + proc_macro_execution_strategy: ProcMacroExecutionStrategy = (ProcMacroExecutionStrategy::SameThread, + parse_proc_macro_execution_strategy, [UNTRACKED], + "how to run proc-macro code (default: same-thread)"), + profile_closures: bool = (false, parse_no_flag, [UNTRACKED], + "profile size of closures"), + profile_sample_use: Option<PathBuf> = (None, parse_opt_pathbuf, [TRACKED], + "use the given `.prof` file for sampled profile-guided optimization (also known as AutoFDO)"), + profiler_runtime: String = (String::from("profiler_builtins"), parse_string, [TRACKED], + "name of the profiler runtime crate to automatically inject (default: `profiler_builtins`)"), + query_dep_graph: bool = (false, parse_bool, [UNTRACKED], + "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<u32> = (None, parse_opt_number, [TRACKED], + "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.\ + It is UNSOUND to link together crates that use different values for this flag!"), + relax_elf_relocations: Option<bool> = (None, parse_opt_bool, [TRACKED], + "whether ELF relocations can be relaxed"), + remap_cwd_prefix: Option<PathBuf> = (None, parse_opt_pathbuf, [TRACKED], + "remap paths under the current working directory to this path prefix"), + remap_path_scope: RemapPathScopeComponents = (RemapPathScopeComponents::all(), parse_remap_path_scope, [TRACKED], + "remap path scope (default: all)"), + remark_dir: Option<PathBuf> = (None, parse_opt_pathbuf, [UNTRACKED], + "directory into which to write optimization remarks (if not specified, they will be \ +written to standard error output)"), + sanitizer: SanitizerSet = (SanitizerSet::empty(), parse_sanitizers, [TRACKED], + "use a sanitizer"), + sanitizer_cfi_canonical_jump_tables: Option<bool> = (Some(true), parse_opt_bool, [TRACKED], + "enable canonical jump tables (default: yes)"), + sanitizer_cfi_generalize_pointers: Option<bool> = (None, parse_opt_bool, [TRACKED], + "enable generalizing pointer types (default: no)"), + sanitizer_cfi_normalize_integers: Option<bool> = (None, parse_opt_bool, [TRACKED], + "enable normalizing integer types (default: no)"), + sanitizer_dataflow_abilist: Vec<String> = (Vec::new(), parse_comma_list, [TRACKED], + "additional ABI list files that control how shadow parameters are passed (comma separated)"), + sanitizer_memory_track_origins: usize = (0, parse_sanitizer_memory_track_origins, [TRACKED], + "enable origins tracking in MemorySanitizer"), + sanitizer_recover: SanitizerSet = (SanitizerSet::empty(), parse_sanitizers, [TRACKED], + "enable recovery for selected sanitizers"), + saturating_float_casts: Option<bool> = (None, parse_opt_bool, [TRACKED], + "make float->int casts UB-free: numbers outside the integer type's range are clipped to \ + the max/min integer respectively, and NaN is mapped to 0 (default: yes)"), + self_profile: SwitchWithOptPath = (SwitchWithOptPath::Disabled, + parse_switch_with_opt_path, [UNTRACKED], + "run the self profiler and output the raw event data"), + self_profile_counter: String = ("wall-time".to_string(), parse_string, [UNTRACKED], + "counter used by the self profiler (default: `wall-time`), one of: + `wall-time` (monotonic clock, i.e. `std::time::Instant`) + `instructions:u` (retired instructions, userspace-only) + `instructions-minus-irqs:u` (subtracting hardware interrupt counts for extra accuracy)" + ), + /// keep this in sync with the event filter names in librustc_data_structures/profiling.rs + self_profile_events: Option<Vec<String>> = (None, parse_opt_comma_list, [UNTRACKED], + "specify the events recorded by the self profiler; + for example: `-Z self-profile-events=default,query-keys` + all options: none, all, default, generic-activity, query-provider, query-cache-hit + query-blocked, incr-cache-load, incr-result-hashing, query-keys, function-args, args, llvm, artifact-sizes"), + share_generics: Option<bool> = (None, parse_opt_bool, [TRACKED], + "make the current crate share its generic instantiations"), + shell_argfiles: bool = (false, parse_bool, [UNTRACKED], + "allow argument files to be specified with POSIX \"shell-style\" argument quoting"), + show_span: Option<String> = (None, parse_opt_string, [TRACKED], + "show spans for compiler debugging (expr|pat|ty)"), + simulate_remapped_rust_src_base: Option<PathBuf> = (None, parse_opt_pathbuf, [TRACKED], + "simulate the effect of remap-debuginfo = true at bootstrapping by remapping path \ + to rust's source base directory. only meant for testing purposes"), + small_data_threshold: Option<usize> = (None, parse_opt_number, [TRACKED], + "Set the threshold for objects to be stored in a \"small data\" section"), + span_debug: bool = (false, parse_bool, [UNTRACKED], + "forward proc_macro::Span's `Debug` impl to `Span`"), + /// o/w tests have closure@path + span_free_formats: bool = (false, parse_bool, [UNTRACKED], + "exclude spans when debug-printing compiler state (default: no)"), + split_dwarf_inlining: bool = (false, parse_bool, [TRACKED], + "provide minimal debug info in the object/executable to facilitate online \ + symbolication/stack traces in the absence of .dwo/.dwp files when using Split DWARF"), + split_dwarf_kind: SplitDwarfKind = (SplitDwarfKind::Split, parse_split_dwarf_kind, [TRACKED], + "split dwarf variant (only if -Csplit-debuginfo is enabled and on relevant platform) + (default: `split`) + + `split`: sections which do not require relocation are written into a DWARF object (`.dwo`) + file which is ignored by the linker + `single`: sections which do not require relocation are written into object file but ignored + by the linker"), + split_lto_unit: Option<bool> = (None, parse_opt_bool, [TRACKED], + "enable LTO unit splitting (default: no)"), + src_hash_algorithm: Option<SourceFileHashAlgorithm> = (None, parse_src_file_hash, [TRACKED], + "hash algorithm of source files in debug info (`md5`, `sha1`, or `sha256`)"), + #[rustc_lint_opt_deny_field_access("use `Session::stack_protector` instead of this field")] + stack_protector: StackProtector = (StackProtector::None, parse_stack_protector, [TRACKED], + "control stack smash protection strategy (`rustc --print stack-protector-strategies` for details)"), + staticlib_allow_rdylib_deps: bool = (false, parse_bool, [TRACKED], + "allow staticlibs to have rust dylib dependencies"), + staticlib_prefer_dynamic: bool = (false, parse_bool, [TRACKED], + "prefer dynamic linking to static linking for staticlibs (default: no)"), + strict_init_checks: bool = (false, parse_bool, [TRACKED], + "control if mem::uninitialized and mem::zeroed panic on more UB"), + #[rustc_lint_opt_deny_field_access("use `Session::teach` instead of this field")] + teach: bool = (false, parse_bool, [TRACKED], + "show extended diagnostic help (default: no)"), + temps_dir: Option<String> = (None, parse_opt_string, [UNTRACKED], + "the directory the intermediate files are written to"), + terminal_urls: TerminalUrl = (TerminalUrl::No, parse_terminal_url, [UNTRACKED], + "use the OSC 8 hyperlink terminal specification to print hyperlinks in the compiler output"), + #[rustc_lint_opt_deny_field_access("use `Session::lto` instead of this field")] + thinlto: Option<bool> = (None, parse_opt_bool, [TRACKED], + "enable ThinLTO when possible"), + /// We default to 1 here since we want to behave like + /// a sequential compiler for now. This'll likely be adjusted + /// in the future. Note that -Zthreads=0 is the way to get + /// the num_cpus behavior. + #[rustc_lint_opt_deny_field_access("use `Session::threads` instead of this field")] + threads: usize = (1, parse_threads, [UNTRACKED], + "use a thread pool with N threads"), + time_llvm_passes: bool = (false, parse_bool, [UNTRACKED], + "measure time of each LLVM pass (default: no)"), + time_passes: bool = (false, parse_bool, [UNTRACKED], + "measure time of each rustc pass (default: no)"), + time_passes_format: TimePassesFormat = (TimePassesFormat::Text, parse_time_passes_format, [UNTRACKED], + "the format to use for -Z time-passes (`text` (default) or `json`)"), + tiny_const_eval_limit: bool = (false, parse_bool, [TRACKED], + "sets a tiny, non-configurable limit for const eval; useful for compiler tests"), + #[rustc_lint_opt_deny_field_access("use `Session::tls_model` instead of this field")] + tls_model: Option<TlsModel> = (None, parse_tls_model, [TRACKED], + "choose the TLS model to use (`rustc --print tls-models` for details)"), + trace_macros: bool = (false, parse_bool, [UNTRACKED], + "for every macro invocation, print its name and arguments (default: no)"), + track_diagnostics: bool = (false, parse_bool, [UNTRACKED], + "tracks where in rustc a diagnostic was emitted"), + // Diagnostics are considered side-effects of a query (see `QuerySideEffects`) and are saved + // alongside query results and changes to translation options can affect diagnostics - so + // translation options should be tracked. + translate_additional_ftl: Option<PathBuf> = (None, parse_opt_pathbuf, [TRACKED], + "additional fluent translation to preferentially use (for testing translation)"), + translate_directionality_markers: bool = (false, parse_bool, [TRACKED], + "emit directionality isolation markers in translated diagnostics"), + translate_lang: Option<LanguageIdentifier> = (None, parse_opt_langid, [TRACKED], + "language identifier for diagnostic output"), + translate_remapped_path_to_local_path: bool = (true, parse_bool, [TRACKED], + "translate remapped paths into local paths when possible (default: yes)"), + trap_unreachable: Option<bool> = (None, parse_opt_bool, [TRACKED], + "generate trap instructions for unreachable intrinsics (default: use target setting, usually yes)"), + treat_err_as_bug: Option<NonZero<usize>> = (None, parse_treat_err_as_bug, [TRACKED], + "treat the `val`th error that occurs as bug (default if not specified: 0 - don't treat errors as bugs. \ + default if specified without a value: 1 - treat the first error as bug)"), + trim_diagnostic_paths: bool = (true, parse_bool, [UNTRACKED], + "in diagnostics, use heuristics to shorten paths referring to items"), + tune_cpu: Option<String> = (None, parse_opt_string, [TRACKED], + "select processor to schedule for (`rustc --print target-cpus` for details)"), + #[rustc_lint_opt_deny_field_access("use `Session::ub_checks` instead of this field")] + ub_checks: Option<bool> = (None, parse_opt_bool, [TRACKED], + "emit runtime checks for Undefined Behavior (default: -Cdebug-assertions)"), + ui_testing: bool = (false, parse_bool, [UNTRACKED], + "emit compiler diagnostics in a form suitable for UI testing (default: no)"), + uninit_const_chunk_threshold: usize = (16, parse_number, [TRACKED], + "allow generating const initializers with mixed init/uninit chunks, \ + and set the maximum number of chunks for which this is allowed (default: 16)"), + unleash_the_miri_inside_of_you: bool = (false, parse_bool, [TRACKED], + "take the brakes off const evaluation. NOTE: this is unsound (default: no)"), + unpretty: Option<String> = (None, parse_unpretty, [UNTRACKED], + "present the input source, unstable (and less-pretty) variants; + `normal`, `identified`, + `expanded`, `expanded,identified`, + `expanded,hygiene` (with internal representations), + `ast-tree` (raw AST before expansion), + `ast-tree,expanded` (raw AST after expansion), + `hir` (the HIR), `hir,identified`, + `hir,typed` (HIR with types for each node), + `hir-tree` (dump the raw HIR), + `thir-tree`, `thir-flat`, + `mir` (the MIR), or `mir-cfg` (graphviz formatted MIR)"), + unsound_mir_opts: bool = (false, parse_bool, [TRACKED], + "enable unsound and buggy MIR optimizations (default: no)"), + /// This name is kind of confusing: Most unstable options enable something themselves, while + /// this just allows "normal" options to be feature-gated. + #[rustc_lint_opt_deny_field_access("use `Session::unstable_options` instead of this field")] + unstable_options: bool = (false, parse_bool, [UNTRACKED], + "adds unstable command line options to rustc interface (default: no)"), + use_ctors_section: Option<bool> = (None, parse_opt_bool, [TRACKED], + "use legacy .ctors section for initializers rather than .init_array"), + use_sync_unwind: Option<bool> = (None, parse_opt_bool, [TRACKED], + "Generate sync unwind tables instead of async unwind tables (default: no)"), + validate_mir: bool = (false, parse_bool, [UNTRACKED], + "validate MIR after each transformation"), + verbose_asm: bool = (false, parse_bool, [TRACKED], + "add descriptive comments from LLVM to the assembly (may change behavior) (default: no)"), + #[rustc_lint_opt_deny_field_access("use `Session::verbose_internals` instead of this field")] + verbose_internals: bool = (false, parse_bool, [TRACKED_NO_CRATE_HASH], + "in general, enable more debug printouts (default: no)"), + #[rustc_lint_opt_deny_field_access("use `Session::verify_llvm_ir` instead of this field")] + verify_llvm_ir: bool = (false, parse_bool, [TRACKED], + "verify LLVM IR (default: no)"), + virtual_function_elimination: bool = (false, parse_bool, [TRACKED], + "enables dead virtual function elimination optimization. \ + Requires `-Clto[=[fat,yes]]`"), + wasi_exec_model: Option<WasiExecModel> = (None, parse_wasi_exec_model, [TRACKED], + "whether to build a wasi command or reactor"), + wasm_c_abi: WasmCAbi = (WasmCAbi::Legacy, parse_wasm_c_abi, [TRACKED], + "use spec-compliant C ABI for `wasm32-unknown-unknown` (default: legacy)"), + write_long_types_to_disk: bool = (true, parse_bool, [UNTRACKED], + "whether long type names should be written to files instead of being printed in errors"), + // tidy-alphabetical-end + + // If you add a new option, please update: + // - compiler/rustc_interface/src/tests.rs + // - src/doc/unstable-book/src/compiler-flags +} diff --git a/compiler/rustc_session/src/output.rs b/compiler/rustc_session/src/output.rs new file mode 100644 index 00000000000..357d746c184 --- /dev/null +++ b/compiler/rustc_session/src/output.rs @@ -0,0 +1,266 @@ +//! Related to out filenames of compilation (e.g. binaries). + +use std::path::Path; + +use rustc_ast::{self as ast, attr}; +use rustc_errors::FatalError; +use rustc_span::symbol::sym; +use rustc_span::{Span, Symbol}; + +use crate::Session; +use crate::config::{self, CrateType, Input, OutFileName, OutputFilenames, OutputType}; +use crate::errors::{ + self, CrateNameDoesNotMatch, CrateNameEmpty, CrateNameInvalid, FileIsNotWriteable, + InvalidCharacterInCrateName, InvalidCrateNameHelp, +}; + +pub fn out_filename( + sess: &Session, + crate_type: CrateType, + outputs: &OutputFilenames, + crate_name: Symbol, +) -> OutFileName { + let default_filename = filename_for_input(sess, crate_type, crate_name, outputs); + let out_filename = outputs + .outputs + .get(&OutputType::Exe) + .and_then(|s| s.to_owned()) + .or_else(|| outputs.single_output_file.clone()) + .unwrap_or(default_filename); + + if let OutFileName::Real(ref path) = out_filename { + check_file_is_writeable(path, sess); + } + + out_filename +} + +/// Make sure files are writeable. Mac, FreeBSD, and Windows system linkers +/// check this already -- however, the Linux linker will happily overwrite a +/// read-only file. We should be consistent. +pub fn check_file_is_writeable(file: &Path, sess: &Session) { + if !is_writeable(file) { + sess.dcx().emit_fatal(FileIsNotWriteable { file }); + } +} + +fn is_writeable(p: &Path) -> bool { + match p.metadata() { + Err(..) => true, + Ok(m) => !m.permissions().readonly(), + } +} + +pub fn find_crate_name(sess: &Session, attrs: &[ast::Attribute]) -> Symbol { + let validate = |s: Symbol, span: Option<Span>| { + validate_crate_name(sess, s, span); + s + }; + + // Look in attributes 100% of the time to make sure the attribute is marked + // as used. After doing this, however, we still prioritize a crate name from + // the command line over one found in the #[crate_name] attribute. If we + // find both we ensure that they're the same later on as well. + let attr_crate_name = + attr::find_by_name(attrs, sym::crate_name).and_then(|at| at.value_str().map(|s| (at, s))); + + if let Some(ref s) = sess.opts.crate_name { + let s = Symbol::intern(s); + if let Some((attr, name)) = attr_crate_name { + if name != s { + sess.dcx().emit_err(CrateNameDoesNotMatch { span: attr.span, s, name }); + } + } + return validate(s, None); + } + + if let Some((attr, s)) = attr_crate_name { + return validate(s, Some(attr.span)); + } + if let Input::File(ref path) = sess.io.input { + if let Some(s) = path.file_stem().and_then(|s| s.to_str()) { + if s.starts_with('-') { + sess.dcx().emit_err(CrateNameInvalid { s }); + } else { + return validate(Symbol::intern(&s.replace('-', "_")), None); + } + } + } + + Symbol::intern("rust_out") +} + +pub fn validate_crate_name(sess: &Session, s: Symbol, sp: Option<Span>) { + let mut err_count = 0; + { + if s.is_empty() { + err_count += 1; + sess.dcx().emit_err(CrateNameEmpty { span: sp }); + } + for c in s.as_str().chars() { + if c.is_alphanumeric() { + continue; + } + if c == '_' { + continue; + } + err_count += 1; + sess.dcx().emit_err(InvalidCharacterInCrateName { + span: sp, + character: c, + crate_name: s, + crate_name_help: if sp.is_none() { + Some(InvalidCrateNameHelp::AddCrateName) + } else { + None + }, + }); + } + } + + if err_count > 0 { + FatalError.raise(); + } +} + +pub fn filename_for_metadata(sess: &Session, outputs: &OutputFilenames) -> OutFileName { + let out_filename = outputs.path(OutputType::Metadata); + if let OutFileName::Real(ref path) = out_filename { + check_file_is_writeable(path, sess); + } + out_filename +} + +pub fn filename_for_input( + sess: &Session, + crate_type: CrateType, + crate_name: Symbol, + outputs: &OutputFilenames, +) -> OutFileName { + let libname = format!("{}{}", crate_name, sess.opts.cg.extra_filename); + + match crate_type { + CrateType::Rlib => { + OutFileName::Real(outputs.out_directory.join(&format!("lib{libname}.rlib"))) + } + CrateType::Cdylib | CrateType::ProcMacro | CrateType::Dylib => { + let (prefix, suffix) = (&sess.target.dll_prefix, &sess.target.dll_suffix); + OutFileName::Real(outputs.out_directory.join(&format!("{prefix}{libname}{suffix}"))) + } + CrateType::Staticlib => { + let (prefix, suffix) = (&sess.target.staticlib_prefix, &sess.target.staticlib_suffix); + OutFileName::Real(outputs.out_directory.join(&format!("{prefix}{libname}{suffix}"))) + } + CrateType::Executable => { + let suffix = &sess.target.exe_suffix; + let out_filename = outputs.path(OutputType::Exe); + if let OutFileName::Real(ref path) = out_filename { + if suffix.is_empty() { + out_filename + } else { + OutFileName::Real(path.with_extension(&suffix[1..])) + } + } else { + out_filename + } + } + } +} + +/// Returns default crate type for target +/// +/// Default crate type is used when crate type isn't provided neither +/// through cmd line arguments nor through crate attributes +/// +/// It is CrateType::Executable for all platforms but iOS as there is no +/// way to run iOS binaries anyway without jailbreaking and +/// interaction with Rust code through static library is the only +/// option for now +pub fn default_output_for_target(sess: &Session) -> CrateType { + if !sess.target.executables { CrateType::Staticlib } else { CrateType::Executable } +} + +/// Checks if target supports crate_type as output +pub fn invalid_output_for_target(sess: &Session, crate_type: CrateType) -> bool { + if let CrateType::Cdylib | CrateType::Dylib | CrateType::ProcMacro = crate_type { + if !sess.target.dynamic_linking { + return true; + } + if sess.crt_static(Some(crate_type)) && !sess.target.crt_static_allows_dylibs { + return true; + } + } + if let CrateType::ProcMacro | CrateType::Dylib = crate_type + && sess.target.only_cdylib + { + return true; + } + if let CrateType::Executable = crate_type + && !sess.target.executables + { + return true; + } + + false +} + +pub const CRATE_TYPES: &[(Symbol, CrateType)] = &[ + (sym::rlib, CrateType::Rlib), + (sym::dylib, CrateType::Dylib), + (sym::cdylib, CrateType::Cdylib), + (sym::lib, config::default_lib_output()), + (sym::staticlib, CrateType::Staticlib), + (sym::proc_dash_macro, CrateType::ProcMacro), + (sym::bin, CrateType::Executable), +]; + +pub fn categorize_crate_type(s: Symbol) -> Option<CrateType> { + Some(CRATE_TYPES.iter().find(|(key, _)| *key == s)?.1) +} + +pub fn collect_crate_types(session: &Session, attrs: &[ast::Attribute]) -> Vec<CrateType> { + // If we're generating a test executable, then ignore all other output + // styles at all other locations + if session.opts.test { + return vec![CrateType::Executable]; + } + + // Only check command line flags if present. If no types are specified by + // command line, then reuse the empty `base` Vec to hold the types that + // will be found in crate attributes. + // JUSTIFICATION: before wrapper fn is available + #[allow(rustc::bad_opt_access)] + let mut base = session.opts.crate_types.clone(); + if base.is_empty() { + let attr_types = attrs.iter().filter_map(|a| { + if a.has_name(sym::crate_type) + && let Some(s) = a.value_str() + { + categorize_crate_type(s) + } else { + None + } + }); + base.extend(attr_types); + if base.is_empty() { + base.push(default_output_for_target(session)); + } else { + base.sort(); + base.dedup(); + } + } + + base.retain(|crate_type| { + if invalid_output_for_target(session, *crate_type) { + session.dcx().emit_warn(errors::UnsupportedCrateTypeForTarget { + crate_type: *crate_type, + target_triple: &session.opts.target_triple, + }); + false + } else { + true + } + }); + + base +} diff --git a/compiler/rustc_session/src/parse.rs b/compiler/rustc_session/src/parse.rs new file mode 100644 index 00000000000..21c11655110 --- /dev/null +++ b/compiler/rustc_session/src/parse.rs @@ -0,0 +1,348 @@ +//! Contains `ParseSess` which holds state living beyond what one `Parser` might. +//! It also serves as an input to the parser itself. + +use std::str; + +use rustc_ast::attr::AttrIdGenerator; +use rustc_ast::node_id::NodeId; +use rustc_data_structures::fx::{FxHashMap, FxIndexMap, FxIndexSet}; +use rustc_data_structures::sync::{AppendOnlyVec, Lock, Lrc}; +use rustc_errors::emitter::{HumanEmitter, SilentEmitter, stderr_destination}; +use rustc_errors::{ + ColorConfig, Diag, DiagCtxt, DiagCtxtHandle, DiagMessage, EmissionGuarantee, MultiSpan, + StashKey, fallback_fluent_bundle, +}; +use rustc_feature::{GateIssue, UnstableFeatures, find_feature_issue}; +use rustc_span::edition::Edition; +use rustc_span::hygiene::ExpnId; +use rustc_span::source_map::{FilePathMapping, SourceMap}; +use rustc_span::{Span, Symbol}; + +use crate::Session; +use crate::config::{Cfg, CheckCfg}; +use crate::errors::{ + CliFeatureDiagnosticHelp, FeatureDiagnosticForIssue, FeatureDiagnosticHelp, + FeatureDiagnosticSuggestion, FeatureGateError, SuggestUpgradeCompiler, +}; +use crate::lint::builtin::UNSTABLE_SYNTAX_PRE_EXPANSION; +use crate::lint::{BufferedEarlyLint, BuiltinLintDiag, Lint, LintId}; + +/// Collected spans during parsing for places where a certain feature was +/// used and should be feature gated accordingly in `check_crate`. +#[derive(Default)] +pub struct GatedSpans { + pub spans: Lock<FxHashMap<Symbol, Vec<Span>>>, +} + +impl GatedSpans { + /// Feature gate the given `span` under the given `feature` + /// which is same `Symbol` used in `unstable.rs`. + pub fn gate(&self, feature: Symbol, span: Span) { + self.spans.borrow_mut().entry(feature).or_default().push(span); + } + + /// Ungate the last span under the given `feature`. + /// Panics if the given `span` wasn't the last one. + /// + /// Using this is discouraged unless you have a really good reason to. + pub fn ungate_last(&self, feature: Symbol, span: Span) { + let removed_span = self.spans.borrow_mut().entry(feature).or_default().pop().unwrap(); + debug_assert_eq!(span, removed_span); + } + + /// Prepend the given set of `spans` onto the set in `self`. + pub fn merge(&self, mut spans: FxHashMap<Symbol, Vec<Span>>) { + let mut inner = self.spans.borrow_mut(); + // The entries will be moved to another map so the drain order does not + // matter. + #[allow(rustc::potential_query_instability)] + for (gate, mut gate_spans) in inner.drain() { + spans.entry(gate).or_default().append(&mut gate_spans); + } + *inner = spans; + } +} + +#[derive(Default)] +pub struct SymbolGallery { + /// All symbols occurred and their first occurrence span. + pub symbols: Lock<FxIndexMap<Symbol, Span>>, +} + +impl SymbolGallery { + /// Insert a symbol and its span into symbol gallery. + /// If the symbol has occurred before, ignore the new occurrence. + pub fn insert(&self, symbol: Symbol, span: Span) { + self.symbols.lock().entry(symbol).or_insert(span); + } +} + +// todo: this function now accepts `Session` instead of `ParseSess` and should be relocated +/// Construct a diagnostic for a language feature error due to the given `span`. +/// The `feature`'s `Symbol` is the one you used in `unstable.rs` and `rustc_span::symbols`. +#[track_caller] +pub fn feature_err( + sess: &Session, + feature: Symbol, + span: impl Into<MultiSpan>, + explain: impl Into<DiagMessage>, +) -> Diag<'_> { + feature_err_issue(sess, feature, span, GateIssue::Language, explain) +} + +/// Construct a diagnostic for a feature gate error. +/// +/// This variant allows you to control whether it is a library or language feature. +/// Almost always, you want to use this for a language feature. If so, prefer `feature_err`. +#[track_caller] +pub fn feature_err_issue( + sess: &Session, + feature: Symbol, + span: impl Into<MultiSpan>, + issue: GateIssue, + explain: impl Into<DiagMessage>, +) -> Diag<'_> { + let span = span.into(); + + // Cancel an earlier warning for this same error, if it exists. + if let Some(span) = span.primary_span() { + if let Some(err) = sess.dcx().steal_non_err(span, StashKey::EarlySyntaxWarning) { + err.cancel() + } + } + + let mut err = sess.dcx().create_err(FeatureGateError { span, explain: explain.into() }); + add_feature_diagnostics_for_issue(&mut err, sess, feature, issue, false, None); + err +} + +/// Construct a future incompatibility diagnostic for a feature gate. +/// +/// This diagnostic is only a warning and *does not cause compilation to fail*. +#[track_caller] +pub fn feature_warn(sess: &Session, feature: Symbol, span: Span, explain: &'static str) { + feature_warn_issue(sess, feature, span, GateIssue::Language, explain); +} + +/// Construct a future incompatibility diagnostic for a feature gate. +/// +/// This diagnostic is only a warning and *does not cause compilation to fail*. +/// +/// This variant allows you to control whether it is a library or language feature. +/// Almost always, you want to use this for a language feature. If so, prefer `feature_warn`. +#[allow(rustc::diagnostic_outside_of_impl)] +#[allow(rustc::untranslatable_diagnostic)] +#[track_caller] +pub fn feature_warn_issue( + sess: &Session, + feature: Symbol, + span: Span, + issue: GateIssue, + explain: &'static str, +) { + let mut err = sess.dcx().struct_span_warn(span, explain); + add_feature_diagnostics_for_issue(&mut err, sess, feature, issue, false, None); + + // Decorate this as a future-incompatibility lint as in rustc_middle::lint::lint_level + let lint = UNSTABLE_SYNTAX_PRE_EXPANSION; + let future_incompatible = lint.future_incompatible.as_ref().unwrap(); + err.is_lint(lint.name_lower(), /* has_future_breakage */ false); + err.warn(lint.desc); + err.note(format!("for more information, see {}", future_incompatible.reference)); + + // A later feature_err call can steal and cancel this warning. + err.stash(span, StashKey::EarlySyntaxWarning); +} + +/// Adds the diagnostics for a feature to an existing error. +/// Must be a language feature! +pub fn add_feature_diagnostics<G: EmissionGuarantee>( + err: &mut Diag<'_, G>, + sess: &Session, + feature: Symbol, +) { + add_feature_diagnostics_for_issue(err, sess, feature, GateIssue::Language, false, None); +} + +/// Adds the diagnostics for a feature to an existing error. +/// +/// This variant allows you to control whether it is a library or language feature. +/// Almost always, you want to use this for a language feature. If so, prefer +/// `add_feature_diagnostics`. +#[allow(rustc::diagnostic_outside_of_impl)] // FIXME +pub fn add_feature_diagnostics_for_issue<G: EmissionGuarantee>( + err: &mut Diag<'_, G>, + sess: &Session, + feature: Symbol, + issue: GateIssue, + feature_from_cli: bool, + inject_span: Option<Span>, +) { + if let Some(n) = find_feature_issue(feature, issue) { + err.subdiagnostic(FeatureDiagnosticForIssue { n }); + } + + // #23973: do not suggest `#![feature(...)]` if we are in beta/stable + if sess.psess.unstable_features.is_nightly_build() { + if feature_from_cli { + err.subdiagnostic(CliFeatureDiagnosticHelp { feature }); + } else if let Some(span) = inject_span { + err.subdiagnostic(FeatureDiagnosticSuggestion { feature, span }); + } else { + err.subdiagnostic(FeatureDiagnosticHelp { feature }); + } + + if sess.opts.unstable_opts.ui_testing { + err.subdiagnostic(SuggestUpgradeCompiler::ui_testing()); + } else if let Some(suggestion) = SuggestUpgradeCompiler::new() { + err.subdiagnostic(suggestion); + } + } +} + +/// Info about a parsing session. +pub struct ParseSess { + dcx: DiagCtxt, + pub unstable_features: UnstableFeatures, + pub config: Cfg, + pub check_config: CheckCfg, + pub edition: Edition, + /// Places where raw identifiers were used. This is used to avoid complaining about idents + /// clashing with keywords in new editions. + pub raw_identifier_spans: AppendOnlyVec<Span>, + /// Places where identifiers that contain invalid Unicode codepoints but that look like they + /// should be. Useful to avoid bad tokenization when encountering emoji. We group them to + /// provide a single error per unique incorrect identifier. + pub bad_unicode_identifiers: Lock<FxIndexMap<Symbol, Vec<Span>>>, + source_map: Lrc<SourceMap>, + pub buffered_lints: Lock<Vec<BufferedEarlyLint>>, + /// Contains the spans of block expressions that could have been incomplete based on the + /// operation token that followed it, but that the parser cannot identify without further + /// analysis. + pub ambiguous_block_expr_parse: Lock<FxIndexMap<Span, Span>>, + pub gated_spans: GatedSpans, + pub symbol_gallery: SymbolGallery, + /// Environment variables accessed during the build and their values when they exist. + pub env_depinfo: Lock<FxIndexSet<(Symbol, Option<Symbol>)>>, + /// File paths accessed during the build. + pub file_depinfo: Lock<FxIndexSet<Symbol>>, + /// Whether cfg(version) should treat the current release as incomplete + pub assume_incomplete_release: bool, + /// Spans passed to `proc_macro::quote_span`. Each span has a numerical + /// identifier represented by its position in the vector. + proc_macro_quoted_spans: AppendOnlyVec<Span>, + /// Used to generate new `AttrId`s. Every `AttrId` is unique. + pub attr_id_generator: AttrIdGenerator, +} + +impl ParseSess { + /// Used for testing. + pub fn new(locale_resources: Vec<&'static str>) -> Self { + let fallback_bundle = fallback_fluent_bundle(locale_resources, false); + let sm = Lrc::new(SourceMap::new(FilePathMapping::empty())); + let emitter = Box::new( + HumanEmitter::new(stderr_destination(ColorConfig::Auto), fallback_bundle) + .sm(Some(Lrc::clone(&sm))), + ); + let dcx = DiagCtxt::new(emitter); + ParseSess::with_dcx(dcx, sm) + } + + pub fn with_dcx(dcx: DiagCtxt, source_map: Lrc<SourceMap>) -> Self { + Self { + dcx, + unstable_features: UnstableFeatures::from_environment(None), + config: Cfg::default(), + check_config: CheckCfg::default(), + edition: ExpnId::root().expn_data().edition, + raw_identifier_spans: Default::default(), + bad_unicode_identifiers: Lock::new(Default::default()), + source_map, + buffered_lints: Lock::new(vec![]), + ambiguous_block_expr_parse: Lock::new(Default::default()), + gated_spans: GatedSpans::default(), + symbol_gallery: SymbolGallery::default(), + env_depinfo: Default::default(), + file_depinfo: Default::default(), + assume_incomplete_release: false, + proc_macro_quoted_spans: Default::default(), + attr_id_generator: AttrIdGenerator::new(), + } + } + + pub fn with_silent_emitter( + locale_resources: Vec<&'static str>, + fatal_note: String, + emit_fatal_diagnostic: bool, + ) -> Self { + let fallback_bundle = fallback_fluent_bundle(locale_resources, false); + let sm = Lrc::new(SourceMap::new(FilePathMapping::empty())); + let emitter = Box::new(HumanEmitter::new( + stderr_destination(ColorConfig::Auto), + Lrc::clone(&fallback_bundle), + )); + let fatal_dcx = DiagCtxt::new(emitter); + let dcx = DiagCtxt::new(Box::new(SilentEmitter { + fallback_bundle, + fatal_dcx, + fatal_note: Some(fatal_note), + emit_fatal_diagnostic, + })) + .disable_warnings(); + ParseSess::with_dcx(dcx, sm) + } + + #[inline] + pub fn source_map(&self) -> &SourceMap { + &self.source_map + } + + pub fn clone_source_map(&self) -> Lrc<SourceMap> { + Lrc::clone(&self.source_map) + } + + pub fn buffer_lint( + &self, + lint: &'static Lint, + span: impl Into<MultiSpan>, + node_id: NodeId, + diagnostic: BuiltinLintDiag, + ) { + self.opt_span_buffer_lint(lint, Some(span.into()), node_id, diagnostic) + } + + pub fn opt_span_buffer_lint( + &self, + lint: &'static Lint, + span: Option<MultiSpan>, + node_id: NodeId, + diagnostic: BuiltinLintDiag, + ) { + self.buffered_lints.with_lock(|buffered_lints| { + buffered_lints.push(BufferedEarlyLint { + span, + node_id, + lint_id: LintId::of(lint), + diagnostic, + }); + }); + } + + pub fn save_proc_macro_span(&self, span: Span) -> usize { + self.proc_macro_quoted_spans.push(span) + } + + pub fn proc_macro_quoted_spans(&self) -> impl Iterator<Item = (usize, Span)> + '_ { + // This is equivalent to `.iter().copied().enumerate()`, but that isn't possible for + // AppendOnlyVec, so we resort to this scheme. + self.proc_macro_quoted_spans.iter_enumerated() + } + + pub fn dcx(&self) -> DiagCtxtHandle<'_> { + self.dcx.handle() + } + + pub fn set_dcx(&mut self, dcx: DiagCtxt) { + self.dcx = dcx; + } +} diff --git a/compiler/rustc_session/src/search_paths.rs b/compiler/rustc_session/src/search_paths.rs new file mode 100644 index 00000000000..c148b09c718 --- /dev/null +++ b/compiler/rustc_session/src/search_paths.rs @@ -0,0 +1,117 @@ +use std::path::{Path, PathBuf}; + +use rustc_macros::{Decodable, Encodable, HashStable_Generic}; +use rustc_target::spec::TargetTuple; + +use crate::EarlyDiagCtxt; +use crate::filesearch::make_target_lib_path; + +#[derive(Clone, Debug)] +pub struct SearchPath { + pub kind: PathKind, + pub dir: PathBuf, + pub files: Vec<SearchPathFile>, +} + +/// The obvious implementation of `SearchPath::files` is a `Vec<PathBuf>`. But +/// it is searched repeatedly by `find_library_crate`, and the searches involve +/// checking the prefix and suffix of the filename of each `PathBuf`. This is +/// doable, but very slow, because it involves calls to `file_name` and +/// `extension` that are themselves slow. +/// +/// This type augments the `PathBuf` with an `String` containing the +/// `PathBuf`'s filename. The prefix and suffix checking is much faster on the +/// `String` than the `PathBuf`. (The filename must be valid UTF-8. If it's +/// not, the entry should be skipped, because all Rust output files are valid +/// UTF-8, and so a non-UTF-8 filename couldn't be one we're looking for.) +#[derive(Clone, Debug)] +pub struct SearchPathFile { + pub path: PathBuf, + pub file_name_str: String, +} + +#[derive(PartialEq, Clone, Copy, Debug, Hash, Eq, Encodable, Decodable, HashStable_Generic)] +pub enum PathKind { + Native, + Crate, + Dependency, + Framework, + ExternFlag, + All, +} + +impl PathKind { + pub fn matches(&self, kind: PathKind) -> bool { + match (self, kind) { + (PathKind::All, _) | (_, PathKind::All) => true, + _ => *self == kind, + } + } +} + +impl SearchPath { + pub fn from_cli_opt( + sysroot: &Path, + triple: &TargetTuple, + early_dcx: &EarlyDiagCtxt, + path: &str, + is_unstable_enabled: bool, + ) -> Self { + let (kind, path) = if let Some(stripped) = path.strip_prefix("native=") { + (PathKind::Native, stripped) + } else if let Some(stripped) = path.strip_prefix("crate=") { + (PathKind::Crate, stripped) + } else if let Some(stripped) = path.strip_prefix("dependency=") { + (PathKind::Dependency, stripped) + } else if let Some(stripped) = path.strip_prefix("framework=") { + (PathKind::Framework, stripped) + } else if let Some(stripped) = path.strip_prefix("all=") { + (PathKind::All, stripped) + } else { + (PathKind::All, path) + }; + let dir = match path.strip_prefix("@RUSTC_BUILTIN") { + Some(stripped) => { + if !is_unstable_enabled { + #[allow(rustc::untranslatable_diagnostic)] // FIXME: make this translatable + early_dcx.early_fatal( + "the `-Z unstable-options` flag must also be passed to \ + enable the use of `@RUSTC_BUILTIN`", + ); + } + + make_target_lib_path(sysroot, triple.tuple()).join("builtin").join(stripped) + } + None => PathBuf::from(path), + }; + if dir.as_os_str().is_empty() { + #[allow(rustc::untranslatable_diagnostic)] // FIXME: make this translatable + early_dcx.early_fatal("empty search path given via `-L`"); + } + + Self::new(kind, dir) + } + + pub fn from_sysroot_and_triple(sysroot: &Path, triple: &str) -> Self { + Self::new(PathKind::All, make_target_lib_path(sysroot, triple)) + } + + pub fn new(kind: PathKind, dir: PathBuf) -> Self { + // Get the files within the directory. + let files = match std::fs::read_dir(&dir) { + Ok(files) => files + .filter_map(|e| { + e.ok().and_then(|e| { + e.file_name().to_str().map(|s| SearchPathFile { + path: e.path(), + file_name_str: s.to_string(), + }) + }) + }) + .collect::<Vec<_>>(), + Err(..) => vec![], + }; + + SearchPath { kind, dir, files } + } +} diff --git a/compiler/rustc_session/src/session.rs b/compiler/rustc_session/src/session.rs new file mode 100644 index 00000000000..d8d6b79974f --- /dev/null +++ b/compiler/rustc_session/src/session.rs @@ -0,0 +1,1543 @@ +use std::any::Any; +use std::ops::{Div, Mul}; +use std::path::{Path, PathBuf}; +use std::str::FromStr; +use std::sync::Arc; +use std::sync::atomic::AtomicBool; +use std::sync::atomic::Ordering::SeqCst; +use std::{env, fmt, io}; + +use rustc_data_structures::flock; +use rustc_data_structures::fx::{FxHashMap, FxIndexSet}; +use rustc_data_structures::jobserver::{self, Client}; +use rustc_data_structures::profiling::{SelfProfiler, SelfProfilerRef}; +use rustc_data_structures::sync::{ + AtomicU64, DynSend, DynSync, Lock, Lrc, MappedReadGuard, ReadGuard, RwLock, +}; +use rustc_errors::annotate_snippet_emitter_writer::AnnotateSnippetEmitter; +use rustc_errors::codes::*; +use rustc_errors::emitter::{ + DynEmitter, HumanEmitter, HumanReadableErrorType, OutputTheme, stderr_destination, +}; +use rustc_errors::json::JsonEmitter; +use rustc_errors::registry::Registry; +use rustc_errors::{ + Diag, DiagCtxt, DiagCtxtHandle, DiagMessage, Diagnostic, ErrorGuaranteed, FatalAbort, + FluentBundle, LazyFallbackBundle, TerminalUrl, fallback_fluent_bundle, +}; +use rustc_macros::HashStable_Generic; +pub use rustc_span::def_id::StableCrateId; +use rustc_span::edition::Edition; +use rustc_span::source_map::{FilePathMapping, SourceMap}; +use rustc_span::{FileNameDisplayPreference, RealFileName, Span, Symbol}; +use rustc_target::asm::InlineAsmArch; +use rustc_target::spec::{ + CodeModel, DebuginfoKind, PanicStrategy, RelocModel, RelroLevel, SanitizerSet, + SmallDataThresholdSupport, SplitDebuginfo, StackProtector, SymbolVisibility, Target, + TargetTuple, TlsModel, +}; + +use crate::code_stats::CodeStats; +pub use crate::code_stats::{DataTypeKind, FieldInfo, FieldKind, SizeKind, VariantInfo}; +use crate::config::{ + self, CoverageLevel, CrateType, DebugInfo, ErrorOutputType, FunctionReturn, Input, + InstrumentCoverage, OptLevel, OutFileName, OutputType, RemapPathScopeComponents, + SwitchWithOptPath, +}; +use crate::parse::{ParseSess, add_feature_diagnostics}; +use crate::search_paths::{PathKind, SearchPath}; +use crate::{errors, filesearch, lint}; + +struct OptimizationFuel { + /// If `-zfuel=crate=n` is specified, initially set to `n`, otherwise `0`. + remaining: u64, + /// We're rejecting all further optimizations. + out_of_fuel: bool, +} + +/// The behavior of the CTFE engine when an error occurs with regards to backtraces. +#[derive(Clone, Copy)] +pub enum CtfeBacktrace { + /// Do nothing special, return the error as usual without a backtrace. + Disabled, + /// Capture a backtrace at the point the error is created and return it in the error + /// (to be printed later if/when the error ever actually gets shown to the user). + Capture, + /// Capture a backtrace at the point the error is created and immediately print it out. + Immediate, +} + +/// New-type wrapper around `usize` for representing limits. Ensures that comparisons against +/// limits are consistent throughout the compiler. +#[derive(Clone, Copy, Debug, HashStable_Generic)] +pub struct Limit(pub usize); + +impl Limit { + /// Create a new limit from a `usize`. + pub fn new(value: usize) -> Self { + Limit(value) + } + + /// Check that `value` is within the limit. Ensures that the same comparisons are used + /// throughout the compiler, as mismatches can cause ICEs, see #72540. + #[inline] + pub fn value_within_limit(&self, value: usize) -> bool { + value <= self.0 + } +} + +impl From<usize> for Limit { + fn from(value: usize) -> Self { + Self::new(value) + } +} + +impl fmt::Display for Limit { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.0.fmt(f) + } +} + +impl Div<usize> for Limit { + type Output = Limit; + + fn div(self, rhs: usize) -> Self::Output { + Limit::new(self.0 / rhs) + } +} + +impl Mul<usize> for Limit { + type Output = Limit; + + fn mul(self, rhs: usize) -> Self::Output { + Limit::new(self.0 * rhs) + } +} + +impl rustc_errors::IntoDiagArg for Limit { + fn into_diag_arg(self) -> rustc_errors::DiagArgValue { + self.to_string().into_diag_arg() + } +} + +#[derive(Clone, Copy, Debug, HashStable_Generic)] +pub struct Limits { + /// The maximum recursion limit for potentially infinitely recursive + /// operations such as auto-dereference and monomorphization. + pub recursion_limit: Limit, + /// The size at which the `large_assignments` lint starts + /// being emitted. + pub move_size_limit: Limit, + /// The maximum length of types during monomorphization. + pub type_length_limit: Limit, +} + +pub struct CompilerIO { + pub input: Input, + pub output_dir: Option<PathBuf>, + pub output_file: Option<OutFileName>, + pub temps_dir: Option<PathBuf>, +} + +pub trait LintStoreMarker: Any + DynSync + DynSend {} + +/// Represents the data associated with a compilation +/// session for a single crate. +pub struct Session { + pub target: Target, + pub host: Target, + pub opts: config::Options, + pub host_tlib_path: Lrc<SearchPath>, + pub target_tlib_path: Lrc<SearchPath>, + pub psess: ParseSess, + pub sysroot: PathBuf, + /// Input, input file path and output file path to this compilation process. + pub io: CompilerIO, + + incr_comp_session: RwLock<IncrCompSession>, + + /// Used by `-Z self-profile`. + pub prof: SelfProfilerRef, + + /// Data about code being compiled, gathered during compilation. + pub code_stats: CodeStats, + + /// Tracks fuel info if `-zfuel=crate=n` is specified. + optimization_fuel: Lock<OptimizationFuel>, + + /// Always set to zero and incremented so that we can print fuel expended by a crate. + pub print_fuel: AtomicU64, + + /// Loaded up early on in the initialization of this `Session` to avoid + /// false positives about a job server in our environment. + pub jobserver: Client, + + /// This only ever stores a `LintStore` but we don't want a dependency on that type here. + pub lint_store: Option<Lrc<dyn LintStoreMarker>>, + + /// Should be set if any lints are registered in `lint_store`. + pub registered_lints: bool, + + /// Cap lint level specified by a driver specifically. + pub driver_lint_caps: FxHashMap<lint::LintId, lint::Level>, + + /// Tracks the current behavior of the CTFE engine when an error occurs. + /// Options range from returning the error without a backtrace to returning an error + /// and immediately printing the backtrace to stderr. + /// The `Lock` is only used by miri to allow setting `ctfe_backtrace` after analysis when + /// `MIRI_BACKTRACE` is set. This makes it only apply to miri's errors and not to all CTFE + /// errors. + pub ctfe_backtrace: Lock<CtfeBacktrace>, + + /// This tracks where `-Zunleash-the-miri-inside-of-you` was used to get around a + /// const check, optionally with the relevant feature gate. We use this to + /// warn about unleashing, but with a single diagnostic instead of dozens that + /// drown everything else in noise. + miri_unleashed_features: Lock<Vec<(Span, Option<Symbol>)>>, + + /// Architecture to use for interpreting asm!. + pub asm_arch: Option<InlineAsmArch>, + + /// Set of enabled features for the current target. + pub target_features: FxIndexSet<Symbol>, + + /// Set of enabled features for the current target, including unstable ones. + pub unstable_target_features: FxIndexSet<Symbol>, + + /// The version of the rustc process, possibly including a commit hash and description. + pub cfg_version: &'static str, + + /// The inner atomic value is set to true when a feature marked as `internal` is + /// enabled. Makes it so that "please report a bug" is hidden, as ICEs with + /// internal features are wontfix, and they are usually the cause of the ICEs. + /// None signifies that this is not tracked. + pub using_internal_features: Arc<AtomicBool>, + + /// All commandline args used to invoke the compiler, with @file args fully expanded. + /// This will only be used within debug info, e.g. in the pdb file on windows + /// This is mainly useful for other tools that reads that debuginfo to figure out + /// how to call the compiler with the same arguments. + pub expanded_args: Vec<String>, +} + +#[derive(PartialEq, Eq, PartialOrd, Ord)] +pub enum MetadataKind { + None, + Uncompressed, + Compressed, +} + +#[derive(Clone, Copy)] +pub enum CodegenUnits { + /// Specified by the user. In this case we try fairly hard to produce the + /// number of CGUs requested. + User(usize), + + /// A default value, i.e. not specified by the user. In this case we take + /// more liberties about CGU formation, e.g. avoid producing very small + /// CGUs. + Default(usize), +} + +impl CodegenUnits { + pub fn as_usize(self) -> usize { + match self { + CodegenUnits::User(n) => n, + CodegenUnits::Default(n) => n, + } + } +} + +impl Session { + pub fn miri_unleashed_feature(&self, span: Span, feature_gate: Option<Symbol>) { + self.miri_unleashed_features.lock().push((span, feature_gate)); + } + + pub fn local_crate_source_file(&self) -> Option<RealFileName> { + Some(self.source_map().path_mapping().to_real_filename(self.io.input.opt_path()?)) + } + + fn check_miri_unleashed_features(&self) -> Option<ErrorGuaranteed> { + let mut guar = None; + let unleashed_features = self.miri_unleashed_features.lock(); + if !unleashed_features.is_empty() { + let mut must_err = false; + // Create a diagnostic pointing at where things got unleashed. + self.dcx().emit_warn(errors::SkippingConstChecks { + unleashed_features: unleashed_features + .iter() + .map(|(span, gate)| { + gate.map(|gate| { + must_err = true; + errors::UnleashedFeatureHelp::Named { span: *span, gate } + }) + .unwrap_or(errors::UnleashedFeatureHelp::Unnamed { span: *span }) + }) + .collect(), + }); + + // If we should err, make sure we did. + if must_err && self.dcx().has_errors().is_none() { + // We have skipped a feature gate, and not run into other errors... reject. + guar = Some(self.dcx().emit_err(errors::NotCircumventFeature)); + } + } + guar + } + + /// Invoked all the way at the end to finish off diagnostics printing. + pub fn finish_diagnostics(&self, registry: &Registry) -> Option<ErrorGuaranteed> { + let mut guar = None; + guar = guar.or(self.check_miri_unleashed_features()); + guar = guar.or(self.dcx().emit_stashed_diagnostics()); + self.dcx().print_error_count(registry); + if self.opts.json_future_incompat { + self.dcx().emit_future_breakage_report(); + } + guar + } + + /// Returns true if the crate is a testing one. + pub fn is_test_crate(&self) -> bool { + self.opts.test + } + + /// `feature` must be a language feature. + #[track_caller] + pub fn create_feature_err<'a>(&'a self, err: impl Diagnostic<'a>, feature: Symbol) -> Diag<'a> { + let mut err = self.dcx().create_err(err); + if err.code.is_none() { + #[allow(rustc::diagnostic_outside_of_impl)] + err.code(E0658); + } + add_feature_diagnostics(&mut err, self, feature); + err + } + + /// Record the fact that we called `trimmed_def_paths`, and do some + /// checking about whether its cost was justified. + pub fn record_trimmed_def_paths(&self) { + if self.opts.unstable_opts.print_type_sizes + || self.opts.unstable_opts.query_dep_graph + || self.opts.unstable_opts.dump_mir.is_some() + || self.opts.unstable_opts.unpretty.is_some() + || self.opts.output_types.contains_key(&OutputType::Mir) + || std::env::var_os("RUSTC_LOG").is_some() + { + return; + } + + self.dcx().set_must_produce_diag() + } + + #[inline] + pub fn dcx(&self) -> DiagCtxtHandle<'_> { + self.psess.dcx() + } + + #[inline] + pub fn source_map(&self) -> &SourceMap { + self.psess.source_map() + } + + /// Returns `true` if internal lints should be added to the lint store - i.e. if + /// `-Zunstable-options` is provided and this isn't rustdoc (internal lints can trigger errors + /// to be emitted under rustdoc). + pub fn enable_internal_lints(&self) -> bool { + self.unstable_options() && !self.opts.actually_rustdoc + } + + pub fn instrument_coverage(&self) -> bool { + self.opts.cg.instrument_coverage() != InstrumentCoverage::No + } + + pub fn instrument_coverage_branch(&self) -> bool { + self.instrument_coverage() + && self.opts.unstable_opts.coverage_options.level >= CoverageLevel::Branch + } + + pub fn instrument_coverage_condition(&self) -> bool { + self.instrument_coverage() + && self.opts.unstable_opts.coverage_options.level >= CoverageLevel::Condition + } + + pub fn instrument_coverage_mcdc(&self) -> bool { + self.instrument_coverage() + && self.opts.unstable_opts.coverage_options.level >= CoverageLevel::Mcdc + } + + /// True if `-Zcoverage-options=no-mir-spans` was passed. + pub fn coverage_no_mir_spans(&self) -> bool { + self.opts.unstable_opts.coverage_options.no_mir_spans + } + + pub fn is_sanitizer_cfi_enabled(&self) -> bool { + self.opts.unstable_opts.sanitizer.contains(SanitizerSet::CFI) + } + + pub fn is_sanitizer_cfi_canonical_jump_tables_disabled(&self) -> bool { + self.opts.unstable_opts.sanitizer_cfi_canonical_jump_tables == Some(false) + } + + pub fn is_sanitizer_cfi_canonical_jump_tables_enabled(&self) -> bool { + self.opts.unstable_opts.sanitizer_cfi_canonical_jump_tables == Some(true) + } + + pub fn is_sanitizer_cfi_generalize_pointers_enabled(&self) -> bool { + self.opts.unstable_opts.sanitizer_cfi_generalize_pointers == Some(true) + } + + pub fn is_sanitizer_cfi_normalize_integers_enabled(&self) -> bool { + self.opts.unstable_opts.sanitizer_cfi_normalize_integers == Some(true) + } + + pub fn is_sanitizer_kcfi_enabled(&self) -> bool { + self.opts.unstable_opts.sanitizer.contains(SanitizerSet::KCFI) + } + + pub fn is_split_lto_unit_enabled(&self) -> bool { + self.opts.unstable_opts.split_lto_unit == Some(true) + } + + /// Check whether this compile session and crate type use static crt. + pub fn crt_static(&self, crate_type: Option<CrateType>) -> bool { + if !self.target.crt_static_respected { + // If the target does not opt in to crt-static support, use its default. + return self.target.crt_static_default; + } + + let requested_features = self.opts.cg.target_feature.split(','); + let found_negative = requested_features.clone().any(|r| r == "-crt-static"); + let found_positive = requested_features.clone().any(|r| r == "+crt-static"); + + // JUSTIFICATION: necessary use of crate_types directly (see FIXME below) + #[allow(rustc::bad_opt_access)] + if found_positive || found_negative { + found_positive + } else if crate_type == Some(CrateType::ProcMacro) + || crate_type == None && self.opts.crate_types.contains(&CrateType::ProcMacro) + { + // FIXME: When crate_type is not available, + // we use compiler options to determine the crate_type. + // We can't check `#![crate_type = "proc-macro"]` here. + false + } else { + self.target.crt_static_default + } + } + + pub fn is_wasi_reactor(&self) -> bool { + self.target.options.os == "wasi" + && matches!( + self.opts.unstable_opts.wasi_exec_model, + Some(config::WasiExecModel::Reactor) + ) + } + + /// Returns `true` if the target can use the current split debuginfo configuration. + pub fn target_can_use_split_dwarf(&self) -> bool { + self.target.debuginfo_kind == DebuginfoKind::Dwarf + } + + pub fn generate_proc_macro_decls_symbol(&self, stable_crate_id: StableCrateId) -> String { + format!("__rustc_proc_macro_decls_{:08x}__", stable_crate_id.as_u64()) + } + + pub fn target_filesearch(&self, kind: PathKind) -> filesearch::FileSearch<'_> { + filesearch::FileSearch::new(&self.opts.search_paths, &self.target_tlib_path, kind) + } + pub fn host_filesearch(&self, kind: PathKind) -> filesearch::FileSearch<'_> { + filesearch::FileSearch::new(&self.opts.search_paths, &self.host_tlib_path, kind) + } + + /// Returns a list of directories where target-specific tool binaries are located. Some fallback + /// directories are also returned, for example if `--sysroot` is used but tools are missing + /// (#125246): we also add the bin directories to the sysroot where rustc is located. + pub fn get_tools_search_paths(&self, self_contained: bool) -> Vec<PathBuf> { + let bin_path = filesearch::make_target_bin_path(&self.sysroot, config::host_tuple()); + let fallback_sysroot_paths = filesearch::sysroot_candidates() + .into_iter() + // Ignore sysroot candidate if it was the same as the sysroot path we just used. + .filter(|sysroot| *sysroot != self.sysroot) + .map(|sysroot| filesearch::make_target_bin_path(&sysroot, config::host_tuple())); + let search_paths = std::iter::once(bin_path).chain(fallback_sysroot_paths); + + if self_contained { + // The self-contained tools are expected to be e.g. in `bin/self-contained` in the + // sysroot's `rustlib` path, so we add such a subfolder to the bin path, and the + // fallback paths. + search_paths.flat_map(|path| [path.clone(), path.join("self-contained")]).collect() + } else { + search_paths.collect() + } + } + + pub fn init_incr_comp_session(&self, session_dir: PathBuf, lock_file: flock::Lock) { + let mut incr_comp_session = self.incr_comp_session.borrow_mut(); + + if let IncrCompSession::NotInitialized = *incr_comp_session { + } else { + panic!("Trying to initialize IncrCompSession `{:?}`", *incr_comp_session) + } + + *incr_comp_session = + IncrCompSession::Active { session_directory: session_dir, _lock_file: lock_file }; + } + + pub fn finalize_incr_comp_session(&self, new_directory_path: PathBuf) { + let mut incr_comp_session = self.incr_comp_session.borrow_mut(); + + if let IncrCompSession::Active { .. } = *incr_comp_session { + } else { + panic!("trying to finalize `IncrCompSession` `{:?}`", *incr_comp_session); + } + + // Note: this will also drop the lock file, thus unlocking the directory. + *incr_comp_session = IncrCompSession::Finalized { session_directory: new_directory_path }; + } + + pub fn mark_incr_comp_session_as_invalid(&self) { + let mut incr_comp_session = self.incr_comp_session.borrow_mut(); + + let session_directory = match *incr_comp_session { + IncrCompSession::Active { ref session_directory, .. } => session_directory.clone(), + IncrCompSession::InvalidBecauseOfErrors { .. } => return, + _ => panic!("trying to invalidate `IncrCompSession` `{:?}`", *incr_comp_session), + }; + + // Note: this will also drop the lock file, thus unlocking the directory. + *incr_comp_session = IncrCompSession::InvalidBecauseOfErrors { session_directory }; + } + + pub fn incr_comp_session_dir(&self) -> MappedReadGuard<'_, PathBuf> { + let incr_comp_session = self.incr_comp_session.borrow(); + ReadGuard::map(incr_comp_session, |incr_comp_session| match *incr_comp_session { + IncrCompSession::NotInitialized => panic!( + "trying to get session directory from `IncrCompSession`: {:?}", + *incr_comp_session, + ), + IncrCompSession::Active { ref session_directory, .. } + | IncrCompSession::Finalized { ref session_directory } + | IncrCompSession::InvalidBecauseOfErrors { ref session_directory } => { + session_directory + } + }) + } + + pub fn incr_comp_session_dir_opt(&self) -> Option<MappedReadGuard<'_, PathBuf>> { + self.opts.incremental.as_ref().map(|_| self.incr_comp_session_dir()) + } + + /// We want to know if we're allowed to do an optimization for crate foo from -z fuel=foo=n. + /// This expends fuel if applicable, and records fuel if applicable. + pub fn consider_optimizing( + &self, + get_crate_name: impl Fn() -> Symbol, + msg: impl Fn() -> String, + ) -> bool { + let mut ret = true; + if let Some((ref c, _)) = self.opts.unstable_opts.fuel { + if c == get_crate_name().as_str() { + assert_eq!(self.threads(), 1); + let mut fuel = self.optimization_fuel.lock(); + ret = fuel.remaining != 0; + if fuel.remaining == 0 && !fuel.out_of_fuel { + if self.dcx().can_emit_warnings() { + // We only call `msg` in case we can actually emit warnings. + // Otherwise, this could cause a `must_produce_diag` ICE + // (issue #79546). + self.dcx().emit_warn(errors::OptimisationFuelExhausted { msg: msg() }); + } + fuel.out_of_fuel = true; + } else if fuel.remaining > 0 { + fuel.remaining -= 1; + } + } + } + if let Some(ref c) = self.opts.unstable_opts.print_fuel { + if c == get_crate_name().as_str() { + assert_eq!(self.threads(), 1); + self.print_fuel.fetch_add(1, SeqCst); + } + } + ret + } + + /// Is this edition 2015? + pub fn is_rust_2015(&self) -> bool { + self.edition().is_rust_2015() + } + + /// Are we allowed to use features from the Rust 2018 edition? + pub fn at_least_rust_2018(&self) -> bool { + self.edition().at_least_rust_2018() + } + + /// Are we allowed to use features from the Rust 2021 edition? + pub fn at_least_rust_2021(&self) -> bool { + self.edition().at_least_rust_2021() + } + + /// Are we allowed to use features from the Rust 2024 edition? + pub fn at_least_rust_2024(&self) -> bool { + self.edition().at_least_rust_2024() + } + + /// Returns `true` if we should use the PLT for shared library calls. + pub fn needs_plt(&self) -> bool { + // Check if the current target usually wants PLT to be enabled. + // The user can use the command line flag to override it. + let want_plt = self.target.plt_by_default; + + let dbg_opts = &self.opts.unstable_opts; + + let relro_level = self.opts.cg.relro_level.unwrap_or(self.target.relro_level); + + // Only enable this optimization by default if full relro is also enabled. + // In this case, lazy binding was already unavailable, so nothing is lost. + // This also ensures `-Wl,-z,now` is supported by the linker. + let full_relro = RelroLevel::Full == relro_level; + + // If user didn't explicitly forced us to use / skip the PLT, + // then use it unless the target doesn't want it by default or the full relro forces it on. + dbg_opts.plt.unwrap_or(want_plt || !full_relro) + } + + /// Checks if LLVM lifetime markers should be emitted. + pub fn emit_lifetime_markers(&self) -> bool { + self.opts.optimize != config::OptLevel::No + // AddressSanitizer and KernelAddressSanitizer uses lifetimes to detect use after scope bugs. + // MemorySanitizer uses lifetimes to detect use of uninitialized stack variables. + // HWAddressSanitizer will use lifetimes to detect use after scope bugs in the future. + || self.opts.unstable_opts.sanitizer.intersects(SanitizerSet::ADDRESS | SanitizerSet::KERNELADDRESS | SanitizerSet::MEMORY | SanitizerSet::HWADDRESS) + } + + pub fn diagnostic_width(&self) -> usize { + let default_column_width = 140; + if let Some(width) = self.opts.diagnostic_width { + width + } else if self.opts.unstable_opts.ui_testing { + default_column_width + } else { + termize::dimensions().map_or(default_column_width, |(w, _)| w) + } + } + + /// Returns the default symbol visibility. + pub fn default_visibility(&self) -> SymbolVisibility { + self.opts + .unstable_opts + .default_visibility + .or(self.target.options.default_visibility) + .unwrap_or(SymbolVisibility::Interposable) + } +} + +// JUSTIFICATION: defn of the suggested wrapper fns +#[allow(rustc::bad_opt_access)] +impl Session { + pub fn verbose_internals(&self) -> bool { + self.opts.unstable_opts.verbose_internals + } + + pub fn print_llvm_stats(&self) -> bool { + self.opts.unstable_opts.print_codegen_stats + } + + pub fn verify_llvm_ir(&self) -> bool { + self.opts.unstable_opts.verify_llvm_ir || option_env!("RUSTC_VERIFY_LLVM_IR").is_some() + } + + pub fn binary_dep_depinfo(&self) -> bool { + self.opts.unstable_opts.binary_dep_depinfo + } + + pub fn mir_opt_level(&self) -> usize { + self.opts + .unstable_opts + .mir_opt_level + .unwrap_or_else(|| if self.opts.optimize != OptLevel::No { 2 } else { 1 }) + } + + /// Calculates the flavor of LTO to use for this compilation. + pub fn lto(&self) -> config::Lto { + // If our target has codegen requirements ignore the command line + if self.target.requires_lto { + return config::Lto::Fat; + } + + // If the user specified something, return that. If they only said `-C + // lto` and we've for whatever reason forced off ThinLTO via the CLI, + // then ensure we can't use a ThinLTO. + match self.opts.cg.lto { + config::LtoCli::Unspecified => { + // The compiler was invoked without the `-Clto` flag. Fall + // through to the default handling + } + config::LtoCli::No => { + // The user explicitly opted out of any kind of LTO + return config::Lto::No; + } + config::LtoCli::Yes | config::LtoCli::Fat | config::LtoCli::NoParam => { + // All of these mean fat LTO + return config::Lto::Fat; + } + config::LtoCli::Thin => { + // The user explicitly asked for ThinLTO + return config::Lto::Thin; + } + } + + // Ok at this point the target doesn't require anything and the user + // hasn't asked for anything. Our next decision is whether or not + // we enable "auto" ThinLTO where we use multiple codegen units and + // then do ThinLTO over those codegen units. The logic below will + // either return `No` or `ThinLocal`. + + // If processing command line options determined that we're incompatible + // with ThinLTO (e.g., `-C lto --emit llvm-ir`) then return that option. + if self.opts.cli_forced_local_thinlto_off { + return config::Lto::No; + } + + // If `-Z thinlto` specified process that, but note that this is mostly + // a deprecated option now that `-C lto=thin` exists. + if let Some(enabled) = self.opts.unstable_opts.thinlto { + if enabled { + return config::Lto::ThinLocal; + } else { + return config::Lto::No; + } + } + + // If there's only one codegen unit and LTO isn't enabled then there's + // no need for ThinLTO so just return false. + if self.codegen_units().as_usize() == 1 { + return config::Lto::No; + } + + // Now we're in "defaults" territory. By default we enable ThinLTO for + // optimized compiles (anything greater than O0). + match self.opts.optimize { + config::OptLevel::No => config::Lto::No, + _ => config::Lto::ThinLocal, + } + } + + /// Returns the panic strategy for this compile session. If the user explicitly selected one + /// using '-C panic', use that, otherwise use the panic strategy defined by the target. + pub fn panic_strategy(&self) -> PanicStrategy { + self.opts.cg.panic.unwrap_or(self.target.panic_strategy) + } + + pub fn fewer_names(&self) -> bool { + if let Some(fewer_names) = self.opts.unstable_opts.fewer_names { + fewer_names + } else { + let more_names = self.opts.output_types.contains_key(&OutputType::LlvmAssembly) + || self.opts.output_types.contains_key(&OutputType::Bitcode) + // AddressSanitizer and MemorySanitizer use alloca name when reporting an issue. + || self.opts.unstable_opts.sanitizer.intersects(SanitizerSet::ADDRESS | SanitizerSet::MEMORY); + !more_names + } + } + + pub fn unstable_options(&self) -> bool { + self.opts.unstable_opts.unstable_options + } + + pub fn is_nightly_build(&self) -> bool { + self.opts.unstable_features.is_nightly_build() + } + + pub fn overflow_checks(&self) -> bool { + self.opts.cg.overflow_checks.unwrap_or(self.opts.debug_assertions) + } + + pub fn ub_checks(&self) -> bool { + self.opts.unstable_opts.ub_checks.unwrap_or(self.opts.debug_assertions) + } + + pub fn relocation_model(&self) -> RelocModel { + self.opts.cg.relocation_model.unwrap_or(self.target.relocation_model) + } + + pub fn code_model(&self) -> Option<CodeModel> { + self.opts.cg.code_model.or(self.target.code_model) + } + + pub fn tls_model(&self) -> TlsModel { + self.opts.unstable_opts.tls_model.unwrap_or(self.target.tls_model) + } + + pub fn direct_access_external_data(&self) -> Option<bool> { + self.opts + .unstable_opts + .direct_access_external_data + .or(self.target.direct_access_external_data) + } + + pub fn split_debuginfo(&self) -> SplitDebuginfo { + self.opts.cg.split_debuginfo.unwrap_or(self.target.split_debuginfo) + } + + pub fn stack_protector(&self) -> StackProtector { + if self.target.options.supports_stack_protector { + self.opts.unstable_opts.stack_protector + } else { + StackProtector::None + } + } + + pub fn must_emit_unwind_tables(&self) -> bool { + // This is used to control the emission of the `uwtable` attribute on + // LLVM functions. + // + // Unwind tables are needed when compiling with `-C panic=unwind`, but + // LLVM won't omit unwind tables unless the function is also marked as + // `nounwind`, so users are allowed to disable `uwtable` emission. + // Historically rustc always emits `uwtable` attributes by default, so + // even they can be disabled, they're still emitted by default. + // + // On some targets (including windows), however, exceptions include + // other events such as illegal instructions, segfaults, etc. This means + // that on Windows we end up still needing unwind tables even if the `-C + // panic=abort` flag is passed. + // + // You can also find more info on why Windows needs unwind tables in: + // https://bugzilla.mozilla.org/show_bug.cgi?id=1302078 + // + // If a target requires unwind tables, then they must be emitted. + // Otherwise, we can defer to the `-C force-unwind-tables=<yes/no>` + // value, if it is provided, or disable them, if not. + self.target.requires_uwtable + || self.opts.cg.force_unwind_tables.unwrap_or( + self.panic_strategy() == PanicStrategy::Unwind || self.target.default_uwtable, + ) + } + + /// Returns the number of query threads that should be used for this + /// compilation + #[inline] + pub fn threads(&self) -> usize { + self.opts.unstable_opts.threads + } + + /// Returns the number of codegen units that should be used for this + /// compilation + pub fn codegen_units(&self) -> CodegenUnits { + if let Some(n) = self.opts.cli_forced_codegen_units { + return CodegenUnits::User(n); + } + if let Some(n) = self.target.default_codegen_units { + return CodegenUnits::Default(n as usize); + } + + // If incremental compilation is turned on, we default to a high number + // codegen units in order to reduce the "collateral damage" small + // changes cause. + if self.opts.incremental.is_some() { + return CodegenUnits::Default(256); + } + + // Why is 16 codegen units the default all the time? + // + // The main reason for enabling multiple codegen units by default is to + // leverage the ability for the codegen backend to do codegen and + // optimization in parallel. This allows us, especially for large crates, to + // make good use of all available resources on the machine once we've + // hit that stage of compilation. Large crates especially then often + // take a long time in codegen/optimization and this helps us amortize that + // cost. + // + // Note that a high number here doesn't mean that we'll be spawning a + // large number of threads in parallel. The backend of rustc contains + // global rate limiting through the `jobserver` crate so we'll never + // overload the system with too much work, but rather we'll only be + // optimizing when we're otherwise cooperating with other instances of + // rustc. + // + // Rather a high number here means that we should be able to keep a lot + // of idle cpus busy. By ensuring that no codegen unit takes *too* long + // to build we'll be guaranteed that all cpus will finish pretty closely + // to one another and we should make relatively optimal use of system + // resources + // + // Note that the main cost of codegen units is that it prevents LLVM + // from inlining across codegen units. Users in general don't have a lot + // of control over how codegen units are split up so it's our job in the + // compiler to ensure that undue performance isn't lost when using + // codegen units (aka we can't require everyone to slap `#[inline]` on + // everything). + // + // If we're compiling at `-O0` then the number doesn't really matter too + // much because performance doesn't matter and inlining is ok to lose. + // In debug mode we just want to try to guarantee that no cpu is stuck + // doing work that could otherwise be farmed to others. + // + // In release mode, however (O1 and above) performance does indeed + // matter! To recover the loss in performance due to inlining we'll be + // enabling ThinLTO by default (the function for which is just below). + // This will ensure that we recover any inlining wins we otherwise lost + // through codegen unit partitioning. + // + // --- + // + // Ok that's a lot of words but the basic tl;dr; is that we want a high + // number here -- but not too high. Additionally we're "safe" to have it + // always at the same number at all optimization levels. + // + // As a result 16 was chosen here! Mostly because it was a power of 2 + // and most benchmarks agreed it was roughly a local optimum. Not very + // scientific. + CodegenUnits::Default(16) + } + + pub fn teach(&self, code: ErrCode) -> bool { + self.opts.unstable_opts.teach && self.dcx().must_teach(code) + } + + pub fn edition(&self) -> Edition { + self.opts.edition + } + + pub fn link_dead_code(&self) -> bool { + self.opts.cg.link_dead_code.unwrap_or(false) + } + + pub fn filename_display_preference( + &self, + scope: RemapPathScopeComponents, + ) -> FileNameDisplayPreference { + assert!( + scope.bits().count_ones() == 1, + "one and only one scope should be passed to `Session::filename_display_preference`" + ); + if self.opts.unstable_opts.remap_path_scope.contains(scope) { + FileNameDisplayPreference::Remapped + } else { + FileNameDisplayPreference::Local + } + } +} + +// JUSTIFICATION: part of session construction +#[allow(rustc::bad_opt_access)] +fn default_emitter( + sopts: &config::Options, + registry: rustc_errors::registry::Registry, + source_map: Lrc<SourceMap>, + bundle: Option<Lrc<FluentBundle>>, + fallback_bundle: LazyFallbackBundle, +) -> Box<DynEmitter> { + let macro_backtrace = sopts.unstable_opts.macro_backtrace; + let track_diagnostics = sopts.unstable_opts.track_diagnostics; + let terminal_url = match sopts.unstable_opts.terminal_urls { + TerminalUrl::Auto => { + match (std::env::var("COLORTERM").as_deref(), std::env::var("TERM").as_deref()) { + (Ok("truecolor"), Ok("xterm-256color")) + if sopts.unstable_features.is_nightly_build() => + { + TerminalUrl::Yes + } + _ => TerminalUrl::No, + } + } + t => t, + }; + match sopts.error_format { + config::ErrorOutputType::HumanReadable(kind, color_config) => { + let short = kind.short(); + + if let HumanReadableErrorType::AnnotateSnippet = kind { + let emitter = AnnotateSnippetEmitter::new( + Some(source_map), + bundle, + fallback_bundle, + short, + macro_backtrace, + ); + Box::new(emitter.ui_testing(sopts.unstable_opts.ui_testing)) + } else { + let emitter = HumanEmitter::new(stderr_destination(color_config), fallback_bundle) + .fluent_bundle(bundle) + .sm(Some(source_map)) + .short_message(short) + .teach(sopts.unstable_opts.teach) + .diagnostic_width(sopts.diagnostic_width) + .macro_backtrace(macro_backtrace) + .track_diagnostics(track_diagnostics) + .terminal_url(terminal_url) + .theme(if let HumanReadableErrorType::Unicode = kind { + OutputTheme::Unicode + } else { + OutputTheme::Ascii + }) + .ignored_directories_in_source_blocks( + sopts.unstable_opts.ignore_directory_in_diagnostics_source_blocks.clone(), + ); + Box::new(emitter.ui_testing(sopts.unstable_opts.ui_testing)) + } + } + config::ErrorOutputType::Json { pretty, json_rendered, color_config } => Box::new( + JsonEmitter::new( + Box::new(io::BufWriter::new(io::stderr())), + source_map, + fallback_bundle, + pretty, + json_rendered, + color_config, + ) + .registry(Some(registry)) + .fluent_bundle(bundle) + .ui_testing(sopts.unstable_opts.ui_testing) + .ignored_directories_in_source_blocks( + sopts.unstable_opts.ignore_directory_in_diagnostics_source_blocks.clone(), + ) + .diagnostic_width(sopts.diagnostic_width) + .macro_backtrace(macro_backtrace) + .track_diagnostics(track_diagnostics) + .terminal_url(terminal_url), + ), + } +} + +// JUSTIFICATION: literally session construction +#[allow(rustc::bad_opt_access)] +#[allow(rustc::untranslatable_diagnostic)] // FIXME: make this translatable +pub fn build_session( + early_dcx: EarlyDiagCtxt, + sopts: config::Options, + io: CompilerIO, + bundle: Option<Lrc<rustc_errors::FluentBundle>>, + registry: rustc_errors::registry::Registry, + fluent_resources: Vec<&'static str>, + driver_lint_caps: FxHashMap<lint::LintId, lint::Level>, + target: Target, + sysroot: PathBuf, + cfg_version: &'static str, + ice_file: Option<PathBuf>, + using_internal_features: Arc<AtomicBool>, + expanded_args: Vec<String>, +) -> Session { + // FIXME: This is not general enough to make the warning lint completely override + // normal diagnostic warnings, since the warning lint can also be denied and changed + // later via the source code. + let warnings_allow = sopts + .lint_opts + .iter() + .rfind(|&(key, _)| *key == "warnings") + .is_some_and(|&(_, level)| level == lint::Allow); + let cap_lints_allow = sopts.lint_cap.is_some_and(|cap| cap == lint::Allow); + let can_emit_warnings = !(warnings_allow || cap_lints_allow); + + let host_triple = TargetTuple::from_tuple(config::host_tuple()); + let (host, target_warnings) = Target::search(&host_triple, &sysroot).unwrap_or_else(|e| { + early_dcx.early_fatal(format!("Error loading host specification: {e}")) + }); + for warning in target_warnings.warning_messages() { + early_dcx.early_warn(warning) + } + + let fallback_bundle = fallback_fluent_bundle( + fluent_resources, + sopts.unstable_opts.translate_directionality_markers, + ); + let source_map = rustc_span::source_map::get_source_map().unwrap(); + let emitter = + default_emitter(&sopts, registry, Lrc::clone(&source_map), bundle, fallback_bundle); + + let mut dcx = + DiagCtxt::new(emitter).with_flags(sopts.unstable_opts.dcx_flags(can_emit_warnings)); + if let Some(ice_file) = ice_file { + dcx = dcx.with_ice_file(ice_file); + } + + // Now that the proper handler has been constructed, drop early_dcx to + // prevent accidental use. + drop(early_dcx); + + let self_profiler = if let SwitchWithOptPath::Enabled(ref d) = sopts.unstable_opts.self_profile + { + let directory = + if let Some(ref directory) = d { directory } else { std::path::Path::new(".") }; + + let profiler = SelfProfiler::new( + directory, + sopts.crate_name.as_deref(), + sopts.unstable_opts.self_profile_events.as_deref(), + &sopts.unstable_opts.self_profile_counter, + ); + match profiler { + Ok(profiler) => Some(Arc::new(profiler)), + Err(e) => { + dcx.handle().emit_warn(errors::FailedToCreateProfiler { err: e.to_string() }); + None + } + } + } else { + None + }; + + let mut psess = ParseSess::with_dcx(dcx, source_map); + psess.assume_incomplete_release = sopts.unstable_opts.assume_incomplete_release; + + let host_triple = config::host_tuple(); + let target_triple = sopts.target_triple.tuple(); + let host_tlib_path = Lrc::new(SearchPath::from_sysroot_and_triple(&sysroot, host_triple)); + let target_tlib_path = if host_triple == target_triple { + // Use the same `SearchPath` if host and target triple are identical to avoid unnecessary + // rescanning of the target lib path and an unnecessary allocation. + Lrc::clone(&host_tlib_path) + } else { + Lrc::new(SearchPath::from_sysroot_and_triple(&sysroot, target_triple)) + }; + + let optimization_fuel = Lock::new(OptimizationFuel { + remaining: sopts.unstable_opts.fuel.as_ref().map_or(0, |&(_, i)| i), + out_of_fuel: false, + }); + let print_fuel = AtomicU64::new(0); + + let prof = SelfProfilerRef::new( + self_profiler, + sopts.unstable_opts.time_passes.then(|| sopts.unstable_opts.time_passes_format), + ); + + let ctfe_backtrace = Lock::new(match env::var("RUSTC_CTFE_BACKTRACE") { + Ok(ref val) if val == "immediate" => CtfeBacktrace::Immediate, + Ok(ref val) if val != "0" => CtfeBacktrace::Capture, + _ => CtfeBacktrace::Disabled, + }); + + let asm_arch = if target.allow_asm { InlineAsmArch::from_str(&target.arch).ok() } else { None }; + + let sess = Session { + target, + host, + opts: sopts, + host_tlib_path, + target_tlib_path, + psess, + sysroot, + io, + incr_comp_session: RwLock::new(IncrCompSession::NotInitialized), + prof, + code_stats: Default::default(), + optimization_fuel, + print_fuel, + jobserver: jobserver::client(), + lint_store: None, + registered_lints: false, + driver_lint_caps, + ctfe_backtrace, + miri_unleashed_features: Lock::new(Default::default()), + asm_arch, + target_features: Default::default(), + unstable_target_features: Default::default(), + cfg_version, + using_internal_features, + expanded_args, + }; + + validate_commandline_args_with_session_available(&sess); + + sess +} + +/// Validate command line arguments with a `Session`. +/// +/// If it is useful to have a Session available already for validating a commandline argument, you +/// can do so here. +// JUSTIFICATION: needs to access args to validate them +#[allow(rustc::bad_opt_access)] +fn validate_commandline_args_with_session_available(sess: &Session) { + // Since we don't know if code in an rlib will be linked to statically or + // dynamically downstream, rustc generates `__imp_` symbols that help linkers + // on Windows deal with this lack of knowledge (#27438). Unfortunately, + // these manually generated symbols confuse LLD when it tries to merge + // bitcode during ThinLTO. Therefore we disallow dynamic linking on Windows + // when compiling for LLD ThinLTO. This way we can validly just not generate + // the `dllimport` attributes and `__imp_` symbols in that case. + if sess.opts.cg.linker_plugin_lto.enabled() + && sess.opts.cg.prefer_dynamic + && sess.target.is_like_windows + { + sess.dcx().emit_err(errors::LinkerPluginToWindowsNotSupported); + } + + // Make sure that any given profiling data actually exists so LLVM can't + // decide to silently skip PGO. + if let Some(ref path) = sess.opts.cg.profile_use { + if !path.exists() { + sess.dcx().emit_err(errors::ProfileUseFileDoesNotExist { path }); + } + } + + // Do the same for sample profile data. + if let Some(ref path) = sess.opts.unstable_opts.profile_sample_use { + if !path.exists() { + sess.dcx().emit_err(errors::ProfileSampleUseFileDoesNotExist { path }); + } + } + + // Unwind tables cannot be disabled if the target requires them. + if let Some(include_uwtables) = sess.opts.cg.force_unwind_tables { + if sess.target.requires_uwtable && !include_uwtables { + sess.dcx().emit_err(errors::TargetRequiresUnwindTables); + } + } + + // Sanitizers can only be used on platforms that we know have working sanitizer codegen. + let supported_sanitizers = sess.target.options.supported_sanitizers; + let mut unsupported_sanitizers = sess.opts.unstable_opts.sanitizer - supported_sanitizers; + // Niche: if `fixed-x18`, or effectively switching on `reserved-x18` flag, is enabled + // we should allow Shadow Call Stack sanitizer. + if sess.opts.unstable_opts.fixed_x18 && sess.target.arch == "aarch64" { + unsupported_sanitizers -= SanitizerSet::SHADOWCALLSTACK; + } + match unsupported_sanitizers.into_iter().count() { + 0 => {} + 1 => { + sess.dcx() + .emit_err(errors::SanitizerNotSupported { us: unsupported_sanitizers.to_string() }); + } + _ => { + sess.dcx().emit_err(errors::SanitizersNotSupported { + us: unsupported_sanitizers.to_string(), + }); + } + } + + // Cannot mix and match mutually-exclusive sanitizers. + if let Some((first, second)) = sess.opts.unstable_opts.sanitizer.mutually_exclusive() { + sess.dcx().emit_err(errors::CannotMixAndMatchSanitizers { + first: first.to_string(), + second: second.to_string(), + }); + } + + // Cannot enable crt-static with sanitizers on Linux + if sess.crt_static(None) + && !sess.opts.unstable_opts.sanitizer.is_empty() + && !sess.target.is_like_msvc + { + sess.dcx().emit_err(errors::CannotEnableCrtStaticLinux); + } + + // LLVM CFI requires LTO. + if sess.is_sanitizer_cfi_enabled() + && !(sess.lto() == config::Lto::Fat || sess.opts.cg.linker_plugin_lto.enabled()) + { + sess.dcx().emit_err(errors::SanitizerCfiRequiresLto); + } + + // KCFI requires panic=abort + if sess.is_sanitizer_kcfi_enabled() && sess.panic_strategy() != PanicStrategy::Abort { + sess.dcx().emit_err(errors::SanitizerKcfiRequiresPanicAbort); + } + + // LLVM CFI using rustc LTO requires a single codegen unit. + if sess.is_sanitizer_cfi_enabled() + && sess.lto() == config::Lto::Fat + && (sess.codegen_units().as_usize() != 1) + { + sess.dcx().emit_err(errors::SanitizerCfiRequiresSingleCodegenUnit); + } + + // Canonical jump tables requires CFI. + if sess.is_sanitizer_cfi_canonical_jump_tables_disabled() { + if !sess.is_sanitizer_cfi_enabled() { + sess.dcx().emit_err(errors::SanitizerCfiCanonicalJumpTablesRequiresCfi); + } + } + + // LLVM CFI pointer generalization requires CFI or KCFI. + if sess.is_sanitizer_cfi_generalize_pointers_enabled() { + if !(sess.is_sanitizer_cfi_enabled() || sess.is_sanitizer_kcfi_enabled()) { + sess.dcx().emit_err(errors::SanitizerCfiGeneralizePointersRequiresCfi); + } + } + + // LLVM CFI integer normalization requires CFI or KCFI. + if sess.is_sanitizer_cfi_normalize_integers_enabled() { + if !(sess.is_sanitizer_cfi_enabled() || sess.is_sanitizer_kcfi_enabled()) { + sess.dcx().emit_err(errors::SanitizerCfiNormalizeIntegersRequiresCfi); + } + } + + // LTO unit splitting requires LTO. + if sess.is_split_lto_unit_enabled() + && !(sess.lto() == config::Lto::Fat + || sess.lto() == config::Lto::Thin + || sess.opts.cg.linker_plugin_lto.enabled()) + { + sess.dcx().emit_err(errors::SplitLtoUnitRequiresLto); + } + + // VFE requires LTO. + if sess.lto() != config::Lto::Fat { + if sess.opts.unstable_opts.virtual_function_elimination { + sess.dcx().emit_err(errors::UnstableVirtualFunctionElimination); + } + } + + if sess.opts.unstable_opts.stack_protector != StackProtector::None { + if !sess.target.options.supports_stack_protector { + sess.dcx().emit_warn(errors::StackProtectorNotSupportedForTarget { + stack_protector: sess.opts.unstable_opts.stack_protector, + target_triple: &sess.opts.target_triple, + }); + } + } + + if sess.opts.unstable_opts.small_data_threshold.is_some() { + if sess.target.small_data_threshold_support() == SmallDataThresholdSupport::None { + sess.dcx().emit_warn(errors::SmallDataThresholdNotSupportedForTarget { + target_triple: &sess.opts.target_triple, + }) + } + } + + if sess.opts.unstable_opts.branch_protection.is_some() && sess.target.arch != "aarch64" { + sess.dcx().emit_err(errors::BranchProtectionRequiresAArch64); + } + + if let Some(dwarf_version) = sess.opts.unstable_opts.dwarf_version { + if dwarf_version > 5 { + sess.dcx().emit_err(errors::UnsupportedDwarfVersion { dwarf_version }); + } + } + + if !sess.target.options.supported_split_debuginfo.contains(&sess.split_debuginfo()) + && !sess.opts.unstable_opts.unstable_options + { + sess.dcx() + .emit_err(errors::SplitDebugInfoUnstablePlatform { debuginfo: sess.split_debuginfo() }); + } + + if sess.opts.unstable_opts.embed_source { + let dwarf_version = + sess.opts.unstable_opts.dwarf_version.unwrap_or(sess.target.default_dwarf_version); + + if dwarf_version < 5 { + sess.dcx().emit_warn(errors::EmbedSourceInsufficientDwarfVersion { dwarf_version }); + } + + if sess.opts.debuginfo == DebugInfo::None { + sess.dcx().emit_warn(errors::EmbedSourceRequiresDebugInfo); + } + } + + if sess.opts.unstable_opts.instrument_xray.is_some() && !sess.target.options.supports_xray { + sess.dcx().emit_err(errors::InstrumentationNotSupported { us: "XRay".to_string() }); + } + + if let Some(flavor) = sess.opts.cg.linker_flavor { + if let Some(compatible_list) = sess.target.linker_flavor.check_compatibility(flavor) { + let flavor = flavor.desc(); + sess.dcx().emit_err(errors::IncompatibleLinkerFlavor { flavor, compatible_list }); + } + } + + if sess.opts.unstable_opts.function_return != FunctionReturn::default() { + if sess.target.arch != "x86" && sess.target.arch != "x86_64" { + sess.dcx().emit_err(errors::FunctionReturnRequiresX86OrX8664); + } + } + + if let Some(regparm) = sess.opts.unstable_opts.regparm { + if regparm > 3 { + sess.dcx().emit_err(errors::UnsupportedRegparm { regparm }); + } + if sess.target.arch != "x86" { + sess.dcx().emit_err(errors::UnsupportedRegparmArch); + } + } + + // The code model check applies to `thunk` and `thunk-extern`, but not `thunk-inline`, so it is + // kept as a `match` to force a change if new ones are added, even if we currently only support + // `thunk-extern` like Clang. + match sess.opts.unstable_opts.function_return { + FunctionReturn::Keep => (), + FunctionReturn::ThunkExtern => { + // FIXME: In principle, the inherited base LLVM target code model could be large, + // but this only checks whether we were passed one explicitly (like Clang does). + if let Some(code_model) = sess.code_model() + && code_model == CodeModel::Large + { + sess.dcx().emit_err(errors::FunctionReturnThunkExternRequiresNonLargeCodeModel); + } + } + } + + if sess.opts.cg.soft_float { + if sess.target.arch == "arm" && sess.target.abi == "eabihf" { + sess.dcx().emit_warn(errors::SoftFloatDeprecated); + } else { + // All `use_softfp` does is the equivalent of `-mfloat-abi` in GCC/clang, which only exists on ARM targets. + // We document this flag to only affect `*eabihf` targets, so let's show a warning for all other targets. + sess.dcx().emit_warn(errors::SoftFloatIgnored); + } + } +} + +/// Holds data on the current incremental compilation session, if there is one. +#[derive(Debug)] +enum IncrCompSession { + /// This is the state the session will be in until the incr. comp. dir is + /// needed. + NotInitialized, + /// This is the state during which the session directory is private and can + /// be modified. `_lock_file` is never directly used, but its presence + /// alone has an effect, because the file will unlock when the session is + /// dropped. + Active { session_directory: PathBuf, _lock_file: flock::Lock }, + /// This is the state after the session directory has been finalized. In this + /// state, the contents of the directory must not be modified any more. + Finalized { session_directory: PathBuf }, + /// This is an error state that is reached when some compilation error has + /// occurred. It indicates that the contents of the session directory must + /// not be used, since they might be invalid. + InvalidBecauseOfErrors { session_directory: PathBuf }, +} + +/// A wrapper around an [`DiagCtxt`] that is used for early error emissions. +pub struct EarlyDiagCtxt { + dcx: DiagCtxt, +} + +impl EarlyDiagCtxt { + pub fn new(output: ErrorOutputType) -> Self { + let emitter = mk_emitter(output); + Self { dcx: DiagCtxt::new(emitter) } + } + + /// Swap out the underlying dcx once we acquire the user's preference on error emission + /// format. Any errors prior to that will cause an abort and all stashed diagnostics of the + /// previous dcx will be emitted. + pub fn abort_if_error_and_set_error_format(&mut self, output: ErrorOutputType) { + self.dcx.handle().abort_if_errors(); + + let emitter = mk_emitter(output); + self.dcx = DiagCtxt::new(emitter); + } + + #[allow(rustc::untranslatable_diagnostic)] + #[allow(rustc::diagnostic_outside_of_impl)] + pub fn early_note(&self, msg: impl Into<DiagMessage>) { + self.dcx.handle().note(msg) + } + + #[allow(rustc::untranslatable_diagnostic)] + #[allow(rustc::diagnostic_outside_of_impl)] + pub fn early_help(&self, msg: impl Into<DiagMessage>) { + self.dcx.handle().struct_help(msg).emit() + } + + #[allow(rustc::untranslatable_diagnostic)] + #[allow(rustc::diagnostic_outside_of_impl)] + #[must_use = "ErrorGuaranteed must be returned from `run_compiler` in order to exit with a non-zero status code"] + pub fn early_err(&self, msg: impl Into<DiagMessage>) -> ErrorGuaranteed { + self.dcx.handle().err(msg) + } + + #[allow(rustc::untranslatable_diagnostic)] + #[allow(rustc::diagnostic_outside_of_impl)] + pub fn early_fatal(&self, msg: impl Into<DiagMessage>) -> ! { + self.dcx.handle().fatal(msg) + } + + #[allow(rustc::untranslatable_diagnostic)] + #[allow(rustc::diagnostic_outside_of_impl)] + pub fn early_struct_fatal(&self, msg: impl Into<DiagMessage>) -> Diag<'_, FatalAbort> { + self.dcx.handle().struct_fatal(msg) + } + + #[allow(rustc::untranslatable_diagnostic)] + #[allow(rustc::diagnostic_outside_of_impl)] + pub fn early_warn(&self, msg: impl Into<DiagMessage>) { + self.dcx.handle().warn(msg) + } + + #[allow(rustc::untranslatable_diagnostic)] + #[allow(rustc::diagnostic_outside_of_impl)] + pub fn early_struct_warn(&self, msg: impl Into<DiagMessage>) -> Diag<'_, ()> { + self.dcx.handle().struct_warn(msg) + } +} + +fn mk_emitter(output: ErrorOutputType) -> Box<DynEmitter> { + // FIXME(#100717): early errors aren't translated at the moment, so this is fine, but it will + // need to reference every crate that might emit an early error for translation to work. + let fallback_bundle = + fallback_fluent_bundle(vec![rustc_errors::DEFAULT_LOCALE_RESOURCE], false); + let emitter: Box<DynEmitter> = match output { + config::ErrorOutputType::HumanReadable(kind, color_config) => { + let short = kind.short(); + Box::new( + HumanEmitter::new(stderr_destination(color_config), fallback_bundle) + .theme(if let HumanReadableErrorType::Unicode = kind { + OutputTheme::Unicode + } else { + OutputTheme::Ascii + }) + .short_message(short), + ) + } + config::ErrorOutputType::Json { pretty, json_rendered, color_config } => { + Box::new(JsonEmitter::new( + Box::new(io::BufWriter::new(io::stderr())), + Lrc::new(SourceMap::new(FilePathMapping::empty())), + fallback_bundle, + pretty, + json_rendered, + color_config, + )) + } + }; + emitter +} + +pub trait RemapFileNameExt { + type Output<'a> + where + Self: 'a; + + /// Returns a possibly remapped filename based on the passed scope and remap cli options. + /// + /// One and only one scope should be passed to this method, it will panic otherwise. + fn for_scope(&self, sess: &Session, scope: RemapPathScopeComponents) -> Self::Output<'_>; +} + +impl RemapFileNameExt for rustc_span::FileName { + type Output<'a> = rustc_span::FileNameDisplay<'a>; + + fn for_scope(&self, sess: &Session, scope: RemapPathScopeComponents) -> Self::Output<'_> { + assert!( + scope.bits().count_ones() == 1, + "one and only one scope should be passed to for_scope" + ); + if sess.opts.unstable_opts.remap_path_scope.contains(scope) { + self.prefer_remapped_unconditionaly() + } else { + self.prefer_local() + } + } +} + +impl RemapFileNameExt for rustc_span::RealFileName { + type Output<'a> = &'a Path; + + fn for_scope(&self, sess: &Session, scope: RemapPathScopeComponents) -> Self::Output<'_> { + assert!( + scope.bits().count_ones() == 1, + "one and only one scope should be passed to for_scope" + ); + if sess.opts.unstable_opts.remap_path_scope.contains(scope) { + self.remapped_path_if_available() + } else { + self.local_path_if_available() + } + } +} diff --git a/compiler/rustc_session/src/utils.rs b/compiler/rustc_session/src/utils.rs new file mode 100644 index 00000000000..9182789cf02 --- /dev/null +++ b/compiler/rustc_session/src/utils.rs @@ -0,0 +1,179 @@ +use std::path::{Path, PathBuf}; +use std::sync::OnceLock; + +use rustc_data_structures::profiling::VerboseTimingGuard; +use rustc_fs_util::try_canonicalize; +use rustc_macros::{Decodable, Encodable, HashStable_Generic}; + +use crate::session::Session; + +impl Session { + pub fn timer(&self, what: &'static str) -> VerboseTimingGuard<'_> { + self.prof.verbose_generic_activity(what) + } + /// Used by `-Z self-profile`. + pub fn time<R>(&self, what: &'static str, f: impl FnOnce() -> R) -> R { + self.prof.verbose_generic_activity(what).run(f) + } +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Encodable, Decodable)] +#[derive(HashStable_Generic)] +pub enum NativeLibKind { + /// Static library (e.g. `libfoo.a` on Linux or `foo.lib` on Windows/MSVC) + Static { + /// Whether to bundle objects from static library into produced rlib + bundle: Option<bool>, + /// Whether to link static library without throwing any object files away + whole_archive: Option<bool>, + }, + /// Dynamic library (e.g. `libfoo.so` on Linux) + /// or an import library corresponding to a dynamic library (e.g. `foo.lib` on Windows/MSVC). + Dylib { + /// Whether the dynamic library will be linked only if it satisfies some undefined symbols + as_needed: Option<bool>, + }, + /// Dynamic library (e.g. `foo.dll` on Windows) without a corresponding import library. + RawDylib, + /// A macOS-specific kind of dynamic libraries. + Framework { + /// Whether the framework will be linked only if it satisfies some undefined symbols + as_needed: Option<bool>, + }, + /// Argument which is passed to linker, relative order with libraries and other arguments + /// is preserved + LinkArg, + + /// Module imported from WebAssembly + WasmImportModule, + + /// The library kind wasn't specified, `Dylib` is currently used as a default. + Unspecified, +} + +impl NativeLibKind { + pub fn has_modifiers(&self) -> bool { + match self { + NativeLibKind::Static { bundle, whole_archive } => { + bundle.is_some() || whole_archive.is_some() + } + NativeLibKind::Dylib { as_needed } | NativeLibKind::Framework { as_needed } => { + as_needed.is_some() + } + NativeLibKind::RawDylib + | NativeLibKind::Unspecified + | NativeLibKind::LinkArg + | NativeLibKind::WasmImportModule => false, + } + } + + pub fn is_statically_included(&self) -> bool { + matches!(self, NativeLibKind::Static { .. }) + } + + pub fn is_dllimport(&self) -> bool { + matches!( + self, + NativeLibKind::Dylib { .. } | NativeLibKind::RawDylib | NativeLibKind::Unspecified + ) + } +} + +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Encodable, Decodable)] +#[derive(HashStable_Generic)] +pub struct NativeLib { + pub name: String, + pub new_name: Option<String>, + pub kind: NativeLibKind, + pub verbatim: Option<bool>, +} + +impl NativeLib { + pub fn has_modifiers(&self) -> bool { + self.verbatim.is_some() || self.kind.has_modifiers() + } +} + +/// A path that has been canonicalized along with its original, non-canonicalized form +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] +pub struct CanonicalizedPath { + // Optional since canonicalization can sometimes fail + canonicalized: Option<PathBuf>, + original: PathBuf, +} + +impl CanonicalizedPath { + pub fn new(path: &Path) -> Self { + Self { original: path.to_owned(), canonicalized: try_canonicalize(path).ok() } + } + + pub fn canonicalized(&self) -> &PathBuf { + self.canonicalized.as_ref().unwrap_or(self.original()) + } + + pub fn original(&self) -> &PathBuf { + &self.original + } +} + +/// Gets a list of extra command-line flags provided by the user, as strings. +/// +/// This function is used during ICEs to show more information useful for +/// debugging, since some ICEs only happens with non-default compiler flags +/// (and the users don't always report them). +pub fn extra_compiler_flags() -> Option<(Vec<String>, bool)> { + const ICE_REPORT_COMPILER_FLAGS: &[&str] = &["-Z", "-C", "--crate-type"]; + + const ICE_REPORT_COMPILER_FLAGS_EXCLUDE: &[&str] = &["metadata", "extra-filename"]; + + const ICE_REPORT_COMPILER_FLAGS_STRIP_VALUE: &[&str] = &["incremental"]; + + let mut args = std::env::args_os().map(|arg| arg.to_string_lossy().to_string()); + + let mut result = Vec::new(); + let mut excluded_cargo_defaults = false; + while let Some(arg) = args.next() { + if let Some(a) = ICE_REPORT_COMPILER_FLAGS.iter().find(|a| arg.starts_with(*a)) { + let content = if arg.len() == a.len() { + // A space-separated option, like `-C incremental=foo` or `--crate-type rlib` + match args.next() { + Some(arg) => arg, + None => continue, + } + } else if arg.get(a.len()..a.len() + 1) == Some("=") { + // An equals option, like `--crate-type=rlib` + arg[a.len() + 1..].to_string() + } else { + // A non-space option, like `-Cincremental=foo` + arg[a.len()..].to_string() + }; + let option = content.split_once('=').map(|s| s.0).unwrap_or(&content); + if ICE_REPORT_COMPILER_FLAGS_EXCLUDE.iter().any(|exc| option == *exc) { + excluded_cargo_defaults = true; + } else { + result.push(a.to_string()); + match ICE_REPORT_COMPILER_FLAGS_STRIP_VALUE.iter().find(|s| option == **s) { + Some(s) => result.push(format!("{s}=[REDACTED]")), + None => result.push(content), + } + } + } + } + + if !result.is_empty() { Some((result, excluded_cargo_defaults)) } else { None } +} + +/// Returns whenever rustc was launched by Cargo as opposed to another build system. +/// +/// To be used in diagnostics to avoid printing Cargo specific suggestions to other +/// build systems (like Bazel, Buck2, Makefile, ...). +pub fn was_invoked_from_cargo() -> bool { + static FROM_CARGO: OnceLock<bool> = OnceLock::new(); + + // To be able to detect Cargo, we use the simplest and least intrusive + // way: we check whenever the `CARGO_CRATE_NAME` env is set. + // + // Note that it is common in Makefiles to define the `CARGO` env even + // though we may not have been called by Cargo, so we avoid using it. + *FROM_CARGO.get_or_init(|| std::env::var_os("CARGO_CRATE_NAME").is_some()) +} diff --git a/compiler/rustc_session/src/version.rs b/compiler/rustc_session/src/version.rs new file mode 100644 index 00000000000..1696eaf902b --- /dev/null +++ b/compiler/rustc_session/src/version.rs @@ -0,0 +1,29 @@ +use std::borrow::Cow; +use std::fmt::{self, Display}; + +use rustc_errors::IntoDiagArg; +use rustc_macros::{Decodable, Encodable, HashStable_Generic, current_rustc_version}; + +#[derive(Encodable, Decodable, Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[derive(HashStable_Generic)] +pub struct RustcVersion { + pub major: u16, + pub minor: u16, + pub patch: u16, +} + +impl RustcVersion { + pub const CURRENT: Self = current_rustc_version!(); +} + +impl Display for RustcVersion { + fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(formatter, "{}.{}.{}", self.major, self.minor, self.patch) + } +} + +impl IntoDiagArg for RustcVersion { + fn into_diag_arg(self) -> rustc_errors::DiagArgValue { + rustc_errors::DiagArgValue::Str(Cow::Owned(self.to_string())) + } +} |
