Skip to content
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

2229: Handle capturing a reference into a repr packed struct #82878

Merged
merged 1 commit into from
Mar 13, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 55 additions & 6 deletions compiler/rustc_typeck/src/check/upvar.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1053,6 +1053,52 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
}
}

/// Truncate the capture so that the place being borrowed is in accordance with RFC 1240,
/// which states that it's unsafe to take a reference into a struct marked `repr(packed)`.
fn restrict_repr_packed_field_ref_capture<'tcx>(
tcx: TyCtxt<'tcx>,
param_env: ty::ParamEnv<'tcx>,
place: &Place<'tcx>,
) -> Place<'tcx> {
let pos = place.projections.iter().enumerate().position(|(i, p)| {
let ty = place.ty_before_projection(i);

// Return true for fields of packed structs, unless those fields have alignment 1.
match p.kind {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are a lot of matches and ifs here. It'd be nice to have a comment indicating what you are looking for. Something like:

// Return true for fields of packed structs, unless those fields have alignment 1.

ProjectionKind::Field(..) => match ty.kind() {
ty::Adt(def, _) if def.repr.packed() => {
match tcx.layout_raw(param_env.and(p.ty)) {
nikomatsakis marked this conversation as resolved.
Show resolved Hide resolved
Ok(layout) if layout.align.abi.bytes() == 1 => {
// if the alignment is 1, the type can't be further
// disaligned.
debug!(
"restrict_repr_packed_field_ref_capture: ({:?}) - align = 1",
place
);
false
}
_ => {
debug!("restrict_repr_packed_field_ref_capture: ({:?}) - true", place);
true
}
}
}

_ => false,
},
_ => false,
}
});

let mut place = place.clone();

if let Some(pos) = pos {
place.projections.truncate(pos);
}

place
}

