Skip to main content

bootstrap/core/build_steps/
clippy.rs

1//! Implementation of running clippy on the compiler, standard library and various tools.
2//!
3//! This serves a double purpose:
4//! - The first is to run Clippy itself on in-tree code, in order to test and dogfood it.
5//! - The second is to actually lint the in-tree codebase on CI, with a hard-coded set of rules,
6//!   which is performed by the `x clippy ci` command.
7//!
8//! In order to prepare a build compiler for running clippy, use the
9//! [prepare_compiler_for_check] function. That prepares a
10//! compiler and a standard library
11//! for running Clippy. The second part (actually building Clippy) is performed inside
12//! [Builder::cargo_clippy_cmd]. It would be nice if this was more explicit, and we actually had
13//! to pass a prebuilt Clippy from the outside when running `cargo clippy`, but that would be
14//! (as usual) a massive undertaking/refactoring.
15
16use super::tool::{SourceType, prepare_tool_cargo};
17use crate::Mode;
18use crate::core::build_steps::check::{CompilerForCheck, prepare_compiler_for_check};
19use crate::core::build_steps::compile::{
20    ArtifactKeepMode, run_cargo, rustc_cargo, std_cargo, std_crates_for_make_run,
21};
22use crate::core::builder::{
23    self, Alias, Builder, CommandLineStep, Kind, RunConfig, ShouldRun, StepMetadata,
24    crate_description,
25};
26use crate::core::compiler::Compiler;
27use crate::core::config::TargetSelection;
28use crate::core::config::flags::Subcommand;
29use crate::utils::build_stamp::{self, BuildStamp};
30use crate::utils::helpers;
31
32/// Disable the most spammy clippy lints
33const IGNORED_RULES_FOR_STD_AND_RUSTC: &[&str] = &[
34    "many_single_char_names", // there are a lot in stdarch
35    "collapsible_if",
36    "type_complexity",
37    "missing_safety_doc", // almost 3K warnings
38    "too_many_arguments",
39    "needless_lifetimes", // people want to keep the lifetimes
40    "wrong_self_convention",
41    "approx_constant", // libcore is what defines those
42];
43
44fn lint_args(builder: &Builder<'_>, config: &LintConfig, ignored_rules: &[&str]) -> Vec<String> {
45    fn strings<'a>(arr: &'a [&str]) -> impl Iterator<Item = String> + 'a {
46        arr.iter().copied().map(String::from)
47    }
48
49    let Subcommand::Clippy { fix, allow_dirty, allow_staged, .. } = &builder.config.cmd else {
50        unreachable!("clippy::lint_args can only be called from `clippy` subcommands.");
51    };
52
53    let mut args = vec![];
54    if *fix {
55        #[rustfmt::skip]
56            args.extend(strings(&[
57                "--fix", "-Zunstable-options",
58                // FIXME: currently, `--fix` gives an error while checking tests for libtest,
59                // possibly because libtest is not yet built in the sysroot.
60                // As a workaround, avoid checking tests and benches when passed --fix.
61                "--lib", "--bins", "--examples",
62            ]));
63
64        if *allow_dirty {
65            args.push("--allow-dirty".to_owned());
66        }
67
68        if *allow_staged {
69            args.push("--allow-staged".to_owned());
70        }
71    }
72
73    args.extend(strings(&["--"]));
74
75    if config.deny.is_empty() && config.forbid.is_empty() {
76        args.extend(strings(&["--cap-lints", "warn"]));
77    }
78
79    let all_args = std::env::args().collect::<Vec<_>>();
80    args.extend(get_clippy_rules_in_order(&all_args, config));
81
82    args.extend(ignored_rules.iter().map(|lint| format!("-Aclippy::{lint}")));
83    args.extend(builder.config.free_args.clone());
84    args
85}
86
87/// We need to keep the order of the given clippy lint rules before passing them.
88/// Since clap doesn't offer any useful interface for this purpose out of the box,
89/// we have to handle it manually.
90pub fn get_clippy_rules_in_order(all_args: &[String], config: &LintConfig) -> Vec<String> {
91    let mut result = vec![];
92
93    for (prefix, item) in
94        [("-A", &config.allow), ("-D", &config.deny), ("-W", &config.warn), ("-F", &config.forbid)]
95    {
96        item.iter().for_each(|v| {
97            let rule = format!("{prefix}{v}");
98            // Arguments added by bootstrap in LintConfig won't show up in the all_args list, so
99            // put them at the end of the command line.
100            let position = all_args.iter().position(|t| t == &rule || t == v).unwrap_or(usize::MAX);
101            result.push((position, rule));
102        });
103    }
104
105    result.sort_by_key(|&(position, _)| position);
106    result.into_iter().map(|v| v.1).collect()
107}
108
109#[derive(Debug, Clone, PartialEq, Eq, Hash)]
110pub struct LintConfig {
111    pub allow: Vec<String>,
112    pub warn: Vec<String>,
113    pub deny: Vec<String>,
114    pub forbid: Vec<String>,
115}
116
117impl LintConfig {
118    fn new(builder: &Builder<'_>) -> Self {
119        match builder.config.cmd.clone() {
120            Subcommand::Clippy { allow, deny, warn, forbid, .. } => {
121                Self { allow, warn, deny, forbid }
122            }
123            _ => unreachable!("LintConfig can only be called from `clippy` subcommands."),
124        }
125    }
126
127    fn merge(&self, other: &Self) -> Self {
128        let merged = |self_attr: &[String], other_attr: &[String]| -> Vec<String> {
129            self_attr.iter().cloned().chain(other_attr.iter().cloned()).collect()
130        };
131        // This is written this way to ensure we get a compiler error if we add a new field.
132        Self {
133            allow: merged(&self.allow, &other.allow),
134            warn: merged(&self.warn, &other.warn),
135            deny: merged(&self.deny, &other.deny),
136            forbid: merged(&self.forbid, &other.forbid),
137        }
138    }
139}
140
141#[derive(Debug, Clone, PartialEq, Eq, Hash)]
142pub struct Std {
143    build_compiler: Compiler,
144    target: TargetSelection,
145    config: LintConfig,
146    /// Whether to lint only a subset of crates.
147    crates: Vec<String>,
148}
149
150impl Std {
151    fn new(
152        builder: &Builder<'_>,
153        target: TargetSelection,
154        config: LintConfig,
155        crates: Vec<String>,
156    ) -> Self {
157        Self {
158            build_compiler: builder.compiler(builder.top_stage, builder.host_target),
159            target,
160            config,
161            crates,
162        }
163    }
164
165    fn from_build_compiler(
166        build_compiler: Compiler,
167        target: TargetSelection,
168        config: LintConfig,
169        crates: Vec<String>,
170    ) -> Self {
171        Self { build_compiler, target, config, crates }
172    }
173}
174
175impl CommandLineStep for Std {
176    type Output = ();
177
178    fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
179        run.crate_or_deps("sysroot").path("library")
180    }
181
182    fn is_default_step(_builder: &Builder<'_>) -> bool {
183        true
184    }
185
186    fn make_run(run: RunConfig<'_>) {
187        let crates = std_crates_for_make_run(&run);
188        let config = LintConfig::new(run.builder);
189        run.builder.ensure(Std::new(run.builder, run.target, config, crates));
190    }
191
192    fn run(self, builder: &Builder<'_>) {
193        let target = self.target;
194        let build_compiler = self.build_compiler;
195
196        let mut cargo = builder::Cargo::new(
197            builder,
198            build_compiler,
199            Mode::Std,
200            SourceType::InTree,
201            target,
202            Kind::Clippy,
203        );
204
205        std_cargo(builder, target, &mut cargo, &self.crates);
206
207        let _guard = builder.msg(
208            Kind::Clippy,
209            format_args!("library{}", crate_description(&self.crates)),
210            Mode::Std,
211            build_compiler,
212            target,
213        );
214
215        run_cargo(
216            builder,
217            cargo,
218            lint_args(builder, &self.config, IGNORED_RULES_FOR_STD_AND_RUSTC),
219            &build_stamp::libstd_stamp(builder, build_compiler, target),
220            vec![],
221            ArtifactKeepMode::OnlyRmeta,
222        );
223    }
224
225    fn metadata(&self) -> Option<StepMetadata> {
226        Some(StepMetadata::clippy("std", self.target).built_by(self.build_compiler))
227    }
228}
229
230/// Lints the compiler.
231///
232/// This will build Clippy with the `build_compiler` and use it to lint
233/// in-tree rustc.
234#[derive(Debug, Clone, PartialEq, Eq, Hash)]
235pub struct Rustc {
236    build_compiler: CompilerForCheck,
237    target: TargetSelection,
238    config: LintConfig,
239    /// Whether to lint only a subset of crates.
240    crates: Vec<String>,
241}
242
243impl Rustc {
244    fn new(
245        builder: &Builder<'_>,
246        target: TargetSelection,
247        config: LintConfig,
248        crates: Vec<String>,
249    ) -> Self {
250        Self {
251            build_compiler: prepare_compiler_for_check(builder, target, Mode::Rustc),
252            target,
253            config,
254            crates,
255        }
256    }
257}
258
259impl CommandLineStep for Rustc {
260    type Output = ();
261    const IS_HOST: bool = true;
262
263    fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
264        run.crate_or_deps("rustc-main").path("compiler")
265    }
266
267    fn is_default_step(_builder: &Builder<'_>) -> bool {
268        true
269    }
270
271    fn make_run(run: RunConfig<'_>) {
272        let builder = run.builder;
273        let crates = run.make_run_crates(Alias::Compiler);
274        let config = LintConfig::new(run.builder);
275        run.builder.ensure(Rustc::new(builder, run.target, config, crates));
276    }
277
278    fn run(self, builder: &Builder<'_>) {
279        let build_compiler = self.build_compiler.build_compiler();
280        let target = self.target;
281
282        let mut cargo = builder::Cargo::new(
283            builder,
284            build_compiler,
285            Mode::Rustc,
286            SourceType::InTree,
287            target,
288            Kind::Clippy,
289        );
290
291        rustc_cargo(builder, &mut cargo, target, &build_compiler, &self.crates);
292        self.build_compiler.configure_cargo(&mut cargo);
293
294        // Explicitly pass -p for all compiler crates -- this will force cargo
295        // to also lint the tests/benches/examples for these crates, rather
296        // than just the leaf crate.
297        for krate in &*self.crates {
298            cargo.arg("-p").arg(krate);
299        }
300
301        let _guard = builder.msg(
302            Kind::Clippy,
303            format_args!("compiler{}", crate_description(&self.crates)),
304            Mode::Rustc,
305            build_compiler,
306            target,
307        );
308
309        run_cargo(
310            builder,
311            cargo,
312            lint_args(builder, &self.config, IGNORED_RULES_FOR_STD_AND_RUSTC),
313            &build_stamp::librustc_stamp(builder, build_compiler, target),
314            vec![],
315            ArtifactKeepMode::OnlyRmeta,
316        );
317    }
318
319    fn metadata(&self) -> Option<StepMetadata> {
320        Some(
321            StepMetadata::clippy("rustc", self.target)
322                .built_by(self.build_compiler.build_compiler()),
323        )
324    }
325}
326
327#[derive(Debug, Clone, Hash, PartialEq, Eq)]
328pub struct CodegenGcc {
329    build_compiler: CompilerForCheck,
330    target: TargetSelection,
331    config: LintConfig,
332}
333
334impl CodegenGcc {
335    fn new(builder: &Builder<'_>, target: TargetSelection, config: LintConfig) -> Self {
336        Self {
337            build_compiler: prepare_compiler_for_check(builder, target, Mode::Codegen),
338            target,
339            config,
340        }
341    }
342}
343
344impl CommandLineStep for CodegenGcc {
345    type Output = ();
346
347    fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
348        run.alias("rustc_codegen_gcc")
349    }
350
351    fn make_run(run: RunConfig<'_>) {
352        let builder = run.builder;
353        let config = LintConfig::new(builder);
354        builder.ensure(CodegenGcc::new(builder, run.target, config));
355    }
356
357    fn run(self, builder: &Builder<'_>) -> Self::Output {
358        let build_compiler = self.build_compiler.build_compiler();
359        let target = self.target;
360
361        let mut cargo = prepare_tool_cargo(
362            builder,
363            build_compiler,
364            Mode::Codegen,
365            target,
366            Kind::Clippy,
367            "compiler/rustc_codegen_gcc",
368            SourceType::InTree,
369            &[],
370        );
371        self.build_compiler.configure_cargo(&mut cargo);
372
373        let _guard = builder.msg(
374            Kind::Clippy,
375            "rustc_codegen_gcc",
376            Mode::ToolRustcPrivate,
377            build_compiler,
378            target,
379        );
380
381        let stamp = BuildStamp::new(&builder.cargo_out(build_compiler, Mode::Codegen, target))
382            .with_prefix("rustc_codegen_gcc-check");
383
384        let args = lint_args(builder, &self.config, &[]);
385        run_cargo(builder, cargo, args.clone(), &stamp, vec![], ArtifactKeepMode::OnlyRmeta);
386
387        // Same but we disable the features enabled by default.
388        let mut cargo = prepare_tool_cargo(
389            builder,
390            build_compiler,
391            Mode::Codegen,
392            target,
393            Kind::Clippy,
394            "compiler/rustc_codegen_gcc",
395            SourceType::InTree,
396            &[],
397        );
398        self.build_compiler.configure_cargo(&mut cargo);
399        println!("Now running clippy on `rustc_codegen_gcc` with `--no-default-features`");
400        cargo.arg("--no-default-features");
401        run_cargo(builder, cargo, args, &stamp, vec![], ArtifactKeepMode::OnlyRmeta);
402    }
403
404    fn metadata(&self) -> Option<StepMetadata> {
405        Some(
406            StepMetadata::clippy("rustc_codegen_gcc", self.target)
407                .built_by(self.build_compiler.build_compiler()),
408        )
409    }
410}
411
412macro_rules! lint_any {
413    ($(
414        $name:ident,
415        $path:expr,
416        $readable_name:expr,
417        $mode:expr
418        $(, lint_by_default = $lint_by_default:expr )?
419        ;
420    )+) => {
421        $(
422
423        #[derive(Debug, Clone, Hash, PartialEq, Eq)]
424        pub struct $name {
425            build_compiler: CompilerForCheck,
426            target: TargetSelection,
427            config: LintConfig,
428        }
429
430        impl CommandLineStep for $name {
431            type Output = ();
432
433            fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
434                run.path($path)
435            }
436
437            fn is_default_step(_builder: &Builder<'_>) -> bool {
438                false $( || const { $lint_by_default } )?
439            }
440
441            fn make_run(run: RunConfig<'_>) {
442                let config = LintConfig::new(run.builder);
443                run.builder.ensure($name {
444                    build_compiler: prepare_compiler_for_check(run.builder, run.target, $mode),
445                    target: run.target,
446                    config,
447                });
448            }
449
450            fn run(self, builder: &Builder<'_>) -> Self::Output {
451                let build_compiler = self.build_compiler.build_compiler();
452                let target = self.target;
453                let mut cargo = prepare_tool_cargo(
454                    builder,
455                    build_compiler,
456                    $mode,
457                    target,
458                    Kind::Clippy,
459                    $path,
460                    SourceType::InTree,
461                    &[],
462                );
463                self.build_compiler.configure_cargo(&mut cargo);
464
465                let _guard = builder.msg(
466                    Kind::Clippy,
467                    $readable_name,
468                    $mode,
469                    build_compiler,
470                    target,
471                );
472
473                let stringified_name = stringify!($name).to_lowercase();
474                let stamp = BuildStamp::new(&builder.cargo_out(build_compiler, $mode, target))
475                    .with_prefix(&format!("{}-check", stringified_name));
476
477                run_cargo(
478                    builder,
479                    cargo,
480                    lint_args(builder, &self.config, &[]),
481                    &stamp,
482                    vec![],
483                    ArtifactKeepMode::OnlyRmeta
484                );
485            }
486
487            fn metadata(&self) -> Option<StepMetadata> {
488                Some(StepMetadata::clippy($readable_name, self.target).built_by(self.build_compiler.build_compiler()))
489            }
490        }
491        )+
492    }
493}
494
495// Note: we use ToolTarget instead of ToolBootstrap here, to allow linting in-tree host tools
496// using the in-tree Clippy. Because Mode::ToolBootstrap would always use stage 0 rustc/Clippy.
497lint_any!(
498    Bootstrap, "src/bootstrap", "bootstrap", Mode::ToolTarget;
499    BuildHelper, "src/build_helper", "build_helper", Mode::ToolTarget;
500    BuildManifest, "src/tools/build-manifest", "build-manifest", Mode::ToolTarget;
501    CargoMiri, "src/tools/miri/cargo-miri", "cargo-miri", Mode::ToolRustcPrivate;
502    Clippy, "src/tools/clippy", "clippy", Mode::ToolRustcPrivate;
503    CollectLicenseMetadata, "src/tools/collect-license-metadata", "collect-license-metadata", Mode::ToolTarget;
504    Compiletest, "src/tools/compiletest", "compiletest", Mode::ToolTarget;
505    CoverageDump, "src/tools/coverage-dump", "coverage-dump", Mode::ToolTarget;
506    Jsondocck, "src/tools/jsondocck", "jsondocck", Mode::ToolTarget;
507    Jsondoclint, "src/tools/jsondoclint", "jsondoclint", Mode::ToolTarget;
508    LintDocs, "src/tools/lint-docs", "lint-docs", Mode::ToolTarget;
509    LlvmBitcodeLinker, "src/tools/llvm-bitcode-linker", "llvm-bitcode-linker", Mode::ToolTarget;
510    Miri, "src/tools/miri", "miri", Mode::ToolRustcPrivate;
511    MiroptTestTools, "src/tools/miropt-test-tools", "miropt-test-tools", Mode::ToolTarget;
512    OptDist, "src/tools/opt-dist", "opt-dist", Mode::ToolTarget;
513    RemoteTestClient, "src/tools/remote-test-client", "remote-test-client", Mode::ToolTarget;
514    RemoteTestServer, "src/tools/remote-test-server", "remote-test-server", Mode::ToolTarget;
515    RustAnalyzer, "src/tools/rust-analyzer", "rust-analyzer", Mode::ToolRustcPrivate;
516    Rustdoc, "src/librustdoc", "clippy", Mode::ToolRustcPrivate;
517    Rustfmt, "src/tools/rustfmt", "rustfmt", Mode::ToolRustcPrivate;
518    RustInstaller, "src/tools/rust-installer", "rust-installer", Mode::ToolTarget;
519    Tidy, "src/tools/tidy", "tidy", Mode::ToolTarget;
520    TestFloatParse, "src/tools/test-float-parse", "test-float-parse", Mode::ToolStd;
521);
522
523/// Runs Clippy on in-tree sources of selected projects using in-tree CLippy.
524#[derive(Debug, Clone, PartialEq, Eq, Hash)]
525pub struct CI {
526    target: TargetSelection,
527    config: LintConfig,
528}
529
530impl CommandLineStep for CI {
531    type Output = ();
532
533    fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
534        run.alias("ci")
535    }
536
537    fn is_default_step(_builder: &Builder<'_>) -> bool {
538        false
539    }
540
541    fn make_run(run: RunConfig<'_>) {
542        let config = LintConfig::new(run.builder);
543        run.builder.ensure(CI { target: run.target, config });
544    }
545
546    fn run(self, builder: &Builder<'_>) -> Self::Output {
547        if builder.top_stage != 2 {
548            eprintln!("ERROR: `x clippy ci` should always be executed with --stage 2");
549            helpers::exit_process(1);
550        }
551
552        // We want to check in-tree source using in-tree clippy. However, if we naively did
553        // a stage 2 `x clippy ci`, it would *build* a stage 2 rustc, in order to lint stage 2
554        // std, which is wasteful.
555        // So we want to lint stage 2 [bootstrap/rustc/...], but only stage 1 std rustc_codegen_gcc.
556        // We thus construct the compilers in this step manually, to optimize the number of
557        // steps that get built.
558
559        builder.ensure(Bootstrap {
560            // This will be the stage 1 compiler
561            build_compiler: prepare_compiler_for_check(builder, self.target, Mode::ToolTarget),
562            target: self.target,
563            config: self.config.merge(&LintConfig {
564                allow: vec![],
565                warn: vec![],
566                deny: vec!["warnings".into()],
567                forbid: vec![],
568            }),
569        });
570
571        let library_clippy_cfg = LintConfig {
572            allow: vec!["clippy::all".into()],
573            warn: vec![],
574            deny: vec![
575                "clippy::correctness".into(),
576                "clippy::char_lit_as_u8".into(),
577                "clippy::four_forward_slashes".into(),
578                "clippy::needless_bool".into(),
579                "clippy::needless_bool_assign".into(),
580                "clippy::non_minimal_cfg".into(),
581                "clippy::print_literal".into(),
582                "clippy::same_item_push".into(),
583                "clippy::single_char_add_str".into(),
584                "clippy::to_string_in_format_args".into(),
585                "clippy::unconditional_recursion".into(),
586                "clippy::int_plus_one".into(),
587                "clippy::legacy_numeric_constants".into(),
588                "clippy::zero_divided_by_zero".into(),
589                "clippy::len_zero".into(),
590                "clippy::needless_as_bytes".into(),
591                "clippy::ptr_offset_with_cast".into(),
592                "clippy::let_and_return".into(),
593                "clippy::needless_return".into(),
594                "clippy::needless_borrow".into(),
595                "clippy::op_ref".into(),
596                "clippy::borrow_deref_ref".into(),
597                "clippy::explicit_auto_deref".into(),
598            ],
599            forbid: vec![],
600        };
601        builder.ensure(Std::from_build_compiler(
602            // This will be the stage 1 compiler, to avoid building rustc stage 2 just to lint std
603            builder.compiler(1, self.target),
604            self.target,
605            self.config.merge(&library_clippy_cfg),
606            vec![],
607        ));
608
609        let compiler_clippy_cfg = LintConfig {
610            allow: vec!["clippy::all".into()],
611            warn: vec![],
612            deny: vec![
613                "clippy::correctness".into(),
614                "clippy::char_lit_as_u8".into(),
615                "clippy::clone_on_ref_ptr".into(),
616                "clippy::format_in_format_args".into(),
617                "clippy::four_forward_slashes".into(),
618                "clippy::needless_bool".into(),
619                "clippy::needless_bool_assign".into(),
620                "clippy::non_minimal_cfg".into(),
621                "clippy::print_literal".into(),
622                "clippy::same_item_push".into(),
623                "clippy::single_char_add_str".into(),
624                "clippy::to_string_in_format_args".into(),
625                "clippy::unconditional_recursion".into(),
626                "clippy::mem_replace_with_default".into(),
627            ],
628            forbid: vec![],
629        };
630        // This will lint stage 2 rustc using stage 1 Clippy
631        builder.ensure(Rustc::new(
632            builder,
633            self.target,
634            self.config.merge(&compiler_clippy_cfg),
635            vec![],
636        ));
637
638        let rustc_codegen_gcc = LintConfig {
639            allow: vec![],
640            warn: vec![],
641            deny: vec!["warnings".into()],
642            forbid: vec![],
643        };
644        // This will check stage 2 rustc
645        builder.ensure(CodegenGcc::new(
646            builder,
647            self.target,
648            self.config.merge(&rustc_codegen_gcc),
649        ));
650    }
651}