From 3a73ca587bb8a8fb52d6045fbe31d50d5a56ff19 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Lo=C3=AFc=20BRANSTETT?= <lolo.branstett@numericable.fr>
Date: Wed, 29 Sep 2021 02:39:30 +0200
Subject: [PATCH] Implement --check-cfg option (RFC 3013)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Co-authored-by: Urgau <lolo.branstett@numericable.fr>
Co-authored-by: Marcelina Koƛcielnicka <mwk@0x04.net>
---
 compiler/rustc_attr/src/builtin.rs            | 31 ++++++-
 compiler/rustc_driver/src/lib.rs              |  2 +
 compiler/rustc_interface/src/interface.rs     | 91 ++++++++++++++++++-
 compiler/rustc_interface/src/util.rs          |  8 ++
 compiler/rustc_lint_defs/src/builtin.rs       | 38 ++++++++
 compiler/rustc_session/src/config.rs          | 89 +++++++++++++++++-
 compiler/rustc_session/src/parse.rs           |  4 +
 compiler/rustc_span/src/symbol.rs             |  2 +
 src/librustdoc/core.rs                        |  1 +
 src/librustdoc/doctest.rs                     |  1 +
 src/test/run-make-fulldeps/issue-19371/foo.rs |  1 +
 src/test/ui/check-cfg/empty-names.rs          | 10 ++
 src/test/ui/check-cfg/empty-names.stderr      | 10 ++
 src/test/ui/check-cfg/empty-values.rs         |  6 ++
 src/test/ui/check-cfg/empty-values.stderr     |  2 +
 .../invalid-arguments.anything_else.stderr    |  2 +
 ...nvalid-arguments.names_simple_ident.stderr |  2 +
 src/test/ui/check-cfg/invalid-arguments.rs    | 10 ++
 ...valid-arguments.values_simple_ident.stderr |  2 +
 ...id-arguments.values_string_literals.stderr |  2 +
 src/test/ui/check-cfg/invalid-cfg-name.rs     | 14 +++
 src/test/ui/check-cfg/invalid-cfg-name.stderr | 10 ++
 src/test/ui/check-cfg/invalid-cfg-value.rs    | 17 ++++
 .../ui/check-cfg/invalid-cfg-value.stderr     | 10 ++
 .../feature-gates/feature-gate-check-cfg.rs   |  3 +
 .../feature-gate-check-cfg.stderr             |  2 +
 src/tools/tidy/src/ui_tests.rs                |  2 +-
 27 files changed, 365 insertions(+), 7 deletions(-)
 create mode 100644 src/test/ui/check-cfg/empty-names.rs
 create mode 100644 src/test/ui/check-cfg/empty-names.stderr
 create mode 100644 src/test/ui/check-cfg/empty-values.rs
 create mode 100644 src/test/ui/check-cfg/empty-values.stderr
 create mode 100644 src/test/ui/check-cfg/invalid-arguments.anything_else.stderr
 create mode 100644 src/test/ui/check-cfg/invalid-arguments.names_simple_ident.stderr
 create mode 100644 src/test/ui/check-cfg/invalid-arguments.rs
 create mode 100644 src/test/ui/check-cfg/invalid-arguments.values_simple_ident.stderr
 create mode 100644 src/test/ui/check-cfg/invalid-arguments.values_string_literals.stderr
 create mode 100644 src/test/ui/check-cfg/invalid-cfg-name.rs
 create mode 100644 src/test/ui/check-cfg/invalid-cfg-name.stderr
 create mode 100644 src/test/ui/check-cfg/invalid-cfg-value.rs
 create mode 100644 src/test/ui/check-cfg/invalid-cfg-value.stderr
 create mode 100644 src/test/ui/feature-gates/feature-gate-check-cfg.rs
 create mode 100644 src/test/ui/feature-gates/feature-gate-check-cfg.stderr

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![
         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 d5b520325e5..7113f9b0a2f 100644
--- a/compiler/rustc_session/src/parse.rs
+++ b/compiler/rustc_session/src/parse.rs
@@ -1,6 +1,7 @@
 //! Contains `ParseSess` which holds state living beyond what one `Parser` might.
 //! It also serves as an input to the parser itself.
 
+use crate::config::CheckCfg;
 use crate::lint::{BufferedEarlyLint, BuiltinLintDiagnostics, Lint, LintId};
 use rustc_ast::node_id::NodeId;
 use rustc_data_structures::fx::{FxHashMap, FxHashSet};
@@ -18,6 +19,7 @@ use std::str;
 /// The set of keys (and, optionally, values) that define the compilation
 /// environment of the crate, used to drive conditional compilation.
 pub type CrateConfig = FxHashSet<(Symbol, Option<Symbol>)>;
+pub type CrateCheckConfig = CheckCfg<Symbol>;
 
 /// Collected spans during parsing for places where a certain feature was
 /// used and should be feature gated accordingly in `check_crate`.
@@ -117,6 +119,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<FxHashMap<Span, NodeId>>,
     /// Places where raw identifiers were used. This is used to avoid complaining about idents
@@ -162,6 +165,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 cab1d4e21c9..1d8b5ab75c2 100644
--- a/compiler/rustc_span/src/symbol.rs
+++ b/compiler/rustc_span/src/symbol.rs
@@ -908,6 +908,7 @@ symbols! {
         naked,
         naked_functions,
         name,
+        names,
         native_link_modifiers,
         native_link_modifiers_as_needed,
         native_link_modifiers_bundle,
@@ -1478,6 +1479,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 1f14a333c00..5d2f774f8cf 100644
--- a/src/librustdoc/core.rs
+++ b/src/librustdoc/core.rs
@@ -250,6 +250,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 6c55721c0dd..6f398c17683 100644
--- a/src/librustdoc/doctest.rs
+++ b/src/librustdoc/doctest.rs
@@ -91,6 +91,7 @@ crate fn run(options: RustdocOptions) -> 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 4acabbb70ed..ec711eb4e48 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/empty-names.rs b/src/test/ui/check-cfg/empty-names.rs
new file mode 100644
index 00000000000..046ff0364e2
--- /dev/null
+++ b/src/test/ui/check-cfg/empty-names.rs
@@ -0,0 +1,10 @@
+// Check warning for unexpected cfg
+//
+// check-pass
+// compile-flags: --check-cfg=names() -Z unstable-options
+
+#[cfg(unknown_key = "value")]
+//~^ WARNING unexpected `cfg` condition name
+pub fn f() {}
+
+fn main() {}
diff --git a/src/test/ui/check-cfg/empty-names.stderr b/src/test/ui/check-cfg/empty-names.stderr
new file mode 100644
index 00000000000..f926d1133cc
--- /dev/null
+++ b/src/test/ui/check-cfg/empty-names.stderr
@@ -0,0 +1,10 @@
+warning: unexpected `cfg` condition name
+  --> $DIR/empty-names.rs:6:7
+   |
+LL | #[cfg(unknown_key = "value")]
+   |       ^^^^^^^^^^^^^^^^^^^^^
+   |
+   = note: `#[warn(unexpected_cfgs)]` on by default
+
+warning: 1 warning emitted
+
diff --git a/src/test/ui/check-cfg/empty-values.rs b/src/test/ui/check-cfg/empty-values.rs
new file mode 100644
index 00000000000..38ef9e51c7a
--- /dev/null
+++ b/src/test/ui/check-cfg/empty-values.rs
@@ -0,0 +1,6 @@
+// Check that a an empty values() is rejected
+//
+// check-fail
+// compile-flags: --check-cfg=values() -Z unstable-options
+
+fn main() {}
diff --git a/src/test/ui/check-cfg/empty-values.stderr b/src/test/ui/check-cfg/empty-values.stderr
new file mode 100644
index 00000000000..106d5b7b47f
--- /dev/null
+++ b/src/test/ui/check-cfg/empty-values.stderr
@@ -0,0 +1,2 @@
+error: invalid `--check-cfg` argument: `values()` (expected `names(name1, name2, ... nameN)` or `values(name, "value1", "value2", ... "valueN")`)
+
diff --git a/src/test/ui/check-cfg/invalid-arguments.anything_else.stderr b/src/test/ui/check-cfg/invalid-arguments.anything_else.stderr
new file mode 100644
index 00000000000..850924d993a
--- /dev/null
+++ b/src/test/ui/check-cfg/invalid-arguments.anything_else.stderr
@@ -0,0 +1,2 @@
+error: invalid `--check-cfg` argument: `anything_else(...)` (expected `names(name1, name2, ... nameN)` or `values(name, "value1", "value2", ... "valueN")`)
+
diff --git a/src/test/ui/check-cfg/invalid-arguments.names_simple_ident.stderr b/src/test/ui/check-cfg/invalid-arguments.names_simple_ident.stderr
new file mode 100644
index 00000000000..bdfbc3d54a2
--- /dev/null
+++ b/src/test/ui/check-cfg/invalid-arguments.names_simple_ident.stderr
@@ -0,0 +1,2 @@
+error: invalid `--check-cfg` argument: `names("NOT_IDENT")` (`names()` arguments must be simple identifers)
+
diff --git a/src/test/ui/check-cfg/invalid-arguments.rs b/src/test/ui/check-cfg/invalid-arguments.rs
new file mode 100644
index 00000000000..5090ce3e845
--- /dev/null
+++ b/src/test/ui/check-cfg/invalid-arguments.rs
@@ -0,0 +1,10 @@
+// Check that invalid --check-cfg are rejected
+//
+// check-fail
+// revisions: anything_else names_simple_ident values_simple_ident values_string_literals
+// [anything_else]compile-flags: -Z unstable-options --check-cfg=anything_else(...)
+// [names_simple_ident]compile-flags: -Z unstable-options --check-cfg=names("NOT_IDENT")
+// [values_simple_ident]compile-flags: -Z unstable-options --check-cfg=values("NOT_IDENT")
+// [values_string_literals]compile-flags: -Z unstable-options --check-cfg=values(test,12)
+
+fn main() {}
diff --git a/src/test/ui/check-cfg/invalid-arguments.values_simple_ident.stderr b/src/test/ui/check-cfg/invalid-arguments.values_simple_ident.stderr
new file mode 100644
index 00000000000..b25882baaf3
--- /dev/null
+++ b/src/test/ui/check-cfg/invalid-arguments.values_simple_ident.stderr
@@ -0,0 +1,2 @@
+error: invalid `--check-cfg` argument: `values("NOT_IDENT")` (`values()` first argument must be a simple identifer)
+
diff --git a/src/test/ui/check-cfg/invalid-arguments.values_string_literals.stderr b/src/test/ui/check-cfg/invalid-arguments.values_string_literals.stderr
new file mode 100644
index 00000000000..5853b4741a6
--- /dev/null
+++ b/src/test/ui/check-cfg/invalid-arguments.values_string_literals.stderr
@@ -0,0 +1,2 @@
+error: invalid `--check-cfg` argument: `values(test,12)` (`values()` arguments must be string literals)
+
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 00000000000..8499d3d4448
--- /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 unexpected `cfg` condition name
+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 00000000000..2587685afa0
--- /dev/null
+++ b/src/test/ui/check-cfg/invalid-cfg-name.stderr
@@ -0,0 +1,10 @@
+warning: unexpected `cfg` condition name
+  --> $DIR/invalid-cfg-name.rs:7:7
+   |
+LL | #[cfg(widnows)]
+   |       ^^^^^^^
+   |
+   = note: `#[warn(unexpected_cfgs)]` 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 00000000000..a60095a5aae
--- /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","full") --cfg=feature="rand" -Z unstable-options
+
+#[cfg(feature = "sedre")]
+//~^ WARNING unexpected `cfg` condition value
+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 00000000000..c591d8474a2
--- /dev/null
+++ b/src/test/ui/check-cfg/invalid-cfg-value.stderr
@@ -0,0 +1,10 @@
+warning: unexpected `cfg` condition value
+  --> $DIR/invalid-cfg-value.rs:7:7
+   |
+LL | #[cfg(feature = "sedre")]
+   |       ^^^^^^^^^^^^^^^^^
+   |
+   = note: `#[warn(unexpected_cfgs)]` on by default
+
+warning: 1 warning emitted
+
diff --git a/src/test/ui/feature-gates/feature-gate-check-cfg.rs b/src/test/ui/feature-gates/feature-gate-check-cfg.rs
new file mode 100644
index 00000000000..4012a3b04b5
--- /dev/null
+++ b/src/test/ui/feature-gates/feature-gate-check-cfg.rs
@@ -0,0 +1,3 @@
+// compile-flags: --check-cfg "names()"
+
+fn main() {}
diff --git a/src/test/ui/feature-gates/feature-gate-check-cfg.stderr b/src/test/ui/feature-gates/feature-gate-check-cfg.stderr
new file mode 100644
index 00000000000..9b27c2bc058
--- /dev/null
+++ b/src/test/ui/feature-gates/feature-gate-check-cfg.stderr
@@ -0,0 +1,2 @@
+error: the `-Z unstable-options` flag must also be passed to enable the flag `check-cfg`
+
diff --git a/src/tools/tidy/src/ui_tests.rs b/src/tools/tidy/src/ui_tests.rs
index 203e33e8b1f..95847dcd46b 100644
--- a/src/tools/tidy/src/ui_tests.rs
+++ b/src/tools/tidy/src/ui_tests.rs
@@ -7,7 +7,7 @@ use std::path::Path;
 
 const ENTRY_LIMIT: usize = 1000;
 // FIXME: The following limits should be reduced eventually.
-const ROOT_ENTRY_LIMIT: usize = 982;
+const ROOT_ENTRY_LIMIT: usize = 983;
 const ISSUES_ENTRY_LIMIT: usize = 2310;
 
 fn check_entries(path: &Path, bad: &mut bool) {