]>
Commit | Line | Data |
---|---|---|
f20569fa XL |
1 | #![feature(rustc_private)] |
2 | #![feature(once_cell)] | |
3 | #![cfg_attr(feature = "deny-warnings", deny(warnings))] | |
4 | // warn on lints, that are included in `rust-lang/rust`s bootstrap | |
5 | #![warn(rust_2018_idioms, unused_lifetimes)] | |
6 | // warn on rustc internal lints | |
cdc7bbd5 | 7 | #![warn(rustc::internal)] |
f20569fa XL |
8 | |
9 | // FIXME: switch to something more ergonomic here, once available. | |
10 | // (Currently there is no way to opt into sysroot crates without `extern crate`.) | |
11 | extern crate rustc_driver; | |
12 | extern crate rustc_errors; | |
13 | extern crate rustc_interface; | |
14 | extern crate rustc_session; | |
15 | extern crate rustc_span; | |
16 | ||
17 | use rustc_interface::interface; | |
18 | use rustc_session::parse::ParseSess; | |
19 | use rustc_span::symbol::Symbol; | |
20 | use rustc_tools_util::VersionInfo; | |
21 | ||
22 | use std::borrow::Cow; | |
23 | use std::env; | |
24 | use std::lazy::SyncLazy; | |
25 | use std::ops::Deref; | |
26 | use std::panic; | |
27 | use std::path::{Path, PathBuf}; | |
28 | use std::process::{exit, Command}; | |
29 | ||
30 | /// If a command-line option matches `find_arg`, then apply the predicate `pred` on its value. If | |
31 | /// true, then return it. The parameter is assumed to be either `--arg=value` or `--arg value`. | |
32 | fn arg_value<'a, T: Deref<Target = str>>( | |
33 | args: &'a [T], | |
34 | find_arg: &str, | |
35 | pred: impl Fn(&str) -> bool, | |
36 | ) -> Option<&'a str> { | |
37 | let mut args = args.iter().map(Deref::deref); | |
38 | while let Some(arg) = args.next() { | |
39 | let mut arg = arg.splitn(2, '='); | |
40 | if arg.next() != Some(find_arg) { | |
41 | continue; | |
42 | } | |
43 | ||
44 | match arg.next().or_else(|| args.next()) { | |
45 | Some(v) if pred(v) => return Some(v), | |
46 | _ => {}, | |
47 | } | |
48 | } | |
49 | None | |
50 | } | |
51 | ||
52 | #[test] | |
53 | fn test_arg_value() { | |
54 | let args = &["--bar=bar", "--foobar", "123", "--foo"]; | |
55 | ||
56 | assert_eq!(arg_value(&[] as &[&str], "--foobar", |_| true), None); | |
57 | assert_eq!(arg_value(args, "--bar", |_| false), None); | |
58 | assert_eq!(arg_value(args, "--bar", |_| true), Some("bar")); | |
59 | assert_eq!(arg_value(args, "--bar", |p| p == "bar"), Some("bar")); | |
60 | assert_eq!(arg_value(args, "--bar", |p| p == "foo"), None); | |
61 | assert_eq!(arg_value(args, "--foobar", |p| p == "foo"), None); | |
62 | assert_eq!(arg_value(args, "--foobar", |p| p == "123"), Some("123")); | |
63 | assert_eq!(arg_value(args, "--foo", |_| true), None); | |
64 | } | |
65 | ||
66 | fn track_clippy_args(parse_sess: &mut ParseSess, args_env_var: &Option<String>) { | |
67 | parse_sess.env_depinfo.get_mut().insert(( | |
68 | Symbol::intern("CLIPPY_ARGS"), | |
69 | args_env_var.as_deref().map(Symbol::intern), | |
70 | )); | |
71 | } | |
72 | ||
73 | struct DefaultCallbacks; | |
74 | impl rustc_driver::Callbacks for DefaultCallbacks {} | |
75 | ||
76 | /// This is different from `DefaultCallbacks` that it will inform Cargo to track the value of | |
77 | /// `CLIPPY_ARGS` environment variable. | |
78 | struct RustcCallbacks { | |
79 | clippy_args_var: Option<String>, | |
80 | } | |
81 | ||
82 | impl rustc_driver::Callbacks for RustcCallbacks { | |
83 | fn config(&mut self, config: &mut interface::Config) { | |
84 | let clippy_args_var = self.clippy_args_var.take(); | |
85 | config.parse_sess_created = Some(Box::new(move |parse_sess| { | |
86 | track_clippy_args(parse_sess, &clippy_args_var); | |
87 | })); | |
88 | } | |
89 | } | |
90 | ||
91 | struct ClippyCallbacks { | |
92 | clippy_args_var: Option<String>, | |
93 | } | |
94 | ||
95 | impl rustc_driver::Callbacks for ClippyCallbacks { | |
96 | fn config(&mut self, config: &mut interface::Config) { | |
97 | let previous = config.register_lints.take(); | |
98 | let clippy_args_var = self.clippy_args_var.take(); | |
99 | config.parse_sess_created = Some(Box::new(move |parse_sess| { | |
100 | track_clippy_args(parse_sess, &clippy_args_var); | |
101 | })); | |
cdc7bbd5 | 102 | config.register_lints = Some(Box::new(move |sess, lint_store| { |
f20569fa XL |
103 | // technically we're ~guaranteed that this is none but might as well call anything that |
104 | // is there already. Certainly it can't hurt. | |
105 | if let Some(previous) = &previous { | |
106 | (previous)(sess, lint_store); | |
107 | } | |
108 | ||
17df50a5 | 109 | let conf = clippy_lints::read_conf(sess); |
cdc7bbd5 | 110 | clippy_lints::register_plugins(lint_store, sess, &conf); |
a2a8927a | 111 | clippy_lints::register_pre_expansion_lints(lint_store, sess, &conf); |
cdc7bbd5 | 112 | clippy_lints::register_renamed(lint_store); |
f20569fa XL |
113 | })); |
114 | ||
115 | // FIXME: #4825; This is required, because Clippy lints that are based on MIR have to be | |
116 | // run on the unoptimized MIR. On the other hand this results in some false negatives. If | |
117 | // MIR passes can be enabled / disabled separately, we should figure out, what passes to | |
118 | // use for Clippy. | |
119 | config.opts.debugging_opts.mir_opt_level = Some(0); | |
120 | } | |
121 | } | |
122 | ||
123 | fn display_help() { | |
124 | println!( | |
125 | "\ | |
126 | Checks a package to catch common mistakes and improve your Rust code. | |
127 | ||
128 | Usage: | |
129 | cargo clippy [options] [--] [<opts>...] | |
130 | ||
131 | Common options: | |
132 | -h, --help Print this message | |
133 | --rustc Pass all args to rustc | |
134 | -V, --version Print version info and exit | |
135 | ||
136 | Other options are the same as `cargo check`. | |
137 | ||
138 | To allow or deny a lint from the command line you can use `cargo clippy --` | |
139 | with: | |
140 | ||
141 | -W --warn OPT Set lint warnings | |
142 | -A --allow OPT Set lint allowed | |
143 | -D --deny OPT Set lint denied | |
144 | -F --forbid OPT Set lint forbidden | |
145 | ||
146 | You can use tool lints to allow or deny lints from your code, eg.: | |
147 | ||
148 | #[allow(clippy::needless_lifetimes)] | |
149 | " | |
150 | ); | |
151 | } | |
152 | ||
153 | const BUG_REPORT_URL: &str = "https://github.com/rust-lang/rust-clippy/issues/new"; | |
154 | ||
155 | static ICE_HOOK: SyncLazy<Box<dyn Fn(&panic::PanicInfo<'_>) + Sync + Send + 'static>> = SyncLazy::new(|| { | |
156 | let hook = panic::take_hook(); | |
157 | panic::set_hook(Box::new(|info| report_clippy_ice(info, BUG_REPORT_URL))); | |
158 | hook | |
159 | }); | |
160 | ||
161 | fn report_clippy_ice(info: &panic::PanicInfo<'_>, bug_report_url: &str) { | |
162 | // Invoke our ICE handler, which prints the actual panic message and optionally a backtrace | |
163 | (*ICE_HOOK)(info); | |
164 | ||
165 | // Separate the output with an empty line | |
166 | eprintln!(); | |
167 | ||
168 | let emitter = Box::new(rustc_errors::emitter::EmitterWriter::stderr( | |
169 | rustc_errors::ColorConfig::Auto, | |
170 | None, | |
171 | false, | |
172 | false, | |
173 | None, | |
174 | false, | |
175 | )); | |
176 | let handler = rustc_errors::Handler::with_emitter(true, None, emitter); | |
177 | ||
178 | // a .span_bug or .bug call has already printed what | |
179 | // it wants to print. | |
180 | if !info.payload().is::<rustc_errors::ExplicitBug>() { | |
ee023bcb FG |
181 | let mut d = rustc_errors::Diagnostic::new(rustc_errors::Level::Bug, "unexpected panic"); |
182 | handler.emit_diagnostic(&mut d); | |
f20569fa XL |
183 | } |
184 | ||
185 | let version_info = rustc_tools_util::get_version_info!(); | |
186 | ||
187 | let xs: Vec<Cow<'static, str>> = vec![ | |
188 | "the compiler unexpectedly panicked. this is a bug.".into(), | |
189 | format!("we would appreciate a bug report: {}", bug_report_url).into(), | |
190 | format!("Clippy version: {}", version_info).into(), | |
191 | ]; | |
192 | ||
193 | for note in &xs { | |
cdc7bbd5 | 194 | handler.note_without_error(note); |
f20569fa XL |
195 | } |
196 | ||
197 | // If backtraces are enabled, also print the query stack | |
198 | let backtrace = env::var_os("RUST_BACKTRACE").map_or(false, |x| &x != "0"); | |
199 | ||
200 | let num_frames = if backtrace { None } else { Some(2) }; | |
201 | ||
202 | interface::try_print_query_stack(&handler, num_frames); | |
203 | } | |
204 | ||
205 | fn toolchain_path(home: Option<String>, toolchain: Option<String>) -> Option<PathBuf> { | |
206 | home.and_then(|home| { | |
207 | toolchain.map(|toolchain| { | |
208 | let mut path = PathBuf::from(home); | |
209 | path.push("toolchains"); | |
210 | path.push(toolchain); | |
211 | path | |
212 | }) | |
213 | }) | |
214 | } | |
215 | ||
216 | #[allow(clippy::too_many_lines)] | |
217 | pub fn main() { | |
218 | rustc_driver::init_rustc_env_logger(); | |
219 | SyncLazy::force(&ICE_HOOK); | |
220 | exit(rustc_driver::catch_with_exit_code(move || { | |
221 | let mut orig_args: Vec<String> = env::args().collect(); | |
222 | ||
223 | // Get the sysroot, looking from most specific to this invocation to the least: | |
224 | // - command line | |
225 | // - runtime environment | |
226 | // - SYSROOT | |
227 | // - RUSTUP_HOME, MULTIRUST_HOME, RUSTUP_TOOLCHAIN, MULTIRUST_TOOLCHAIN | |
228 | // - sysroot from rustc in the path | |
229 | // - compile-time environment | |
230 | // - SYSROOT | |
231 | // - RUSTUP_HOME, MULTIRUST_HOME, RUSTUP_TOOLCHAIN, MULTIRUST_TOOLCHAIN | |
232 | let sys_root_arg = arg_value(&orig_args, "--sysroot", |_| true); | |
233 | let have_sys_root_arg = sys_root_arg.is_some(); | |
234 | let sys_root = sys_root_arg | |
235 | .map(PathBuf::from) | |
236 | .or_else(|| std::env::var("SYSROOT").ok().map(PathBuf::from)) | |
237 | .or_else(|| { | |
238 | let home = std::env::var("RUSTUP_HOME") | |
239 | .or_else(|_| std::env::var("MULTIRUST_HOME")) | |
240 | .ok(); | |
241 | let toolchain = std::env::var("RUSTUP_TOOLCHAIN") | |
242 | .or_else(|_| std::env::var("MULTIRUST_TOOLCHAIN")) | |
243 | .ok(); | |
244 | toolchain_path(home, toolchain) | |
245 | }) | |
246 | .or_else(|| { | |
247 | Command::new("rustc") | |
248 | .arg("--print") | |
249 | .arg("sysroot") | |
250 | .output() | |
251 | .ok() | |
252 | .and_then(|out| String::from_utf8(out.stdout).ok()) | |
253 | .map(|s| PathBuf::from(s.trim())) | |
254 | }) | |
255 | .or_else(|| option_env!("SYSROOT").map(PathBuf::from)) | |
256 | .or_else(|| { | |
257 | let home = option_env!("RUSTUP_HOME") | |
258 | .or(option_env!("MULTIRUST_HOME")) | |
259 | .map(ToString::to_string); | |
260 | let toolchain = option_env!("RUSTUP_TOOLCHAIN") | |
261 | .or(option_env!("MULTIRUST_TOOLCHAIN")) | |
262 | .map(ToString::to_string); | |
263 | toolchain_path(home, toolchain) | |
264 | }) | |
265 | .map(|pb| pb.to_string_lossy().to_string()) | |
266 | .expect("need to specify SYSROOT env var during clippy compilation, or use rustup or multirust"); | |
267 | ||
268 | // make "clippy-driver --rustc" work like a subcommand that passes further args to "rustc" | |
269 | // for example `clippy-driver --rustc --version` will print the rustc version that clippy-driver | |
270 | // uses | |
271 | if let Some(pos) = orig_args.iter().position(|arg| arg == "--rustc") { | |
272 | orig_args.remove(pos); | |
273 | orig_args[0] = "rustc".to_string(); | |
274 | ||
275 | // if we call "rustc", we need to pass --sysroot here as well | |
276 | let mut args: Vec<String> = orig_args.clone(); | |
277 | if !have_sys_root_arg { | |
278 | args.extend(vec!["--sysroot".into(), sys_root]); | |
279 | }; | |
280 | ||
281 | return rustc_driver::RunCompiler::new(&args, &mut DefaultCallbacks).run(); | |
282 | } | |
283 | ||
284 | if orig_args.iter().any(|a| a == "--version" || a == "-V") { | |
285 | let version_info = rustc_tools_util::get_version_info!(); | |
286 | println!("{}", version_info); | |
287 | exit(0); | |
288 | } | |
289 | ||
290 | // Setting RUSTC_WRAPPER causes Cargo to pass 'rustc' as the first argument. | |
291 | // We're invoking the compiler programmatically, so we ignore this/ | |
292 | let wrapper_mode = orig_args.get(1).map(Path::new).and_then(Path::file_stem) == Some("rustc".as_ref()); | |
293 | ||
294 | if wrapper_mode { | |
295 | // we still want to be able to invoke it normally though | |
296 | orig_args.remove(1); | |
297 | } | |
298 | ||
299 | if !wrapper_mode && (orig_args.iter().any(|a| a == "--help" || a == "-h") || orig_args.len() == 1) { | |
300 | display_help(); | |
301 | exit(0); | |
302 | } | |
303 | ||
304 | // this conditional check for the --sysroot flag is there so users can call | |
305 | // `clippy_driver` directly | |
306 | // without having to pass --sysroot or anything | |
307 | let mut args: Vec<String> = orig_args.clone(); | |
308 | if !have_sys_root_arg { | |
309 | args.extend(vec!["--sysroot".into(), sys_root]); | |
310 | }; | |
311 | ||
312 | let mut no_deps = false; | |
313 | let clippy_args_var = env::var("CLIPPY_ARGS").ok(); | |
314 | let clippy_args = clippy_args_var | |
315 | .as_deref() | |
316 | .unwrap_or_default() | |
317 | .split("__CLIPPY_HACKERY__") | |
318 | .filter_map(|s| match s { | |
319 | "" => None, | |
320 | "--no-deps" => { | |
321 | no_deps = true; | |
322 | None | |
323 | }, | |
324 | _ => Some(s.to_string()), | |
325 | }) | |
326 | .chain(vec!["--cfg".into(), r#"feature="cargo-clippy""#.into()]) | |
327 | .collect::<Vec<String>>(); | |
328 | ||
329 | // We enable Clippy if one of the following conditions is met | |
330 | // - IF Clippy is run on its test suite OR | |
331 | // - IF Clippy is run on the main crate, not on deps (`!cap_lints_allow`) THEN | |
332 | // - IF `--no-deps` is not set (`!no_deps`) OR | |
333 | // - IF `--no-deps` is set and Clippy is run on the specified primary package | |
f20569fa XL |
334 | let cap_lints_allow = arg_value(&orig_args, "--cap-lints", |val| val == "allow").is_some(); |
335 | let in_primary_package = env::var("CARGO_PRIMARY_PACKAGE").is_ok(); | |
336 | ||
5099ac24 | 337 | let clippy_enabled = !cap_lints_allow && (!no_deps || in_primary_package); |
f20569fa XL |
338 | if clippy_enabled { |
339 | args.extend(clippy_args); | |
340 | } | |
341 | ||
342 | if clippy_enabled { | |
343 | rustc_driver::RunCompiler::new(&args, &mut ClippyCallbacks { clippy_args_var }).run() | |
344 | } else { | |
345 | rustc_driver::RunCompiler::new(&args, &mut RustcCallbacks { clippy_args_var }).run() | |
346 | } | |
347 | })) | |
348 | } |