Skip to content

Commit 8499a8b

Browse files
authored
Rollup merge of #94309 - eholk:issue-57017, r=tmandry
[generator_interior] Be more precise with scopes of borrowed places Previously the generator interior type checking analysis would use the nearest temporary scope as the scope of a borrowed value. This ends up being overly broad for cases such as: ```rust fn status(_client_status: &Client) -> i16 { 200 } fn main() { let client = Client; let g = move || match status(&client) { _status => yield, }; assert_send(g); } ``` In this case, the borrow `&client` could be considered in scope for the entirety of the `match` expression, meaning it would be viewed as live across the `yield`, therefore making the generator not `Send`. In most cases, we want to use the enclosing expression as the scope for a borrowed value which will be less than or equal to the nearest temporary scope. This PR changes the analysis to use the enclosing expression as the scope for most borrows, with the exception of borrowed RValues which are true temporary values that should have the temporary scope. There's one further exception where borrows of a copy such as happens in autoref cases also should be ignored despite being RValues. Joint work with `@nikomatsakis` Fixes #57017 r? `@tmandry`
2 parents 07121c8 + 2fcd542 commit 8499a8b

File tree

6 files changed

+144
-13
lines changed

6 files changed

+144
-13
lines changed

compiler/rustc_typeck/src/check/generator_interior.rs

+20-2
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ use rustc_hir::def_id::DefId;
1313
use rustc_hir::hir_id::HirIdSet;
1414
use rustc_hir::intravisit::{self, Visitor};
1515
use rustc_hir::{Arm, Expr, ExprKind, Guard, HirId, Pat, PatKind};
16-
use rustc_middle::middle::region::{self, YieldData};
16+
use rustc_middle::middle::region::{self, Scope, ScopeData, YieldData};
1717
use rustc_middle::ty::{self, Ty, TyCtxt};
1818
use rustc_span::symbol::sym;
1919
use rustc_span::Span;
@@ -369,7 +369,25 @@ impl<'a, 'tcx> Visitor<'tcx> for InteriorVisitor<'a, 'tcx> {
369369

370370
self.expr_count += 1;
371371

372-
let scope = self.region_scope_tree.temporary_scope(expr.hir_id.local_id);
372+
debug!("is_borrowed_temporary: {:?}", self.drop_ranges.is_borrowed_temporary(expr));
373+
374+
// Typically, the value produced by an expression is consumed by its parent in some way,
375+
// so we only have to check if the parent contains a yield (note that the parent may, for
376+
// example, store the value into a local variable, but then we already consider local
377+
// variables to be live across their scope).
378+
//
379+
// However, in the case of temporary values, we are going to store the value into a
380+
// temporary on the stack that is live for the current temporary scope and then return a
381+
// reference to it. That value may be live across the entire temporary scope.
382+
let scope = if self.drop_ranges.is_borrowed_temporary(expr) {
383+
self.region_scope_tree.temporary_scope(expr.hir_id.local_id)
384+
} else {
385+
debug!("parent_node: {:?}", self.fcx.tcx.hir().find_parent_node(expr.hir_id));
386+
match self.fcx.tcx.hir().find_parent_node(expr.hir_id) {
387+
Some(parent) => Some(Scope { id: parent.local_id, data: ScopeData::Node }),
388+
None => self.region_scope_tree.temporary_scope(expr.hir_id.local_id),
389+
}
390+
};
373391

374392
// If there are adjustments, then record the final type --
375393
// this is the actual value that is being produced.

compiler/rustc_typeck/src/check/generator_interior/drop_ranges.rs

