mirror of
https://github.com/rust-lang/rust-clippy
synced 2025-01-04 09:18:44 +00:00
191 lines
6.4 KiB
Rust
191 lines
6.4 KiB
Rust
|
use clippy_utils::diagnostics::span_lint;
|
||
|
use clippy_utils::macros::{is_format_macro, root_macro_call_first_node, FormatArgsArg, FormatArgsExpn};
|
||
|
use clippy_utils::{is_diag_trait_item, path_to_local, peel_ref_operators};
|
||
|
use if_chain::if_chain;
|
||
|
use rustc_hir::{Expr, ExprKind, Impl, Item, ItemKind, QPath};
|
||
|
use rustc_lint::{LateContext, LateLintPass};
|
||
|
use rustc_session::{declare_tool_lint, impl_lint_pass};
|
||
|
use rustc_span::{sym, symbol::kw, Symbol};
|
||
|
|
||
|
#[derive(Clone, Copy)]
|
||
|
enum FormatTrait {
|
||
|
Debug,
|
||
|
Display,
|
||
|
}
|
||
|
|
||
|
impl FormatTrait {
|
||
|
fn name(self) -> Symbol {
|
||
|
match self {
|
||
|
FormatTrait::Debug => sym::Debug,
|
||
|
FormatTrait::Display => sym::Display,
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
declare_clippy_lint! {
|
||
|
/// ### What it does
|
||
|
/// Checks for format trait implementations (e.g. `Display`) with a recursive call to itself
|
||
|
/// which uses `self` as a parameter.
|
||
|
/// This is typically done indirectly with the `write!` macro or with `to_string()`.
|
||
|
///
|
||
|
/// ### Why is this bad?
|
||
|
/// This will lead to infinite recursion and a stack overflow.
|
||
|
///
|
||
|
/// ### Example
|
||
|
///
|
||
|
/// ```rust
|
||
|
/// use std::fmt;
|
||
|
///
|
||
|
/// struct Structure(i32);
|
||
|
/// impl fmt::Display for Structure {
|
||
|
/// fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||
|
/// write!(f, "{}", self.to_string())
|
||
|
/// }
|
||
|
/// }
|
||
|
///
|
||
|
/// ```
|
||
|
/// Use instead:
|
||
|
/// ```rust
|
||
|
/// use std::fmt;
|
||
|
///
|
||
|
/// struct Structure(i32);
|
||
|
/// impl fmt::Display for Structure {
|
||
|
/// fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||
|
/// write!(f, "{}", self.0)
|
||
|
/// }
|
||
|
/// }
|
||
|
/// ```
|
||
|
#[clippy::version = "1.48.0"]
|
||
|
pub RECURSIVE_FORMAT_IMPL,
|
||
|
correctness,
|
||
|
"Format trait method called while implementing the same Format trait"
|
||
|
}
|
||
|
|
||
|
#[derive(Default)]
|
||
|
pub struct RecursiveFormatImpl {
|
||
|
// Whether we are inside Display or Debug trait impl - None for neither
|
||
|
format_trait_impl: Option<FormatTrait>,
|
||
|
}
|
||
|
|
||
|
impl RecursiveFormatImpl {
|
||
|
pub fn new() -> Self {
|
||
|
Self {
|
||
|
format_trait_impl: None,
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
impl_lint_pass!(RecursiveFormatImpl => [RECURSIVE_FORMAT_IMPL]);
|
||
|
|
||
|
impl<'tcx> LateLintPass<'tcx> for RecursiveFormatImpl {
|
||
|
fn check_item(&mut self, cx: &LateContext<'_>, item: &Item<'_>) {
|
||
|
if let Some(format_trait_impl) = is_format_trait_impl(cx, item) {
|
||
|
self.format_trait_impl = Some(format_trait_impl);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
fn check_item_post(&mut self, cx: &LateContext<'_>, item: &Item<'_>) {
|
||
|
// Assume no nested Impl of Debug and Display within eachother
|
||
|
if is_format_trait_impl(cx, item).is_some() {
|
||
|
self.format_trait_impl = None;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
|
||
|
match self.format_trait_impl {
|
||
|
Some(FormatTrait::Display) => {
|
||
|
check_to_string_in_display(cx, expr);
|
||
|
check_self_in_format_args(cx, expr, FormatTrait::Display);
|
||
|
},
|
||
|
Some(FormatTrait::Debug) => {
|
||
|
check_self_in_format_args(cx, expr, FormatTrait::Debug);
|
||
|
},
|
||
|
None => {},
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
fn check_to_string_in_display(cx: &LateContext<'_>, expr: &Expr<'_>) {
|
||
|
if_chain! {
|
||
|
// Get the hir_id of the object we are calling the method on
|
||
|
if let ExprKind::MethodCall(path, [ref self_arg, ..], _) = expr.kind;
|
||
|
// Is the method to_string() ?
|
||
|
if path.ident.name == sym!(to_string);
|
||
|
// Is the method a part of the ToString trait? (i.e. not to_string() implemented
|
||
|
// separately)
|
||
|
if let Some(expr_def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id);
|
||
|
if is_diag_trait_item(cx, expr_def_id, sym::ToString);
|
||
|
// Is the method is called on self
|
||
|
if let ExprKind::Path(QPath::Resolved(_, path)) = self_arg.kind;
|
||
|
if let [segment] = path.segments;
|
||
|
if segment.ident.name == kw::SelfLower;
|
||
|
then {
|
||
|
span_lint(
|
||
|
cx,
|
||
|
RECURSIVE_FORMAT_IMPL,
|
||
|
expr.span,
|
||
|
"using `self.to_string` in `fmt::Display` implementation will cause infinite recursion",
|
||
|
);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
fn check_self_in_format_args<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, impl_trait: FormatTrait) {
|
||
|
// Check each arg in format calls - do we ever use Display on self (directly or via deref)?
|
||
|
if_chain! {
|
||
|
if let Some(outer_macro) = root_macro_call_first_node(cx, expr);
|
||
|
if let macro_def_id = outer_macro.def_id;
|
||
|
if let Some(format_args) = FormatArgsExpn::find_nested(cx, expr, outer_macro.expn);
|
||
|
if is_format_macro(cx, macro_def_id);
|
||
|
if let Some(args) = format_args.args();
|
||
|
then {
|
||
|
for arg in args {
|
||
|
if arg.format_trait != impl_trait.name() {
|
||
|
continue;
|
||
|
}
|
||
|
check_format_arg_self(cx, expr, &arg, impl_trait);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
fn check_format_arg_self(cx: &LateContext<'_>, expr: &Expr<'_>, arg: &FormatArgsArg<'_>, impl_trait: FormatTrait) {
|
||
|
// Handle multiple dereferencing of references e.g. &&self
|
||
|
// Handle dereference of &self -> self that is equivalent (i.e. via *self in fmt() impl)
|
||
|
// Since the argument to fmt is itself a reference: &self
|
||
|
let reference = peel_ref_operators(cx, arg.value);
|
||
|
let map = cx.tcx.hir();
|
||
|
// Is the reference self?
|
||
|
let symbol_ident = impl_trait.name().to_ident_string();
|
||
|
if path_to_local(reference).map(|x| map.name(x)) == Some(kw::SelfLower) {
|
||
|
span_lint(
|
||
|
cx,
|
||
|
RECURSIVE_FORMAT_IMPL,
|
||
|
expr.span,
|
||
|
&format!(
|
||
|
"using `self` as `{}` in `impl {}` will cause infinite recursion",
|
||
|
&symbol_ident, &symbol_ident
|
||
|
),
|
||
|
);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
fn is_format_trait_impl(cx: &LateContext<'_>, item: &Item<'_>) -> Option<FormatTrait> {
|
||
|
if_chain! {
|
||
|
// Are we at an Impl?
|
||
|
if let ItemKind::Impl(Impl { of_trait: Some(trait_ref), .. }) = &item.kind;
|
||
|
if let Some(did) = trait_ref.trait_def_id();
|
||
|
if let Some(name) = cx.tcx.get_diagnostic_name(did);
|
||
|
then {
|
||
|
// Is Impl for Debug or Display?
|
||
|
match name {
|
||
|
sym::Debug => Some(FormatTrait::Debug),
|
||
|
sym::Display => Some(FormatTrait::Display),
|
||
|
_ => None,
|
||
|
}
|
||
|
} else {
|
||
|
None
|
||
|
}
|
||
|
}
|
||
|
}
|