Skip to content

Commit 612a9b2

Browse files
committed
2229: Handle capturing a reference into a repr packed struct
RFC 1240 states that it is unsafe to capture references into a packed-struct. This PR ensures that when a closure captures a precise path, we aren't violating this safety constraint. To acheive so we restrict the capture precision to the struct itself. An interesting edge case: ```rust struct Foo(String); let foo: Foo; let c = || { println!("{}", foo.0); let x = foo.0; } ``` Given how closures get desugared today, foo.0 will be moved into the closure, making the `println!`, safe. However this can be very subtle and also will be unsafe if the closure gets inline. Closes: rust-lang/project-rfc-2229#33
1 parent dfe519b commit 612a9b2

File tree

5 files changed

+381
-6
lines changed

5 files changed

+381
-6
lines changed

compiler/rustc_typeck/src/check/upvar.rs

+55-6
Original file line numberDiff line numberDiff line change
@@ -1053,6 +1053,52 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
10531053
}
10541054
}
10551055

1056+
/// Truncate the capture so that the place being borrowed is in accordance with RFC 1240,
1057+
/// which states that it's unsafe to take a reference into a struct marked `repr(packed)`.
1058+
fn restrict_repr_packed_field_ref_capture<'tcx>(
1059+
tcx: TyCtxt<'tcx>,
1060+
param_env: ty::ParamEnv<'tcx>,
1061+
place: &Place<'tcx>,
1062+
) -> Place<'tcx> {
1063+
let pos = place.projections.iter().enumerate().position(|(i, p)| {
1064+
let ty = place.ty_before_projection(i);
1065+
1066+
// Return true for fields of packed structs, unless those fields have alignment 1.
1067+
match p.kind {
1068+
ProjectionKind::Field(..) => match ty.kind() {
1069+
ty::Adt(def, _) if def.repr.packed() => {
1070+
match tcx.layout_raw(param_env.and(p.ty)) {
1071+
Ok(layout) if layout.align.abi.bytes() == 1 => {
1072+
// if the alignment is 1, the type can't be further
1073+
// disaligned.
1074+
debug!(
1075+
"restrict_repr_packed_field_ref_capture: ({:?}) - align = 1",
1076+
place
1077+
);
1078+
false
1079+
}
1080+
_ => {
1081+
debug!("restrict_repr_packed_field_ref_capture: ({:?}) - true", place);
1082+
true
1083+
}
1084+
}
1085+
}
1086+
1087+
_ => false,
1088+
},
1089+
_ => false,
1090+
}
1091+
});
1092+
1093+
let mut place = place.clone();
1094+
1095+
if let Some(pos) = pos {
1096+
place.projections.truncate(pos);
1097+
}
1098+
1099+
place
1100+
}
1101+
10561102
struct InferBorrowKind<'a, 'tcx> {
10571103
fcx: &'a FnCtxt<'a, 'tcx>,
10581104

@@ -1391,8 +1437,15 @@ impl<'a, 'tcx> euv::Delegate<'tcx> for InferBorrowKind<'a, 'tcx> {
13911437
place_with_id, diag_expr_id, bk
13921438
);
13931439

1440+
let place = restrict_repr_packed_field_ref_capture(
1441+
self.fcx.tcx,
1442+
self.fcx.param_env,
1443+
&place_with_id.place,
1444+
);
1445+
let place_with_id = PlaceWithHirId { place, ..*place_with_id };
1446+
13941447
if !self.capture_information.contains_key(&place_with_id.place) {
1395-
self.init_capture_info_for_place(place_with_id, diag_expr_id);
1448+
self.init_capture_info_for_place(&place_with_id, diag_expr_id);
13961449
}
13971450