+18-3
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ use crate::check::FnCtxt;
1818
use hir::def_id::DefId;
1919
use hir::{Body, HirId, HirIdMap, Node};
2020
use rustc_data_structures::fx::FxHashMap;
21+
use rustc_data_structures::stable_set::FxHashSet;
2122
use rustc_hir as hir;
2223
use rustc_index::bit_set::BitSet;
2324
use rustc_index::vec::IndexVec;
@@ -41,7 +42,7 @@ pub fn compute_drop_ranges<'a, 'tcx>(
4142
let consumed_borrowed_places = find_consumed_and_borrowed(fcx, def_id, body);
4243

4344
let num_exprs = fcx.tcx.region_scope_tree(def_id).body_expr_count(body.id()).unwrap_or(0);
44-
let mut drop_ranges = build_control_flow_graph(
45+
let (mut drop_ranges, borrowed_temporaries) = build_control_flow_graph(
4546
fcx.tcx.hir(),
4647
fcx.tcx,
4748
&fcx.typeck_results.borrow(),
@@ -52,11 +53,20 @@ pub fn compute_drop_ranges<'a, 'tcx>(
5253

5354
drop_ranges.propagate_to_fixpoint();
5455

55-
DropRanges { tracked_value_map: drop_ranges.tracked_value_map, nodes: drop_ranges.nodes }
56+
debug!("borrowed_temporaries = {borrowed_temporaries:?}");
57+
DropRanges {
58+
tracked_value_map: drop_ranges.tracked_value_map,
59+
nodes: drop_ranges.nodes,
60+
borrowed_temporaries: Some(borrowed_temporaries),
61+
}
5662
} else {
5763
// If drop range tracking is not enabled, skip all the analysis and produce an
5864
// empty set of DropRanges.
59-
DropRanges { tracked_value_map: FxHashMap::default(), nodes: IndexVec::new() }
65+
DropRanges {
66+
tracked_value_map: FxHashMap::default(),
67+
nodes: IndexVec::new(),
68+
borrowed_temporaries: None,
69+
}
6070
}
6171
}
6272

@@ -161,6 +171,7 @@ impl TryFrom<&PlaceWithHirId<'_>> for TrackedValue {
161171
pub struct DropRanges {
162172
tracked_value_map: FxHashMap<TrackedValue, TrackedValueIndex>,
163173
nodes: IndexVec<PostOrderId, NodeInfo>,
174+
borrowed_temporaries: Option<FxHashSet<HirId>>,
164175
}
165176

166177
impl DropRanges {
@@ -174,6 +185,10 @@ impl DropRanges {
174185
})
175186
}
176187

188+
pub fn is_borrowed_temporary(&self, expr: &hir::Expr<'_>) -> bool {
189+
if let Some(b) = &self.borrowed_temporaries { b.contains(&expr.hir_id) } else { true }
190+
}
191+
177192
/// Returns a reference to the NodeInfo for a node, panicking if it does not exist
178193
fn expect_node(&self, id: PostOrderId) -> &NodeInfo {
179194
&self.nodes[id]

compiler/rustc_typeck/src/check/generator_interior/drop_ranges/cfg_build.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use hir::{
66
intravisit::{self, Visitor},
77
Body, Expr, ExprKind, Guard, HirId, LoopIdError,
88
};
9-
use rustc_data_structures::fx::FxHashMap;
9+
use rustc_data_structures::{fx::FxHashMap, stable_set::FxHashSet};
1010
use rustc_hir as hir;
1111
use rustc_index::vec::IndexVec;
1212
use rustc_middle::{
@@ -27,14 +27,14 @@ pub(super) fn build_control_flow_graph<'tcx>(
2727
consumed_borrowed_places: ConsumedAndBorrowedPlaces,
2828
body: &'tcx Body<'tcx>,
2929
num_exprs: usize,
30-
) -> DropRangesBuilder {
30+
) -> (DropRangesBuilder, FxHashSet<HirId>) {
3131
let mut drop_range_visitor =
3232
DropRangeVisitor::new(hir, tcx, typeck_results, consumed_borrowed_places, num_exprs);
3333
intravisit::walk_body(&mut drop_range_visitor, body);
3434

3535
drop_range_visitor.drop_ranges.process_deferred_edges();
3636

37-
drop_range_visitor.drop_ranges
37+
(drop_range_visitor.drop_ranges, drop_range_visitor.places.borrowed_temporaries)
3838
}
3939

4040
/// This struct is used to gather the information for `DropRanges` to determine the regions of the

compiler/rustc_typeck/src/check/generator_interior/drop_ranges/record_consumed_borrow.rs

+72-2
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use crate::{
66
use hir::{def_id::DefId, Body, HirId, HirIdMap};
77
use rustc_data_structures::stable_set::FxHashSet;
88
use rustc_hir as hir;
9+
use rustc_middle::hir::place::{PlaceBase, Projection, ProjectionKind};
910
use rustc_middle::ty::{ParamEnv, TyCtxt};
1011

1112
pub(super) fn find_consumed_and_borrowed<'a, 'tcx>(
@@ -27,8 +28,12 @@ pub(super) struct ConsumedAndBorrowedPlaces {
2728
/// Note that this set excludes "partial drops" -- for example, a statement like `drop(x.y)` is
2829
/// not considered a drop of `x`, although it would be a drop of `x.y`.
2930
pub(super) consumed: HirIdMap<FxHashSet<TrackedValue>>,
31+
3032
/// A set of hir-ids of values or variables that are borrowed at some point within the body.
3133
pub(super) borrowed: FxHashSet<TrackedValue>,
34+
35+
/// A set of hir-ids of values or variables that are borrowed at some point within the body.
36+
pub(super) borrowed_temporaries: FxHashSet<HirId>,
3237
}
3338

3439
/// Works with ExprUseVisitor to find interesting values for the drop range analysis.
@@ -49,6 +54,7 @@ impl<'tcx> ExprUseDelegate<'tcx> {
4954
places: ConsumedAndBorrowedPlaces {
5055
consumed: <_>::default(),
5156
borrowed: <_>::default(),
57+
borrowed_temporaries: <_>::default(),
5258
},
5359
}
5460
}
@@ -96,12 +102,76 @@ impl<'tcx> expr_use_visitor::Delegate<'tcx> for ExprUseDelegate<'tcx> {
96102
&mut self,
97103
place_with_id: &expr_use_visitor::PlaceWithHirId<'tcx>,
98104
diag_expr_id: HirId,
99-
_bk: rustc_middle::ty::BorrowKind,
105+
bk: rustc_middle::ty::BorrowKind,
106+
) {
107+
debug!(
108+
"borrow: place_with_id = {place_with_id:?}, diag_expr_id={diag_expr_id:?}, \
109+
borrow_kind={bk:?}"
110+
);
111+
112+
self.places
113+
.borrowed
114+
.insert(TrackedValue::from_place_with_projections_allowed(place_with_id));
115+
116+
// Ordinarily a value is consumed by it's parent, but in the special case of a
117+
// borrowed RValue, we create a reference that lives as long as the temporary scope
118+
// for that expression (typically, the innermost statement, but sometimes the enclosing
119+
// block). We record this fact here so that later in generator_interior
120+
// we can use the correct scope.
121+
//
122+
// We special case borrows through a dereference (`&*x`, `&mut *x` where `x` is
123+
// some rvalue expression), since these are essentially a copy of a pointer.
124+
// In other words, this borrow does not refer to the
125+
// temporary (`*x`), but to the referent (whatever `x` is a borrow of).
126+
//
127+
// We were considering that we might encounter problems down the line if somehow,
128+
// some part of the compiler were to look at this result and try to use it to
129+
// drive a borrowck-like analysis (this does not currently happen, as of this writing).
130+
// But even this should be fine, because the lifetime of the dereferenced reference
131+
// found in the rvalue is only significant as an intermediate 'link' to the value we
132+
// are producing, and we separately track whether that value is live over a yield.
133+
// Example:
134+
//
135+
// ```notrust
136+
// fn identity<T>(x: &mut T) -> &mut T { x }
137+
// let a: A = ...;
138+
// let y: &'y mut A = &mut *identity(&'a mut a);
139+
// ^^^^^^^^^^^^^^^^^^^^^^^^^ the borrow we are talking about
140+
// ```
141+
//
142+
// The expression `*identity(...)` is a deref of an rvalue,
143+
// where the `identity(...)` (the rvalue) produces a return type
144+
// of `&'rv mut A`, where `'a: 'rv`. We then assign this result to
145+
// `'y`, resulting in (transitively) `'a: 'y` (i.e., while `y` is in use,
146+
// `a` will be considered borrowed). Other parts of the code will ensure
147+
// that if `y` is live over a yield, `&'y mut A` appears in the generator
148+
// state. If `'y` is live, then any sound region analysis must conclude
149+
// that `'a` is also live. So if this causes a bug, blame some other
150+
// part of the code!
151+
let is_deref = place_with_id
152+
.place
153+
.projections
154+
.iter()
155+
.any(|Projection { kind, .. }| *kind == ProjectionKind::Deref);
156+
157+
if let (false, PlaceBase::Rvalue) = (is_deref, place_with_id.place.base) {
158+
self.places.borrowed_temporaries.insert(place_with_id.hir_id);
159+
}
160+
}
161+
162+
fn copy(
163+
&mut self,
164+
place_with_id: &expr_use_visitor::PlaceWithHirId<'tcx>,
165+
_diag_expr_id: HirId,
100166
) {
101-
debug!("borrow {:?}; diag_expr_id={:?}", place_with_id, diag_expr_id);
167+
debug!("copy: place_with_id = {place_with_id:?}");
168+
102169
self.places
103170
.borrowed
104171
.insert(TrackedValue::from_place_with_projections_allowed(place_with_id));
172+
173+
// For copied we treat this mostly like a borrow except that we don't add the place
174+
// to borrowed_temporaries because the copy is consumed.
105175
}
106176

107177
fn mutate(

compiler/rustc_typeck/src/expr_use_visitor.rs

+9-3
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,14 @@ pub trait Delegate<'tcx> {
4747
bk: ty::BorrowKind,
4848
);
4949

50+
/// The value found at `place` is being copied.
51+
/// `diag_expr_id` is the id used for diagnostics (see `consume` for more details).
52+
fn copy(&mut self, place_with_id: &PlaceWithHirId<'tcx>, diag_expr_id: hir::HirId) {
53+
// In most cases, copying data from `x` is equivalent to doing `*&x`, so by default
54+
// we treat a copy of `x` as a borrow of `x`.
55+
self.borrow(place_with_id, diag_expr_id, ty::BorrowKind::ImmBorrow)
56+
}
57+
5058
/// The path at `assignee_place` is being assigned to.
5159
/// `diag_expr_id` is the id used for diagnostics (see `consume` for more details).
5260
fn mutate(&mut self, assignee_place: &PlaceWithHirId<'tcx>, diag_expr_id: hir::HirId);
@@ -836,9 +844,7 @@ fn delegate_consume<'a, 'tcx>(
836844

837845
match mode {
838846
ConsumeMode::Move => delegate.consume(place_with_id, diag_expr_id),
839-
ConsumeMode::Copy => {
840-
delegate.borrow(place_with_id, diag_expr_id, ty::BorrowKind::ImmBorrow)
841-
}
847+
ConsumeMode::Copy => delegate.copy(place_with_id, diag_expr_id),
842848
}
843849
}
844850

src/test/ui/generator/issue-57017.rs

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// check-pass
2+
// compile-flags: -Zdrop-tracking
3+
#![feature(generators, negative_impls)]
4+
5+
struct Client;
6+
7+
impl !Sync for Client {}
8+
9+
fn status(_client_status: &Client) -> i16 {
10+
200
11+
}
12+
13+
fn assert_send<T: Send>(_thing: T) {}
14+
15+
// This is the same bug as issue 57017, but using yield instead of await
16+
fn main() {
17+
let client = Client;
18+
let g = move || match status(&client) {
19+
_status => yield,
20+
};
21+
assert_send(g);
22+
}

0 commit comments

Comments
 (0)