Add disallowed_macros lint

This commit is contained in:
Alex Macleod 2022-10-05 13:44:01 +00:00
parent 425e1ea73d
commit 86c86c3742
23 changed files with 500 additions and 110 deletions

View file

@ -1,14 +1,15 @@
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::{match_def_path, paths};
use rustc_data_structures::fx::FxHashMap;
use rustc_hir::def::{Namespace, Res};
use rustc_hir::def_id::DefId;
use rustc_hir::{def::Res, AsyncGeneratorKind, Body, BodyId, GeneratorKind};
use rustc_hir::{AsyncGeneratorKind, Body, BodyId, GeneratorKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty::GeneratorInteriorTypeCause;
use rustc_session::{declare_tool_lint, impl_lint_pass};
use rustc_span::{sym, Span};
use crate::utils::conf::DisallowedType;
use crate::utils::conf::DisallowedPath;
declare_clippy_lint! {
/// ### What it does
@ -171,12 +172,12 @@ impl_lint_pass!(AwaitHolding => [AWAIT_HOLDING_LOCK, AWAIT_HOLDING_REFCELL_REF,
#[derive(Debug)]
pub struct AwaitHolding {
conf_invalid_types: Vec<DisallowedType>,
def_ids: FxHashMap<DefId, DisallowedType>,
conf_invalid_types: Vec<DisallowedPath>,
def_ids: FxHashMap<DefId, DisallowedPath>,
}
impl AwaitHolding {
pub(crate) fn new(conf_invalid_types: Vec<DisallowedType>) -> Self {
pub(crate) fn new(conf_invalid_types: Vec<DisallowedPath>) -> Self {
Self {
conf_invalid_types,
def_ids: FxHashMap::default(),
@ -187,11 +188,8 @@ impl AwaitHolding {
impl LateLintPass<'_> for AwaitHolding {
fn check_crate(&mut self, cx: &LateContext<'_>) {
for conf in &self.conf_invalid_types {
let path = match conf {
DisallowedType::Simple(path) | DisallowedType::WithReason { path, .. } => path,
};
let segs: Vec<_> = path.split("::").collect();
if let Res::Def(_, id) = clippy_utils::def_path_res(cx, &segs) {
let segs: Vec<_> = conf.path().split("::").collect();
if let Res::Def(_, id) = clippy_utils::def_path_res(cx, &segs, Some(Namespace::TypeNS)) {
self.def_ids.insert(id, conf.clone());
}
}
@ -256,20 +254,18 @@ impl AwaitHolding {
}
}
fn emit_invalid_type(cx: &LateContext<'_>, span: Span, disallowed: &DisallowedType) {
let (type_name, reason) = match disallowed {
DisallowedType::Simple(path) => (path, &None),
DisallowedType::WithReason { path, reason } => (path, reason),
};
fn emit_invalid_type(cx: &LateContext<'_>, span: Span, disallowed: &DisallowedPath) {
span_lint_and_then(
cx,
AWAIT_HOLDING_INVALID_TYPE,
span,
&format!("`{type_name}` may not be held across an `await` point per `clippy.toml`",),
&format!(
"`{}` may not be held across an `await` point per `clippy.toml`",
disallowed.path()
),
|diag| {
if let Some(reason) = reason {
diag.note(reason.clone());
if let Some(reason) = disallowed.reason() {
diag.note(reason);
}
},
);

View file

@ -0,0 +1,151 @@
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::macros::macro_backtrace;
use rustc_data_structures::fx::FxHashSet;
use rustc_hir::def::{Namespace, Res};
use rustc_hir::def_id::DefIdMap;
use rustc_hir::{Expr, ForeignItem, HirId, ImplItem, Item, Pat, Path, Stmt, TraitItem, Ty};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::{declare_tool_lint, impl_lint_pass};
use rustc_span::{ExpnId, Span};
use crate::utils::conf;
declare_clippy_lint! {
/// ### What it does
/// Denies the configured macros in clippy.toml
///
/// Note: Even though this lint is warn-by-default, it will only trigger if
/// macros are defined in the clippy.toml file.
///
/// ### Why is this bad?
/// Some macros are undesirable in certain contexts, and it's beneficial to
/// lint for them as needed.
///
/// ### Example
/// An example clippy.toml configuration:
/// ```toml
/// # clippy.toml
/// disallowed-macros = [
/// # Can use a string as the path of the disallowed macro.
/// "std::print",
/// # Can also use an inline table with a `path` key.
/// { path = "std::println" },
/// # When using an inline table, can add a `reason` for why the macro
/// # is disallowed.
/// { path = "serde::Serialize", reason = "no serializing" },
/// ]
/// ```
/// ```
/// use serde::Serialize;
///
/// // Example code where clippy issues a warning
/// println!("warns");
///
/// // The diagnostic will contain the message "no serializing"
/// #[derive(Serialize)]
/// struct Data {
/// name: String,
/// value: usize,
/// }
/// ```
#[clippy::version = "1.65.0"]
pub DISALLOWED_MACROS,
style,
"use of a disallowed macro"
}
pub struct DisallowedMacros {
conf_disallowed: Vec<conf::DisallowedPath>,
disallowed: DefIdMap<usize>,
seen: FxHashSet<ExpnId>,
}
impl DisallowedMacros {
pub fn new(conf_disallowed: Vec<conf::DisallowedPath>) -> Self {
Self {
conf_disallowed,
disallowed: DefIdMap::default(),
seen: FxHashSet::default(),
}
}
fn check(&mut self, cx: &LateContext<'_>, span: Span) {
if self.conf_disallowed.is_empty() {
return;
}
for mac in macro_backtrace(span) {
if !self.seen.insert(mac.expn) {
return;
}
if let Some(&index) = self.disallowed.get(&mac.def_id) {
let conf = &self.conf_disallowed[index];
span_lint_and_then(
cx,
DISALLOWED_MACROS,
mac.span,
&format!("use of a disallowed macro `{}`", conf.path()),
|diag| {
if let Some(reason) = conf.reason() {
diag.note(&format!("{reason} (from clippy.toml)"));
}
},
);
}
}
}
}
impl_lint_pass!(DisallowedMacros => [DISALLOWED_MACROS]);
impl LateLintPass<'_> for DisallowedMacros {
fn check_crate(&mut self, cx: &LateContext<'_>) {
for (index, conf) in self.conf_disallowed.iter().enumerate() {
let segs: Vec<_> = conf.path().split("::").collect();
if let Res::Def(_, id) = clippy_utils::def_path_res(cx, &segs, Some(Namespace::MacroNS)) {
self.disallowed.insert(id, index);
}
}
}
fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
self.check(cx, expr.span);
}
fn check_stmt(&mut self, cx: &LateContext<'_>, stmt: &Stmt<'_>) {
self.check(cx, stmt.span);
}
fn check_ty(&mut self, cx: &LateContext<'_>, ty: &Ty<'_>) {
self.check(cx, ty.span);
}
fn check_pat(&mut self, cx: &LateContext<'_>, pat: &Pat<'_>) {
self.check(cx, pat.span);
}
fn check_item(&mut self, cx: &LateContext<'_>, item: &Item<'_>) {
self.check(cx, item.span);
self.check(cx, item.vis_span);
}
fn check_foreign_item(&mut self, cx: &LateContext<'_>, item: &ForeignItem<'_>) {
self.check(cx, item.span);
self.check(cx, item.vis_span);
}
fn check_impl_item(&mut self, cx: &LateContext<'_>, item: &ImplItem<'_>) {
self.check(cx, item.span);
self.check(cx, item.vis_span);
}
fn check_trait_item(&mut self, cx: &LateContext<'_>, item: &TraitItem<'_>) {
self.check(cx, item.span);
}
fn check_path(&mut self, cx: &LateContext<'_>, path: &Path<'_>, _: HirId) {
self.check(cx, path.span);
}
}

View file

@ -1,7 +1,9 @@
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::{fn_def_id, get_parent_expr, path_def_id};
use rustc_hir::{def::Res, def_id::DefIdMap, Expr, ExprKind};
use rustc_hir::def::{Namespace, Res};
use rustc_hir::def_id::DefIdMap;
use rustc_hir::{Expr, ExprKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::{declare_tool_lint, impl_lint_pass};
@ -58,12 +60,12 @@ declare_clippy_lint! {
#[derive(Clone, Debug)]
pub struct DisallowedMethods {
conf_disallowed: Vec<conf::DisallowedMethod>,
conf_disallowed: Vec<conf::DisallowedPath>,
disallowed: DefIdMap<usize>,
}
impl DisallowedMethods {
pub fn new(conf_disallowed: Vec<conf::DisallowedMethod>) -> Self {
pub fn new(conf_disallowed: Vec<conf::DisallowedPath>) -> Self {
Self {
conf_disallowed,
disallowed: DefIdMap::default(),
@ -77,7 +79,7 @@ impl<'tcx> LateLintPass<'tcx> for DisallowedMethods {
fn check_crate(&mut self, cx: &LateContext<'_>) {
for (index, conf) in self.conf_disallowed.iter().enumerate() {
let segs: Vec<_> = conf.path().split("::").collect();
if let Res::Def(_, id) = clippy_utils::def_path_res(cx, &segs) {
if let Res::Def(_, id) = clippy_utils::def_path_res(cx, &segs, Some(Namespace::ValueNS)) {
self.disallowed.insert(id, index);
}
}
@ -102,10 +104,7 @@ impl<'tcx> LateLintPass<'tcx> for DisallowedMethods {
};
let msg = format!("use of a disallowed method `{}`", conf.path());
span_lint_and_then(cx, DISALLOWED_METHODS, expr.span, &msg, |diag| {
if let conf::DisallowedMethod::WithReason {
reason: Some(reason), ..
} = conf
{
if let Some(reason) = conf.reason() {
diag.note(&format!("{reason} (from clippy.toml)"));
}
});

View file

@ -1,7 +1,9 @@
use clippy_utils::diagnostics::span_lint_and_then;
use rustc_data_structures::fx::FxHashMap;
use rustc_hir::{def::Res, def_id::DefId, Item, ItemKind, PolyTraitRef, PrimTy, Ty, TyKind, UseKind};
use rustc_hir::def::{Namespace, Res};
use rustc_hir::def_id::DefId;
use rustc_hir::{Item, ItemKind, PolyTraitRef, PrimTy, Ty, TyKind, UseKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::{declare_tool_lint, impl_lint_pass};
use rustc_span::Span;
@ -50,13 +52,13 @@ declare_clippy_lint! {
}
#[derive(Clone, Debug)]
pub struct DisallowedTypes {
conf_disallowed: Vec<conf::DisallowedType>,
conf_disallowed: Vec<conf::DisallowedPath>,
def_ids: FxHashMap<DefId, Option<String>>,
prim_tys: FxHashMap<PrimTy, Option<String>>,
}
impl DisallowedTypes {
pub fn new(conf_disallowed: Vec<conf::DisallowedType>) -> Self {
pub fn new(conf_disallowed: Vec<conf::DisallowedPath>) -> Self {
Self {
conf_disallowed,
def_ids: FxHashMap::default(),
@ -86,15 +88,9 @@ impl_lint_pass!(DisallowedTypes => [DISALLOWED_TYPES]);
impl<'tcx> LateLintPass<'tcx> for DisallowedTypes {
fn check_crate(&mut self, cx: &LateContext<'_>) {
for conf in &self.conf_disallowed {
let (path, reason) = match conf {
conf::DisallowedType::Simple(path) => (path, None),
conf::DisallowedType::WithReason { path, reason } => (
path,
reason.as_ref().map(|reason| format!("{reason} (from clippy.toml)")),
),
};
let segs: Vec<_> = path.split("::").collect();
match clippy_utils::def_path_res(cx, &segs) {
let segs: Vec<_> = conf.path().split("::").collect();
let reason = conf.reason().map(|reason| format!("{reason} (from clippy.toml)"));
match clippy_utils::def_path_res(cx, &segs, Some(Namespace::TypeNS)) {
Res::Def(_, id) => {
self.def_ids.insert(id, reason);
},

View file

@ -45,6 +45,7 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![
LintId::of(derivable_impls::DERIVABLE_IMPLS),
LintId::of(derive::DERIVE_HASH_XOR_EQ),
LintId::of(derive::DERIVE_ORD_XOR_PARTIAL_ORD),
LintId::of(disallowed_macros::DISALLOWED_MACROS),
LintId::of(disallowed_methods::DISALLOWED_METHODS),
LintId::of(disallowed_names::DISALLOWED_NAMES),
LintId::of(disallowed_types::DISALLOWED_TYPES),

View file

@ -114,6 +114,7 @@ store.register_lints(&[
derive::DERIVE_PARTIAL_EQ_WITHOUT_EQ,
derive::EXPL_IMPL_CLONE_ON_COPY,
derive::UNSAFE_DERIVE_DESERIALIZE,
disallowed_macros::DISALLOWED_MACROS,
disallowed_methods::DISALLOWED_METHODS,
disallowed_names::DISALLOWED_NAMES,
disallowed_script_idents::DISALLOWED_SCRIPT_IDENTS,

View file

@ -15,6 +15,7 @@ store.register_group(true, "clippy::style", Some("clippy_style"), vec![
LintId::of(default::FIELD_REASSIGN_WITH_DEFAULT),
LintId::of(default_instead_of_iter_empty::DEFAULT_INSTEAD_OF_ITER_EMPTY),
LintId::of(dereference::NEEDLESS_BORROW),
LintId::of(disallowed_macros::DISALLOWED_MACROS),
LintId::of(disallowed_methods::DISALLOWED_METHODS),
LintId::of(disallowed_names::DISALLOWED_NAMES),
LintId::of(disallowed_types::DISALLOWED_TYPES),

View file

@ -199,6 +199,7 @@ mod default_union_representation;
mod dereference;
mod derivable_impls;
mod derive;
mod disallowed_macros;
mod disallowed_methods;
mod disallowed_names;
mod disallowed_script_idents;
@ -820,6 +821,8 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
store.register_late_pass(|_| Box::new(unwrap_in_result::UnwrapInResult));
store.register_late_pass(|_| Box::new(semicolon_if_nothing_returned::SemicolonIfNothingReturned));
store.register_late_pass(|_| Box::new(async_yields_async::AsyncYieldsAsync));
let disallowed_macros = conf.disallowed_macros.clone();
store.register_late_pass(move |_| Box::new(disallowed_macros::DisallowedMacros::new(disallowed_macros.clone())));
let disallowed_methods = conf.disallowed_methods.clone();
store.register_late_pass(move |_| Box::new(disallowed_methods::DisallowedMethods::new(disallowed_methods.clone())));
store.register_early_pass(|| Box::new(asm_syntax::InlineAsmX86AttSyntax));

View file

@ -58,7 +58,8 @@ impl_lint_pass!(ImportRename => [MISSING_ENFORCED_IMPORT_RENAMES]);
impl LateLintPass<'_> for ImportRename {
fn check_crate(&mut self, cx: &LateContext<'_>) {
for Rename { path, rename } in &self.conf_renames {
if let Res::Def(_, id) = clippy_utils::def_path_res(cx, &path.split("::").collect::<Vec<_>>()) {
let segs = path.split("::").collect::<Vec<_>>();
if let Res::Def(_, id) = clippy_utils::def_path_res(cx, &segs, None) {
self.renames.insert(id, Symbol::intern(rename));
}
}

View file

@ -39,28 +39,28 @@ pub struct Rename {
pub rename: String,
}
/// A single disallowed method, used by the `DISALLOWED_METHODS` lint.
#[derive(Clone, Debug, Deserialize)]
#[serde(untagged)]
pub enum DisallowedMethod {
pub enum DisallowedPath {
Simple(String),
WithReason { path: String, reason: Option<String> },
}
impl DisallowedMethod {
impl DisallowedPath {
pub fn path(&self) -> &str {
let (Self::Simple(path) | Self::WithReason { path, .. }) = self;
path
}
}
/// A single disallowed type, used by the `DISALLOWED_TYPES` lint.
#[derive(Clone, Debug, Deserialize)]
#[serde(untagged)]
pub enum DisallowedType {
Simple(String),
WithReason { path: String, reason: Option<String> },
pub fn reason(&self) -> Option<&str> {
match self {
Self::WithReason {
reason: Some(reason), ..
} => Some(reason),
_ => None,
}
}
}
/// Conf with parse errors
@ -315,14 +315,18 @@ define_Conf! {
///
/// Whether to allow certain wildcard imports (prelude, super in tests).
(warn_on_all_wildcard_imports: bool = false),
/// Lint: DISALLOWED_MACROS.
///
/// The list of disallowed macros, written as fully qualified paths.
(disallowed_macros: Vec<crate::utils::conf::DisallowedPath> = Vec::new()),
/// Lint: DISALLOWED_METHODS.
///
/// The list of disallowed methods, written as fully qualified paths.
(disallowed_methods: Vec<crate::utils::conf::DisallowedMethod> = Vec::new()),
(disallowed_methods: Vec<crate::utils::conf::DisallowedPath> = Vec::new()),
/// Lint: DISALLOWED_TYPES.
///
/// The list of disallowed types, written as fully qualified paths.
(disallowed_types: Vec<crate::utils::conf::DisallowedType> = Vec::new()),
(disallowed_types: Vec<crate::utils::conf::DisallowedPath> = Vec::new()),
/// Lint: UNREADABLE_LITERAL.
///
/// Should the fraction of a decimal be linted to include separators.
@ -362,7 +366,7 @@ define_Conf! {
/// For example, `[_, _, _, e, ..]` is a slice pattern with 4 elements.
(max_suggested_slice_pattern_length: u64 = 3),
/// Lint: AWAIT_HOLDING_INVALID_TYPE
(await_holding_invalid_types: Vec<crate::utils::conf::DisallowedType> = Vec::new()),
(await_holding_invalid_types: Vec<crate::utils::conf::DisallowedPath> = Vec::new()),
/// Lint: LARGE_INCLUDE_FILE.
///
/// The maximum size of a file included via `include_bytes!()` or `include_str!()`, in bytes

View file

@ -15,7 +15,7 @@ use rustc_ast::visit::FnKind;
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
use rustc_errors::Applicability;
use rustc_hir as hir;
use rustc_hir::def::{DefKind, Res};
use rustc_hir::def::{DefKind, Namespace, Res};
use rustc_hir::def_id::DefId;
use rustc_hir::hir_id::CRATE_HIR_ID;
use rustc_hir::intravisit::Visitor;
@ -920,7 +920,7 @@ impl<'tcx> LateLintPass<'tcx> for UnnecessaryDefPath {
// Extract the path to the matched type
if let Some(segments) = path_to_matched_type(cx, item_arg);
let segments: Vec<&str> = segments.iter().map(|sym| &**sym).collect();
if let Some(def_id) = def_path_res(cx, &segments[..]).opt_def_id();
if let Some(def_id) = def_path_res(cx, &segments[..], None).opt_def_id();
then {
// def_path_res will match field names before anything else, but for this we want to match
// inherent functions first.
@ -952,7 +952,7 @@ impl<'tcx> LateLintPass<'tcx> for UnnecessaryDefPath {
Item::DiagnosticItem(*item_name),
)
} else if let Some(lang_item) = cx.tcx.lang_items().items().iter().position(|id| *id == Some(def_id)) {
let lang_items = def_path_res(cx, &["rustc_hir", "lang_items", "LangItem"]).def_id();
let lang_items = def_path_res(cx, &["rustc_hir", "lang_items", "LangItem"], Some(Namespace::TypeNS)).def_id();
let item_name = cx.tcx.adt_def(lang_items).variants().iter().nth(lang_item).unwrap().name;
(
"use of a def path to a `LangItem`",
@ -1115,7 +1115,7 @@ fn read_mir_alloc_def_path<'tcx>(cx: &LateContext<'tcx>, alloc: &'tcx Allocation
// This is not a complete resolver for paths. It works on all the paths currently used in the paths
// module. That's all it does and all it needs to do.
pub fn check_path(cx: &LateContext<'_>, path: &[&str]) -> bool {
if def_path_res(cx, path) != Res::Err {
if def_path_res(cx, path, None) != Res::Err {
return true;
}
@ -1206,7 +1206,7 @@ impl<'tcx> LateLintPass<'tcx> for InterningDefinedSymbol {
}
for &module in &[&paths::KW_MODULE, &paths::SYM_MODULE] {
if let Some(def_id) = def_path_res(cx, module).opt_def_id() {
if let Some(def_id) = def_path_res(cx, module, None).opt_def_id() {
for item in cx.tcx.module_children(def_id).iter() {
if_chain! {
if let Res::Def(DefKind::Const, item_def_id) = item.res;