mirror of
https://github.com/rust-lang/rust-clippy
synced 2024-12-19 17:44:21 +00:00
257259118c
Some command-line options accessible through `sess.opts` are best accessed through wrapper functions on `Session`, `TyCtxt` or otherwise, rather than through field access on the option struct in the `Session`. Adds a new lint which triggers on those options that should be accessed through a wrapper function so that this is prohibited. Options are annotated with a new attribute `rustc_lint_opt_deny_field_access` which can specify the error message (i.e. "use this other function instead") to be emitted. A simpler alternative would be to simply rename the options in the option type so that it is clear they should not be used, however this doesn't prevent uses, just discourages them. Another alternative would be to make the option fields private, and adding accessor functions on the option types, however the wrapper functions sometimes rely on additional state from `Session` or `TyCtxt` which wouldn't be available in an function on the option type, so the accessor would simply make the field available and its use would be discouraged too. Signed-off-by: David Wood <david.wood@huawei.com>
353 lines
13 KiB
Rust
353 lines
13 KiB
Rust
#![feature(rustc_private)]
|
|
#![feature(once_cell)]
|
|
#![cfg_attr(feature = "deny-warnings", deny(warnings))]
|
|
// warn on lints, that are included in `rust-lang/rust`s bootstrap
|
|
#![warn(rust_2018_idioms, unused_lifetimes)]
|
|
// warn on rustc internal lints
|
|
#![warn(rustc::internal)]
|
|
|
|
// FIXME: switch to something more ergonomic here, once available.
|
|
// (Currently there is no way to opt into sysroot crates without `extern crate`.)
|
|
extern crate rustc_driver;
|
|
extern crate rustc_errors;
|
|
extern crate rustc_interface;
|
|
extern crate rustc_session;
|
|
extern crate rustc_span;
|
|
|
|
use rustc_interface::interface;
|
|
use rustc_session::parse::ParseSess;
|
|
use rustc_span::symbol::Symbol;
|
|
use rustc_tools_util::VersionInfo;
|
|
|
|
use std::borrow::Cow;
|
|
use std::env;
|
|
use std::ops::Deref;
|
|
use std::panic;
|
|
use std::path::{Path, PathBuf};
|
|
use std::process::{exit, Command};
|
|
use std::sync::LazyLock;
|
|
|
|
/// If a command-line option matches `find_arg`, then apply the predicate `pred` on its value. If
|
|
/// true, then return it. The parameter is assumed to be either `--arg=value` or `--arg value`.
|
|
fn arg_value<'a, T: Deref<Target = str>>(
|
|
args: &'a [T],
|
|
find_arg: &str,
|
|
pred: impl Fn(&str) -> bool,
|
|
) -> Option<&'a str> {
|
|
let mut args = args.iter().map(Deref::deref);
|
|
while let Some(arg) = args.next() {
|
|
let mut arg = arg.splitn(2, '=');
|
|
if arg.next() != Some(find_arg) {
|
|
continue;
|
|
}
|
|
|
|
match arg.next().or_else(|| args.next()) {
|
|
Some(v) if pred(v) => return Some(v),
|
|
_ => {},
|
|
}
|
|
}
|
|
None
|
|
}
|
|
|
|
#[test]
|
|
fn test_arg_value() {
|
|
let args = &["--bar=bar", "--foobar", "123", "--foo"];
|
|
|
|
assert_eq!(arg_value(&[] as &[&str], "--foobar", |_| true), None);
|
|
assert_eq!(arg_value(args, "--bar", |_| false), None);
|
|
assert_eq!(arg_value(args, "--bar", |_| true), Some("bar"));
|
|
assert_eq!(arg_value(args, "--bar", |p| p == "bar"), Some("bar"));
|
|
assert_eq!(arg_value(args, "--bar", |p| p == "foo"), None);
|
|
assert_eq!(arg_value(args, "--foobar", |p| p == "foo"), None);
|
|
assert_eq!(arg_value(args, "--foobar", |p| p == "123"), Some("123"));
|
|
assert_eq!(arg_value(args, "--foobar", |p| p.contains("12")), Some("123"));
|
|
assert_eq!(arg_value(args, "--foo", |_| true), None);
|
|
}
|
|
|
|
fn track_clippy_args(parse_sess: &mut ParseSess, args_env_var: &Option<String>) {
|
|
parse_sess.env_depinfo.get_mut().insert((
|
|
Symbol::intern("CLIPPY_ARGS"),
|
|
args_env_var.as_deref().map(Symbol::intern),
|
|
));
|
|
}
|
|
|
|
struct DefaultCallbacks;
|
|
impl rustc_driver::Callbacks for DefaultCallbacks {}
|
|
|
|
/// This is different from `DefaultCallbacks` that it will inform Cargo to track the value of
|
|
/// `CLIPPY_ARGS` environment variable.
|
|
struct RustcCallbacks {
|
|
clippy_args_var: Option<String>,
|
|
}
|
|
|
|
impl rustc_driver::Callbacks for RustcCallbacks {
|
|
fn config(&mut self, config: &mut interface::Config) {
|
|
let clippy_args_var = self.clippy_args_var.take();
|
|
config.parse_sess_created = Some(Box::new(move |parse_sess| {
|
|
track_clippy_args(parse_sess, &clippy_args_var);
|
|
}));
|
|
}
|
|
}
|
|
|
|
struct ClippyCallbacks {
|
|
clippy_args_var: Option<String>,
|
|
}
|
|
|
|
impl rustc_driver::Callbacks for ClippyCallbacks {
|
|
// JUSTIFICATION: necessary in clippy driver to set `mir_opt_level`
|
|
#[cfg_attr(not(bootstrap), allow(rustc::bad_opt_access))]
|
|
fn config(&mut self, config: &mut interface::Config) {
|
|
let previous = config.register_lints.take();
|
|
let clippy_args_var = self.clippy_args_var.take();
|
|
config.parse_sess_created = Some(Box::new(move |parse_sess| {
|
|
track_clippy_args(parse_sess, &clippy_args_var);
|
|
}));
|
|
config.register_lints = Some(Box::new(move |sess, lint_store| {
|
|
// technically we're ~guaranteed that this is none but might as well call anything that
|
|
// is there already. Certainly it can't hurt.
|
|
if let Some(previous) = &previous {
|
|
(previous)(sess, lint_store);
|
|
}
|
|
|
|
let conf = clippy_lints::read_conf(sess);
|
|
clippy_lints::register_plugins(lint_store, sess, &conf);
|
|
clippy_lints::register_pre_expansion_lints(lint_store, sess, &conf);
|
|
clippy_lints::register_renamed(lint_store);
|
|
}));
|
|
|
|
// FIXME: #4825; This is required, because Clippy lints that are based on MIR have to be
|
|
// run on the unoptimized MIR. On the other hand this results in some false negatives. If
|
|
// MIR passes can be enabled / disabled separately, we should figure out, what passes to
|
|
// use for Clippy.
|
|
config.opts.unstable_opts.mir_opt_level = Some(0);
|
|
}
|
|
}
|
|
|
|
fn display_help() {
|
|
println!(
|
|
"\
|
|
Checks a package to catch common mistakes and improve your Rust code.
|
|
|
|
Usage:
|
|
cargo clippy [options] [--] [<opts>...]
|
|
|
|
Common options:
|
|
-h, --help Print this message
|
|
--rustc Pass all args to rustc
|
|
-V, --version Print version info and exit
|
|
|
|
Other options are the same as `cargo check`.
|
|
|
|
To allow or deny a lint from the command line you can use `cargo clippy --`
|
|
with:
|
|
|
|
-W --warn OPT Set lint warnings
|
|
-A --allow OPT Set lint allowed
|
|
-D --deny OPT Set lint denied
|
|
-F --forbid OPT Set lint forbidden
|
|
|
|
You can use tool lints to allow or deny lints from your code, eg.:
|
|
|
|
#[allow(clippy::needless_lifetimes)]
|
|
"
|
|
);
|
|
}
|
|
|
|
const BUG_REPORT_URL: &str = "https://github.com/rust-lang/rust-clippy/issues/new";
|
|
|
|
type PanicCallback = dyn Fn(&panic::PanicInfo<'_>) + Sync + Send + 'static;
|
|
static ICE_HOOK: LazyLock<Box<PanicCallback>> = LazyLock::new(|| {
|
|
let hook = panic::take_hook();
|
|
panic::set_hook(Box::new(|info| report_clippy_ice(info, BUG_REPORT_URL)));
|
|
hook
|
|
});
|
|
|
|
fn report_clippy_ice(info: &panic::PanicInfo<'_>, bug_report_url: &str) {
|
|
// Invoke our ICE handler, which prints the actual panic message and optionally a backtrace
|
|
(*ICE_HOOK)(info);
|
|
|
|
// Separate the output with an empty line
|
|
eprintln!();
|
|
|
|
let fallback_bundle = rustc_errors::fallback_fluent_bundle(rustc_errors::DEFAULT_LOCALE_RESOURCES, false);
|
|
let emitter = Box::new(rustc_errors::emitter::EmitterWriter::stderr(
|
|
rustc_errors::ColorConfig::Auto,
|
|
None,
|
|
None,
|
|
fallback_bundle,
|
|
false,
|
|
false,
|
|
None,
|
|
false,
|
|
));
|
|
let handler = rustc_errors::Handler::with_emitter(true, None, emitter);
|
|
|
|
// a .span_bug or .bug call has already printed what
|
|
// it wants to print.
|
|
if !info.payload().is::<rustc_errors::ExplicitBug>() {
|
|
let mut d = rustc_errors::Diagnostic::new(rustc_errors::Level::Bug, "unexpected panic");
|
|
handler.emit_diagnostic(&mut d);
|
|
}
|
|
|
|
let version_info = rustc_tools_util::get_version_info!();
|
|
|
|
let xs: Vec<Cow<'static, str>> = vec![
|
|
"the compiler unexpectedly panicked. this is a bug.".into(),
|
|
format!("we would appreciate a bug report: {}", bug_report_url).into(),
|
|
format!("Clippy version: {}", version_info).into(),
|
|
];
|
|
|
|
for note in &xs {
|
|
handler.note_without_error(note.as_ref());
|
|
}
|
|
|
|
// If backtraces are enabled, also print the query stack
|
|
let backtrace = env::var_os("RUST_BACKTRACE").map_or(false, |x| &x != "0");
|
|
|
|
let num_frames = if backtrace { None } else { Some(2) };
|
|
|
|
interface::try_print_query_stack(&handler, num_frames);
|
|
}
|
|
|
|
fn toolchain_path(home: Option<String>, toolchain: Option<String>) -> Option<PathBuf> {
|
|
home.and_then(|home| {
|
|
toolchain.map(|toolchain| {
|
|
let mut path = PathBuf::from(home);
|
|
path.push("toolchains");
|
|
path.push(toolchain);
|
|
path
|
|
})
|
|
})
|
|
}
|
|
|
|
#[allow(clippy::too_many_lines)]
|
|
pub fn main() {
|
|
rustc_driver::init_rustc_env_logger();
|
|
LazyLock::force(&ICE_HOOK);
|
|
exit(rustc_driver::catch_with_exit_code(move || {
|
|
let mut orig_args: Vec<String> = env::args().collect();
|
|
|
|
// Get the sysroot, looking from most specific to this invocation to the least:
|
|
// - command line
|
|
// - runtime environment
|
|
// - SYSROOT
|
|
// - RUSTUP_HOME, MULTIRUST_HOME, RUSTUP_TOOLCHAIN, MULTIRUST_TOOLCHAIN
|
|
// - sysroot from rustc in the path
|
|
// - compile-time environment
|
|
// - SYSROOT
|
|
// - RUSTUP_HOME, MULTIRUST_HOME, RUSTUP_TOOLCHAIN, MULTIRUST_TOOLCHAIN
|
|
let sys_root_arg = arg_value(&orig_args, "--sysroot", |_| true);
|
|
let have_sys_root_arg = sys_root_arg.is_some();
|
|
let sys_root = sys_root_arg
|
|
.map(PathBuf::from)
|
|
.or_else(|| std::env::var("SYSROOT").ok().map(PathBuf::from))
|
|
.or_else(|| {
|
|
let home = std::env::var("RUSTUP_HOME")
|
|
.or_else(|_| std::env::var("MULTIRUST_HOME"))
|
|
.ok();
|
|
let toolchain = std::env::var("RUSTUP_TOOLCHAIN")
|
|
.or_else(|_| std::env::var("MULTIRUST_TOOLCHAIN"))
|
|
.ok();
|
|
toolchain_path(home, toolchain)
|
|
})
|
|
.or_else(|| {
|
|
Command::new("rustc")
|
|
.arg("--print")
|
|
.arg("sysroot")
|
|
.output()
|
|
.ok()
|
|
.and_then(|out| String::from_utf8(out.stdout).ok())
|
|
.map(|s| PathBuf::from(s.trim()))
|
|
})
|
|
.or_else(|| option_env!("SYSROOT").map(PathBuf::from))
|
|
.or_else(|| {
|
|
let home = option_env!("RUSTUP_HOME")
|
|
.or(option_env!("MULTIRUST_HOME"))
|
|
.map(ToString::to_string);
|
|
let toolchain = option_env!("RUSTUP_TOOLCHAIN")
|
|
.or(option_env!("MULTIRUST_TOOLCHAIN"))
|
|
.map(ToString::to_string);
|
|
toolchain_path(home, toolchain)
|
|
})
|
|
.map(|pb| pb.to_string_lossy().to_string())
|
|
.expect("need to specify SYSROOT env var during clippy compilation, or use rustup or multirust");
|
|
|
|
// make "clippy-driver --rustc" work like a subcommand that passes further args to "rustc"
|
|
// for example `clippy-driver --rustc --version` will print the rustc version that clippy-driver
|
|
// uses
|
|
if let Some(pos) = orig_args.iter().position(|arg| arg == "--rustc") {
|
|
orig_args.remove(pos);
|
|
orig_args[0] = "rustc".to_string();
|
|
|
|
// if we call "rustc", we need to pass --sysroot here as well
|
|
let mut args: Vec<String> = orig_args.clone();
|
|
if !have_sys_root_arg {
|
|
args.extend(vec!["--sysroot".into(), sys_root]);
|
|
};
|
|
|
|
return rustc_driver::RunCompiler::new(&args, &mut DefaultCallbacks).run();
|
|
}
|
|
|
|
if orig_args.iter().any(|a| a == "--version" || a == "-V") {
|
|
let version_info = rustc_tools_util::get_version_info!();
|
|
println!("{}", version_info);
|
|
exit(0);
|
|
}
|
|
|
|
// Setting RUSTC_WRAPPER causes Cargo to pass 'rustc' as the first argument.
|
|
// We're invoking the compiler programmatically, so we ignore this/
|
|
let wrapper_mode = orig_args.get(1).map(Path::new).and_then(Path::file_stem) == Some("rustc".as_ref());
|
|
|
|
if wrapper_mode {
|
|
// we still want to be able to invoke it normally though
|
|
orig_args.remove(1);
|
|
}
|
|
|
|
if !wrapper_mode && (orig_args.iter().any(|a| a == "--help" || a == "-h") || orig_args.len() == 1) {
|
|
display_help();
|
|
exit(0);
|
|
}
|
|
|
|
// this conditional check for the --sysroot flag is there so users can call
|
|
// `clippy_driver` directly
|
|
// without having to pass --sysroot or anything
|
|
let mut args: Vec<String> = orig_args.clone();
|
|
if !have_sys_root_arg {
|
|
args.extend(vec!["--sysroot".into(), sys_root]);
|
|
};
|
|
|
|
let mut no_deps = false;
|
|
let clippy_args_var = env::var("CLIPPY_ARGS").ok();
|
|
let clippy_args = clippy_args_var
|
|
.as_deref()
|
|
.unwrap_or_default()
|
|
.split("__CLIPPY_HACKERY__")
|
|
.filter_map(|s| match s {
|
|
"" => None,
|
|
"--no-deps" => {
|
|
no_deps = true;
|
|
None
|
|
},
|
|
_ => Some(s.to_string()),
|
|
})
|
|
.chain(vec!["--cfg".into(), r#"feature="cargo-clippy""#.into()])
|
|
.collect::<Vec<String>>();
|
|
|
|
// We enable Clippy if one of the following conditions is met
|
|
// - IF Clippy is run on its test suite OR
|
|
// - IF Clippy is run on the main crate, not on deps (`!cap_lints_allow`) THEN
|
|
// - IF `--no-deps` is not set (`!no_deps`) OR
|
|
// - IF `--no-deps` is set and Clippy is run on the specified primary package
|
|
let cap_lints_allow = arg_value(&orig_args, "--cap-lints", |val| val == "allow").is_some()
|
|
&& arg_value(&orig_args, "--force-warn", |val| val.contains("clippy::")).is_none();
|
|
let in_primary_package = env::var("CARGO_PRIMARY_PACKAGE").is_ok();
|
|
|
|
let clippy_enabled = !cap_lints_allow && (!no_deps || in_primary_package);
|
|
if clippy_enabled {
|
|
args.extend(clippy_args);
|
|
rustc_driver::RunCompiler::new(&args, &mut ClippyCallbacks { clippy_args_var }).run()
|
|
} else {
|
|
rustc_driver::RunCompiler::new(&args, &mut RustcCallbacks { clippy_args_var }).run()
|
|
}
|
|
}))
|
|
}
|