Skip to content

Incompleteness: a &mut stored in a Vec (Seq/array-backed container) never has its prophecy resolved at drop, so safe programs are wrongly rejected as Unsat #202

Description

@coord-e

Summary

When a &mut T is stored in a Vec<&mut T> (more generally, in any value whose model contains a Seq/Array), the reference's prophecy is never resolved — not even when the Vec is dropped. Env::dropping_formula_for_term walks &mut, Box (is_own), tuples, and enums to emit the final == current fact that closes out each live &mut's prophecy, but it has no arm for Type::Array (the sort that backs the Seq/Vec model), so the array's elements fall through to the empty else branch. The referent's prophecy variable is left unconstrained (havoc), and every subsequent assertion about the referent becomes unprovable ⇒ the program is rejected as Unsat even though it never panics.

This is an incompleteness bug (safe programs wrongly rejected) — not an unsoundness and not a panic.

Minimal reproducer

#[thrust::callable]fnf(a:&muti64){let old = *a;{letmut v:Vec<&muti64> = Vec::new();
v.push(&mut*a);}// `v` dropped here; `a` was never written throughassert!(*a == old);}fnmain(){}
$ cargo run -- -Adead_code -C debug-assertions=false min.rs &&echo safeerror: verification error: Unsat

a is never written (the &mut *a is only pushed into v, which is then dropped), so *a == old holds for every input and the program never panics. Thrust reports Unsat.

A write-through variant is rejected the same way and is likewise safe at runtime:

#[thrust::callable]fnf(a:&muti64){{letmut v:Vec<&muti64> = Vec::new();
v.push(&mut*a);*v[0] = 5;}assert!(*a == 5);// holds at runtime; Thrust: Unsat}fnmain(){}

(Both programs were compiled with real rustc and executed over a range of inputs including i64::MIN/i64::MAX; neither ever panics.)

The gap is specific to the Seq/array-backed container

Every other container resolves the prophecy correctly on the identical shape — only the Vec case is rejected:

&mut i64 held in … then dropped without further writesexpectedThrust
Vec<&mut i64>SAFEUnsat
Box<&mut i64>SAFESAFE ✓
(&mut i64,) (tuple)SAFESAFE ✓
enum-variant payloadSAFESAFE ✓

And Vec<&mut _> is otherwise supported: pushing and reading/writing through the element before the Vec is dropped verifies correctly —

#[thrust::callable]fnf(a:&muti64){letmut v:Vec<&muti64> = Vec::new();
v.push(&mut*a);*v[0] = 5;assert!(*v[0] == 5);// SAFE}fnmain(){}

— so it is only the drop-time prophecy resolution of the contained &mut that is missing. Wrapping the Vec in a tuple (let _w = (v, 0);) is rejected the same way, confirming the miss is inside the array walk, not at the top level.

Root cause

Env::dropping_formula_for_term (src/refine/env.rs:1112) emits the prophecy-closing fact for each &mut reachable in the dropped value's type:

if ty.is_mut(){
term.clone().mut_final().equal_to(term.mut_current()).into()// resolves the prophecy}elseif ty.is_own(){// recurse into Box payload}elseifletSome(tty) = ty.as_tuple(){// recurse into each tuple element}elseifletSome(ety) = ty.as_enum(){// recurse into each variant field (via a matcher predicate)}else{
chc::Body::default()// <-- Type::Array (hence Seq/Vec) lands here: nothing emitted}

There is no arm for Type::Array. The Vec/Seq model is Seq { array: Array<Int, T>, length: Int }, so dropping a Vec<&mut i64> recurses through the Seq struct into its array: Array<Int, &mut i64> field, which matches none of the arms and yields an empty body. The &mut elements' final == current facts are never emitted, the referent's prophecy stays free, and the post-drop assertion is unprovable.

Suggested direction

An Array arm would need to resolve the prophecy for every live element in [0, length), which — unlike the fixed-arity tuple/enum cases — requires a universally-quantified drop fact over the array index (analogous to how the enum arm introduces a matcher_pred, but ranging over 0 <= i < length). Until then, any &mut that flows through a Vec/slice/array and is dropped cannot be reasoned about after its container's scope ends.

Relation to existing issues

Distinct from the other drop/prophecy issues:

Notes

  • No numerical-range / overflow concern.
  • Not a panic — a clean Unsat verdict.

Environment

  • branch main @ 6953863
  • solver: Z3 (HORN / Spacer)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions