Bug Report
Cairo version:
scarb 2.18.0 (e6144df0f 2026-04-21)
cairo: 2.18.0 (https://crates.io/crates/cairo-lang-compiler/2.18.0)
sierra: 1.8.0
arch: aarch64-apple-darwin
Current behavior:
A 4-line program with a manual Destruct<T> impl whose body does not consume self causes cairo-execute (and any other frontend that runs lowering) to hang in add_destructs (i.e., the destructor-insertion pass) with unbounded virtual-memory growth, crashing my machine with enormous amount of memory usage.
Expected behavior:
The compiler should either accept and run, returning 1, or reject at type-check with a diagnostic. (Instead I got out of memory and DOS'ed my machine)
Steps to reproduce:
running cairo-execute --single-file repro.cairo --build-only --output-path /tmp/out.json
produces these: observations
- VSZ grows monotonically to >400 GB
- RSS oscillates between 2-5 MB
- CPU usage eventually drops near zero (process becomes mostly stuck)
- No diagnostic, no panic, no progress, eventually crashed my machine
Related code:
repro.cairo:
struct D {}
impl DDestruct of Destruct<D> { fn destruct(self: D) nopanic { } }
#[executable]
fn main() -> felt252 { let _ = D {}; 1 }
Other information:
The loop originates in crates/cairo-lang-lowering/src/destructs.rs, in the add_destructs pass.
When add_destructs runs on the body of DDestruct::destruct:
drop_aux is invoked when self: D reaches end-of-scope unconsumed.
var.info.destruct_impl resolves to DDestruct — the only Destruct<D> impl available.
- A
DestructionEntry::Plain { impl_id: DDestruct, .. } is pushed; it is later spliced into the function as a DDestruct::destruct(self) call before the return.
- The synthesized call's own
self parameter is an unconsumed D. A downstream consumer treats it as needing another destructor, and the chain does not converge.
I confirmed it in my machine (I have implemented and verified a defensive recursion-guard at the drop_aux Plain branch in destructs.rs that detects when the entry's impl_id equals the impl currently being lowered. On the same repro that previously hung, the patched compiler exits in ~2 s with a localized Rust panic and a workaround hint) and will push a PR to fix that shortly.
Bug Report
Cairo version:
scarb 2.18.0 (e6144df0f 2026-04-21)
cairo: 2.18.0 (https://crates.io/crates/cairo-lang-compiler/2.18.0)
sierra: 1.8.0
arch: aarch64-apple-darwin
Current behavior:
A 4-line program with a manual
Destruct<T>impl whose body does not consumeselfcausescairo-execute(and any other frontend that runs lowering) to hang inadd_destructs(i.e., the destructor-insertion pass) with unbounded virtual-memory growth, crashing my machine with enormous amount of memory usage.Expected behavior:
The compiler should either accept and run, returning 1, or reject at type-check with a diagnostic. (Instead I got out of memory and DOS'ed my machine)
Steps to reproduce:
running
cairo-execute --single-file repro.cairo --build-only --output-path /tmp/out.jsonproduces these: observations
Related code:
repro.cairo:
Other information:
The loop originates in
crates/cairo-lang-lowering/src/destructs.rs, in theadd_destructspass.When
add_destructsruns on the body ofDDestruct::destruct:drop_auxis invoked whenself: Dreaches end-of-scope unconsumed.var.info.destruct_implresolves toDDestruct— the onlyDestruct<D>impl available.DestructionEntry::Plain { impl_id: DDestruct, .. }is pushed; it is later spliced into the function as aDDestruct::destruct(self)call before the return.selfparameter is an unconsumedD. A downstream consumer treats it as needing another destructor, and the chain does not converge.I confirmed it in my machine (I have implemented and verified a defensive recursion-guard at the
drop_auxPlain branch in destructs.rs that detects when the entry's impl_id equals the impl currently being lowered. On the same repro that previously hung, the patched compiler exits in ~2 s with a localized Rust panic and a workaround hint) and will push a PR to fix that shortly.