Skip to content

Commit 99baddb

Browse files
committedDec 15, 2020
Auto merge of #78068 - RalfJung:union-safe-assign, r=nikomatsakis
consider assignments of union field of ManuallyDrop type safe Assigning to `Copy` union fields is safe because that assignment will never drop anything. However, with #77547, unions may also have `ManuallyDrop` fields, and their assignments are currently still unsafe. That seems unnecessary though, as assigning `ManuallyDrop` does not drop anything either, and is thus safe even for union fields. I assume this will at least require FCP.
2 parents e99a89c + 0bb82c4 commit 99baddb

File tree

6 files changed

+172
-106
lines changed

6 files changed

+172
-106
lines changed
 

‎compiler/rustc_middle/src/mir/mod.rs

+15
Original file line numberDiff line numberDiff line change
@@ -1756,6 +1756,21 @@ impl<'tcx> Place<'tcx> {
17561756
pub fn as_ref(&self) -> PlaceRef<'tcx> {
17571757
PlaceRef { local: self.local, projection: &self.projection }
17581758
}
1759+
1760+
/// Iterate over the projections in evaluation order, i.e., the first element is the base with
1761+
/// its projection and then subsequently more projections are added.
1762+
/// As a concrete example, given the place a.b.c, this would yield:
1763+
/// - (a, .b)
1764+
/// - (a.b, .c)
1765+
/// Given a place without projections, the iterator is empty.
1766+
pub fn iter_projections(
1767+
self,
1768+
) -> impl Iterator<Item = (PlaceRef<'tcx>, PlaceElem<'tcx>)> + DoubleEndedIterator {
1769+
self.projection.iter().enumerate().map(move |(i, proj)| {
1770+
let base = PlaceRef { local: self.local, projection: &self.projection[..i] };
1771+
(base, proj)
1772+
})
1773+
}
17591774
}
17601775

17611776
impl From<Local> for Place<'_> {

