Summary
rustc_mir_dataflow::value_analysis::Map::for_each_aliasing_place invalidates enum Variant/Discriminant siblings on a write, but does not invalidate Field siblings when the parent is a union. Because union fields alias the same storage, a write to U.b must flood the tracked state of U.a. It doesn't, so JumpThreading (default in -C opt-level=1+) and DataflowConstProp (-Zmir-opt-level=3) consume stale values and miscompile.
Security / soundness implication: This is a violation of Rust's core memory-safety contract. Miri confirms the source program is well-defined, yet the optimized binary executes invalid memory accesses (I was able to POC: controlled heap OOB reads, cross-allocation corruption, and function-pointer hijacking in synthetic tests). The developer need not write unsafe in the application crate a Miri-clean library encapsulating a union tag is sufficient.
Regression:
- Clean: 1.80.0, 1.80.1
- Affected: 1.81.0 → 1.98.0 stable, 1.100.0-nightly
Minimal reproducer:
union U { a: u8, b: u8 }
#[inline(never)]
fn f(x: u8) -> u8 {
let mut u = U { a: x };
let r = unsafe { match u.a { 0 => 100, _ => 200 } };
u.b = 5; // overwrites the same byte as u.a
// BUG: JumpThreading threads this to the `0` arm using the stale pre-write value
let r2 = unsafe { match u.a { 0 => 1, _ => 2 } };
r + r2
}
fn main() {
assert_eq!(f(0), 102); // fails in release: gets 101
assert_eq!(f(7), 202); // ok
}
Results:
- Build -C opt-level=0 -> f(0) 102
- Build -C opt-level=2 -> f(0) 101
- Build -C opt-level=2 -Zmir-enable-passes=-JumpThreading -> f(0) 102
- Build cargo miri run -> f(0) 102
Root cause:
for_each_aliasing_place (compiler/rustc_mir_dataflow/src/value_analysis.rs, stable 1.98.0 ) has for_each_variant_sibling for enums but no equivalent for union Field siblings:
if let TrackElem::Variant(..) | TrackElem::Discriminant = elem {
self.for_each_variant_sibling(index, sub, f);
}
// Missing: if TrackElem::Field(..) && parent_is_union { flood siblings }
Additional note:
A transferred shape also miscompiles: facts cross siblings via copy assignments (match u.a → u.b = 5 → u.c = u.a → match u.c), because process_copy in the backward pass renames the active condition from c onto a, and the write to b does not flood a.
Miri confirms the source program is well-defined; the defect is purely in the optimizer.
Summary
rustc_mir_dataflow::value_analysis::Map::for_each_aliasing_place invalidates enum Variant/Discriminant siblings on a write, but does not invalidate Field siblings when the parent is a union. Because union fields alias the same storage, a write to U.b must flood the tracked state of U.a. It doesn't, so JumpThreading (default in -C opt-level=1+) and DataflowConstProp (-Zmir-opt-level=3) consume stale values and miscompile.
Security / soundness implication: This is a violation of Rust's core memory-safety contract. Miri confirms the source program is well-defined, yet the optimized binary executes invalid memory accesses (I was able to POC: controlled heap OOB reads, cross-allocation corruption, and function-pointer hijacking in synthetic tests). The developer need not write unsafe in the application crate a Miri-clean library encapsulating a union tag is sufficient.
Regression:
Minimal reproducer:
Results:
Root cause:
for_each_aliasing_place (compiler/rustc_mir_dataflow/src/value_analysis.rs, stable 1.98.0 ) has for_each_variant_sibling for enums but no equivalent for union Field siblings:
Additional note:
A transferred shape also miscompiles: facts cross siblings via copy assignments (match u.a → u.b = 5 → u.c = u.a → match u.c), because process_copy in the backward pass renames the active condition from c onto a, and the write to b does not flood a.
Miri confirms the source program is well-defined; the defect is purely in the optimizer.