//! 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 itself //! 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, sym}; use rustc_target::spec::{PanicStrategy, RelocModel, SanitizerSet, Target}; 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)>; /// 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>, /// Well known names (only used for diagnostics purposes) pub well_known_names: FxHashSet, } pub enum ExpectedValues { Some(FxHashSet>), Any, } impl ExpectedValues { fn insert(&mut self, value: T) -> bool { match self { ExpectedValues::Some(expecteds) => expecteds.insert(Some(value)), ExpectedValues::Any => false, } } } impl Extend for ExpectedValues { fn extend>(&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 { fn extend>(&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), 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::contract_checks, None) => disallow(cfg, "-Z contract-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_has_reliable_f16, None | Some(_)) | (sym::target_has_reliable_f16_math, None | Some(_)) | (sym::target_has_reliable_f128, None | Some(_)) | (sym::target_has_reliable_f128_math, None | Some(_)) | (sym::target_thread_local, None) => disallow(cfg, "--target"), (sym::fmt_debug, None | Some(_)) => disallow(cfg, "-Z fmt-debug"), (sym::emscripten_wasm_eh, None | Some(_)) => disallow(cfg, "-Z emscripten_wasm_eh"), _ => {} } } } /// 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); } // Nightly-only implementation detail for the `panic_unwind` and `unwind` crates. if sess.is_nightly_build() && sess.opts.unstable_opts.emscripten_wasm_eh { ins_none!(sym::emscripten_wasm_eh); } if sess.contract_checks() { ins_none!(sym::contract_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. // // One exception is the `test` cfg which is consider to be a "user-space" // cfg, despite being also set by in `default_configuration` above. // It allows the build system to "deny" using the config by not marking it // as expected (e.g. `lib.test = false` for Cargo). // // 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.in_cfg()) .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_disjoint_mut(VALUES) else { panic!("unable to get all the check-cfg values buckets"); }; for target in Target::builtins().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::ub_checks, no_values); ins!(sym::contract_checks, no_values); ins!(sym::unix, no_values); ins!(sym::windows, no_values); } }