Skip to content

release-mode heap corruption with non-generic FnOnce(Vec<usize>) #155241

@AndreiKrutikov

Description

@AndreiKrutikov

View all comments

I tried this code:

#![cfg_attr(not(test), no_std)]

extern crate alloc;

use alloc::vec::Vec;

fn with(f: impl FnOnce(Vec<usize>)) {
    f(Vec::new())
}

#[test]
fn repro_len_ne_unreachable() {
    with(|mut v| v.resize(2, 1));
    with(|v| {
        if v.len() != 0 {
            unreachable!();
        }
    });
}

I expected to see this happen: the test should pass, because each with call receives a fresh empty Vec<usize>, so the second closure should observe v.len() == 0 and never enter the unreachable!() branch.

Instead, this happened: in --profile release, the test process aborts due to allocator corruption.

Observed results:

Windows x86_64-pc-windows-msvc:
    process aborts with 0xc0000374 (STATUS_HEAP_CORRUPTION)

Linux x86_64-unknown-linux-gnu (Docker rust:latest image):
    free(): double free detected in tcache 2
    process aborts with SIGABRT

Two nearby controls do not reproduce:

fn with_value<T>(f: impl FnOnce(Vec<T>)) {
    f(Vec::new())
}

#[test]
fn control_generic_helper() {
    with_value(|mut v: Vec<usize>| v.resize(2, 1));
    with_value(|v: Vec<usize>| {
        if v.len() != 0 {
            unreachable!();
        }
    });
}

#[test]
fn repro_len_ne_unreachable_unchecked() {
    with(|mut v| v.resize(2, 1));
    with(|v| {
        if v.len() != 0 {
            unsafe {
                core::hint::unreachable_unchecked();
            }
        }
    });
}

Commands used:

cargo +nightly miri test repro_len_ne_unreachable -- --exact --nocapture
$env:CARGO_TARGET_DIR='target/report-repro'; cargo test --profile release repro_len_ne_unreachable -- --exact --nocapture
$env:CARGO_TARGET_DIR='target/report-control-generic'; cargo test --profile release control_generic_helper -- --exact --nocapture
$env:CARGO_TARGET_DIR='target/report-control-unchecked'; cargo test --profile release repro_len_ne_unreachable_unchecked -- --exact --nocapture
docker run --rm -v ${PWD}:/work -w /work rust:latest cargo test --profile release repro_len_ne_unreachable -- --exact --nocapture

Miri did not report UB on an earlier form of this reproducer.

Version history:

stable-1.74.1: ok
stable-1.75.0: ok
stable-1.76.0: ok
stable-1.77.0: first wrong result, panics at `unreachable!()`
stable-1.85.0: still panics at `unreachable!()`
stable-1.90.0: still panics at `unreachable!()`
stable-1.91.0: first stable with STATUS_HEAP_CORRUPTION
stable-1.92.0: STATUS_HEAP_CORRUPTION
stable-1.93.0: STATUS_HEAP_CORRUPTION
stable-1.94.0: STATUS_HEAP_CORRUPTION
nightly-2026-04-12: STATUS_HEAP_CORRUPTION

Nightly boundaries I checked:

last good nightly: nightly-2024-01-16, rustc 1.77.0-nightly (714b29a17 2024-01-15)
first bad nightly: nightly-2024-01-17, rustc 1.77.0-nightly (098d4fd74 2024-01-16), wrong panic at `unreachable!()`
earliest tested nightly with heap corruption: nightly-2024-01-18, rustc 1.77.0-nightly (6ae4cfbbb 2024-01-17)

last tested nightly with wrong panic before the later stable corruption window: nightly-2025-08-12, rustc 1.91.0-nightly (1ebbd87a6 2025-08-11)
first tested nightly with heap corruption in that window: nightly-2025-08-14, rustc 1.91.0-nightly (3672a55b7 2025-08-13)

So on stable, the bug first appears as an incorrect panic in 1.77.0 and becomes heap corruption in 1.91.0. On nightly, the first wrong result is between 2024-01-16 and 2024-01-17.

Meta

rustc --version --verbose:

rustc 1.94.0 (4a4ef493e 2026-03-02)
binary: rustc
commit-hash: 4a4ef493e3a1488c6e321570238084b38948f6db
commit-date: 2026-03-02
host: x86_64-pc-windows-msvc
release: 1.94.0
LLVM version: 21.1.8

Nightly also reproduces:

rustc 1.97.0-nightly (14196dbfa 2026-04-12)
binary: rustc
commit-hash: 14196dbfa3eb7c30195251eac092b1b86c8a2d84
commit-date: 2026-04-12
host: x86_64-pc-windows-msvc
release: 1.97.0-nightly
LLVM version: 22.1.2

Linux container reproduction:

running 1 test
free(): double free detected in tcache 2
error: test failed, to rerun pass `--lib`

Caused by:
    process didn't exit successfully: `/work/target/release/deps/repro_test-9864353ff121d9c3 repro_len_ne_unreachable --exact --nocapture` (signal: 6, SIGABRT: process abort signal)

Additional stable versions checked:

rustc 1.74.1 (a28077b28 2023-12-04)
rustc 1.75.0 (82e1608df 2023-12-21)
rustc 1.76.0 (07dca489a 2024-02-04)
rustc 1.77.0 (aedd173a2 2024-03-17)
rustc 1.85.0 (4d91de4e4 2025-02-17)
rustc 1.90.0 (1159e78c4 2025-09-14)
rustc 1.91.0 (f8297e351 2025-10-28)
rustc 1.92.0 (ded5c06cf 2025-12-08)
rustc 1.93.0 (254b59607 2026-01-19)
Backtrace

I reran the reproducer with RUST_BACKTRACE=1, but there was still no Rust backtrace.
The process aborts immediately with:

running 1 test
error: test failed, to rerun pass `--lib`

Caused by:
  process didn't exit successfully: `C:\Users\akrut\repro_test\target/report-backtrace\release\deps\repro_test-1f8b8a2035f2538a.exe repro_len_ne_unreachable --exact --nocapture` (exit code: 0xc0000374, STATUS_HEAP_CORRUPTION)

Pinned by theemathas

Metadata

Metadata

Assignees

Labels

A-closuresArea: Closures (`|…| { … }`)A-codegenArea: Code generationC-bugCategory: This is a bug.I-miscompileIssue: Correct Rust code lowers to incorrect machine codeP-highHigh priorityT-compilerRelevant to the compiler team, which will review and decide on the PR/issue.regression-from-stable-to-stablePerformance or correctness regression from one stable version to another.

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions