Sitelet https://github.com/starkware-libs/cairo/issues/9894
Skip to content

bug: (unbounded memory allocation) Compiler hangs and allocates unbounded memory when a manual Destruct<T> impl body does not consume self #9894

Description

@anon-researchers-123

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:

  1. drop_aux is invoked when self: D reaches end-of-scope unconsumed.
  2. var.info.destruct_impl resolves to DDestruct — the only Destruct<D> impl available.
  3. A DestructionEntry::Plain { impl_id: DDestruct, .. } is pushed; it is later spliced into the function as a DDestruct::destruct(self) call before the return.
  4. 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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions