Implement auto strong returned references - #1728
Open
kris-gaudel wants to merge 7 commits into
Open
Conversation
A `&mut T` returned by a call is invariant, so reads through it are imprecise and every write is a weak update: two reads of `*r` aren't known to be equal, and `*r = 20; assert(*r == 20)` fails. Unfold such a return into a pointer to a fresh local location holding `T`, the same way `&strg` arguments are handled by `unfold_local_ptrs`. Reads are then precise and writes are strong updates, checked against `T` when `fold_local_ptrs` folds the pointer back at the end of the block. Folding a location that other bindings still point to now has to revert those pointers, so `fold_local_ptrs` first calls `ptrs_to_refs` to turn each live `ptr(l)` back into the `&mut` it stands for. Refs flux-rs#1714 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`ptrs_to_refs` handles a pointer into a *field* of the location being folded by giving it the field's current type rather than the bound, an arm no test covered. Exercise it with a `&mut` taken into a field of a returned `&mut`, and check that a write through it is still caught when the pointer is folded back. Refs flux-rs#1714 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The fold at the top of `check_terminator` bounds a local pointer to the block that uses it, but it cannot cover the one edge the pointer legitimately crosses: the edge out of the call that created it. If that successor is a join point, `ptr(l)` reaches `join_ty` and hits a `tracked_span_bug`; if it is an inlined exit block, the pointer reaches `check_ret` and the bound recorded on the location is never checked. Fold in `check_goto` for both cases, so the invariant is that a local pointer never outlives the block it is used in. Refs flux-rs#1714 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`fold` had no arm for `ptr(mut, l)`, so a pointer nested in a struct reached `check_constructor`, which relates the fields with a plain `subtyping` backed by `DummyEnv` and hits a `tracked_span_bug`. This is reachable before `fold_local_ptrs` bounds the pointer at the terminator, because folding the struct can happen in a statement: an unsize coercion of a borrow of it does exactly that. Fold the pointer as part of folding the value holding it, so a folded type never mentions the location and `DummyEnv` stays a true statement. `fold` has to return an `InferResult` for the subtyping this fires, and shares `fold_local_ptr` with `fold_local_ptrs`. Refs flux-rs#1714 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`unfold_returned_refs` hands a returned struct back unfolded, but the fold/unfold analysis runs on the mir and so never learns about it: it inserts no ghost statement to fold the struct back, and the `Downcast` escapes into any use of the value as a whole, where subtyping against the folded type reports incompatible types. A struct holding a `&mut` is never `Copy`, so the only reads that can see one are a move and the return place. Fold there. Refs flux-rs#1714 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Converting a pointer to a local pointer location into a reference, e.g. by an unsize coercion, blocks the location. `fold_local_ptrs` then folded the blocked type back at the end of the block and related it to the bound, reporting incompatible types. The reference takes over from the pointer, so that conversion is where the strong window closes: check the bound there and stop treating the location as a local pointer. This also plugs a hole, since `PtrToRefBound::Identity` never checked the bound at all, so a write through the pointer before the coercion went unchecked. A pointer into a *field* of the location blocks only that field and leaves the location to be folded back as usual, so the fold unblocks first. Refs flux-rs#1714 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Member
|
This PR introduces (new) unsoundness. The following is correctly rejected in #[flux::sig(fn(bool[true]))]
fn assert(_: bool) {}
#[flux::sig(fn(&mut usize{v: v > 0}) -> &mut usize{v: v > 0})]
fn id(x: &mut usize) -> &mut usize {
x
}
fn bad() {
let mut w = 1;
let r = id(&mut w);
let _a = [&mut *r];
*r = 0;
assert(w > 0);
} |
Contributor
|
(I haven't seen the code in the PR yet) but is the unsoundness because the effect of the |
Member
|
I haven't reviewed the implementation, so I can't determine whether the issue is in the implementation or an existing unsoundness that's surfacing because there are more unfolds. The |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #1714