13981451
match bk {
@@ -1409,11 +1462,7 @@ impl<'a, 'tcx> euv::Delegate<'tcx> for InferBorrowKind<'a, 'tcx> {
14091462
fn mutate(&mut self, assignee_place: &PlaceWithHirId<'tcx>, diag_expr_id: hir::HirId) {
14101463
debug!("mutate(assignee_place={:?}, diag_expr_id={:?})", assignee_place, diag_expr_id);
14111464

1412-
if !self.capture_information.contains_key(&assignee_place.place) {
1413-
self.init_capture_info_for_place(assignee_place, diag_expr_id);
1414-
}
1415-
1416-
self.adjust_upvar_borrow_kind_for_mut(assignee_place, diag_expr_id);
1465+
self.borrow(assignee_place, diag_expr_id, ty::BorrowKind::MutBorrow);
14171466
}
14181467
}
14191468

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
// check-pass
2+
3+
#![feature(capture_disjoint_fields)]
4+
//~^ WARNING: the feature `capture_disjoint_fields` is incomplete
5+
6+
// Given how the closure desugaring is implemented (at least at the time of writing this test),
7+
// we don't need to truncate the captured path to a reference into a packed-struct if the field
8+
// being referenced will be moved into the closure, since it's safe to move out a field from a
9+
// packed-struct.
10+
//
11+
// However to avoid surprises for the user, or issues when the closure is
12+
// inlined we will truncate the capture to access just the struct regardless of if the field
13+
// might get moved into the closure.
14+
//
15+
// It is possible for someone to try writing the code that relies on the desugaring to access a ref
16+
// into a packed-struct without explicity using unsafe. Here we test that the compiler warns the
17+
// user that such an access is still unsafe.
18+
fn test_missing_unsafe_warning_on_repr_packed() {
19+
#[repr(packed)]
20+
struct Foo { x: String }
21+
22+
let foo = Foo { x: String::new() };
23+
24+
let c = || {
25+
println!("{}", foo.x);
26+
//~^ WARNING: borrow of packed field is unsafe and requires unsafe function or block
27+
//~| WARNING: this was previously accepted by the compiler but is being phased out
28+
let _z = foo.x;
29+
};
30+
31+
c();
32+
}
33+
34+
fn main() {
35+
test_missing_unsafe_warning_on_repr_packed();
36+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
warning: the feature `capture_disjoint_fields` is incomplete and may not be safe to use and/or cause compiler crashes
2+
--> $DIR/repr_packed.rs:3:12
3+
|
4+
LL | #![feature(capture_disjoint_fields)]
5+
| ^^^^^^^^^^^^^^^^^^^^^^^
6+
|
7+
= note: `#[warn(incomplete_features)]` on by default
8+
= note: see issue #53488 <https://github.com/rust-lang/rust/issues/53488> for more information
9+
10+
warning: borrow of packed field is unsafe and requires unsafe function or block (error E0133)
11+
--> $DIR/repr_packed.rs:25:24
12+
|
13+
LL | println!("{}", foo.x);
14+
| ^^^^^
15+
|
16+
= note: `#[warn(safe_packed_borrows)]` on by default
17+
= warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
18+
= note: for more information, see issue #46043 <https://github.com/rust-lang/rust/issues/46043>
19+
= note: fields of packed structs might be misaligned: dereferencing a misaligned pointer or even just creating a misaligned reference is undefined behavior
20+
21+
warning: 2 warnings emitted
22+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
#![feature(capture_disjoint_fields)]
2+
//~^ WARNING: the feature `capture_disjoint_fields` is incomplete
3+
//~| `#[warn(incomplete_features)]` on by default
4+
//~| see issue #53488 <https://github.com/rust-lang/rust/issues/53488>
5+
6+
#![feature(rustc_attrs)]
7+
8+
// `u8` aligned at a byte and are unaffected by repr(packed).
9+
// Therefore we can precisely (and safely) capture references to both the fields.
10+
fn test_alignment_not_affected() {
11+
#[repr(packed)]
12+
struct Foo { x: u8, y: u8 }
13+
14+
let mut foo = Foo { x: 0, y: 0 };
15+
16+
let mut c = #[rustc_capture_analysis]
17+
//~^ ERROR: attributes on expressions are experimental
18+
//~| NOTE: see issue #15701 <https://github.com/rust-lang/rust/issues/15701>
19+
|| {
20+
//~^ ERROR: First Pass analysis includes:
21+
//~| ERROR: Min Capture analysis includes:
22+
let z1: &u8 = &foo.x;
23+
//~^ NOTE: Capturing foo[(0, 0)] -> ImmBorrow
24+
//~| NOTE: Min Capture foo[(0, 0)] -> ImmBorrow
25+
let z2: &mut u8 = &mut foo.y;
26+
//~^ NOTE: Capturing foo[(1, 0)] -> MutBorrow
27+
//~| NOTE: Min Capture foo[(1, 0)] -> MutBorrow
28+
29+
*z2 = 42;
30+
31+
println!("({}, {})", z1, z2);
32+
};
33+
34+
c();
35+
}
36+
37+
// `String`, `u16` are not aligned at a one byte boundry and are thus affected by repr(packed).
38+
//
39+
// Here we test that the closure doesn't capture a reference point to `foo.x` but
40+
// rather capture `foo` entirely.
41+
fn test_alignment_affected() {
42+
#[repr(packed)]
43+
struct Foo { x: String, y: u16 }
44+
45+
let mut foo = Foo { x: String::new(), y: 0 };
46+
47+
let mut c = #[rustc_capture_analysis]
48+
//~^ ERROR: attributes on expressions are experimental
49+
//~| NOTE: see issue #15701 <https://github.com/rust-lang/rust/issues/15701>
50+
|| {
51+
//~^ ERROR: First Pass analysis includes:
52+
//~| ERROR: Min Capture analysis includes:
53+
let z1: &String = &foo.x;
54+
let z2: &mut u16 = &mut foo.y;
55+
//~^ NOTE: Capturing foo[] -> MutBorrow
56+
//~| NOTE: Min Capture foo[] -> MutBorrow
57+
58+
59+
*z2 = 42;
60+
61+
println!("({}, {})", z1, z2);
62+
};
63+
64+
c();
65+
}
66+
67+
// Given how the closure desugaring is implemented (at least at the time of writing this test),
68+
// we don't need to truncate the captured path to a reference into a packed-struct if the field
69+
// being referenced will be moved into the closure, since it's safe to move out a field from a
70+
// packed-struct.
71+
//
72+
// However to avoid surprises for the user, or issues when the closure is
73+
// inlined we will truncate the capture to access just the struct regardless of if the field
74+
// might get moved into the closure.
75+
fn test_truncation_when_ref_and_move() {
76+
#[repr(packed)]
77+
struct Foo { x: String }
78+
79+
let mut foo = Foo { x: String::new() };
80+
81+
let c = #[rustc_capture_analysis]
82+
//~^ ERROR: attributes on expressions are experimental
83+
//~| NOTE: see issue #15701 <https://github.com/rust-lang/rust/issues/15701>
84+
|| {
85+
//~^ ERROR: First Pass analysis includes:
86+
//~| ERROR: Min Capture analysis includes:
87+
println!("{}", foo.x);
88+
//~^ NOTE: Capturing foo[] -> ImmBorrow
89+
//~| NOTE: Min Capture foo[] -> ByValue
90+
//~| NOTE: foo[] used here
91+
let _z = foo.x;
92+
//~^ NOTE: Capturing foo[(0, 0)] -> ByValue
93+
//~| NOTE: foo[] captured as ByValue here
94+
};
95+
96+
c();
97+
}
98+
99+
fn main() {
100+
test_truncation_when_ref_and_move();
101+
test_alignment_affected();
102+
test_alignment_not_affected();
103+
}

0 commit comments

Comments
 (0)