Skip to content
Open
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
2 changes: 1 addition & 1 deletion compiler/rustc_ast/src/attr/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -481,7 +481,7 @@ impl MetaItem {
}
}

fn from_tokens(iter: &mut TokenStreamIter<'_>) -> Option<MetaItem> {
pub fn from_tokens(iter: &mut TokenStreamIter<'_>) -> Option<MetaItem> {
// FIXME: Share code with `parse_path`.
let tt = iter.next().map(|tt| TokenTree::uninterpolate(tt));
let path = match tt.as_deref() {
Expand Down
150 changes: 146 additions & 4 deletions compiler/rustc_attr_parsing/src/attributes/cfg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use std::convert::identity;
use rustc_ast::token::Delimiter;
use rustc_ast::tokenstream::DelimSpan;
use rustc_ast::{AttrItem, Attribute, LitKind, ast, token};
use rustc_errors::{Applicability, PResult, msg};
use rustc_errors::{Applicability, Diagnostic, PResult, msg};
use rustc_feature::{
AttrSuggestionStyle, AttributeTemplate, Features, GatedCfg, find_gated_cfg, template,
};
Expand Down Expand Up @@ -85,9 +85,151 @@ pub fn parse_cfg<S: Stage>(
parse_cfg_entry(cx, single).ok()
}

fn generate_cfg_replacement(entry: &CfgEntry) -> String {
generate_cfg_replacement_inner(entry, true, false)
}

fn generate_cfg_replacement_inner(entry: &CfgEntry, is_top_level: bool, parent_is_not: bool) -> String {
match entry {
CfgEntry::NameValue { name, value, .. } => {
let name = name.as_str();
match value {
Some(value) => format!(
"{}std::env::var(\"CARGO_CFG_{}\").unwrap_or_default() == \"{value}\"",
if parent_is_not { "!" } else { "" },
name.to_uppercase(),
),
None => format!(
"std::env::var(\"CARGO_CFG_{}\"){}",
name.to_uppercase(),
if parent_is_not { ".is_err()" } else { ".is_ok()" },
),
}
}
CfgEntry::Any(entries, _) => {
match entries.as_slice() {
[] => if parent_is_not { "true" } else { "false" }.to_string(),
[entry] => generate_cfg_replacement_inner(&entry, is_top_level, parent_is_not),
_ => format!(
"{not}{open_paren}{cond}{closing_paren}",
not = if parent_is_not { "!" } else { "" },
open_paren = if !parent_is_not && is_top_level { "" } else { "(" },
cond = entries
.iter()
.map(|cfg| generate_cfg_replacement_inner(cfg, false, false))
.collect::<Vec<_>>()
.join(" || "),
closing_paren = if !parent_is_not && is_top_level { "" } else { ")" },
),
}
}
CfgEntry::All(entries, _) => {
match entries.as_slice() {
[] => if parent_is_not { "false" } else { "true" }.to_string(),
[entry] => generate_cfg_replacement_inner(&entry, is_top_level, parent_is_not),
_ => format!(
"{not}{open_paren}{cond}{closing_paren}",
not = if parent_is_not { "!" } else { "" },
open_paren = if !parent_is_not && is_top_level { "" } else { "(" },
cond = entries
.iter()
.map(|cfg| generate_cfg_replacement_inner(cfg, false, false))
.collect::<Vec<_>>()
.join(" && "),
closing_paren = if !parent_is_not && is_top_level { "" } else { ")" },
),
}
}
CfgEntry::Not(cfg, _) => generate_cfg_replacement_inner(cfg, is_top_level, true),
_ => String::new(),
}
}

fn misleading_cfgs(entry: &CfgEntry, spans: &mut Vec<Span>, has_ok_cfgs: &mut bool) {
match entry {
CfgEntry::All(entries, _) | CfgEntry::Any(entries, _) => {
for entry in entries {
misleading_cfgs(entry, spans, has_ok_cfgs);
}
}
CfgEntry::Not(entry, _) => misleading_cfgs(entry, spans, has_ok_cfgs),
CfgEntry::Bool(..) | CfgEntry::Version(..) => {
*has_ok_cfgs = true;
}
CfgEntry::NameValue { name, value, span } => {
match value {
Some(_) => {
let name = name.as_str();
if name.starts_with("target_") {
spans.push(*span);
} else {
*has_ok_cfgs = true;
}
}
None => {
if [sym::windows, sym::unix].contains(&name) {
spans.push(*span);
} else {
*has_ok_cfgs = true;
}
}
}
}
}
}

fn check_unexpected_cfgs<S: Stage>(cx: &mut AcceptContext<'_, '_, S>, entry: &CfgEntry, _span: Span, is_cfg_macro: bool) {
let mut spans = Vec::new();
let mut has_ok_cfgs = false;
misleading_cfgs(entry, &mut spans, &mut has_ok_cfgs);
if spans.is_empty() {
return;
}
if !is_cfg_macro || has_ok_cfgs {
cx.emit_dyn_lint(
UNEXPECTED_CFGS,
|dcx, level| crate::errors::UnexpectedCfg {
span: None,
suggestion_message: "",
}.into_diag(dcx, level),
spans,
);
return;
}
let replacement = generate_cfg_replacement(entry);
// The span including the `cfg!()` macro.
let span = cx.attr_span;
cx.emit_dyn_lint(
UNEXPECTED_CFGS,
move |dcx, level| crate::errors::UnexpectedCfg {
span: Some(span),
suggestion_message: &replacement,
}.into_diag(dcx, level),
span,
);
}

pub fn parse_cfg_entry_macro<S: Stage>(
cx: &mut AcceptContext<'_, '_, S>,
item: &MetaItemOrLitParser,
) -> Result<CfgEntry, ErrorGuaranteed> {
let entry = parse_cfg_entry_inner(cx, item)?;
check_unexpected_cfgs(cx, &entry, item.span(), true);
Ok(entry)
}

pub fn parse_cfg_entry<S: Stage>(
cx: &mut AcceptContext<'_, '_, S>,
item: &MetaItemOrLitParser,
) -> Result<CfgEntry, ErrorGuaranteed> {
let entry = parse_cfg_entry_inner(cx, item)?;
check_unexpected_cfgs(cx, &entry, item.span(), false);
Ok(entry)
}

fn parse_cfg_entry_inner<S: Stage>(
cx: &mut AcceptContext<'_, '_, S>,
item: &MetaItemOrLitParser,
) -> Result<CfgEntry, ErrorGuaranteed> {
Ok(match item {
MetaItemOrLitParser::MetaItemParser(meta) => match meta.args() {
Expand All @@ -96,14 +238,14 @@ pub fn parse_cfg_entry<S: Stage>(
let Some(single) = list.single() else {
return Err(cx.adcx().expected_single_argument(list.span, list.len()));
};
CfgEntry::Not(Box::new(parse_cfg_entry(cx, single)?), list.span)
CfgEntry::Not(Box::new(parse_cfg_entry_inner(cx, single)?), list.span)
}
Some(sym::any) => CfgEntry::Any(
list.mixed().flat_map(|sub_item| parse_cfg_entry(cx, sub_item)).collect(),
list.mixed().flat_map(|sub_item| parse_cfg_entry_inner(cx, sub_item)).collect(),
list.span,
),
Some(sym::all) => CfgEntry::All(
list.mixed().flat_map(|sub_item| parse_cfg_entry(cx, sub_item)).collect(),
list.mixed().flat_map(|sub_item| parse_cfg_entry_inner(cx, sub_item)).collect(),
list.span,
),
Some(sym::target) => parse_cfg_entry_target(cx, list, meta.span())?,
Expand Down
12 changes: 12 additions & 0 deletions compiler/rustc_attr_parsing/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -321,3 +321,15 @@ pub(crate) struct IncorrectDoNotRecommendLocation {
#[label("not a trait implementation")]
pub target_span: Span,
}

#[derive(Diagnostic)]
#[diag("target-based cfg should be avoided in build scripts")]
pub(crate) struct UnexpectedCfg<'a> {
#[suggestion(
"use cargo environment variables if possible",
code = "{suggestion_message}",
applicability = "maybe-incorrect",
)]
pub span: Option<Span>,
pub suggestion_message: &'a str,
}
1 change: 1 addition & 0 deletions compiler/rustc_attr_parsing/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ pub mod validate_attr;
pub use attributes::AttributeSafety;
pub use attributes::cfg::{
CFG_TEMPLATE, EvalConfigResult, eval_config_entry, parse_cfg, parse_cfg_attr, parse_cfg_entry,
parse_cfg_entry_macro,
};
pub use attributes::cfg_select::*;
pub use attributes::util::{is_builtin_attr, parse_version};
Expand Down
4 changes: 2 additions & 2 deletions compiler/rustc_builtin_macros/src/cfg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use rustc_ast::{AttrStyle, token};
use rustc_attr_parsing::parser::{AllowExprMetavar, MetaItemOrLitParser};
use rustc_attr_parsing::{
self as attr, AttributeParser, AttributeSafety, CFG_TEMPLATE, ParsedDescription, ShouldEmit,
parse_cfg_entry,
parse_cfg_entry_macro,
};
use rustc_expand::base::{DummyResult, ExpandResult, ExtCtxt, MacEager, MacroExpanderResult};
use rustc_hir::attrs::CfgEntry;
Expand Down Expand Up @@ -63,7 +63,7 @@ fn parse_cfg(cx: &ExtCtxt<'_>, span: Span, tts: TokenStream) -> Result<CfgEntry,
Some(cx.ecfg.features),
ShouldEmit::ErrorsAndLints { recovery: Recovery::Allowed },
&meta,
parse_cfg_entry,
parse_cfg_entry_macro,
&CFG_TEMPLATE,
)?;

Expand Down
2 changes: 1 addition & 1 deletion compiler/rustc_lint/src/early/diagnostics/check_cfg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ fn cargo_help_sub(
inst: &impl Fn(EscapeQuotes) -> String,
) -> lints::UnexpectedCfgCargoHelp {
// We don't want to suggest the `build.rs` way to expected cfgs if we are already in a
// `build.rs`. We therefor do a best effort check (looking if the `--crate-name` is
// `build.rs`. We therefore do a best effort check (looking if the `--crate-name` is
// `build_script_build`) to try to figure out if we are building a Cargo build script

let unescaped = &inst(EscapeQuotes::No);
Expand Down
27 changes: 27 additions & 0 deletions compiler/rustc_lint_defs/src/builtin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ declare_lint_pass! {
MALFORMED_DIAGNOSTIC_ATTRIBUTES,
MALFORMED_DIAGNOSTIC_FORMAT_LITERALS,
META_VARIABLE_MISUSE,
MISLEADING_CFG_IN_BUILD_SCRIPT,
MISPLACED_DIAGNOSTIC_ATTRIBUTES,
MISSING_ABI,
MISSING_UNSAFE_ON_EXTERN,
Expand Down Expand Up @@ -5695,3 +5696,29 @@ declare_lint! {
report_in_deps: false,
};
}

declare_lint! {
/// Checks for usage of `#[cfg]`/`#[cfg_attr]`/`cfg!()` in `build.rs` scripts.
///
/// ### Explanation
///
/// It checks the `cfg` values for the *host*, not the target. For example, `cfg!(windows)` is
/// true when compiling on Windows, so it will give the wrong answer if you are cross compiling.
/// This is because build scripts run on the machine performing compilation, rather than on the
/// target.
///
/// ### Example
///
/// ```rust,ignore (should only be run in cargo build scripts)
/// if cfg!(windows) {}
/// ```
///
/// Use instead:
///
/// ```rust
/// if std::env::var("CARGO_CFG_WINDOWS").is_ok() {}
/// ```
pub MISLEADING_CFG_IN_BUILD_SCRIPT,
Allow,
"use of host configs in `build.rs` scripts"
}
1 change: 1 addition & 0 deletions src/tools/lint-docs/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,7 @@ impl<'a> LintExtractor<'a> {
if matches!(
lint.name.as_str(),
"unused_features" // broken lint
| "misleading_cfg_in_build_script" // only run in cargo build scripts
) {
return Ok(());
}
Expand Down
47 changes: 47 additions & 0 deletions tests/ui/lint/misleading_cfg_in_build_script.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// This test checks the `cfg()` attributes/macros in cargo build scripts.
//
//@ no-auto-check-cfg

#![deny(misleading_cfg_in_build_script)]
#![allow(dead_code)]

#[cfg(windows)]
//~^ ERROR: misleading_cfg_in_build_script
fn unused_windows_fn() {}
#[cfg(not(windows))]
//~^ ERROR: misleading_cfg_in_build_script
fn unused_not_windows_fn() {}
#[cfg(any(windows, feature = "yellow", unix))]
//~^ ERROR: misleading_cfg_in_build_script
fn pink() {}

// Should not lint.
#[cfg(feature = "green")]
fn pink() {}
#[cfg(bob)]
fn bob() {}

fn main() {
if cfg!(windows) {}
//~^ ERROR: misleading_cfg_in_build_script
if cfg!(not(windows)) {}
//~^ ERROR: misleading_cfg_in_build_script
if cfg!(target_os = "freebsd") {}
//~^ ERROR: misleading_cfg_in_build_script
if cfg!(any(target_os = "freebsd", windows)) {}
//~^ ERROR: misleading_cfg_in_build_script
if cfg!(not(any(target_os = "freebsd", windows))) {}
//~^ ERROR: misleading_cfg_in_build_script
if cfg!(all(target_os = "freebsd", windows)) {}
//~^ ERROR: misleading_cfg_in_build_script
if cfg!(not(all(target_os = "freebsd", windows))) {}
//~^ ERROR: misleading_cfg_in_build_script
if cfg!(all(target_os = "freebsd", any(windows, not(feature = "red")))) {}
//~^ ERROR: misleading_cfg_in_build_script

// Should not warn.
if cfg!(any()) {}
if cfg!(all()) {}
if cfg!(feature = "blue") {}
if cfg!(bob) {}
}
Loading
Loading