From b72f906181c0c707ded1e123edc51e6c71c06b99 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Wed, 11 Mar 2026 14:16:39 +0100 Subject: [PATCH 1/6] Make `MetaItem::from_tokens` public --- compiler/rustc_ast/src/attr/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/rustc_ast/src/attr/mod.rs b/compiler/rustc_ast/src/attr/mod.rs index 0374a86d3eb1c..fa8f3376f24d5 100644 --- a/compiler/rustc_ast/src/attr/mod.rs +++ b/compiler/rustc_ast/src/attr/mod.rs @@ -481,7 +481,7 @@ impl MetaItem { } } - fn from_tokens(iter: &mut TokenStreamIter<'_>) -> Option { + pub fn from_tokens(iter: &mut TokenStreamIter<'_>) -> Option { // FIXME: Share code with `parse_path`. let tt = iter.next().map(|tt| TokenTree::uninterpolate(tt)); let path = match tt.as_deref() { From cfa81d84eea155e614832eb36bd3d2b43ff68560 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Wed, 11 Mar 2026 12:29:15 +0100 Subject: [PATCH 2/6] Add new `misleading_cfg_in_build_script` rustc lint --- .../src/attributes/diagnostic/check_cfg.rs | 2 +- compiler/rustc_lint/src/lib.rs | 3 + .../src/misleading_cfg_in_build_script.rs | 290 ++++++++++++++++++ 3 files changed, 294 insertions(+), 1 deletion(-) create mode 100644 compiler/rustc_lint/src/misleading_cfg_in_build_script.rs diff --git a/compiler/rustc_attr_parsing/src/attributes/diagnostic/check_cfg.rs b/compiler/rustc_attr_parsing/src/attributes/diagnostic/check_cfg.rs index 33db99c6bc520..0690592928b69 100644 --- a/compiler/rustc_attr_parsing/src/attributes/diagnostic/check_cfg.rs +++ b/compiler/rustc_attr_parsing/src/attributes/diagnostic/check_cfg.rs @@ -63,7 +63,7 @@ fn cargo_help_sub( inst: &impl Fn(EscapeQuotes) -> String, ) -> errors::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); diff --git a/compiler/rustc_lint/src/lib.rs b/compiler/rustc_lint/src/lib.rs index ea0e657f7edef..2791626fdab03 100644 --- a/compiler/rustc_lint/src/lib.rs +++ b/compiler/rustc_lint/src/lib.rs @@ -58,6 +58,7 @@ pub mod lifetime_syntax; mod lints; mod macro_expr_fragment_specifier_2024_migration; mod map_unit_fn; +mod misleading_cfg_in_build_script; mod multiple_supertrait_upcastable; mod non_ascii_idents; mod non_fmt_panic; @@ -102,6 +103,7 @@ use let_underscore::*; use lifetime_syntax::*; use macro_expr_fragment_specifier_2024_migration::*; use map_unit_fn::*; +use misleading_cfg_in_build_script::MisleadingCfgInBuildScript; use multiple_supertrait_upcastable::*; use non_ascii_idents::*; use non_fmt_panic::NonPanicFmt; @@ -156,6 +158,7 @@ early_lint_methods!( pub BuiltinCombinedPreExpansionLintPass, [ KeywordIdents: KeywordIdents, + MisleadingCfgInBuildScript: MisleadingCfgInBuildScript, ] ] ); diff --git a/compiler/rustc_lint/src/misleading_cfg_in_build_script.rs b/compiler/rustc_lint/src/misleading_cfg_in_build_script.rs new file mode 100644 index 0000000000000..435b65184e7f7 --- /dev/null +++ b/compiler/rustc_lint/src/misleading_cfg_in_build_script.rs @@ -0,0 +1,290 @@ +use rustc_ast::ast::{Attribute, MacCall}; +use rustc_ast::tokenstream::TokenStream; +use rustc_ast::{MetaItem, MetaItemInner, MetaItemKind}; +use rustc_errors::{Applicability, DiagDecorator}; +use rustc_session::{declare_lint, declare_lint_pass}; +use rustc_span::{Span, Symbol, sym}; + +use crate::{EarlyContext, EarlyLintPass, LintContext}; + +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 (can 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" +} + +declare_lint_pass!(MisleadingCfgInBuildScript => [MISLEADING_CFG_IN_BUILD_SCRIPT]); + +/// Represents the AST of `cfg` attribute and `cfg!` macro. +#[derive(Debug)] +enum CfgAst { + /// Represents an OS family such as "unix" or "windows". + OsFamily(Symbol), + /// The `any()` attribute. + Any(Vec), + /// The `all()` attribute. + All(Vec), + /// The `not()` attribute. + Not(Box), + /// Any `target_* = ""` key/value attribute. + TargetKeyValue(Symbol, Symbol), + /// the `feature = ""` attribute with its associated value. + Feature(Symbol), +} + +impl CfgAst { + fn has_only_features(&self) -> bool { + match self { + Self::OsFamily(_) | Self::TargetKeyValue(_, _) => false, + Self::Any(v) | Self::All(v) => v.is_empty() || v.iter().all(CfgAst::has_only_features), + Self::Not(v) => v.has_only_features(), + Self::Feature(_) => true, + } + } + + fn generate_replacement(&self) -> String { + self.generate_replacement_inner(true, false) + } + + fn generate_replacement_inner(&self, is_top_level: bool, parent_is_not: bool) -> String { + match self { + Self::OsFamily(os) => format!( + "std::env::var(\"CARGO_CFG_{}\"){}", + os.as_str().to_uppercase(), + if parent_is_not { ".is_err()" } else { ".is_ok()" }, + ), + Self::TargetKeyValue(cfg_target, s) => format!( + "{}std::env::var(\"CARGO_CFG_{}\").unwrap_or_default() == \"{s}\"", + if parent_is_not { "!" } else { "" }, + cfg_target.as_str().to_uppercase(), + ), + Self::Any(v) => { + if v.is_empty() { + if parent_is_not { "true" } else { "false" }.to_string() + } else if v.len() == 1 { + v[0].generate_replacement_inner(is_top_level, parent_is_not) + } else { + format!( + "{not}{open_paren}{cond}{closing_paren}", + not = if parent_is_not { "!" } else { "" }, + open_paren = if !parent_is_not && is_top_level { "" } else { "(" }, + cond = v + .iter() + .map(|i| i.generate_replacement_inner(false, false)) + .collect::>() + .join(" || "), + closing_paren = if !parent_is_not && is_top_level { "" } else { ")" }, + ) + } + } + Self::All(v) => { + if v.is_empty() { + if parent_is_not { "false" } else { "true" }.to_string() + } else if v.len() == 1 { + v[0].generate_replacement_inner(is_top_level, parent_is_not) + } else { + format!( + "{not}{open_paren}{cond}{closing_paren}", + not = if parent_is_not { "!" } else { "" }, + open_paren = if !parent_is_not && is_top_level { "" } else { "(" }, + cond = v + .iter() + .map(|i| i.generate_replacement_inner(false, false)) + .collect::>() + .join(" && "), + closing_paren = if !parent_is_not && is_top_level { "" } else { ")" }, + ) + } + } + Self::Not(i) => i.generate_replacement_inner(is_top_level, true), + Self::Feature(s) => format!( + "cfg!({}feature = {s}{})", + if parent_is_not { "not(" } else { "" }, + if parent_is_not { ")" } else { "" }, + ), + } + } +} + +fn parse_meta_item(meta: MetaItem, has_unknown: &mut bool, out: &mut Vec) { + let Some(name) = meta.name() else { + *has_unknown = true; + return; + }; + match meta.kind { + MetaItemKind::Word => { + if [sym::windows, sym::unix].contains(&name) { + out.push(CfgAst::OsFamily(name)); + return; + } + } + MetaItemKind::NameValue(item) => { + if name == sym::feature { + out.push(CfgAst::Feature(item.symbol)); + return; + } else if name.as_str().starts_with("target_") { + out.push(CfgAst::TargetKeyValue(name, item.symbol)); + return; + } + } + MetaItemKind::List(item) => { + if !*has_unknown && [sym::any, sym::not, sym::all].contains(&name) { + let mut sub_out = Vec::new(); + + for sub in item { + if let MetaItemInner::MetaItem(item) = sub { + parse_meta_item(item, has_unknown, &mut sub_out); + if *has_unknown { + return; + } + } + } + if name == sym::any { + out.push(CfgAst::Any(sub_out)); + return; + } else if name == sym::all { + out.push(CfgAst::All(sub_out)); + return; + // `not()` can only have one argument. + } else if sub_out.len() == 1 { + out.push(CfgAst::Not(Box::new(sub_out.pop().unwrap()))); + return; + } + } + } + } + *has_unknown = true; +} + +fn parse_macro_args(tokens: &TokenStream, has_unknown: &mut bool, out: &mut Vec) { + if let Some(meta) = MetaItem::from_tokens(&mut tokens.iter()) { + parse_meta_item(meta, has_unknown, out); + } +} + +fn get_invalid_cfg_attrs(attr: &MetaItem, spans: &mut Vec, has_unknown: &mut bool) { + let Some(ident) = attr.ident() else { return }; + if [sym::unix, sym::windows].contains(&ident.name) { + spans.push(attr.span); + } else if attr.value_str().is_some() && ident.name.as_str().starts_with("target_") { + spans.push(attr.span); + } else if let Some(sub_attrs) = attr.meta_item_list() { + for sub_attr in sub_attrs { + if let Some(meta) = sub_attr.meta_item() { + get_invalid_cfg_attrs(meta, spans, has_unknown); + } + } + } else { + *has_unknown = true; + } +} + +fn is_build_script(cx: &EarlyContext<'_>) -> bool { + rustc_session::utils::was_invoked_from_cargo() + && cx.sess().opts.crate_name.as_deref() == Some("build_script_build") +} + +const ERROR_MESSAGE: &str = "target-based cfg should be avoided in build scripts"; + +impl EarlyLintPass for MisleadingCfgInBuildScript { + fn check_attribute(&mut self, cx: &EarlyContext<'_>, attr: &Attribute) { + if !is_build_script(cx) { + return; + } + + let mut spans = Vec::new(); + let mut has_unknown = false; + match attr.name() { + Some(sym::cfg) if let Some(meta) = attr.meta() => { + get_invalid_cfg_attrs(&meta, &mut spans, &mut has_unknown); + } + Some(sym::cfg_attr) + if let Some(sub_attrs) = attr.meta_item_list() + && let Some(meta) = sub_attrs.first().and_then(|a| a.meta_item()) => + { + get_invalid_cfg_attrs(meta, &mut spans, &mut has_unknown); + } + _ => return, + } + if !spans.is_empty() { + if has_unknown { + // If the `cfg`/`cfg_attr` attribute contains not only invalid items, we display + // spans of all invalid items. + cx.emit_span_lint( + MISLEADING_CFG_IN_BUILD_SCRIPT, + spans, + DiagDecorator(|diag| { + diag.primary_message(ERROR_MESSAGE); + }), + ); + } else { + // No "good" item in the `cfg`/`cfg_attr` attribute so we can use the span of the + // whole attribute directly. + cx.emit_span_lint( + MISLEADING_CFG_IN_BUILD_SCRIPT, + attr.span, + DiagDecorator(|diag| { + diag.primary_message(ERROR_MESSAGE); + }), + ); + } + } + } + + fn check_mac(&mut self, cx: &EarlyContext<'_>, call: &MacCall) { + if !is_build_script(cx) { + return; + } + + if call.path.segments.len() == 1 && call.path.segments[0].ident.name == sym::cfg { + let mut ast = Vec::new(); + let mut has_unknown = false; + parse_macro_args(&call.args.tokens, &mut has_unknown, &mut ast); + if !has_unknown && ast.len() > 1 { + cx.emit_span_lint( + MISLEADING_CFG_IN_BUILD_SCRIPT, + call.span(), + DiagDecorator(|diag| { + diag.primary_message(ERROR_MESSAGE); + }), + ); + } else if let Some(ast) = ast.get(0) + && !ast.has_only_features() + { + let span = call.span(); + cx.emit_span_lint( + MISLEADING_CFG_IN_BUILD_SCRIPT, + span, + DiagDecorator(|diag| { + diag.primary_message(ERROR_MESSAGE).span_suggestion( + span, + "use cargo environment variables if possible", + ast.generate_replacement(), + Applicability::MaybeIncorrect, + ); + }), + ); + } + } + } +} From 049c566e97be8aca3cb0daa8f7f7d6bb8bb0fdb4 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Wed, 11 Mar 2026 14:35:39 +0100 Subject: [PATCH 3/6] Add ui test for new `misleading_cfg_in_build_script` lint --- ...misleading_cfg_in_build_script-no-cargo.rs | 36 +++++++++ .../ui/lint/misleading_cfg_in_build_script.rs | 49 ++++++++++++ .../misleading_cfg_in_build_script.stderr | 74 +++++++++++++++++++ 3 files changed, 159 insertions(+) create mode 100644 tests/ui/lint/misleading_cfg_in_build_script-no-cargo.rs create mode 100644 tests/ui/lint/misleading_cfg_in_build_script.rs create mode 100644 tests/ui/lint/misleading_cfg_in_build_script.stderr diff --git a/tests/ui/lint/misleading_cfg_in_build_script-no-cargo.rs b/tests/ui/lint/misleading_cfg_in_build_script-no-cargo.rs new file mode 100644 index 0000000000000..42cdcc83601a3 --- /dev/null +++ b/tests/ui/lint/misleading_cfg_in_build_script-no-cargo.rs @@ -0,0 +1,36 @@ +// This test ensures that the `misleading_cfg_in_build_script` is not emitted if not +// in a cargo `build.rs` script. +// +//@ no-auto-check-cfg +//@ check-pass + +#![deny(misleading_cfg_in_build_script)] +#![allow(dead_code)] + +#[cfg(windows)] +fn unused_windows_fn() {} +#[cfg(not(windows))] +fn unused_not_windows_fn() {} +#[cfg(any(windows, feature = "yellow", unix))] +fn pink() {} + +#[cfg(feature = "green")] +fn pink() {} +#[cfg(bob)] +fn bob() {} + +fn main() { + if cfg!(windows) {} + if cfg!(not(windows)) {} + if cfg!(target_os = "freebsd") {} + if cfg!(any(target_os = "freebsd", windows)) {} + if cfg!(not(any(target_os = "freebsd", windows))) {} + if cfg!(all(target_os = "freebsd", windows)) {} + if cfg!(not(all(target_os = "freebsd", windows))) {} + if cfg!(all(target_os = "freebsd", any(windows, not(feature = "red")))) {} + + if cfg!(any()) {} + if cfg!(all()) {} + if cfg!(feature = "blue") {} + if cfg!(bob) {} +} diff --git a/tests/ui/lint/misleading_cfg_in_build_script.rs b/tests/ui/lint/misleading_cfg_in_build_script.rs new file mode 100644 index 0000000000000..75df4addec0ff --- /dev/null +++ b/tests/ui/lint/misleading_cfg_in_build_script.rs @@ -0,0 +1,49 @@ +// This test checks the `cfg()` attributes/macros in cargo build scripts. +// +//@ no-auto-check-cfg +//@ rustc-env:CARGO_CRATE_NAME=build_script_build +//@ compile-flags:--crate-name=build_script_build + +#![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) {} +} diff --git a/tests/ui/lint/misleading_cfg_in_build_script.stderr b/tests/ui/lint/misleading_cfg_in_build_script.stderr new file mode 100644 index 0000000000000..7152d40feafd9 --- /dev/null +++ b/tests/ui/lint/misleading_cfg_in_build_script.stderr @@ -0,0 +1,74 @@ +error: target-based cfg should be avoided in build scripts + --> $DIR/misleading_cfg_in_build_script.rs:10:1 + | +LL | #[cfg(windows)] + | ^^^^^^^^^^^^^^^ + | +note: the lint level is defined here + --> $DIR/misleading_cfg_in_build_script.rs:7:9 + | +LL | #![deny(misleading_cfg_in_build_script)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: target-based cfg should be avoided in build scripts + --> $DIR/misleading_cfg_in_build_script.rs:13:1 + | +LL | #[cfg(not(windows))] + | ^^^^^^^^^^^^^^^^^^^^ + +error: target-based cfg should be avoided in build scripts + --> $DIR/misleading_cfg_in_build_script.rs:16:11 + | +LL | #[cfg(any(windows, feature = "yellow", unix))] + | ^^^^^^^ ^^^^ + +error: target-based cfg should be avoided in build scripts + --> $DIR/misleading_cfg_in_build_script.rs:27:8 + | +LL | if cfg!(windows) {} + | ^^^^^^^^^^^^^ help: use cargo environment variables if possible: `std::env::var("CARGO_CFG_WINDOWS").is_ok()` + +error: target-based cfg should be avoided in build scripts + --> $DIR/misleading_cfg_in_build_script.rs:29:8 + | +LL | if cfg!(not(windows)) {} + | ^^^^^^^^^^^^^^^^^^ help: use cargo environment variables if possible: `std::env::var("CARGO_CFG_WINDOWS").is_err()` + +error: target-based cfg should be avoided in build scripts + --> $DIR/misleading_cfg_in_build_script.rs:31:8 + | +LL | if cfg!(target_os = "freebsd") {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use cargo environment variables if possible: `std::env::var("CARGO_CFG_TARGET_OS").unwrap_or_default() == "freebsd"` + +error: target-based cfg should be avoided in build scripts + --> $DIR/misleading_cfg_in_build_script.rs:33:8 + | +LL | if cfg!(any(target_os = "freebsd", windows)) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use cargo environment variables if possible: `std::env::var("CARGO_CFG_TARGET_OS").unwrap_or_default() == "freebsd" || std::env::var("CARGO_CFG_WINDOWS").is_ok()` + +error: target-based cfg should be avoided in build scripts + --> $DIR/misleading_cfg_in_build_script.rs:35:8 + | +LL | if cfg!(not(any(target_os = "freebsd", windows))) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use cargo environment variables if possible: `!(std::env::var("CARGO_CFG_TARGET_OS").unwrap_or_default() == "freebsd" || std::env::var("CARGO_CFG_WINDOWS").is_ok())` + +error: target-based cfg should be avoided in build scripts + --> $DIR/misleading_cfg_in_build_script.rs:37:8 + | +LL | if cfg!(all(target_os = "freebsd", windows)) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use cargo environment variables if possible: `std::env::var("CARGO_CFG_TARGET_OS").unwrap_or_default() == "freebsd" && std::env::var("CARGO_CFG_WINDOWS").is_ok()` + +error: target-based cfg should be avoided in build scripts + --> $DIR/misleading_cfg_in_build_script.rs:39:8 + | +LL | if cfg!(not(all(target_os = "freebsd", windows))) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use cargo environment variables if possible: `!(std::env::var("CARGO_CFG_TARGET_OS").unwrap_or_default() == "freebsd" && std::env::var("CARGO_CFG_WINDOWS").is_ok())` + +error: target-based cfg should be avoided in build scripts + --> $DIR/misleading_cfg_in_build_script.rs:41:8 + | +LL | if cfg!(all(target_os = "freebsd", any(windows, not(feature = "red")))) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use cargo environment variables if possible: `std::env::var("CARGO_CFG_TARGET_OS").unwrap_or_default() == "freebsd" && (std::env::var("CARGO_CFG_WINDOWS").is_ok() || cfg!(not(feature = red)))` + +error: aborting due to 11 previous errors + From 1c5065d7378851477b25205633875732b4a2a10e Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Wed, 11 Mar 2026 23:02:39 +0100 Subject: [PATCH 4/6] Add `misleading_cfg_in_build_script` to the list of lints which cannot have a compiler output generated by the lint-docs script --- src/tools/lint-docs/src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/tools/lint-docs/src/lib.rs b/src/tools/lint-docs/src/lib.rs index f7d487333e32b..739de9e801a59 100644 --- a/src/tools/lint-docs/src/lib.rs +++ b/src/tools/lint-docs/src/lib.rs @@ -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(()); } From 7c548cb325108f29e88c091b695722b666bcbac9 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Thu, 19 Mar 2026 14:33:31 +0100 Subject: [PATCH 5/6] Remove `is_build_script` check for `misleading_cfg_in_build_script` rustc lint --- .../src/misleading_cfg_in_build_script.rs | 15 +------- ...misleading_cfg_in_build_script-no-cargo.rs | 36 ------------------- .../ui/lint/misleading_cfg_in_build_script.rs | 2 -- .../misleading_cfg_in_build_script.stderr | 24 ++++++------- 4 files changed, 13 insertions(+), 64 deletions(-) delete mode 100644 tests/ui/lint/misleading_cfg_in_build_script-no-cargo.rs diff --git a/compiler/rustc_lint/src/misleading_cfg_in_build_script.rs b/compiler/rustc_lint/src/misleading_cfg_in_build_script.rs index 435b65184e7f7..fce3c017ec2a7 100644 --- a/compiler/rustc_lint/src/misleading_cfg_in_build_script.rs +++ b/compiler/rustc_lint/src/misleading_cfg_in_build_script.rs @@ -19,7 +19,7 @@ declare_lint! { /// /// ### Example /// - /// ```rust,ignore (can only be run in cargo build scripts) + /// ```rust,ignore (should only be run in cargo build scripts) /// if cfg!(windows) {} /// ``` /// @@ -199,19 +199,10 @@ fn get_invalid_cfg_attrs(attr: &MetaItem, spans: &mut Vec, has_unknown: &m } } -fn is_build_script(cx: &EarlyContext<'_>) -> bool { - rustc_session::utils::was_invoked_from_cargo() - && cx.sess().opts.crate_name.as_deref() == Some("build_script_build") -} - const ERROR_MESSAGE: &str = "target-based cfg should be avoided in build scripts"; impl EarlyLintPass for MisleadingCfgInBuildScript { fn check_attribute(&mut self, cx: &EarlyContext<'_>, attr: &Attribute) { - if !is_build_script(cx) { - return; - } - let mut spans = Vec::new(); let mut has_unknown = false; match attr.name() { @@ -252,10 +243,6 @@ impl EarlyLintPass for MisleadingCfgInBuildScript { } fn check_mac(&mut self, cx: &EarlyContext<'_>, call: &MacCall) { - if !is_build_script(cx) { - return; - } - if call.path.segments.len() == 1 && call.path.segments[0].ident.name == sym::cfg { let mut ast = Vec::new(); let mut has_unknown = false; diff --git a/tests/ui/lint/misleading_cfg_in_build_script-no-cargo.rs b/tests/ui/lint/misleading_cfg_in_build_script-no-cargo.rs deleted file mode 100644 index 42cdcc83601a3..0000000000000 --- a/tests/ui/lint/misleading_cfg_in_build_script-no-cargo.rs +++ /dev/null @@ -1,36 +0,0 @@ -// This test ensures that the `misleading_cfg_in_build_script` is not emitted if not -// in a cargo `build.rs` script. -// -//@ no-auto-check-cfg -//@ check-pass - -#![deny(misleading_cfg_in_build_script)] -#![allow(dead_code)] - -#[cfg(windows)] -fn unused_windows_fn() {} -#[cfg(not(windows))] -fn unused_not_windows_fn() {} -#[cfg(any(windows, feature = "yellow", unix))] -fn pink() {} - -#[cfg(feature = "green")] -fn pink() {} -#[cfg(bob)] -fn bob() {} - -fn main() { - if cfg!(windows) {} - if cfg!(not(windows)) {} - if cfg!(target_os = "freebsd") {} - if cfg!(any(target_os = "freebsd", windows)) {} - if cfg!(not(any(target_os = "freebsd", windows))) {} - if cfg!(all(target_os = "freebsd", windows)) {} - if cfg!(not(all(target_os = "freebsd", windows))) {} - if cfg!(all(target_os = "freebsd", any(windows, not(feature = "red")))) {} - - if cfg!(any()) {} - if cfg!(all()) {} - if cfg!(feature = "blue") {} - if cfg!(bob) {} -} diff --git a/tests/ui/lint/misleading_cfg_in_build_script.rs b/tests/ui/lint/misleading_cfg_in_build_script.rs index 75df4addec0ff..e7c840d596ac5 100644 --- a/tests/ui/lint/misleading_cfg_in_build_script.rs +++ b/tests/ui/lint/misleading_cfg_in_build_script.rs @@ -1,8 +1,6 @@ // This test checks the `cfg()` attributes/macros in cargo build scripts. // //@ no-auto-check-cfg -//@ rustc-env:CARGO_CRATE_NAME=build_script_build -//@ compile-flags:--crate-name=build_script_build #![deny(misleading_cfg_in_build_script)] #![allow(dead_code)] diff --git a/tests/ui/lint/misleading_cfg_in_build_script.stderr b/tests/ui/lint/misleading_cfg_in_build_script.stderr index 7152d40feafd9..e2c75999db929 100644 --- a/tests/ui/lint/misleading_cfg_in_build_script.stderr +++ b/tests/ui/lint/misleading_cfg_in_build_script.stderr @@ -1,71 +1,71 @@ error: target-based cfg should be avoided in build scripts - --> $DIR/misleading_cfg_in_build_script.rs:10:1 + --> $DIR/misleading_cfg_in_build_script.rs:8:1 | LL | #[cfg(windows)] | ^^^^^^^^^^^^^^^ | note: the lint level is defined here - --> $DIR/misleading_cfg_in_build_script.rs:7:9 + --> $DIR/misleading_cfg_in_build_script.rs:5:9 | LL | #![deny(misleading_cfg_in_build_script)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: target-based cfg should be avoided in build scripts - --> $DIR/misleading_cfg_in_build_script.rs:13:1 + --> $DIR/misleading_cfg_in_build_script.rs:11:1 | LL | #[cfg(not(windows))] | ^^^^^^^^^^^^^^^^^^^^ error: target-based cfg should be avoided in build scripts - --> $DIR/misleading_cfg_in_build_script.rs:16:11 + --> $DIR/misleading_cfg_in_build_script.rs:14:11 | LL | #[cfg(any(windows, feature = "yellow", unix))] | ^^^^^^^ ^^^^ error: target-based cfg should be avoided in build scripts - --> $DIR/misleading_cfg_in_build_script.rs:27:8 + --> $DIR/misleading_cfg_in_build_script.rs:25:8 | LL | if cfg!(windows) {} | ^^^^^^^^^^^^^ help: use cargo environment variables if possible: `std::env::var("CARGO_CFG_WINDOWS").is_ok()` error: target-based cfg should be avoided in build scripts - --> $DIR/misleading_cfg_in_build_script.rs:29:8 + --> $DIR/misleading_cfg_in_build_script.rs:27:8 | LL | if cfg!(not(windows)) {} | ^^^^^^^^^^^^^^^^^^ help: use cargo environment variables if possible: `std::env::var("CARGO_CFG_WINDOWS").is_err()` error: target-based cfg should be avoided in build scripts - --> $DIR/misleading_cfg_in_build_script.rs:31:8 + --> $DIR/misleading_cfg_in_build_script.rs:29:8 | LL | if cfg!(target_os = "freebsd") {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use cargo environment variables if possible: `std::env::var("CARGO_CFG_TARGET_OS").unwrap_or_default() == "freebsd"` error: target-based cfg should be avoided in build scripts - --> $DIR/misleading_cfg_in_build_script.rs:33:8 + --> $DIR/misleading_cfg_in_build_script.rs:31:8 | LL | if cfg!(any(target_os = "freebsd", windows)) {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use cargo environment variables if possible: `std::env::var("CARGO_CFG_TARGET_OS").unwrap_or_default() == "freebsd" || std::env::var("CARGO_CFG_WINDOWS").is_ok()` error: target-based cfg should be avoided in build scripts - --> $DIR/misleading_cfg_in_build_script.rs:35:8 + --> $DIR/misleading_cfg_in_build_script.rs:33:8 | LL | if cfg!(not(any(target_os = "freebsd", windows))) {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use cargo environment variables if possible: `!(std::env::var("CARGO_CFG_TARGET_OS").unwrap_or_default() == "freebsd" || std::env::var("CARGO_CFG_WINDOWS").is_ok())` error: target-based cfg should be avoided in build scripts - --> $DIR/misleading_cfg_in_build_script.rs:37:8 + --> $DIR/misleading_cfg_in_build_script.rs:35:8 | LL | if cfg!(all(target_os = "freebsd", windows)) {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use cargo environment variables if possible: `std::env::var("CARGO_CFG_TARGET_OS").unwrap_or_default() == "freebsd" && std::env::var("CARGO_CFG_WINDOWS").is_ok()` error: target-based cfg should be avoided in build scripts - --> $DIR/misleading_cfg_in_build_script.rs:39:8 + --> $DIR/misleading_cfg_in_build_script.rs:37:8 | LL | if cfg!(not(all(target_os = "freebsd", windows))) {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use cargo environment variables if possible: `!(std::env::var("CARGO_CFG_TARGET_OS").unwrap_or_default() == "freebsd" && std::env::var("CARGO_CFG_WINDOWS").is_ok())` error: target-based cfg should be avoided in build scripts - --> $DIR/misleading_cfg_in_build_script.rs:41:8 + --> $DIR/misleading_cfg_in_build_script.rs:39:8 | LL | if cfg!(all(target_os = "freebsd", any(windows, not(feature = "red")))) {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use cargo environment variables if possible: `std::env::var("CARGO_CFG_TARGET_OS").unwrap_or_default() == "freebsd" && (std::env::var("CARGO_CFG_WINDOWS").is_ok() || cfg!(not(feature = red)))` From 05e54635196b73cfe4510da6b21c20715e698326 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Fri, 10 Apr 2026 12:03:34 +0200 Subject: [PATCH 6/6] Move `misleading_cfg_in_build_script` directly into attribute parsing --- .../rustc_attr_parsing/src/attributes/cfg.rs | 146 ++++++++- compiler/rustc_attr_parsing/src/errors.rs | 12 + compiler/rustc_attr_parsing/src/lib.rs | 1 + compiler/rustc_builtin_macros/src/cfg.rs | 4 +- compiler/rustc_lint/src/lib.rs | 3 - .../src/misleading_cfg_in_build_script.rs | 277 ------------------ compiler/rustc_lint_defs/src/builtin.rs | 27 ++ 7 files changed, 185 insertions(+), 285 deletions(-) delete mode 100644 compiler/rustc_lint/src/misleading_cfg_in_build_script.rs diff --git a/compiler/rustc_attr_parsing/src/attributes/cfg.rs b/compiler/rustc_attr_parsing/src/attributes/cfg.rs index 4cc07ceaf231f..2b455766b9f70 100644 --- a/compiler/rustc_attr_parsing/src/attributes/cfg.rs +++ b/compiler/rustc_attr_parsing/src/attributes/cfg.rs @@ -9,6 +9,7 @@ use rustc_feature::{ }; use rustc_hir::attrs::CfgEntry; use rustc_hir::{AttrPath, RustcVersion, Target}; +use rustc_lint_defs::builtin::MISLEADING_CFG_IN_BUILD_SCRIPT; use rustc_parse::parser::{ForceCollect, Parser, Recovery}; use rustc_parse::{exp, parse_in}; use rustc_session::Session; @@ -79,9 +80,148 @@ pub fn parse_cfg(cx: &mut AcceptContext<'_, '_>, args: &ArgParser) -> Option 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::>() + .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::>() + .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, 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( + cx: &mut AcceptContext<'_, '_>, + 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_lint( + MISLEADING_CFG_IN_BUILD_SCRIPT, + crate::errors::UnexpectedCfg { span: None, suggestion_message: String::new() }, + spans, + ); + return; + } + let replacement = generate_cfg_replacement(entry); + // The span including the `cfg!()` macro. + let span = cx.attr_span; + cx.emit_lint( + MISLEADING_CFG_IN_BUILD_SCRIPT, + crate::errors::UnexpectedCfg { span: Some(span), suggestion_message: replacement }, + span, + ); +} + +pub fn parse_cfg_entry_macro( + cx: &mut AcceptContext<'_, '_>, + item: &MetaItemOrLitParser, +) -> Result { + let entry = parse_cfg_entry_inner(cx, item)?; + check_unexpected_cfgs(cx, &entry, item.span(), true); + Ok(entry) +} + pub fn parse_cfg_entry( cx: &mut AcceptContext<'_, '_>, item: &MetaItemOrLitParser, +) -> Result { + let entry = parse_cfg_entry_inner(cx, item)?; + check_unexpected_cfgs(cx, &entry, item.span(), false); + Ok(entry) +} + +fn parse_cfg_entry_inner( + cx: &mut AcceptContext<'_, '_>, + item: &MetaItemOrLitParser, ) -> Result { Ok(match item { MetaItemOrLitParser::MetaItemParser(meta) => match meta.args() { @@ -90,14 +230,14 @@ pub fn parse_cfg_entry( let Some(single) = list.as_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())?, diff --git a/compiler/rustc_attr_parsing/src/errors.rs b/compiler/rustc_attr_parsing/src/errors.rs index d2c9c1b1eb807..af74f9bd75d59 100644 --- a/compiler/rustc_attr_parsing/src/errors.rs +++ b/compiler/rustc_attr_parsing/src/errors.rs @@ -326,6 +326,18 @@ pub(crate) struct IncorrectDoNotRecommendLocation { pub target_span: Span, } +#[derive(Diagnostic)] +#[diag("target-based cfg should be avoided in build scripts")] +pub(crate) struct UnexpectedCfg { + #[suggestion( + "use cargo environment variables if possible", + code = "{suggestion_message}", + applicability = "maybe-incorrect" + )] + pub span: Option, + pub suggestion_message: String, +} + #[derive(Diagnostic)] #[diag("malformed `doc` attribute input")] #[warning( diff --git a/compiler/rustc_attr_parsing/src/lib.rs b/compiler/rustc_attr_parsing/src/lib.rs index 24e10e19ca976..82808df372977 100644 --- a/compiler/rustc_attr_parsing/src/lib.rs +++ b/compiler/rustc_attr_parsing/src/lib.rs @@ -116,6 +116,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}; diff --git a/compiler/rustc_builtin_macros/src/cfg.rs b/compiler/rustc_builtin_macros/src/cfg.rs index 2872cff0fdc7a..892f7326c4e7b 100644 --- a/compiler/rustc_builtin_macros/src/cfg.rs +++ b/compiler/rustc_builtin_macros/src/cfg.rs @@ -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; @@ -63,7 +63,7 @@ fn parse_cfg(cx: &ExtCtxt<'_>, span: Span, tts: TokenStream) -> Result [MISLEADING_CFG_IN_BUILD_SCRIPT]); - -/// Represents the AST of `cfg` attribute and `cfg!` macro. -#[derive(Debug)] -enum CfgAst { - /// Represents an OS family such as "unix" or "windows". - OsFamily(Symbol), - /// The `any()` attribute. - Any(Vec), - /// The `all()` attribute. - All(Vec), - /// The `not()` attribute. - Not(Box), - /// Any `target_* = ""` key/value attribute. - TargetKeyValue(Symbol, Symbol), - /// the `feature = ""` attribute with its associated value. - Feature(Symbol), -} - -impl CfgAst { - fn has_only_features(&self) -> bool { - match self { - Self::OsFamily(_) | Self::TargetKeyValue(_, _) => false, - Self::Any(v) | Self::All(v) => v.is_empty() || v.iter().all(CfgAst::has_only_features), - Self::Not(v) => v.has_only_features(), - Self::Feature(_) => true, - } - } - - fn generate_replacement(&self) -> String { - self.generate_replacement_inner(true, false) - } - - fn generate_replacement_inner(&self, is_top_level: bool, parent_is_not: bool) -> String { - match self { - Self::OsFamily(os) => format!( - "std::env::var(\"CARGO_CFG_{}\"){}", - os.as_str().to_uppercase(), - if parent_is_not { ".is_err()" } else { ".is_ok()" }, - ), - Self::TargetKeyValue(cfg_target, s) => format!( - "{}std::env::var(\"CARGO_CFG_{}\").unwrap_or_default() == \"{s}\"", - if parent_is_not { "!" } else { "" }, - cfg_target.as_str().to_uppercase(), - ), - Self::Any(v) => { - if v.is_empty() { - if parent_is_not { "true" } else { "false" }.to_string() - } else if v.len() == 1 { - v[0].generate_replacement_inner(is_top_level, parent_is_not) - } else { - format!( - "{not}{open_paren}{cond}{closing_paren}", - not = if parent_is_not { "!" } else { "" }, - open_paren = if !parent_is_not && is_top_level { "" } else { "(" }, - cond = v - .iter() - .map(|i| i.generate_replacement_inner(false, false)) - .collect::>() - .join(" || "), - closing_paren = if !parent_is_not && is_top_level { "" } else { ")" }, - ) - } - } - Self::All(v) => { - if v.is_empty() { - if parent_is_not { "false" } else { "true" }.to_string() - } else if v.len() == 1 { - v[0].generate_replacement_inner(is_top_level, parent_is_not) - } else { - format!( - "{not}{open_paren}{cond}{closing_paren}", - not = if parent_is_not { "!" } else { "" }, - open_paren = if !parent_is_not && is_top_level { "" } else { "(" }, - cond = v - .iter() - .map(|i| i.generate_replacement_inner(false, false)) - .collect::>() - .join(" && "), - closing_paren = if !parent_is_not && is_top_level { "" } else { ")" }, - ) - } - } - Self::Not(i) => i.generate_replacement_inner(is_top_level, true), - Self::Feature(s) => format!( - "cfg!({}feature = {s}{})", - if parent_is_not { "not(" } else { "" }, - if parent_is_not { ")" } else { "" }, - ), - } - } -} - -fn parse_meta_item(meta: MetaItem, has_unknown: &mut bool, out: &mut Vec) { - let Some(name) = meta.name() else { - *has_unknown = true; - return; - }; - match meta.kind { - MetaItemKind::Word => { - if [sym::windows, sym::unix].contains(&name) { - out.push(CfgAst::OsFamily(name)); - return; - } - } - MetaItemKind::NameValue(item) => { - if name == sym::feature { - out.push(CfgAst::Feature(item.symbol)); - return; - } else if name.as_str().starts_with("target_") { - out.push(CfgAst::TargetKeyValue(name, item.symbol)); - return; - } - } - MetaItemKind::List(item) => { - if !*has_unknown && [sym::any, sym::not, sym::all].contains(&name) { - let mut sub_out = Vec::new(); - - for sub in item { - if let MetaItemInner::MetaItem(item) = sub { - parse_meta_item(item, has_unknown, &mut sub_out); - if *has_unknown { - return; - } - } - } - if name == sym::any { - out.push(CfgAst::Any(sub_out)); - return; - } else if name == sym::all { - out.push(CfgAst::All(sub_out)); - return; - // `not()` can only have one argument. - } else if sub_out.len() == 1 { - out.push(CfgAst::Not(Box::new(sub_out.pop().unwrap()))); - return; - } - } - } - } - *has_unknown = true; -} - -fn parse_macro_args(tokens: &TokenStream, has_unknown: &mut bool, out: &mut Vec) { - if let Some(meta) = MetaItem::from_tokens(&mut tokens.iter()) { - parse_meta_item(meta, has_unknown, out); - } -} - -fn get_invalid_cfg_attrs(attr: &MetaItem, spans: &mut Vec, has_unknown: &mut bool) { - let Some(ident) = attr.ident() else { return }; - if [sym::unix, sym::windows].contains(&ident.name) { - spans.push(attr.span); - } else if attr.value_str().is_some() && ident.name.as_str().starts_with("target_") { - spans.push(attr.span); - } else if let Some(sub_attrs) = attr.meta_item_list() { - for sub_attr in sub_attrs { - if let Some(meta) = sub_attr.meta_item() { - get_invalid_cfg_attrs(meta, spans, has_unknown); - } - } - } else { - *has_unknown = true; - } -} - -const ERROR_MESSAGE: &str = "target-based cfg should be avoided in build scripts"; - -impl EarlyLintPass for MisleadingCfgInBuildScript { - fn check_attribute(&mut self, cx: &EarlyContext<'_>, attr: &Attribute) { - let mut spans = Vec::new(); - let mut has_unknown = false; - match attr.name() { - Some(sym::cfg) if let Some(meta) = attr.meta() => { - get_invalid_cfg_attrs(&meta, &mut spans, &mut has_unknown); - } - Some(sym::cfg_attr) - if let Some(sub_attrs) = attr.meta_item_list() - && let Some(meta) = sub_attrs.first().and_then(|a| a.meta_item()) => - { - get_invalid_cfg_attrs(meta, &mut spans, &mut has_unknown); - } - _ => return, - } - if !spans.is_empty() { - if has_unknown { - // If the `cfg`/`cfg_attr` attribute contains not only invalid items, we display - // spans of all invalid items. - cx.emit_span_lint( - MISLEADING_CFG_IN_BUILD_SCRIPT, - spans, - DiagDecorator(|diag| { - diag.primary_message(ERROR_MESSAGE); - }), - ); - } else { - // No "good" item in the `cfg`/`cfg_attr` attribute so we can use the span of the - // whole attribute directly. - cx.emit_span_lint( - MISLEADING_CFG_IN_BUILD_SCRIPT, - attr.span, - DiagDecorator(|diag| { - diag.primary_message(ERROR_MESSAGE); - }), - ); - } - } - } - - fn check_mac(&mut self, cx: &EarlyContext<'_>, call: &MacCall) { - if call.path.segments.len() == 1 && call.path.segments[0].ident.name == sym::cfg { - let mut ast = Vec::new(); - let mut has_unknown = false; - parse_macro_args(&call.args.tokens, &mut has_unknown, &mut ast); - if !has_unknown && ast.len() > 1 { - cx.emit_span_lint( - MISLEADING_CFG_IN_BUILD_SCRIPT, - call.span(), - DiagDecorator(|diag| { - diag.primary_message(ERROR_MESSAGE); - }), - ); - } else if let Some(ast) = ast.get(0) - && !ast.has_only_features() - { - let span = call.span(); - cx.emit_span_lint( - MISLEADING_CFG_IN_BUILD_SCRIPT, - span, - DiagDecorator(|diag| { - diag.primary_message(ERROR_MESSAGE).span_suggestion( - span, - "use cargo environment variables if possible", - ast.generate_replacement(), - Applicability::MaybeIncorrect, - ); - }), - ); - } - } - } -} diff --git a/compiler/rustc_lint_defs/src/builtin.rs b/compiler/rustc_lint_defs/src/builtin.rs index c6892419443f8..80e1171885bd4 100644 --- a/compiler/rustc_lint_defs/src/builtin.rs +++ b/compiler/rustc_lint_defs/src/builtin.rs @@ -74,6 +74,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, @@ -5741,3 +5742,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" +}