diff --git a/compiler/rustc_attr/src/builtin.rs b/compiler/rustc_attr/src/builtin.rs index 0ab452fb42dc6..2d154d7e5b260 100644 --- a/compiler/rustc_attr/src/builtin.rs +++ b/compiler/rustc_attr/src/builtin.rs @@ -1,10 +1,14 @@ //! Parsing and validation of builtin attributes -use rustc_ast::{self as ast, Attribute, Lit, LitKind, MetaItem, MetaItemKind, NestedMetaItem}; +use rustc_ast::{ + self as ast, node_id::CRATE_NODE_ID, 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::{INVALID_CFG_NAME, INVALID_CFG_VALUE}; use rustc_session::parse::{feature_err, ParseSess}; use rustc_session::Session; use rustc_span::hygiene::Transparency; @@ -463,7 +467,30 @@ pub fn cfg_matches(cfg: &ast::MetaItem, sess: &ParseSess, features: Option<&Feat } MetaItemKind::NameValue(..) | MetaItemKind::Word => { let ident = cfg.ident().expect("multi-segment cfg predicate"); - sess.config.contains(&(ident.name, cfg.value_str())) + let value = cfg.value_str(); + if sess.check_config.names_checked + && !sess.check_config.names_valid.contains(&ident.name) + { + sess.buffer_lint( + INVALID_CFG_NAME, + cfg.span, + CRATE_NODE_ID, + "unknown condition name used", + ); + } + if let Some(val) = value { + if sess.check_config.values_checked.contains(&ident.name) + && !sess.check_config.values_valid.contains(&(ident.name, val)) + { + sess.buffer_lint( + INVALID_CFG_VALUE, + cfg.span, + CRATE_NODE_ID, + "unknown condition value used", + ); + } + } + sess.config.contains(&(ident.name, value)) } } }) diff --git a/compiler/rustc_driver/src/lib.rs b/compiler/rustc_driver/src/lib.rs index 4c6a2baaef1e5..9312475f69def 100644 --- a/compiler/rustc_driver/src/lib.rs +++ b/compiler/rustc_driver/src/lib.rs @@ -214,10 +214,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 8393826aa1285..77def6ec229ee 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::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}; @@ -123,6 +124,81 @@ pub fn parse_cfgspecs(cfgspecs: Vec) -> FxHashSet<(String, Option) -> CheckCfg { + rustc_span::create_default_session_if_not_set_then(move |_| { + let mut cfg = CheckCfg { + names_checked: false, + names_valid: Default::default(), + values_checked: Default::default(), + values_valid: Default::default(), + }; + for s in specs { + let sess = ParseSess::with_silent_emitter(); + let filename = FileName::cfg_spec_source_code(&s); + let mut parser = new_parser_from_source_str(&sess, filename, s.to_string()); + + macro_rules! error { + ($reason: expr) => { + early_error( + ErrorOutputType::default(), + &format!( + concat!("invalid `--check-cfg` argument: `{}` (", $reason, ")"), + s + ), + ); + }; + } + + 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; + } 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(lit) = val.literal() { + if let LitKind::Str(s, _) = lit.kind { + cfg.values_valid + .insert((ident.to_string(), s.to_string())); + continue; + } + } + error!("`values()` arguments must be string literals"); + } + } else { + error!("`values()` first argument must be a simple identifer"); + } + continue; + } + } + } + } + Ok(..) => {} + Err(err) => err.cancel(), + } + + error!( + r#"expected `names(name1, name2, ... nameN)` or `values(name, "value1", "value2", ... "valueN")`"# + ); + } + cfg + }) +} + /// The compiler configuration pub struct Config { /// Command line options @@ -130,6 +206,7 @@ pub struct Config { /// cfg! configuration in addition to the default ones pub crate_cfg: FxHashSet<(String, Option)>, + pub crate_check_cfg: CheckCfg, pub input: Input, pub input_path: Option, @@ -173,6 +250,7 @@ pub fn create_compiler_and_run(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 a1d1b63c8fafe..7c9037a8b3024 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}; @@ -64,6 +65,7 @@ pub fn add_configuration( pub fn create_session( sopts: config::Options, cfg: FxHashSet<(String, Option)>, + check_cfg: CheckCfg, diagnostic_output: DiagnosticOutput, file_loader: Option>, input_path: Option, @@ -99,7 +101,11 @@ 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_check_config(check_cfg); + config::fill_check_config_well_known(&mut check_cfg); + config::fill_check_config_actual(&mut check_cfg, &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 5830ce26fea3f..43085d127e67d 100644 --- a/compiler/rustc_lint_defs/src/builtin.rs +++ b/compiler/rustc_lint_defs/src/builtin.rs @@ -2956,6 +2956,80 @@ declare_lint! { "detects large moves or copies", } +declare_lint! { + /// The `invalid_cfg_name` lint detects invalid 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(invalid_cfg_name)]` 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 is used. The known + /// condition names include names passed in `--check-cfg`, `--cfg`, and some well-known + /// names built into the compiler. + pub INVALID_CFG_NAME, + Warn, + "detects invalid #[cfg] condition names", +} + +declare_lint! { + /// The `invalid_cfg_value` lint detects invalid conditional compilation conditions. + /// + /// ### Example + /// + /// ```text + /// rustc --check-cfg 'values(feature, "serde")' + /// ``` + /// + /// ```rust,ignore (needs command line option) + /// #[cfg(feature = "sedre"))] + /// fn foo() {} + /// ``` + /// + /// This will produce: + /// + /// ```text + /// warning: unknown condition value used + /// --> lint_example.rs:1:7 + /// | + /// 1 | #[cfg(feature = "sedre")] + /// | ^^^^^^^^^^^^^^^^^ + /// | + /// = note: `#[warn(invalid_cfg_value)]` on by default + /// ``` + /// + /// ### Explanation + /// + /// This lint is only active when a `--check-cfg='values(...)'` option has been passed + /// to the compiler and triggers whenever an unknown condition value is used for the + /// given name. The known condition values include values passed in `--check-cfg` + /// and `--cfg`. + pub INVALID_CFG_VALUE, + Warn, + "detects invalid #[cfg] condition values", +} + declare_lint_pass! { /// Does nothing as a lint pass, but registers some `Lint`s /// that are used by other parts of the compiler. @@ -3050,6 +3124,8 @@ declare_lint_pass! { BREAK_WITH_LABEL_AND_LOOP, UNUSED_ATTRIBUTES, NON_EXHAUSTIVE_OMITTED_PATTERNS, + INVALID_CFG_NAME, + INVALID_CFG_VALUE, ] } diff --git a/compiler/rustc_session/src/config.rs b/compiler/rustc_session/src/config.rs index 32aa035e1cdec..ec048c1566984 100644 --- a/compiler/rustc_session/src/config.rs +++ b/compiler/rustc_session/src/config.rs @@ -16,7 +16,7 @@ use rustc_target::spec::{SplitDebuginfo, Target, TargetTriple, TargetWarnings}; 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}; @@ -885,6 +885,91 @@ pub fn to_crate_config(cfg: FxHashSet<(String, Option)>) -> 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 { + /// Set if `names()` checking is enabled + pub names_checked: bool, + /// The union of all `names()` + pub names_valid: FxHashSet, + /// The set of names for which `values()` was used + pub values_checked: FxHashSet, + /// The set of all (name, value) pairs passed in `values()` + pub values_valid: FxHashSet<(String, String)>, +} + +impl Default for CheckCfg { + fn default() -> Self { + CheckCfg { + names_checked: false, + names_valid: FxHashSet::default(), + values_checked: FxHashSet::default(), + values_valid: FxHashSet::default(), + } + } +} + +/// 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_check_config(cfg: CheckCfg) -> CrateCheckConfig { + let mut res = CrateCheckConfig { + names_checked: cfg.names_checked, + names_valid: cfg.names_valid.into_iter().map(|a| Symbol::intern(&a)).collect(), + values_checked: cfg.values_checked.into_iter().map(|a| Symbol::intern(&a)).collect(), + values_valid: cfg + .values_valid + .into_iter() + .map(|(a, b)| (Symbol::intern(&a), Symbol::intern(&b))) + .collect(), + }; + res.names_valid.extend(res.values_checked.iter().copied()); + res +} + +/// Fills a `CrateCheckConfig` with well-known configuration names. +pub fn fill_check_config_well_known(check_cfg: &mut CrateCheckConfig) { + const WELL_KNOWN_NAMES: &[Symbol] = &[ + sym::target_os, + sym::windows, + sym::unix, + 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 { + check_cfg.names_valid.insert(name); + } +} + +/// Fills a `CrateCheckConfig` with configuration names and values that are actually active. +pub fn fill_check_config_actual( + check_cfg: &mut CrateCheckConfig, + cfg: &FxHashSet<(Symbol, Option)>, +) { + for &(k, v) in cfg { + check_cfg.names_valid.insert(k); + if let Some(v) = v { + check_cfg.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. @@ -1028,6 +1113,7 @@ pub fn rustc_short_optgroups() -> Vec { vec![ opt::flag_s("h", "help", "Display this message"), opt::multi_s("", "cfg", "Configure the compilation environment", "SPEC"), + opt::multi("", "check-cfg", "Provide list of valid cfg options for checking", "SPEC"), opt::multi_s( "L", "", diff --git a/compiler/rustc_session/src/parse.rs b/compiler/rustc_session/src/parse.rs index a007b53030271..10916a107db12 100644 --- a/compiler/rustc_session/src/parse.rs +++ b/compiler/rustc_session/src/parse.rs @@ -19,6 +19,28 @@ use std::str; /// environment of the crate, used to drive conditional compilation. pub type CrateConfig = FxHashSet<(Symbol, Option)>; +pub struct CrateCheckConfig { + /// Set if `names()` checking is enabled + pub names_checked: bool, + /// The union of all `names()` + pub names_valid: FxHashSet, + /// The set of names for which `values()` was used + pub values_checked: FxHashSet, + /// The set of all (name, value) pairs passed in `values()` + pub values_valid: FxHashSet<(Symbol, Symbol)>, +} + +impl Default for CrateCheckConfig { + fn default() -> Self { + Self { + names_checked: false, + names_valid: FxHashSet::default(), + values_checked: FxHashSet::default(), + values_valid: FxHashSet::default(), + } + } +} + /// Collected spans during parsing for places where a certain feature was /// used and should be feature gated accordingly in `check_crate`. #[derive(Default)] @@ -117,6 +139,7 @@ pub struct ParseSess { pub span_diagnostic: Handler, pub unstable_features: UnstableFeatures, pub config: CrateConfig, + pub check_config: CrateCheckConfig, pub edition: Edition, pub missing_fragment_specifiers: Lock>, /// Places where raw identifiers were used. This is used for feature-gating raw identifiers. @@ -157,6 +180,7 @@ impl ParseSess { span_diagnostic: handler, unstable_features: UnstableFeatures::from_environment(None), config: FxHashSet::default(), + check_config: CrateCheckConfig::default(), edition: ExpnId::root().expn_data().edition, missing_fragment_specifiers: Default::default(), raw_identifier_spans: Lock::new(Vec::new()), diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index 7cb4e18398cdc..3f1147977762c 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -846,6 +846,7 @@ symbols! { naked, naked_functions, name, + names, native_link_modifiers, native_link_modifiers_as_needed, native_link_modifiers_bundle, @@ -1394,6 +1395,7 @@ symbols! { va_list, va_start, val, + values, var, variant_count, vec, diff --git a/src/librustdoc/core.rs b/src/librustdoc/core.rs index ddbe68762ee04..cde9a085c5ec1 100644 --- a/src/librustdoc/core.rs +++ b/src/librustdoc/core.rs @@ -254,6 +254,7 @@ crate fn create_config( interface::Config { opts: sessopts, crate_cfg: interface::parse_cfgspecs(cfgs), + crate_check_cfg: interface::parse_check_cfg(vec![]), input, input_path: cpath, output_file: None, diff --git a/src/librustdoc/doctest.rs b/src/librustdoc/doctest.rs index ac760fad103c1..de5211a487aaa 100644 --- a/src/librustdoc/doctest.rs +++ b/src/librustdoc/doctest.rs @@ -90,6 +90,7 @@ crate fn run(options: Options) -> Result<(), ErrorReported> { let config = interface::Config { opts: sessopts, crate_cfg: interface::parse_cfgspecs(cfgs), + crate_check_cfg: interface::parse_check_cfg(vec![]), input, input_path: None, output_file: None, diff --git a/src/test/run-make-fulldeps/issue-19371/foo.rs b/src/test/run-make-fulldeps/issue-19371/foo.rs index 4acabbb70ede2..ec711eb4e487e 100644 --- a/src/test/run-make-fulldeps/issue-19371/foo.rs +++ b/src/test/run-make-fulldeps/issue-19371/foo.rs @@ -49,6 +49,7 @@ fn compile(code: String, output: PathBuf, sysroot: PathBuf) { let config = interface::Config { opts, crate_cfg: Default::default(), + crate_check_cfg: Default::default(), input, input_path: None, output_file: Some(output), diff --git a/src/test/ui/check-cfg/invalid-cfg-name.rs b/src/test/ui/check-cfg/invalid-cfg-name.rs new file mode 100644 index 0000000000000..46334da4572f6 --- /dev/null +++ b/src/test/ui/check-cfg/invalid-cfg-name.rs @@ -0,0 +1,14 @@ +// Check warning for invalid configuration name +// +// edition:2018 +// check-pass +// compile-flags: --check-cfg=names() -Z unstable-options + +#[cfg(widnows)] +//~^ WARNING unknown condition name used +pub fn f() {} + +#[cfg(windows)] +pub fn g() {} + +pub fn main() {} diff --git a/src/test/ui/check-cfg/invalid-cfg-name.stderr b/src/test/ui/check-cfg/invalid-cfg-name.stderr new file mode 100644 index 0000000000000..3ad800945b17f --- /dev/null +++ b/src/test/ui/check-cfg/invalid-cfg-name.stderr @@ -0,0 +1,10 @@ +warning: unknown condition name used + --> $DIR/invalid-cfg-name.rs:7:7 + | +LL | #[cfg(widnows)] + | ^^^^^^^ + | + = note: `#[warn(invalid_cfg_name)]` on by default + +warning: 1 warning emitted + diff --git a/src/test/ui/check-cfg/invalid-cfg-value.rs b/src/test/ui/check-cfg/invalid-cfg-value.rs new file mode 100644 index 0000000000000..ae2b578712341 --- /dev/null +++ b/src/test/ui/check-cfg/invalid-cfg-value.rs @@ -0,0 +1,17 @@ +// Check warning for invalid configuration value +// +// edition:2018 +// check-pass +// compile-flags: --check-cfg=values(feature,"serde") --cfg=feature="rand" -Z unstable-options + +#[cfg(feature = "sedre")] +//~^ WARNING unknown condition value used +pub fn f() {} + +#[cfg(feature = "serde")] +pub fn g() {} + +#[cfg(feature = "rand")] +pub fn h() {} + +pub fn main() {} diff --git a/src/test/ui/check-cfg/invalid-cfg-value.stderr b/src/test/ui/check-cfg/invalid-cfg-value.stderr new file mode 100644 index 0000000000000..64835da7349c8 --- /dev/null +++ b/src/test/ui/check-cfg/invalid-cfg-value.stderr @@ -0,0 +1,10 @@ +warning: unknown condition value used + --> $DIR/invalid-cfg-value.rs:7:7 + | +LL | #[cfg(feature = "sedre")] + | ^^^^^^^^^^^^^^^^^ + | + = note: `#[warn(invalid_cfg_value)]` on by default + +warning: 1 warning emitted +