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, } 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 { 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 } } }