Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement --check-cfg option (RFC 3013), take 2 #93915

Merged
merged 1 commit into from
Feb 19, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 28 additions & 3 deletions compiler/rustc_attr/src/builtin.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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))
}
}
})
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 @@ -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,
Expand Down
91 changes: 89 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::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};
Expand Down Expand Up @@ -140,13 +141,98 @@ 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");
Urgau marked this conversation as resolved.
Show resolved Hide resolved
}
}
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"
Urgau marked this conversation as resolved.
Show resolved Hide resolved
);
}
}

continue 'specs;
} else {
error!(
"`values()` first argument must be a simple identifer"
Urgau marked this conversation as resolved.
Show resolved Hide resolved
);
}
}
}
}
}
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
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 @@ -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(),
Expand Down
8 changes: 8 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 @@ -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>,
Expand Down Expand Up @@ -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))
}
Expand Down
38 changes: 38 additions & 0 deletions compiler/rustc_lint_defs/src/builtin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -3055,6 +3092,7 @@ declare_lint_pass! {
DEPRECATED_CFG_ATTR_CRATE_TYPE_NAME,
DUPLICATE_MACRO_ATTRIBUTES,
SUSPICIOUS_AUTO_TRAIT_IMPLS,
UNEXPECTED_CFGS,
]
}

Expand Down
89 changes: 88 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::{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};
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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",
"",
Expand Down
Loading