|
| 1 | +use clippy_utils::diagnostics::span_lint; |
| 2 | +use clippy_utils::macros::{is_format_macro, root_macro_call_first_node, FormatArgsArg, FormatArgsExpn}; |
| 3 | +use clippy_utils::{is_diag_trait_item, path_to_local, peel_ref_operators}; |
| 4 | +use if_chain::if_chain; |
| 5 | +use rustc_hir::{Expr, ExprKind, Impl, Item, ItemKind, QPath}; |
| 6 | +use rustc_lint::{LateContext, LateLintPass}; |
| 7 | +use rustc_session::{declare_tool_lint, impl_lint_pass}; |
| 8 | +use rustc_span::{sym, symbol::kw, Symbol}; |
| 9 | + |
| 10 | +#[derive(Clone, Copy)] |
| 11 | +enum FormatTrait { |
| 12 | + Debug, |
| 13 | + Display, |
| 14 | +} |
| 15 | + |
| 16 | +impl FormatTrait { |
| 17 | + fn name(self) -> Symbol { |
| 18 | + match self { |
| 19 | + FormatTrait::Debug => sym::Debug, |
| 20 | + FormatTrait::Display => sym::Display, |
| 21 | + } |
| 22 | + } |
| 23 | +} |
| 24 | + |
| 25 | +declare_clippy_lint! { |
| 26 | + /// ### What it does |
| 27 | + /// Checks for format trait implementations (e.g. `Display`) with a recursive call to itself |
| 28 | + /// which uses `self` as a parameter. |
| 29 | + /// This is typically done indirectly with the `write!` macro or with `to_string()`. |
| 30 | + /// |
| 31 | + /// ### Why is this bad? |
| 32 | + /// This will lead to infinite recursion and a stack overflow. |
| 33 | + /// |
| 34 | + /// ### Example |
| 35 | + /// |
| 36 | + /// ```rust |
| 37 | + /// use std::fmt; |
| 38 | + /// |
| 39 | + /// struct Structure(i32); |
| 40 | + /// impl fmt::Display for Structure { |
| 41 | + /// fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
| 42 | + /// write!(f, "{}", self.to_string()) |
| 43 | + /// } |
| 44 | + /// } |
| 45 | + /// |
| 46 | + /// ``` |
| 47 | + /// Use instead: |
| 48 | + /// ```rust |
| 49 | + /// use std::fmt; |
| 50 | + /// |
| 51 | + /// struct Structure(i32); |
| 52 | + /// impl fmt::Display for Structure { |
| 53 | + /// fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
| 54 | + /// write!(f, "{}", self.0) |
| 55 | + /// } |
| 56 | + /// } |
| 57 | + /// ``` |
| 58 | + #[clippy::version = "1.48.0"] |
| 59 | + pub RECURSIVE_FORMAT_IMPL, |
| 60 | + correctness, |
| 61 | + "Format trait method called while implementing the same Format trait" |
| 62 | +} |
| 63 | + |
| 64 | +#[derive(Default)] |
| 65 | +pub struct RecursiveFormatImpl { |
| 66 | + // Whether we are inside Display or Debug trait impl - None for neither |
| 67 | + format_trait_impl: Option<FormatTrait>, |
| 68 | +} |
| 69 | + |
| 70 | +impl RecursiveFormatImpl { |
| 71 | + pub fn new() -> Self { |
| 72 | + Self { |
| 73 | + format_trait_impl: None, |
| 74 | + } |
| 75 | + } |
| 76 | +} |
| 77 | + |
| 78 | +impl_lint_pass!(RecursiveFormatImpl => [RECURSIVE_FORMAT_IMPL]); |
| 79 | + |
| 80 | +impl<'tcx> LateLintPass<'tcx> for RecursiveFormatImpl { |
| 81 | + fn check_item(&mut self, cx: &LateContext<'_>, item: &Item<'_>) { |
| 82 | + if let Some(format_trait_impl) = is_format_trait_impl(cx, item) { |
| 83 | + self.format_trait_impl = Some(format_trait_impl); |
| 84 | + } |
| 85 | + } |
| 86 | + |
| 87 | + fn check_item_post(&mut self, cx: &LateContext<'_>, item: &Item<'_>) { |
| 88 | + // Assume no nested Impl of Debug and Display within eachother |
| 89 | + if is_format_trait_impl(cx, item).is_some() { |
| 90 | + self.format_trait_impl = None; |
| 91 | + } |
| 92 | + } |
| 93 | + |
| 94 | + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { |
| 95 | + match self.format_trait_impl { |
| 96 | + Some(FormatTrait::Display) => { |
| 97 | + check_to_string_in_display(cx, expr); |
| 98 | + check_self_in_format_args(cx, expr, FormatTrait::Display); |
| 99 | + }, |
| 100 | + Some(FormatTrait::Debug) => { |
| 101 | + check_self_in_format_args(cx, expr, FormatTrait::Debug); |
| 102 | + }, |
| 103 | + None => {}, |
| 104 | + } |
| 105 | + } |
| 106 | +} |
| 107 | + |
| 108 | +fn check_to_string_in_display(cx: &LateContext<'_>, expr: &Expr<'_>) { |
| 109 | + if_chain! { |
| 110 | + // Get the hir_id of the object we are calling the method on |
| 111 | + if let ExprKind::MethodCall(path, [ref self_arg, ..], _) = expr.kind; |
| 112 | + // Is the method to_string() ? |
| 113 | + if path.ident.name == sym!(to_string); |
| 114 | + // Is the method a part of the ToString trait? (i.e. not to_string() implemented |
| 115 | + // separately) |
| 116 | + if let Some(expr_def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id); |
| 117 | + if is_diag_trait_item(cx, expr_def_id, sym::ToString); |
| 118 | + // Is the method is called on self |
| 119 | + if let ExprKind::Path(QPath::Resolved(_, path)) = self_arg.kind; |
| 120 | + if let [segment] = path.segments; |
| 121 | + if segment.ident.name == kw::SelfLower; |
| 122 | + then { |
| 123 | + span_lint( |
| 124 | + cx, |
| 125 | + RECURSIVE_FORMAT_IMPL, |
| 126 | + expr.span, |
| 127 | + "using `self.to_string` in `fmt::Display` implementation will cause infinite recursion", |
| 128 | + ); |
| 129 | + } |
| 130 | + } |
| 131 | +} |
| 132 | + |
| 133 | +fn check_self_in_format_args<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, impl_trait: FormatTrait) { |
| 134 | + // Check each arg in format calls - do we ever use Display on self (directly or via deref)? |
| 135 | + if_chain! { |
| 136 | + if let Some(outer_macro) = root_macro_call_first_node(cx, expr); |
| 137 | + if let macro_def_id = outer_macro.def_id; |
| 138 | + if let Some(format_args) = FormatArgsExpn::find_nested(cx, expr, outer_macro.expn); |
| 139 | + if is_format_macro(cx, macro_def_id); |
| 140 | + if let Some(args) = format_args.args(); |
| 141 | + then { |
| 142 | + for arg in args { |
| 143 | + if arg.format_trait != impl_trait.name() { |
| 144 | + continue; |
| 145 | + } |
| 146 | + check_format_arg_self(cx, expr, &arg, impl_trait); |
| 147 | + } |
| 148 | + } |
| 149 | + } |
| 150 | +} |
| 151 | + |
| 152 | +fn check_format_arg_self(cx: &LateContext<'_>, expr: &Expr<'_>, arg: &FormatArgsArg<'_>, impl_trait: FormatTrait) { |
| 153 | + // Handle multiple dereferencing of references e.g. &&self |
| 154 | + // Handle dereference of &self -> self that is equivalent (i.e. via *self in fmt() impl) |
| 155 | + // Since the argument to fmt is itself a reference: &self |
| 156 | + let reference = peel_ref_operators(cx, arg.value); |
| 157 | + let map = cx.tcx.hir(); |
| 158 | + // Is the reference self? |
| 159 | + let symbol_ident = impl_trait.name().to_ident_string(); |
| 160 | + if path_to_local(reference).map(|x| map.name(x)) == Some(kw::SelfLower) { |
| 161 | + span_lint( |
| 162 | + cx, |
| 163 | + RECURSIVE_FORMAT_IMPL, |
| 164 | + expr.span, |
| 165 | + &format!( |
| 166 | + "using `self` as `{}` in `impl {}` will cause infinite recursion", |
| 167 | + &symbol_ident, &symbol_ident |
| 168 | + ), |
| 169 | + ); |
| 170 | + } |
| 171 | +} |
| 172 | + |
| 173 | +fn is_format_trait_impl(cx: &LateContext<'_>, item: &Item<'_>) -> Option<FormatTrait> { |
| 174 | + if_chain! { |
| 175 | + // Are we at an Impl? |
| 176 | + if let ItemKind::Impl(Impl { of_trait: Some(trait_ref), .. }) = &item.kind; |
| 177 | + if let Some(did) = trait_ref.trait_def_id(); |
| 178 | + if let Some(name) = cx.tcx.get_diagnostic_name(did); |
| 179 | + then { |
| 180 | + // Is Impl for Debug or Display? |
| 181 | + match name { |
| 182 | + sym::Debug => Some(FormatTrait::Debug), |
| 183 | + sym::Display => Some(FormatTrait::Display), |
| 184 | + _ => None, |
| 185 | + } |
| 186 | + } else { |
| 187 | + None |
| 188 | + } |
| 189 | + } |
| 190 | +} |
0 commit comments