diff options
| author | Loïc BRANSTETT <lolo.branstett@numericable.fr> | 2021-09-29 02:39:30 +0200 |
|---|---|---|
| committer | Loïc BRANSTETT <lolo.branstett@numericable.fr> | 2022-02-16 13:03:12 +0100 |
| commit | 3a73ca587bb8a8fb52d6045fbe31d50d5a56ff19 (patch) | |
| tree | 37a0d4e25436b3524bb8e1b876f009dad1824f64 /compiler | |
| parent | 6499c5e7fc173a3f55b7a3bd1e6a50e9edef782d (diff) | |
| download | rust-3a73ca587bb8a8fb52d6045fbe31d50d5a56ff19.tar.gz rust-3a73ca587bb8a8fb52d6045fbe31d50d5a56ff19.zip | |
Implement --check-cfg option (RFC 3013)
Co-authored-by: Urgau <lolo.branstett@numericable.fr> Co-authored-by: Marcelina Kościelnicka <mwk@0x04.net>
Diffstat (limited to 'compiler')
| -rw-r--r-- | compiler/rustc_attr/src/builtin.rs | 31 | ||||
| -rw-r--r-- | compiler/rustc_driver/src/lib.rs | 2 | ||||
| -rw-r--r-- | compiler/rustc_interface/src/interface.rs | 91 | ||||
| -rw-r--r-- | compiler/rustc_interface/src/util.rs | 8 | ||||
| -rw-r--r-- | compiler/rustc_lint_defs/src/builtin.rs | 38 | ||||
| -rw-r--r-- | compiler/rustc_session/src/config.rs | 89 | ||||
| -rw-r--r-- | compiler/rustc_session/src/parse.rs | 4 | ||||
| -rw-r--r-- | compiler/rustc_span/src/symbol.rs | 2 |
8 files changed, 259 insertions, 6 deletions
diff --git a/compiler/rustc_attr/src/builtin.rs b/compiler/rustc_attr/src/builtin.rs index dca7f5dd487..49043e9f5f9 100644 --- a/compiler/rustc_attr/src/builtin.rs +++ b/compiler/rustc_attr/src/builtin.rs @@ -1,10 +1,13 @@ //! Parsing and validation of builtin attributes -use rustc_ast::{self as ast, Attribute, Lit, LitKind, MetaItem, MetaItemKind, NestedMetaItem}; +use rustc_ast as ast; +use rustc_ast::node_id::CRATE_NODE_ID; +use rustc_ast::{Attribute, Lit, LitKind, MetaItem, MetaItemKind, NestedMetaItem}; use rustc_ast_pretty::pprust; use rustc_errors::{struct_span_err, Applicability}; use rustc_feature::{find_gated_cfg, is_builtin_attr_name, Features, GatedCfg}; use rustc_macros::HashStable_Generic; +use rustc_session::lint::builtin::UNEXPECTED_CFGS; use rustc_session::parse::{feature_err, ParseSess}; use rustc_session::Session; use rustc_span::hygiene::Transparency; @@ -458,8 +461,30 @@ pub fn cfg_matches(cfg: &ast::MetaItem, sess: &ParseSess, features: Option<&Feat true } MetaItemKind::NameValue(..) | MetaItemKind::Word => { - let ident = cfg.ident().expect("multi-segment cfg predicate"); - sess.config.contains(&(ident.name, cfg.value_str())) + let name = cfg.ident().expect("multi-segment cfg predicate").name; + let value = cfg.value_str(); + if sess.check_config.names_checked && !sess.check_config.names_valid.contains(&name) + { + sess.buffer_lint( + UNEXPECTED_CFGS, + cfg.span, + CRATE_NODE_ID, + "unexpected `cfg` condition name", + ); + } + if let Some(val) = value { + if sess.check_config.values_checked.contains(&name) + && !sess.check_config.values_valid.contains(&(name, val)) + { + sess.buffer_lint( + UNEXPECTED_CFGS, + cfg.span, + CRATE_NODE_ID, + "unexpected `cfg` condition value", + ); + } + } + sess.config.contains(&(name, value)) } } }) diff --git a/compiler/rustc_driver/src/lib.rs b/compiler/rustc_driver/src/lib.rs index ca4e7b5142e..dd235063b5c 100644 --- a/compiler/rustc_driver/src/lib.rs +++ b/compiler/rustc_driver/src/lib.rs @@ -216,10 +216,12 @@ fn run_compiler( } let cfg = interface::parse_cfgspecs(matches.opt_strs("cfg")); + let check_cfg = interface::parse_check_cfg(matches.opt_strs("check-cfg")); let (odir, ofile) = make_output(&matches); let mut config = interface::Config { opts: sopts, crate_cfg: cfg, + crate_check_cfg: check_cfg, input: Input::File(PathBuf::new()), input_path: None, output_file: ofile, diff --git a/compiler/rustc_interface/src/interface.rs b/compiler/rustc_interface/src/interface.rs index 237aef1cf23..81d33411c4e 100644 --- a/compiler/rustc_interface/src/interface.rs +++ b/compiler/rustc_interface/src/interface.rs @@ -2,7 +2,7 @@ pub use crate::passes::BoxedResolver; use crate::util; use rustc_ast::token; -use rustc_ast::{self as ast, MetaItemKind}; +use rustc_ast::{self as ast, LitKind, MetaItemKind}; use rustc_codegen_ssa::traits::CodegenBackend; use rustc_data_structures::fx::{FxHashMap, FxHashSet}; use rustc_data_structures::sync::Lrc; @@ -13,12 +13,13 @@ use rustc_lint::LintStore; use rustc_middle::ty; use rustc_parse::maybe_new_parser_from_source_str; use rustc_query_impl::QueryCtxt; -use rustc_session::config::{self, ErrorOutputType, Input, OutputFilenames}; +use rustc_session::config::{self, CheckCfg, ErrorOutputType, Input, OutputFilenames}; use rustc_session::early_error; use rustc_session::lint; use rustc_session::parse::{CrateConfig, ParseSess}; use rustc_session::{DiagnosticOutput, Session}; use rustc_span::source_map::{FileLoader, FileName}; +use rustc_span::symbol::sym; use std::path::PathBuf; use std::result; use std::sync::{Arc, Mutex}; @@ -140,6 +141,90 @@ pub fn parse_cfgspecs(cfgspecs: Vec<String>) -> FxHashSet<(String, Option<String }) } +/// Converts strings provided as `--check-cfg [specs]` into a `CheckCfg`. +pub fn parse_check_cfg(specs: Vec<String>) -> CheckCfg { + rustc_span::create_default_session_if_not_set_then(move |_| { + let mut cfg = CheckCfg::default(); + + 'specs: for s in specs { + let sess = ParseSess::with_silent_emitter(Some(format!( + "this error occurred on the command line: `--check-cfg={}`", + s + ))); + let filename = FileName::cfg_spec_source_code(&s); + + macro_rules! error { + ($reason: expr) => { + early_error( + ErrorOutputType::default(), + &format!( + concat!("invalid `--check-cfg` argument: `{}` (", $reason, ")"), + s + ), + ); + }; + } + + match maybe_new_parser_from_source_str(&sess, filename, s.to_string()) { + Ok(mut parser) => match &mut parser.parse_meta_item() { + Ok(meta_item) if parser.token == token::Eof => { + if let Some(args) = meta_item.meta_item_list() { + if meta_item.has_name(sym::names) { + cfg.names_checked = true; + for arg in args { + if arg.is_word() && arg.ident().is_some() { + let ident = arg.ident().expect("multi-segment cfg key"); + cfg.names_valid.insert(ident.name.to_string()); + } else { + error!("`names()` arguments must be simple identifers"); + } + } + continue 'specs; + } else if meta_item.has_name(sym::values) { + if let Some((name, values)) = args.split_first() { + if name.is_word() && name.ident().is_some() { + let ident = name.ident().expect("multi-segment cfg key"); + cfg.values_checked.insert(ident.to_string()); + for val in values { + if let Some(LitKind::Str(s, _)) = + val.literal().map(|lit| &lit.kind) + { + cfg.values_valid + .insert((ident.to_string(), s.to_string())); + } else { + error!( + "`values()` arguments must be string literals" + ); + } + } + + continue 'specs; + } else { + error!( + "`values()` first argument must be a simple identifer" + ); + } + } + } + } + } + Ok(..) => {} + Err(err) => err.cancel(), + }, + Err(errs) => errs.into_iter().for_each(|mut err| err.cancel()), + } + + error!( + "expected `names(name1, name2, ... nameN)` or \ + `values(name, \"value1\", \"value2\", ... \"valueN\")`" + ); + } + + cfg.names_valid.extend(cfg.values_checked.iter().cloned()); + cfg + }) +} + /// The compiler configuration pub struct Config { /// Command line options @@ -147,6 +232,7 @@ pub struct Config { /// cfg! configuration in addition to the default ones pub crate_cfg: FxHashSet<(String, Option<String>)>, + pub crate_check_cfg: CheckCfg, pub input: Input, pub input_path: Option<PathBuf>, @@ -190,6 +276,7 @@ pub fn create_compiler_and_run<R>(config: Config, f: impl FnOnce(&Compiler) -> R let (mut sess, codegen_backend) = util::create_session( config.opts, config.crate_cfg, + config.crate_check_cfg, config.diagnostic_output, config.file_loader, config.input_path.clone(), diff --git a/compiler/rustc_interface/src/util.rs b/compiler/rustc_interface/src/util.rs index 6d9183eda9d..4b1b01de549 100644 --- a/compiler/rustc_interface/src/util.rs +++ b/compiler/rustc_interface/src/util.rs @@ -15,6 +15,7 @@ use rustc_parse::validate_attr; use rustc_query_impl::QueryCtxt; use rustc_resolve::{self, Resolver}; use rustc_session as session; +use rustc_session::config::CheckCfg; use rustc_session::config::{self, CrateType}; use rustc_session::config::{ErrorOutputType, Input, OutputFilenames}; use rustc_session::lint::{self, BuiltinLintDiagnostics, LintBuffer}; @@ -67,6 +68,7 @@ pub fn add_configuration( pub fn create_session( sopts: config::Options, cfg: FxHashSet<(String, Option<String>)>, + check_cfg: CheckCfg, diagnostic_output: DiagnosticOutput, file_loader: Option<Box<dyn FileLoader + Send + Sync + 'static>>, input_path: Option<PathBuf>, @@ -102,7 +104,13 @@ pub fn create_session( let mut cfg = config::build_configuration(&sess, config::to_crate_config(cfg)); add_configuration(&mut cfg, &mut sess, &*codegen_backend); + + let mut check_cfg = config::to_crate_check_config(check_cfg); + check_cfg.fill_well_known(); + check_cfg.fill_actual(&cfg); + sess.parse_sess.config = cfg; + sess.parse_sess.check_config = check_cfg; (Lrc::new(sess), Lrc::new(codegen_backend)) } diff --git a/compiler/rustc_lint_defs/src/builtin.rs b/compiler/rustc_lint_defs/src/builtin.rs index f4eba25475e..adec1a3ab00 100644 --- a/compiler/rustc_lint_defs/src/builtin.rs +++ b/compiler/rustc_lint_defs/src/builtin.rs @@ -2957,6 +2957,43 @@ declare_lint! { }; } +declare_lint! { + /// The `unexpected_cfgs` lint detects unexpected conditional compilation conditions. + /// + /// ### Example + /// + /// ```text + /// rustc --check-cfg 'names()' + /// ``` + /// + /// ```rust,ignore (needs command line option) + /// #[cfg(widnows)] + /// fn foo() {} + /// ``` + /// + /// This will produce: + /// + /// ```text + /// warning: unknown condition name used + /// --> lint_example.rs:1:7 + /// | + /// 1 | #[cfg(widnows)] + /// | ^^^^^^^ + /// | + /// = note: `#[warn(unexpected_cfgs)]` on by default + /// ``` + /// + /// ### Explanation + /// + /// This lint is only active when a `--check-cfg='names(...)'` option has been passed + /// to the compiler and triggers whenever an unknown condition name or value is used. + /// The known condition include names or values passed in `--check-cfg`, `--cfg`, and some + /// well-knows names and values built into the compiler. + pub UNEXPECTED_CFGS, + Warn, + "detects unexpected names and values in `#[cfg]` conditions", +} + declare_lint_pass! { /// Does nothing as a lint pass, but registers some `Lint`s /// that are used by other parts of the compiler. @@ -3055,6 +3092,7 @@ declare_lint_pass! { DEPRECATED_CFG_ATTR_CRATE_TYPE_NAME, DUPLICATE_MACRO_ATTRIBUTES, SUSPICIOUS_AUTO_TRAIT_IMPLS, + UNEXPECTED_CFGS, ] } diff --git a/compiler/rustc_session/src/config.rs b/compiler/rustc_session/src/config.rs index 8630ffec241..f90766875d2 100644 --- a/compiler/rustc_session/src/config.rs +++ b/compiler/rustc_session/src/config.rs @@ -16,7 +16,7 @@ use rustc_target::spec::{LinkerFlavor, SplitDebuginfo, Target, TargetTriple, Tar use rustc_serialize::json; -use crate::parse::CrateConfig; +use crate::parse::{CrateCheckConfig, CrateConfig}; use rustc_feature::UnstableFeatures; use rustc_span::edition::{Edition, DEFAULT_EDITION, EDITION_NAME_LIST, LATEST_STABLE_EDITION}; use rustc_span::source_map::{FileName, FilePathMapping}; @@ -921,6 +921,7 @@ pub const fn default_lib_output() -> CrateType { } fn default_configuration(sess: &Session) -> CrateConfig { + // NOTE: This should be kept in sync with `CrateCheckConfig::fill_well_known` below. let end = &sess.target.endian; let arch = &sess.target.arch; let wordsz = sess.target.pointer_width.to_string(); @@ -1005,6 +1006,91 @@ pub fn to_crate_config(cfg: FxHashSet<(String, Option<String>)>) -> CrateConfig cfg.into_iter().map(|(a, b)| (Symbol::intern(&a), b.map(|b| Symbol::intern(&b)))).collect() } +/// The parsed `--check-cfg` options +pub struct CheckCfg<T = String> { + /// Set if `names()` checking is enabled + pub names_checked: bool, + /// The union of all `names()` + pub names_valid: FxHashSet<T>, + /// The set of names for which `values()` was used + pub values_checked: FxHashSet<T>, + /// The set of all (name, value) pairs passed in `values()` + pub values_valid: FxHashSet<(T, T)>, +} + +impl<T> Default for CheckCfg<T> { + fn default() -> Self { + CheckCfg { + names_checked: false, + names_valid: FxHashSet::default(), + values_checked: FxHashSet::default(), + values_valid: FxHashSet::default(), + } + } +} + +impl<T> CheckCfg<T> { + fn map_data<O: Eq + Hash>(&self, f: impl Fn(&T) -> O) -> CheckCfg<O> { + CheckCfg { + names_checked: self.names_checked, + names_valid: self.names_valid.iter().map(|a| f(a)).collect(), + values_checked: self.values_checked.iter().map(|a| f(a)).collect(), + values_valid: self.values_valid.iter().map(|(a, b)| (f(a), f(b))).collect(), + } + } +} + +/// Converts the crate `--check-cfg` options from `String` to `Symbol`. +/// `rustc_interface::interface::Config` accepts this in the compiler configuration, +/// but the symbol interner is not yet set up then, so we must convert it later. +pub fn to_crate_check_config(cfg: CheckCfg) -> CrateCheckConfig { + cfg.map_data(|s| Symbol::intern(s)) +} + +impl CrateCheckConfig { + /// Fills a `CrateCheckConfig` with well-known configuration names. + pub fn fill_well_known(&mut self) { + // NOTE: This should be kept in sync with `default_configuration` + const WELL_KNOWN_NAMES: &[Symbol] = &[ + sym::unix, + sym::windows, + sym::target_os, + sym::target_family, + sym::target_arch, + sym::target_endian, + sym::target_pointer_width, + sym::target_env, + sym::target_abi, + sym::target_vendor, + sym::target_thread_local, + sym::target_has_atomic_load_store, + sym::target_has_atomic, + sym::target_has_atomic_equal_alignment, + sym::panic, + sym::sanitize, + sym::debug_assertions, + sym::proc_macro, + sym::test, + sym::doc, + sym::doctest, + sym::feature, + ]; + for &name in WELL_KNOWN_NAMES { + self.names_valid.insert(name); + } + } + + /// Fills a `CrateCheckConfig` with configuration names and values that are actually active. + pub fn fill_actual(&mut self, cfg: &CrateConfig) { + for &(k, v) in cfg { + self.names_valid.insert(k); + if let Some(v) = v { + self.values_valid.insert((k, v)); + } + } + } +} + pub fn build_configuration(sess: &Session, mut user_cfg: CrateConfig) -> CrateConfig { // Combine the configuration requested by the session (command line) with // some default and generated configuration items. @@ -1148,6 +1234,7 @@ pub fn rustc_short_optgroups() -> Vec<RustcOptGroup> { vec