‎compiler/rustc_middle/src/mir/query.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ pub enum UnsafetyViolationDetails {
4646
UseOfMutableStatic,
4747
UseOfExternStatic,
4848
DerefOfRawPointer,
49-
AssignToNonCopyUnionField,
49+
AssignToDroppingUnionField,
5050
AccessToUnionField,
5151
MutationOfLayoutConstrainedField,
5252
BorrowOfLayoutConstrainedField,
@@ -94,8 +94,8 @@ impl UnsafetyViolationDetails {
9494
"raw pointers may be NULL, dangling or unaligned; they can violate aliasing rules \
9595
and cause data races: all of these are undefined behavior",
9696
),
97-
AssignToNonCopyUnionField => (
98-
"assignment to non-`Copy` union field",
97+
AssignToDroppingUnionField => (
98+
"assignment to union field that might need dropping",
9999
"the previous content of the field will be dropped, which causes undefined \
100100
behavior if the field was not properly initialized",
101101
),

‎compiler/rustc_middle/src/mir/tcx.rs

+9
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,15 @@ impl<'tcx> Place<'tcx> {
136136
}
137137
}
138138

139+
impl<'tcx> PlaceRef<'tcx> {
140+
pub fn ty<D>(&self, local_decls: &D, tcx: TyCtxt<'tcx>) -> PlaceTy<'tcx>
141+
where
142+
D: HasLocalDecls<'tcx>,
143+
{
144+
Place::ty_from(self.local, &self.projection, local_decls, tcx)
145+
}
146+
}
147+
139148
pub enum RvalueInitializationState {
140149
Shallow,
141150
Deep,

‎compiler/rustc_mir/src/transform/check_unsafety.rs

+93-72
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,9 @@ impl<'a, 'tcx> Visitor<'tcx> for UnsafetyChecker<'a, 'tcx> {
181181
self.check_mut_borrowing_layout_constrained_field(*place, context.is_mutating_use());
182182
}
183183

184+
// Check for borrows to packed fields.
185+
// `is_disaligned` already traverses the place to consider all projections after the last
186+
// `Deref`, so this only needs to be called once at the top level.
184187
if context.is_borrow() {
185188
if util::is_disaligned(self.tcx, self.body, self.param_env, *place) {
186189
self.require_unsafe(
@@ -190,87 +193,105 @@ impl<'a, 'tcx> Visitor<'tcx> for UnsafetyChecker<'a, 'tcx> {
190193
}
191194
}
192195

193-
for (i, elem) in place.projection.iter().enumerate() {
194-
let proj_base = &place.projection[..i];
195-
if context.is_borrow() {
196-
if util::is_disaligned(self.tcx, self.body, self.param_env, *place) {
196+
// Some checks below need the extra metainfo of the local declaration.
197+
let decl = &self.body.local_decls[place.local];
198+
199+
// Check the base local: it might be an unsafe-to-access static. We only check derefs of the
200+
// temporary holding the static pointer to avoid duplicate errors
201+
// <https://github.com/rust-lang/rust/pull/78068#issuecomment-731753506>.
202+
if decl.internal && place.projection.first() == Some(&ProjectionElem::Deref) {
203+
// If the projection root is an artifical local that we introduced when
204+
// desugaring `static`, give a more specific error message
205+
// (avoid the general "raw pointer" clause below, that would only be confusing).
206+
if let Some(box LocalInfo::StaticRef { def_id, .. }) = decl.local_info {
207+
if self.tcx.is_mutable_static(def_id) {
197208
self.require_unsafe(
198-
UnsafetyViolationKind::BorrowPacked,
199-
UnsafetyViolationDetails::BorrowOfPackedField,
209+
UnsafetyViolationKind::General,
210+
UnsafetyViolationDetails::UseOfMutableStatic,
200211
);
212+
return;
213+
} else if self.tcx.is_foreign_item(def_id) {
214+
self.require_unsafe(
215+
UnsafetyViolationKind::General,
216+
UnsafetyViolationDetails::UseOfExternStatic,
217+
);
218+
return;
201219
}
202220
}
203-
let source_info = self.source_info;
204-
if let [] = proj_base {
205-
let decl = &self.body.local_decls[place.local];
206-
if decl.internal {
207-
// If the projection root is an artifical local that we introduced when
208-
// desugaring `static`, give a more specific error message
209-
// (avoid the general "raw pointer" clause below, that would only be confusing).
210-
if let Some(box LocalInfo::StaticRef { def_id, .. }) = decl.local_info {
211-
if self.tcx.is_mutable_static(def_id) {
212-
self.require_unsafe(
213-
UnsafetyViolationKind::General,
214-
UnsafetyViolationDetails::UseOfMutableStatic,
215-
);
216-
return;
217-
} else if self.tcx.is_foreign_item(def_id) {
218-
self.require_unsafe(
219-
UnsafetyViolationKind::General,
220-
UnsafetyViolationDetails::UseOfExternStatic,
221-
);
222-
return;
223-
}
224-
} else {
225-
// Internal locals are used in the `move_val_init` desugaring.
226-
// We want to check unsafety against the source info of the
227-
// desugaring, rather than the source info of the RHS.
228-
self.source_info = self.body.local_decls[place.local].source_info;
229-
}
221+
}
222+
223+
// Check for raw pointer `Deref`.
224+
for (base, proj) in place.iter_projections() {
225+
if proj == ProjectionElem::Deref {
226+
let source_info = self.source_info; // Backup source_info so we can restore it later.
227+
if base.projection.is_empty() && decl.internal {
228+
// Internal locals are used in the `move_val_init` desugaring.
229+
// We want to check unsafety against the source info of the
230+
// desugaring, rather than the source info of the RHS.
231+
self.source_info = self.body.local_decls[place.local].source_info;
232+
}
233+
let base_ty = base.ty(self.body, self.tcx).ty;
234+
if base_ty.is_unsafe_ptr() {
235+
self.require_unsafe(
236+
UnsafetyViolationKind::GeneralAndConstFn,
237+
UnsafetyViolationDetails::DerefOfRawPointer,
238+
)
230239
}
240+
self.source_info = source_info; // Restore backed-up source_info.
231241
}
232-
let base_ty = Place::ty_from(place.local, proj_base, self.body, self.tcx).ty;
233-
match base_ty.kind() {
234-
ty::RawPtr(..) => self.require_unsafe(
235-
UnsafetyViolationKind::GeneralAndConstFn,
236-
UnsafetyViolationDetails::DerefOfRawPointer,
237-
),
238-
ty::Adt(adt, _) => {
239-
if adt.is_union() {
240-
if context == PlaceContext::MutatingUse(MutatingUseContext::Store)
241-
|| context == PlaceContext::MutatingUse(MutatingUseContext::Drop)
242-
|| context == PlaceContext::MutatingUse(MutatingUseContext::AsmOutput)
243-
{
244-
let elem_ty = match elem {
245-
ProjectionElem::Field(_, ty) => ty,
246-
_ => span_bug!(
247-
self.source_info.span,
248-
"non-field projection {:?} from union?",
249-
place
250-
),
251-
};
252-
if !elem_ty.is_copy_modulo_regions(
253-
self.tcx.at(self.source_info.span),
254-
self.param_env,
255-
) {
256-
self.require_unsafe(
257-
UnsafetyViolationKind::GeneralAndConstFn,
258-
UnsafetyViolationDetails::AssignToNonCopyUnionField,
259-
)
260-
} else {
261-
// write to non-move union, safe
262-
}
263-
} else {
264-
self.require_unsafe(
265-
UnsafetyViolationKind::GeneralAndConstFn,
266-
UnsafetyViolationDetails::AccessToUnionField,
267-
)
268-
}
242+
}
243+
244+
// Check for union fields. For this we traverse right-to-left, as the last `Deref` changes
245+
// whether we *read* the union field or potentially *write* to it (if this place is being assigned to).
246+
let mut saw_deref = false;
247+
for (base, proj) in place.iter_projections().rev() {
248+
if proj == ProjectionElem::Deref {
249+
saw_deref = true;
250+
continue;
251+
}
252+
253+
let base_ty = base.ty(self.body, self.tcx).ty;
254+
if base_ty.ty_adt_def().map_or(false, |adt| adt.is_union()) {
255+
// If we did not hit a `Deref` yet and the overall place use is an assignment, the
256+
// rules are different.
257+
let assign_to_field = !saw_deref
258+
&& matches!(
259+
context,
260+
PlaceContext::MutatingUse(
261+
MutatingUseContext::Store
262+
| MutatingUseContext::Drop
263+
| MutatingUseContext::AsmOutput
264+
)
265+
);
266+
// If this is just an assignment, determine if the assigned type needs dropping.
267+
if assign_to_field {
268+
// We have to check the actual type of the assignment, as that determines if the
269+
// old value is being dropped.
270+
let assigned_ty = place.ty(&self.body.local_decls, self.tcx).ty;
271+
// To avoid semver hazard, we only consider `Copy` and `ManuallyDrop` non-dropping.
272+
let manually_drop = assigned_ty
273+
.ty_adt_def()
274+
.map_or(false, |adt_def| adt_def.is_manually_drop());
275+
let nodrop = manually_drop
276+
|| assigned_ty.is_copy_modulo_regions(
277+
self.tcx.at(self.source_info.span),
278+
self.param_env,
279+
);
280+
if !nodrop {
281+
self.require_unsafe(
282+
UnsafetyViolationKind::GeneralAndConstFn,
283+
UnsafetyViolationDetails::AssignToDroppingUnionField,
284+
);
285+
} else {
286+
// write to non-drop union field, safe
269287
}
288+
} else {
289+
self.require_unsafe(
290+
UnsafetyViolationKind::GeneralAndConstFn,
291+
UnsafetyViolationDetails::AccessToUnionField,
292+
)
270293
}
271-
_ => {}
272294
}
273-
self.source_info = source_info;
274295
}
275296
}
276297
}

‎src/test/ui/union/union-unsafe.rs

+24-3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1+
#![feature(untagged_unions)]
12
use std::mem::ManuallyDrop;
3+
use std::cell::RefCell;
24

35
union U1 {
46
a: u8
@@ -16,9 +18,28 @@ union U4<T: Copy> {
1618
a: T
1719
}
1820

21+
union URef {
22+
p: &'static mut i32,
23+
}
24+
25+
union URefCell { // field that does not drop but is not `Copy`, either
26+
a: (RefCell<i32>, i32),
27+
}
28+
29+
fn deref_union_field(mut u: URef) {
30+
// Not an assignment but an access to the union field!
31+
*(u.p) = 13; //~ ERROR access to union field is unsafe
32+
}
33+
34+
fn assign_noncopy_union_field(mut u: URefCell) {
35+
u.a = (RefCell::new(0), 1); //~ ERROR assignment to union field that might need dropping
36+
u.a.0 = RefCell::new(0); //~ ERROR assignment to union field that might need dropping
37+
u.a.1 = 1; // OK
38+
}
39+
1940
fn generic_noncopy<T: Default>() {
2041
let mut u3 = U3 { a: ManuallyDrop::new(T::default()) };
21-
u3.a = ManuallyDrop::new(T::default()); //~ ERROR assignment to non-`Copy` union field is unsafe
42+
u3.a = ManuallyDrop::new(T::default()); // OK (assignment does not drop)
2243
*u3.a = T::default(); //~ ERROR access to union field is unsafe
2344
}
2445

@@ -41,14 +62,14 @@ fn main() {
4162
// let U1 { .. } = u1; // OK
4263

4364
let mut u2 = U2 { a: ManuallyDrop::new(String::from("old")) }; // OK
44-
u2.a = ManuallyDrop::new(String::from("new")); //~ ERROR assignment to non-`Copy` union
65+
u2.a = ManuallyDrop::new(String::from("new")); // OK (assignment does not drop)
4566
*u2.a = String::from("new"); //~ ERROR access to union field is unsafe
4667

4768
let mut u3 = U3 { a: ManuallyDrop::new(0) }; // OK
4869
u3.a = ManuallyDrop::new(1); // OK
4970
*u3.a = 1; //~ ERROR access to union field is unsafe
5071

5172
let mut u3 = U3 { a: ManuallyDrop::new(String::from("old")) }; // OK
52-
u3.a = ManuallyDrop::new(String::from("new")); //~ ERROR assignment to non-`Copy` union
73+
u3.a = ManuallyDrop::new(String::from("new")); // OK (assignment does not drop)
5374
*u3.a = String::from("new"); //~ ERROR access to union field is unsafe
5475
}

‎src/test/ui/union/union-unsafe.stderr

+28-28
Original file line numberDiff line numberDiff line change
@@ -1,85 +1,85 @@
1-
error[E0133]: assignment to non-`Copy` union field is unsafe and requires unsafe function or block
2-
--> $DIR/union-unsafe.rs:21:5
1+
error[E0133]: access to union field is unsafe and requires unsafe function or block
2+
--> $DIR/union-unsafe.rs:31:5
3+
|
4+
LL | *(u.p) = 13;
5+
| ^^^^^^^^^^^ access to union field
6+
|
7+
= note: the field may not be properly initialized: using uninitialized data will cause undefined behavior
8+
9+
error[E0133]: assignment to union field that might need dropping is unsafe and requires unsafe function or block
10+
--> $DIR/union-unsafe.rs:35:5
311
|
4-
LL | u3.a = ManuallyDrop::new(T::default());
5-
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ assignment to non-`Copy` union field
12+
LL | u.a = (RefCell::new(0), 1);
13+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ assignment to union field that might need dropping
14+
|
15+
= note: the previous content of the field will be dropped, which causes undefined behavior if the field was not properly initialized
16+
17+
error[E0133]: assignment to union field that might need dropping is unsafe and requires unsafe function or block
18+
--> $DIR/union-unsafe.rs:36:5
19+
|
20+
LL | u.a.0 = RefCell::new(0);
21+
| ^^^^^^^^^^^^^^^^^^^^^^^ assignment to union field that might need dropping
622
|
723
= note: the previous content of the field will be dropped, which causes undefined behavior if the field was not properly initialized
824

925
error[E0133]: access to union field is unsafe and requires unsafe function or block
10-
--> $DIR/union-unsafe.rs:22:6
26+
--> $DIR/union-unsafe.rs:43:6
1127
|
1228
LL | *u3.a = T::default();
1329
| ^^^^ access to union field
1430
|
1531
= note: the field may not be properly initialized: using uninitialized data will cause undefined behavior
1632

1733
error[E0133]: access to union field is unsafe and requires unsafe function or block
18-
--> $DIR/union-unsafe.rs:28:6
34+
--> $DIR/union-unsafe.rs:49:6
1935
|
2036
LL | *u3.a = T::default();
2137
| ^^^^ access to union field
2238
|
2339
= note: the field may not be properly initialized: using uninitialized data will cause undefined behavior
2440

2541
error[E0133]: access to union field is unsafe and requires unsafe function or block
26-
--> $DIR/union-unsafe.rs:36:13
42+
--> $DIR/union-unsafe.rs:57:13
2743
|
2844
LL | let a = u1.a;
2945
| ^^^^ access to union field
3046
|
3147
= note: the field may not be properly initialized: using uninitialized data will cause undefined behavior
3248

3349
error[E0133]: access to union field is unsafe and requires unsafe function or block
34-
--> $DIR/union-unsafe.rs:39:14
50+
--> $DIR/union-unsafe.rs:60:14
3551
|
3652
LL | let U1 { a } = u1;
3753
| ^ access to union field
3854
|
3955
= note: the field may not be properly initialized: using uninitialized data will cause undefined behavior
4056

4157
error[E0133]: access to union field is unsafe and requires unsafe function or block
42-
--> $DIR/union-unsafe.rs:40:20
58+
--> $DIR/union-unsafe.rs:61:20
4359
|
4460
LL | if let U1 { a: 12 } = u1 {}
4561
| ^^ access to union field
4662
|
4763
= note: the field may not be properly initialized: using uninitialized data will cause undefined behavior
4864

49-
error[E0133]: assignment to non-`Copy` union field is unsafe and requires unsafe function or block
50-
--> $DIR/union-unsafe.rs:44:5
51-
|
52-
LL | u2.a = ManuallyDrop::new(String::from("new"));
53-
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ assignment to non-`Copy` union field
54-
|
55-
= note: the previous content of the field will be dropped, which causes undefined behavior if the field was not properly initialized
56-
5765
error[E0133]: access to union field is unsafe and requires unsafe function or block
58-
--> $DIR/union-unsafe.rs:45:6
66+
--> $DIR/union-unsafe.rs:66:6
5967
|
6068
LL | *u2.a = String::from("new");
6169
| ^^^^ access to union field
6270
|
6371
= note: the field may not be properly initialized: using uninitialized data will cause undefined behavior
6472

6573
error[E0133]: access to union field is unsafe and requires unsafe function or block
66-
--> $DIR/union-unsafe.rs:49:6
74+
--> $DIR/union-unsafe.rs:70:6
6775
|
6876
LL | *u3.a = 1;
6977
| ^^^^ access to union field
7078
|
7179
= note: the field may not be properly initialized: using uninitialized data will cause undefined behavior
7280

73-
error[E0133]: assignment to non-`Copy` union field is unsafe and requires unsafe function or block
74-
--> $DIR/union-unsafe.rs:52:5
75-
|
76-
LL | u3.a = ManuallyDrop::new(String::from("new"));
77-
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ assignment to non-`Copy` union field
78-
|
79-
= note: the previous content of the field will be dropped, which causes undefined behavior if the field was not properly initialized
80-
8181
error[E0133]: access to union field is unsafe and requires unsafe function or block
82-
--> $DIR/union-unsafe.rs:53:6
82+
--> $DIR/union-unsafe.rs:74:6
8383
|
8484
LL | *u3.a = String::from("new");
8585
| ^^^^ access to union field

0 commit comments

Comments
 (0)
Please sign in to comment.