struct InferBorrowKind<'a, 'tcx> {
fcx: &'a FnCtxt<'a, 'tcx>,

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

let place = restrict_repr_packed_field_ref_capture(
self.fcx.tcx,
self.fcx.param_env,
&place_with_id.place,
);
let place_with_id = PlaceWithHirId { place, ..*place_with_id };

if !self.capture_information.contains_key(&place_with_id.place) {
self.init_capture_info_for_place(place_with_id, diag_expr_id);
self.init_capture_info_for_place(&place_with_id, diag_expr_id);
}

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

if !self.capture_information.contains_key(&assignee_place.place) {
self.init_capture_info_for_place(assignee_place, diag_expr_id);
}

self.adjust_upvar_borrow_kind_for_mut(assignee_place, diag_expr_id);
self.borrow(assignee_place, diag_expr_id, ty::BorrowKind::MutBorrow);
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// check-pass

#![feature(capture_disjoint_fields)]
//~^ WARNING: the feature `capture_disjoint_fields` is incomplete

// Given how the closure desugaring is implemented (at least at the time of writing this test),
// we don't need to truncate the captured path to a reference into a packed-struct if the field
// being referenced will be moved into the closure, since it's safe to move out a field from a
// packed-struct.
//
// However to avoid surprises for the user, or issues when the closure is
// inlined we will truncate the capture to access just the struct regardless of if the field
// might get moved into the closure.
//
// It is possible for someone to try writing the code that relies on the desugaring to access a ref
// into a packed-struct without explicity using unsafe. Here we test that the compiler warns the
// user that such an access is still unsafe.
fn test_missing_unsafe_warning_on_repr_packed() {
#[repr(packed)]
struct Foo { x: String }

let foo = Foo { x: String::new() };

let c = || {
println!("{}", foo.x);
//~^ WARNING: borrow of packed field is unsafe and requires unsafe function or block
//~| WARNING: this was previously accepted by the compiler but is being phased out
let _z = foo.x;
};

c();
}

fn main() {
test_missing_unsafe_warning_on_repr_packed();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
warning: the feature `capture_disjoint_fields` is incomplete and may not be safe to use and/or cause compiler crashes
--> $DIR/repr_packed.rs:3:12
|
LL | #![feature(capture_disjoint_fields)]
| ^^^^^^^^^^^^^^^^^^^^^^^
|
= note: `#[warn(incomplete_features)]` on by default
= note: see issue #53488 <https://github.com/rust-lang/rust/issues/53488> for more information

warning: borrow of packed field is unsafe and requires unsafe function or block (error E0133)
--> $DIR/repr_packed.rs:25:24
|
LL | println!("{}", foo.x);
| ^^^^^
|
= note: `#[warn(safe_packed_borrows)]` on by default
= warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
= note: for more information, see issue #46043 <https://github.com/rust-lang/rust/issues/46043>
= note: fields of packed structs might be misaligned: dereferencing a misaligned pointer or even just creating a misaligned reference is undefined behavior

warning: 2 warnings emitted

103 changes: 103 additions & 0 deletions src/test/ui/closures/2229_closure_analysis/repr_packed.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
#![feature(capture_disjoint_fields)]
//~^ WARNING: the feature `capture_disjoint_fields` is incomplete
//~| `#[warn(incomplete_features)]` on by default
//~| see issue #53488 <https://github.com/rust-lang/rust/issues/53488>

#![feature(rustc_attrs)]

// `u8` aligned at a byte and are unaffected by repr(packed).
// Therefore we can precisely (and safely) capture references to both the fields.
fn test_alignment_not_affected() {
#[repr(packed)]
struct Foo { x: u8, y: u8 }

let mut foo = Foo { x: 0, y: 0 };

let mut c = #[rustc_capture_analysis]
//~^ ERROR: attributes on expressions are experimental
//~| NOTE: see issue #15701 <https://github.com/rust-lang/rust/issues/15701>
|| {
//~^ ERROR: First Pass analysis includes:
//~| ERROR: Min Capture analysis includes:
let z1: &u8 = &foo.x;
//~^ NOTE: Capturing foo[(0, 0)] -> ImmBorrow
//~| NOTE: Min Capture foo[(0, 0)] -> ImmBorrow
let z2: &mut u8 = &mut foo.y;
//~^ NOTE: Capturing foo[(1, 0)] -> MutBorrow
//~| NOTE: Min Capture foo[(1, 0)] -> MutBorrow

*z2 = 42;

println!("({}, {})", z1, z2);
};

c();
}

// `String`, `u16` are not aligned at a one byte boundry and are thus affected by repr(packed).
//
// Here we test that the closure doesn't capture a reference point to `foo.x` but
// rather capture `foo` entirely.
fn test_alignment_affected() {
#[repr(packed)]
struct Foo { x: String, y: u16 }

let mut foo = Foo { x: String::new(), y: 0 };

let mut c = #[rustc_capture_analysis]
//~^ ERROR: attributes on expressions are experimental
//~| NOTE: see issue #15701 <https://github.com/rust-lang/rust/issues/15701>
|| {
//~^ ERROR: First Pass analysis includes:
//~| ERROR: Min Capture analysis includes:
let z1: &String = &foo.x;
let z2: &mut u16 = &mut foo.y;
//~^ NOTE: Capturing foo[] -> MutBorrow
//~| NOTE: Min Capture foo[] -> MutBorrow


*z2 = 42;

println!("({}, {})", z1, z2);
};

c();
}

// Given how the closure desugaring is implemented (at least at the time of writing this test),
// we don't need to truncate the captured path to a reference into a packed-struct if the field
// being referenced will be moved into the closure, since it's safe to move out a field from a
// packed-struct.
//
// However to avoid surprises for the user, or issues when the closure is
// inlined we will truncate the capture to access just the struct regardless of if the field
// might get moved into the closure.
fn test_truncation_when_ref_and_move() {
#[repr(packed)]
struct Foo { x: String }

let mut foo = Foo { x: String::new() };

let c = #[rustc_capture_analysis]
//~^ ERROR: attributes on expressions are experimental
//~| NOTE: see issue #15701 <https://github.com/rust-lang/rust/issues/15701>
|| {
//~^ ERROR: First Pass analysis includes:
//~| ERROR: Min Capture analysis includes:
println!("{}", foo.x);
//~^ NOTE: Capturing foo[] -> ImmBorrow
//~| NOTE: Min Capture foo[] -> ByValue
//~| NOTE: foo[] used here
let _z = foo.x;
//~^ NOTE: Capturing foo[(0, 0)] -> ByValue
//~| NOTE: foo[] captured as ByValue here
};

c();
}

fn main() {
test_truncation_when_ref_and_move();
test_alignment_affected();
test_alignment_not_affected();
}
Loading