From 8be6f229d3b6e7e0dbe4b62df66bc610d18f092b Mon Sep 17 00:00:00 2001 From: jaideeppyne Date: Wed, 16 Sep 2026 14:38:53 +0530 Subject: [PATCH] Detect infinite recursion for #[error("{}", self)] The existing check only flagged {self} in the format string. The equivalent form #[error("{}", self)] still compiled and overflowed the stack at runtime. Treat a Display of extra argument self the same way. --- impl/src/fmt.rs | 19 +++++++++++++++++++ tests/ui/unconditional-recursion-2.rs | 9 +++++++++ tests/ui/unconditional-recursion-2.stderr | 21 +++++++++++++++++++++ 3 files changed, 49 insertions(+) create mode 100644 tests/ui/unconditional-recursion-2.rs create mode 100644 tests/ui/unconditional-recursion-2.stderr diff --git a/impl/src/fmt.rs b/impl/src/fmt.rs index 2988ca37..0c6e23dc 100644 --- a/impl/src/fmt.rs +++ b/impl/src/fmt.rs @@ -53,6 +53,14 @@ impl Display<'_> { None => return Ok(()), }; let member = match next { + '}' => { + // `{}` displays extra argument 0. `#[error("{}", self)]` + // recurses through Display the same way `{self}` does. + if first_unnamed.as_ref().is_some_and(extra_arg_is_self) { + infinite_recursive = true; + } + continue; + } '0'..='9' => { let int = take_int(&mut read); if !extra_positional_arguments_allowed { @@ -108,6 +116,9 @@ impl Display<'_> { } }; infinite_recursive |= member == *"self" && bound == Trait::Display; + infinite_recursive |= bound == Trait::Display + && matches!(&member, MemberUnraw::Unnamed(index) if index.index == 0) + && first_unnamed.as_ref().is_some_and(extra_arg_is_self); let field = match member_index.get(&member) { Some(&field) => field, None => { @@ -270,6 +281,14 @@ fn is_syn_full() -> bool { } } +fn extra_arg_is_self(tokens: &TokenStream) -> bool { + let mut iter = tokens.clone().into_iter(); + match (iter.next(), iter.next()) { + (Some(TokenTree::Ident(ident)), None) => ident == "self", + _ => false, + } +} + fn take_int<'a>(read: &mut &'a str) -> &'a str { let mut int_len = 0; for ch in read.chars() { diff --git a/tests/ui/unconditional-recursion-2.rs b/tests/ui/unconditional-recursion-2.rs new file mode 100644 index 00000000..b521e909 --- /dev/null +++ b/tests/ui/unconditional-recursion-2.rs @@ -0,0 +1,9 @@ +use thiserror::Error; + +#[derive(Error, Debug)] +#[error("{}", self)] +pub struct Error; + +fn main() { + __FAIL__; +} diff --git a/tests/ui/unconditional-recursion-2.stderr b/tests/ui/unconditional-recursion-2.stderr new file mode 100644 index 00000000..5f37be78 --- /dev/null +++ b/tests/ui/unconditional-recursion-2.stderr @@ -0,0 +1,21 @@ +error[E0425]: cannot find value `__FAIL__` in this scope + --> tests/ui/unconditional-recursion-2.rs:8:5 + | +8 | __FAIL__; + | ^^^^^^^^ not found in this scope + +warning: function cannot return without recursing + --> tests/ui/unconditional-recursion-2.rs:4:9 + | +4 | #[error("{}", self)] + | ^^^^ + | | + | cannot return without recursing + | recursive call site + | + = help: a `loop` may express intention better if this is on purpose +note: the lint level is defined here + --> tests/ui/unconditional-recursion-2.rs:4:9 + | +4 | #[error("{}", self)] + | ^^^^