From e6d8c3cfb843b301105122e54c109b3d99d38984 Mon Sep 17 00:00:00 2001 From: Alessio Rocchi Date: Tue, 14 Apr 2026 15:57:30 +0200 Subject: [PATCH] Cap iteration count in ScalarReplacementOfAggregates The SROA pass repeatedly flattens aggregate locals into their field locals, looping until a fixed point is reached. When the MIR body involves a type whose layout is cyclic (e.g. `struct T(T)` reached via an associated type projection), the layout cycle error is emitted but compilation continues into MIR optimization. Inside SROA, `iter_fields` then keeps returning a single field whose normalized type is the enclosing struct itself, so each iteration allocates a fresh replacement local and the fixed-point loop never terminates. Bound the loop to a small constant. In practice two iterations capture essentially all of SROA's value and non-contrived code does not exceed ten, so 10 is sufficient and guarantees termination on tainted input. --- compiler/rustc_mir_transform/src/sroa.rs | 10 ++++++- tests/ui/layout/hang-sroa-layout-cycle.rs | 30 +++++++++++++++++++ tests/ui/layout/hang-sroa-layout-cycle.stderr | 14 +++++++++ 3 files changed, 53 insertions(+), 1 deletion(-) create mode 100644 tests/ui/layout/hang-sroa-layout-cycle.rs create mode 100644 tests/ui/layout/hang-sroa-layout-cycle.stderr diff --git a/compiler/rustc_mir_transform/src/sroa.rs b/compiler/rustc_mir_transform/src/sroa.rs index a6ed66c8427b3..58c294abd1496 100644 --- a/compiler/rustc_mir_transform/src/sroa.rs +++ b/compiler/rustc_mir_transform/src/sroa.rs @@ -28,9 +28,17 @@ impl<'tcx> crate::MirPass<'tcx> for ScalarReplacementOfAggregates { return; } + // Cap the number of iterations to avoid non-termination on tainted + // input (e.g. when layout cycle errors cause `iter_fields` to keep + // producing self-referential field types, cf. + // https://github.com/rust-lang/rust/issues/153205). In practice, 2 + // iterations capture nearly all the value and more than 10 is never + // needed for non-contrived code, so 10 is enough. + const MAX_ITERATIONS: usize = 10; + let mut excluded = excluded_locals(body); let typing_env = body.typing_env(tcx); - loop { + for _ in 0..MAX_ITERATIONS { debug!(?excluded); let escaping = escaping_locals(tcx, &excluded, body); debug!(?escaping); diff --git a/tests/ui/layout/hang-sroa-layout-cycle.rs b/tests/ui/layout/hang-sroa-layout-cycle.rs new file mode 100644 index 0000000000000..7099a952a64ed --- /dev/null +++ b/tests/ui/layout/hang-sroa-layout-cycle.rs @@ -0,0 +1,30 @@ +//@ build-fail +//~^ ERROR cycle detected when computing layout of `Thing` +//@ compile-flags: -Copt-level=3 + +// Regression test for https://github.com/rust-lang/rust/issues/153205: +// a struct that contains itself via an associated type used to cause the +// `ScalarReplacementOfAggregates` MIR pass to loop forever after the +// layout-cycle error was emitted. The SROA pass now bounds its iteration +// count, so the compile terminates with the expected layout-cycle error +// rather than hanging. + +trait Apply { + type Output; +} + +struct Identity; + +impl Apply for Identity { + type Output = T; +} + +struct Thing(A::Output); + +fn foo() { + let _x: Thing; +} + +fn main() { + foo::(); +} diff --git a/tests/ui/layout/hang-sroa-layout-cycle.stderr b/tests/ui/layout/hang-sroa-layout-cycle.stderr new file mode 100644 index 0000000000000..0cf5f4b4fd181 --- /dev/null +++ b/tests/ui/layout/hang-sroa-layout-cycle.stderr @@ -0,0 +1,14 @@ +error[E0391]: cycle detected when computing layout of `Thing` + | + = note: ...which requires computing layout of `::Output>`... + = note: ...which again requires computing layout of `Thing`, completing the cycle +note: cycle used when optimizing MIR for `main` + --> $DIR/hang-sroa-layout-cycle.rs:28:1 + | +LL | fn main() { + | ^^^^^^^^^ + = note: see https://rustc-dev-guide.rust-lang.org/overview.html#queries and https://rustc-dev-guide.rust-lang.org/query.html for more information + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0391`.