-
Notifications
You must be signed in to change notification settings - Fork 12.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fixes NLL: error from URL crate #47917
Conversation
A quick note about this initial solution, I'm not all that confident that I should be using a |
src/librustc_mir/borrow_check/mod.rs
Outdated
// When this function is called as a result of an `access_terminator` call attempting | ||
// to drop a struct, if that struct does not have a destructor, then we need to check | ||
// each of the fields in the struct. See #47703. | ||
let (access, places) = if let ContextKind::Drop = context.kind { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, I had intended for us think to put this code here:
rust/src/librustc_mir/borrow_check/mod.rs
Lines 467 to 473 in 8ccab7e
self.access_place( | |
ContextKind::Drop.new(loc), | |
(drop_place, span), | |
(Deep, Write(WriteKind::StorageDeadOrDrop)), | |
LocalMutationIsAllowed::Yes, | |
flow_state, | |
); |
That is, we would invoke access_place
more than once. We might want to make the context
carry the original path being dropped though, for error message purposes, not sure.
Also, I think this needs to be recursive -- that is, once we construct the place foo.f
or the field f
, we need to recursively check if that should be broken down. In other words, if we alter the original test to add one more layer of struct surrounding the MyStruct
, it should still pass:
struct MyMyStruct<'a> {
field: MyStruct<'a>
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be resolved now.
src/librustc_mir/borrow_check/mod.rs
Outdated
}; | ||
let place = Place::Projection(Box::new(proj)); | ||
|
||
match field_ty.sty { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
instead of this match, we could invoke:
self.visit_terminator_drop(self, loc, term, flow_state, &place, span);
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've changed this locally, will push with other changes.
src/librustc_mir/borrow_check/mod.rs
Outdated
self.access_place( | ||
ContextKind::Drop.new(loc), | ||
(&place, span), | ||
(Shallow(None), Write(WriteKind::StorageDeadOrDrop)), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In particular, this Shallow(None)
is wrong -- we should fall through to the Deep
case below.
Here is why: imagine this is a struct with a destructor. Then we will be running random user code, it could touch anything within!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wasn't sure about the Shallow(None)
, but when using the place for just the field, we still get an error, so figured it must have been necessary:
DEBUG:<unknown>: MirBorrowckCtxt::process_terminator(bb0[4], Terminator { source_info: SourceInfo { span: src/test/run-pass/issue-47703.rs:25:50: 25:50, scope: scope[0] }, kind: drop(_1) -> [return: bb2, unwind: bb1] }): borrows in effect: [&mut (*(_1.0: &mut ())), &mut (*(_1.0: &mut ()))@active] borrows generated: [] inits: [_0, _1] uninits: [_2] move_out: [] ever_init: [mp1@src/test/run-pass/issue-47703.rs:25:16: 25:20 (Deep), mp0@src/test/run-pass/issue-47703.rs:25:36: 25:50 (Deep)]
DEBUG:<unknown>: check_access_permissions((_1.0: &mut ()), Write(StorageDeadOrDrop), Yes)
DEBUG:<unknown>: places_conflict((*(_1.0: &mut ())),(_1.0: &mut ()),Deep)
DEBUG:<unknown>: places_conflict: components [_1, (_1.0: &mut ()), (*(_1.0: &mut ()))] / [_1, (_1.0: &mut ())]
DEBUG:<unknown>: places_conflict: Some(_1) vs. Some(_1)
DEBUG:<unknown>: place_element_conflict: DISJOINT-OR-EQ-LOCAL
DEBUG:<unknown>: places_conflict: Some((_1.0: &mut ())) vs. Some((_1.0: &mut ()))
DEBUG:<unknown>: place_element_conflict: DISJOINT-OR-EQ-FIELD
DEBUG:<unknown>: places_conflict: Some((*(_1.0: &mut ()))) vs. None
DEBUG:<unknown>: places_conflict: None vs. None
DEBUG:<unknown>: places_conflict: full borrow, CONFLICT
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm able to change this so that the access_place
ends up matching the borrow_place
, ie. (*(_1.0: &mut ()))
on both sides. This also does not fix the error.
DEBUG:<unknown>: MirBorrowckCtxt::process_terminator(bb0[4], Terminator { source_info: SourceInfo { span: src/test/run-pass/issue-47703.rs:25:50: 25:50, scope: scope[0] }, kind: drop(_1) -> [return: bb2, unwind: bb1] }): borrows in effect: [&mut (*(_1.0: &mut ())), &mut (*(_1.0: &mut ()))@active] borrows generated: [] inits: [_0, _1] uninits: [_2] move_out: [] ever_init: [mp1@src/test/run-pass/issue-47703.rs:25:16: 25:20 (Deep), mp0@src/test/run-pass/issue-47703.rs:25:36: 25:50 (Deep)]
DEBUG:<unknown>: check_access_permissions((*(_1.0: &mut ())), Write(StorageDeadOrDrop), Yes)
DEBUG:<unknown>: places_conflict((*(_1.0: &mut ())),(*(_1.0: &mut ())),Deep)
DEBUG:<unknown>: places_conflict: components [_1, (_1.0: &mut ()), (*(_1.0: &mut ()))] / [_1, (_1.0: &mut ()), (*(_1.0: &mut ()))]
DEBUG:<unknown>: places_conflict: Some(_1) vs. Some(_1)
DEBUG:<unknown>: place_element_conflict: DISJOINT-OR-EQ-LOCAL
DEBUG:<unknown>: places_conflict: Some((_1.0: &mut ())) vs. Some((_1.0: &mut ()))
DEBUG:<unknown>: place_element_conflict: DISJOINT-OR-EQ-FIELD
DEBUG:<unknown>: places_conflict: Some((*(_1.0: &mut ()))) vs. Some((*(_1.0: &mut ())))
DEBUG:<unknown>: place_element_conflict: DISJOINT-OR-EQ-DEREF
DEBUG:<unknown>: places_conflict: None vs. None
DEBUG:<unknown>: places_conflict: full borrow, CONFLICT
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm a bit confused by what you are showing me -- you are saying that if you don't use the "shallow" case, you get an error on the original test case?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes. If I change it to Deep
(or let it fall through to that case), then I get the error on the original test case. That's why I had it as Shallow(None)
initially.
src/librustc_mir/borrow_check/mod.rs
Outdated
// See #47703. | ||
ty::TyAdt(def, substs) if def.is_struct() && !def.has_dtor(self.tcx) => { | ||
for (index, field) in def.all_fields().enumerate() { | ||
let field_ty = field.ty(self.tcx, substs); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sadly, before landing we're going to have to normalize associated types here. I have to dig a bit to see what's the most convenient way to do that. Probably we can erase regions, actually, which might simplify our lives a bit.
I've pushed what changes I made locally as requested by @nikomatsakis. It will fail the tests because the previous version used Also, the line below is included because it was something I tried out to see if it would help, I don't think it's what we should have: |
OK, I pushed a few tweaks, but we still need to solve the normalization problem. |
Marking as S-blocked for now -- I have an in-progress branch that may be very helpful here. |
☔ The latest upstream changes (presumably #47489) made this pull request unmergeable. Please resolve the merge conflicts. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK, I laid out how I think we can make the transformation. If we do this, it's probably worth adding some comments to access_place
that the Place<'tcx>
may have a region-erased type in some cases. But I don't think that should matter, those bits of code ought not to be testing the region. Certainly my searches through the code suggest that this is the case. (All instances of TyRef
ignore the region, for example.)
loc: Location, | ||
term: &Terminator<'tcx>, | ||
flow_state: &Flows<'cx, 'gcx, 'tcx>, | ||
drop_place: &Place<'tcx>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This will take an extra argument:
erased_drop_place_ty: Ty<'gcx>,
Note that it belongs in the global arena ('gcx
) -- this is possible because its regions have been erased.
src/librustc_mir/borrow_check/mod.rs
Outdated
LocalMutationIsAllowed::Yes, | ||
flow_state, | ||
); | ||
self.visit_terminator_drop(loc, term, flow_state, drop_place, span); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here, we will start by erasing the type of the drop_place
, roughly like so:
let gcx = self.tcx.global_tcx();
// Compute the type with accurate region information.
let drop_place_ty = drop_place.ty(self.mir, self.tcx);
// Erase the regions.
let drop_place_ty = self.tcx.erase_regions(&drop_place_ty);
// "Lift" into the gcx -- once the regions are erased, this type should be in the global arenas; this "lift" operation basically just asserts that this is true, but that's useful later.
let drop_place_ty = gcx.lift(&drop_place_ty).unwrap();
(Note that I am intentionally shadowing drop_place_ty
here, because we don't want to accidentally access the intermediate values.)
Then we'll add this drop_place_ty
as an argument to visit_terminator_drop
src/librustc_mir/borrow_check/mod.rs
Outdated
drop_place: &Place<'tcx>, | ||
span: Span, | ||
) { | ||
let ty = drop_place.ty(self.mir, self.tcx).to_ty(self.tcx); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
remove this line
src/librustc_mir/borrow_check/mod.rs
Outdated
span: Span, | ||
) { | ||
let ty = drop_place.ty(self.mir, self.tcx).to_ty(self.tcx); | ||
match ty.sty { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
becomes
match erased_drop_place_ty.sty {
...
}
src/librustc_mir/borrow_check/mod.rs
Outdated
ty::TyAdt(def, substs) if def.is_struct() && !def.has_dtor(self.tcx) => { | ||
for (index, field) in def.all_fields().enumerate() { | ||
let place = drop_place.clone(); | ||
let place = place.field(Field::new(index), field.ty(self.tcx, substs)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here, we can normalize like so:
let gcx = self.tcx.global_tcx();
let field_ty = field.ty(gcx, substs);
let field_ty = gcx.normalize_associated_types_in_env(&field_ty, self.param_env);
let place = place.field(Field::new(index), field_ty);
src/librustc_mir/borrow_check/mod.rs
Outdated
// "needs drop". If so, we assume that the destructor | ||
// may access any data it likes (i.e., a Deep Write). | ||
let gcx = self.tcx.global_tcx(); | ||
let erased_ty = gcx.lift(&self.tcx.erase_regions(&ty)).unwrap(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here, the regions etc are already erased, so we can just remove this code
src/librustc_mir/borrow_check/mod.rs
Outdated
// may access any data it likes (i.e., a Deep Write). | ||
let gcx = self.tcx.global_tcx(); | ||
let erased_ty = gcx.lift(&self.tcx.erase_regions(&ty)).unwrap(); | ||
if erased_ty.needs_drop(gcx, self.param_env) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here we would use if erased_drop_place_ty.needs_drop(...) {
…ruct has no dtor.
Also, add some comments and remove extra deref.
babf202
to
159b742
Compare
@nikomatsakis pushed up rebased changes that include associated type normalization. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Left a few nits, but seems good.
src/librustc_mir/borrow_check/mod.rs
Outdated
let drop_place_ty = self.tcx.erase_regions(&drop_place_ty).to_ty(self.tcx); | ||
|
||
// "Lift" into the gcx -- once regions are erased, this type should be in the | ||
// global areans; this "lift" operation basically just asserts that is true, but |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: arenas, not "areans"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oops, fixed.
src/librustc_mir/borrow_check/mod.rs
Outdated
term: &Terminator<'tcx>, | ||
flow_state: &Flows<'cx, 'gcx, 'tcx>, | ||
drop_place: &Place<'tcx>, | ||
erased_drop_place_ty: &'gcx ty::TyS<'gcx>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is always written Ty<'gcx>
. We never reference TyS
outside of the ty
module if we can help it. =)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed.
src/librustc_mir/borrow_check/mod.rs
Outdated
@@ -2093,7 +2159,7 @@ impl<'cx, 'gcx, 'tcx> MirBorrowckCtxt<'cx, 'gcx, 'tcx> { | |||
while let Some(i) = elems_incoming.next() { | |||
let borrowed = &data[i.borrow_index()]; | |||
|
|||
if self.places_conflict(&borrowed.borrowed_place, place, access) { | |||
if self.places_conflict(&borrowed.borrowed_place, &place, access) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why this diff?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have no idea what that came from. My bad.
159b742
to
98904c2
Compare
@nikomatsakis fixed those nits. |
@bors r+ |
📌 Commit 98904c2 has been approved by |
Fixes NLL: error from URL crate Fixes #47703. r? @nikomatsakis
☀️ Test successful - status-appveyor, status-travis |
Access individual fields of tuples, closures and generators on drop. Fixes #48623, by extending the change in #47917 to closures. Also does this for tuples and generators for consistency. Enums are unchanged because there is now way to borrow `*enum.field` without borrowing `enum.field` at the moment, so any error would be reported when the enum goes out of scope. Unions aren't changed because unions they don't automatically drop their fields. r? @nikomatsakis
Fixes #47703.
r? @nikomatsakis