Skip to content

Commit

Permalink
Implement --check-cfg option (RFC 3013)
Browse files Browse the repository at this point in the history
  • Loading branch information
mwkmwkmwk committed Sep 29, 2021
1 parent 8f8092c commit 49897f1
Show file tree
Hide file tree
Showing 14 changed files with 348 additions and 5 deletions.
31 changes: 29 additions & 2 deletions compiler/rustc_attr/src/builtin.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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))
}
}
})
Expand Down
2 changes: 2 additions & 0 deletions compiler/rustc_driver/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
82 changes: 80 additions & 2 deletions compiler/rustc_interface/src/interface.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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};
Expand Down Expand Up @@ -123,13 +124,89 @@ pub fn parse_cfgspecs(cfgspecs: Vec<String>) -> FxHashSet<(String, Option<String
})
}

/// Converts strings provided as `--check-cfg [spec]` 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 {
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
pub opts: config::Options,

/// 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>,
Expand Down Expand Up @@ -173,6 +250,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(),
Expand Down
6 changes: 6 additions & 0 deletions compiler/rustc_interface/src/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -64,6 +65,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>,
Expand Down Expand Up @@ -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))
}
Expand Down
76 changes: 76 additions & 0 deletions compiler/rustc_lint_defs/src/builtin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -3050,6 +3124,8 @@ declare_lint_pass! {
BREAK_WITH_LABEL_AND_LOOP,
UNUSED_ATTRIBUTES,
NON_EXHAUSTIVE_OMITTED_PATTERNS,
INVALID_CFG_NAME,
INVALID_CFG_VALUE,
]
}

Expand Down
77 changes: 76 additions & 1 deletion compiler/rustc_session/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -885,6 +885,80 @@ 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 {
/// Set if `names()` checking is enabled
pub names_checked: bool,
/// The union of all `names()`
pub names_valid: FxHashSet<String>,
/// The set of names for which `values()` was used
pub values_checked: FxHashSet<String>,
/// The set of all (name, value) pairs passed in `values()`
pub values_valid: FxHashSet<(String, String)>,
}

/// 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<Symbol>)>,
) {
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.
Expand Down Expand Up @@ -1028,6 +1102,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",
"",
Expand Down
Loading

0 comments on commit 49897f1

Please sign in to comment.