Repository navigation
program miscompiles to division by zero with checked_div().unwrap_unchecked() #164431
Description
I tried this code:
static mut a: u64 = 0u64;
static mut b: bool = false;
static mut c: *mut u8 = 0 as *mut u8;
static mut d: bool = false;
static mut e: *mut u8 = (&raw mut a).cast::<u8>();
static mut f: bool = false;
unsafe fn h0() {
let mut h = false;
let mut i = false;
let mut j = 8997201173137027981;
let mut k = 0u32;
let mut l = 0u64;
let mut st = 0;
loop {
match st {
0 => {
st = 1;
}
1 => {
l = j ^ l;
st = 3;
}
3 => {
h = f;
i = (0 >= (l as i64));
k = k.wrapping_add(4050710252);
if h { st = 6; } else { st = 5; }
}
5 => {
f = true;
if k == 4050710252 { st = 1; } else { st = 8; }
}
6 => {
unsafe { e = (&raw mut e).cast(); }
unsafe { g(0); }
st = 9;
}
9 => {
if i { st = 13; } else { st = 14; }
}
10 => {
i = ((j as i64) < -2451427860696281925i64);
j = a;
if h { st = 9; } else { st = 12; }
}
13 => {
if i { st = 22; } else { st = 23; }
}
14 => {
if h { st = 27; } else { st = 28; }
}
22 => {
st = 10;
}
27 => {
j = (unsafe { (l as i64).checked_div((j as i64)).unwrap_unchecked() } as u64);
st = 48;
}
_ => break,
}
}
}
unsafe fn g(mut m: u64) {
let mut n = false;
let mut o = 0u8;
let mut t = 93;
loop {
match t {
4 => {
if n { t = 18; } else { t = 17; }
}
17 => {
unsafe { std::ptr::write(e.cast::<*mut u8>(), 0 as *mut u8); }
m = e as usize as u64;
c = (&raw mut o).cast();
t = 93;
}
93 => {
d = d | b;
let (_, u) = m.overflowing_mul(m);
b = u;
n = d & b;
b = c.is_null();
if n | b { t = 4; } else { t = 6; a = 1; }
}
_ => break,
}
}
}
fn main() {
unsafe { h0(); }
}```
Save the code as `case.rs`, then run:
```text
rustc +nightly --edition=2024 -C opt-level=0 -Awarnings case.rs -o case_o0 && ./case_o0
rustc +nightly --edition=2024 -C opt-level=2 -Awarnings case.rs -o case_o2 && ./case_o2I expected both commands to exit successfully.
Instead, the optimized executable terminates with SIGFPE (exit code 136). On x86-64, gdb stops at a generated div instruction with a zero divisor.
The current reduction is 92 numbered lines / 2141 bytes. The file contains 91 newline characters because it does not end with an extra newline. The md5 is e08323f83dd8b2ee86a758bba4278b6d.
Observed with the latest nightly tested:
rustc 1.101.0-nightly (7df81381d 2026-10-14)
binary: rustc
commit-hash: 7df81381d027710708209f36b98d11b1c88f8d2e
commit-date: 2026-10-14
host: x86_64-unknown-linux-gnu
release: 1.101.0-nightly
LLVM version: 23.1.3
Results, six runs per configuration:
-O0 -O1 -O2 -O3 -Os -Oz
case 0/6 6/6 6/6 6/6 6/6 6/6
Checks
- Miri with Stacked Borrows: clean.
- Miri with Tree Borrows: clean.
- Adding
#[inline(never)]togmakes the optimized crash disappear. - Replacing the
checked_div(...).unwrap_unchecked()expression withwrapping_divmakes the optimized crash disappear. - A non-PIE build did not reproduce in 10 runs; the default PIE build reproduces.
The failure appears to involve cross-function inlining, the lowering of checked_div(...).unwrap_unchecked(), llvm.assume/undef on the None path, address-dependent pointer state, and later LLVM optimization. I have not isolated the exact LLVM pass transformation that produces the zero divisor.
Meta
rustc +nightly --version --verbose:
rustc 1.101.0-nightly (7df81381d 2026-10-14)
binary: rustc
commit-hash: 7df81381d027710708209f36b98d11b1c88f8d2e
commit-date: 2026-10-14
host: x86_64-unknown-linux-gnu
release: 1.101.0-nightly
LLVM version: 23.1.3
Activity
maxdexh commentedon Oct 16, 2026
Reproduces with mir-opt-level=0
maxdexh commentedon Oct 16, 2026
Godbolt link: https://godbolt.org/z/ab6j6ea8a
This also reproduces on 1.90, before LLVM 23
apiraino commentedon Oct 16, 2026
Even on 1.87.0. IIUC: https://godbolt.org/z/9x1Tejbv4
Assigning priority (discussion on Zulip).
@rustbot label -I-prioritize +P-medium
jieyouxu commentedon Oct 16, 2026
Would be good if someone could try to see if they can minimize this further. While the example is self-contained it's still quite large.
RobertObkircher commentedon Oct 16, 2026
I tried to simplify it a bit but I'm out of time for today.
https://play.rust-lang.org/?version=stable&mode=release&edition=2024&gist=e5e024da76356cd272c177de70902170
ds84182 commentedon Oct 16, 2026
force inlining
gfixes the miscompilation so LLVM thinks some global variables aren't modifiedGBra-4-669 commentedon Oct 17, 2026
I think this is already fixed upstream.
The report's nightly (
7df81381d, LLVM 23.1.3) just misses the LLVM bump in #164254. Onnightly-2026-10-16(LLVM 23.1.4) bothcase.rsand the reducer exit 0 at-O1/-O2/-O3; onnightly-2026-10-15case.rsSIGFPEs 3/3 and the reducer exits 101. So this is probably closable asllvm-fixed-upstream, like #164137.The
checked_divis only where the wrong value becomes fatal. The cause looks like the GlobalsAA bug from #164137 (a global whose own address is stored into itself isn't treated as address-taken): llvm/llvm-project#213232, fixed by llvm/llvm-project#213631.Reducer, Miri-clean (Stacked and Tree Borrows), wrong at
-O1+ on 1.87.0 and 1.99.0 (godbolt). This should be usable as a regression test: