Auto merge of #82878 - sexxi-goose:repr_packed, r=nikomatsakis

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 where we decided to restrict precision:
```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: https://github.com/rust-lang/project-rfc-2229/issues/33

r? `@nikomatsakis`
This commit is contained in:
bors 2021-03-13 09:47:07 +00:00
commit 178bd9130e
5 changed files with 381 additions and 6 deletions

View file

@ -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();
}

View file

@ -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

View file

@ -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();
}

View file

@ -0,0 +1,165 @@
error[E0658]: attributes on expressions are experimental
--> $DIR/repr_packed.rs:16:17
|
LL | let mut c = #[rustc_capture_analysis]
| ^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: see issue #15701 <https://github.com/rust-lang/rust/issues/15701> for more information
= help: add `#![feature(stmt_expr_attributes)]` to the crate attributes to enable
error[E0658]: attributes on expressions are experimental
--> $DIR/repr_packed.rs:47:17
|
LL | let mut c = #[rustc_capture_analysis]
| ^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: see issue #15701 <https://github.com/rust-lang/rust/issues/15701> for more information
= help: add `#![feature(stmt_expr_attributes)]` to the crate attributes to enable
error[E0658]: attributes on expressions are experimental
--> $DIR/repr_packed.rs:81:13
|
LL | let c = #[rustc_capture_analysis]
| ^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: see issue #15701 <https://github.com/rust-lang/rust/issues/15701> for more information
= help: add `#![feature(stmt_expr_attributes)]` to the crate attributes to enable
warning: the feature `capture_disjoint_fields` is incomplete and may not be safe to use and/or cause compiler crashes
--> $DIR/repr_packed.rs:1: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
error: First Pass analysis includes:
--> $DIR/repr_packed.rs:19:5
|
LL | / || {
LL | |
LL | |
LL | | let z1: &u8 = &foo.x;
... |
LL | | println!("({}, {})", z1, z2);
LL | | };
| |_____^
|
note: Capturing foo[(0, 0)] -> ImmBorrow
--> $DIR/repr_packed.rs:22:24
|
LL | let z1: &u8 = &foo.x;
| ^^^^^
note: Capturing foo[(1, 0)] -> MutBorrow
--> $DIR/repr_packed.rs:25:32
|
LL | let z2: &mut u8 = &mut foo.y;
| ^^^^^
error: Min Capture analysis includes:
--> $DIR/repr_packed.rs:19:5
|
LL | / || {
LL | |
LL | |
LL | | let z1: &u8 = &foo.x;
... |
LL | | println!("({}, {})", z1, z2);
LL | | };
| |_____^
|
note: Min Capture foo[(0, 0)] -> ImmBorrow
--> $DIR/repr_packed.rs:22:24
|
LL | let z1: &u8 = &foo.x;
| ^^^^^
note: Min Capture foo[(1, 0)] -> MutBorrow
--> $DIR/repr_packed.rs:25:32
|
LL | let z2: &mut u8 = &mut foo.y;
| ^^^^^
error: First Pass analysis includes:
--> $DIR/repr_packed.rs:50:5
|
LL | / || {
LL | |
LL | |
LL | | let z1: &String = &foo.x;
... |
LL | | println!("({}, {})", z1, z2);
LL | | };
| |_____^
|
note: Capturing foo[] -> MutBorrow
--> $DIR/repr_packed.rs:54:33
|
LL | let z2: &mut u16 = &mut foo.y;
| ^^^^^
error: Min Capture analysis includes:
--> $DIR/repr_packed.rs:50:5
|
LL | / || {
LL | |
LL | |
LL | | let z1: &String = &foo.x;
... |
LL | | println!("({}, {})", z1, z2);
LL | | };
| |_____^
|
note: Min Capture foo[] -> MutBorrow
--> $DIR/repr_packed.rs:54:33
|
LL | let z2: &mut u16 = &mut foo.y;
| ^^^^^
error: First Pass analysis includes:
--> $DIR/repr_packed.rs:84:5
|
LL | / || {
LL | |
LL | |
LL | | println!("{}", foo.x);
... |
LL | |
LL | | };
| |_____^
|
note: Capturing foo[] -> ImmBorrow
--> $DIR/repr_packed.rs:87:24
|
LL | println!("{}", foo.x);
| ^^^^^
note: Capturing foo[(0, 0)] -> ByValue
--> $DIR/repr_packed.rs:91:18
|
LL | let _z = foo.x;
| ^^^^^
error: Min Capture analysis includes:
--> $DIR/repr_packed.rs:84:5
|
LL | / || {
LL | |
LL | |
LL | | println!("{}", foo.x);
... |
LL | |
LL | | };
| |_____^
|
note: Min Capture foo[] -> ByValue
--> $DIR/repr_packed.rs:87:24
|
LL | println!("{}", foo.x);
| ^^^^^ foo[] used here
...
LL | let _z = foo.x;
| ^^^^^ foo[] captured as ByValue here
error: aborting due to 9 previous errors; 1 warning emitted
For more information about this error, try `rustc --explain E0658`.