Skip to content

Commit 81a769f

Browse files
committed
Auto merge of #75584 - RalfJung:union-no-deref, r=matthewjasper
do not apply DerefMut on union field This implements the part of [RFC 2514](https://github.com/rust-lang/rfcs/blob/master/text/2514-union-initialization-and-drop.md) about `DerefMut`. Unlike described in the RFC, we only apply this warning specifically when doing `DerefMut` of a `ManuallyDrop` field; that is really the case we are worried about here. @matthewjasper suggested I patch `convert_place_derefs_to_mutable` and `convert_place_op_to_mutable` for this, but I could not find anything to do in `convert_place_op_to_mutable` and this is sufficient to make the test pass. However, maybe there are some other cases this misses? I have no familiarity with this code. This is a breaking change *in theory*, if someone used `ManuallyDrop<T>` in a union field and relied on automatic `DerefMut`. But on stable this means `T: Copy`, so the `ManuallyDrop` is rather pointless. Cc #55149
2 parents 02fe309 + 66b340f commit 81a769f

File tree

3 files changed

+109
-2
lines changed

3 files changed

+109
-2
lines changed

compiler/rustc_typeck/src/check/place_op.rs

+25-2
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
193193
/// Convert auto-derefs, indices, etc of an expression from `Deref` and `Index`
194194
/// into `DerefMut` and `IndexMut` respectively.
195195
///
196-
/// This is a second pass of typechecking derefs/indices. We need this we do not
196+
/// This is a second pass of typechecking derefs/indices. We need this because we do not
197197
/// always know whether a place needs to be mutable or not in the first pass.
198198
/// This happens whether there is an implicit mutable reborrow, e.g. when the type
199199
/// is used as the receiver of a method call.
@@ -211,13 +211,21 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
211211
debug!("convert_place_derefs_to_mutable: exprs={:?}", exprs);
212212

213213
// Fix up autoderefs and derefs.
214+
let mut inside_union = false;
214215
for (i, &expr) in exprs.iter().rev().enumerate() {
215216
debug!("convert_place_derefs_to_mutable: i={} expr={:?}", i, expr);
216217

218+
let mut source = self.node_ty(expr.hir_id);
219+
if matches!(expr.kind, hir::ExprKind::Unary(hir::UnOp::UnDeref, _)) {
220+
// Clear previous flag; after a pointer indirection it does not apply any more.
221+
inside_union = false;
222+
}
223+
if source.ty_adt_def().map_or(false, |adt| adt.is_union()) {
224+
inside_union = true;
225+
}
217226
// Fix up the autoderefs. Autorefs can only occur immediately preceding
218227
// overloaded place ops, and will be fixed by them in order to get
219228
// the correct region.
220-
let mut source = self.node_ty(expr.hir_id);
221229
// Do not mutate adjustments in place, but rather take them,
222230
// and replace them after mutating them, to avoid having the
223231
// typeck results borrowed during (`deref_mut`) method resolution.
@@ -236,6 +244,21 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
236244
if let ty::Ref(region, _, mutbl) = *method.sig.output().kind() {
237245
*deref = OverloadedDeref { region, mutbl };
238246
}
247+
// If this is a union field, also throw an error for `DerefMut` of `ManuallyDrop` (see RFC 2514).
248+
// This helps avoid accidental drops.
249+
if inside_union
250+
&& source.ty_adt_def().map_or(false, |adt| adt.is_manually_drop())
251+
{
252+
let mut err = self.tcx.sess.struct_span_err(
253+
expr.span,
254+
"not automatically applying `DerefMut` on `ManuallyDrop` union field",
255+
);
256+
err.help(
257+
"writing to this reference calls the destructor for the old value",
258+
);
259+
err.help("add an explicit `*` if that is desired, or call `ptr::write` to not run the destructor");
260+
err.emit();
261+
}
239262
}
240263
}
241264
source = adjustment.target;

src/test/ui/union/union-deref.rs

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
// ignore-tidy-linelength
2+
//! Test the part of RFC 2514 that is about not applying `DerefMut` coercions
3+
//! of union fields.
4+
#![feature(untagged_unions)]
5+
6+
use std::mem::ManuallyDrop;
7+
8+
union U1<T> { x:(), f: ManuallyDrop<(T,)> }
9+
10+
union U2<T> { x:(), f: (ManuallyDrop<(T,)>,) }
11+
12+
fn main() {
13+
let mut u : U1<Vec<i32>> = U1 { x: () };
14+
unsafe { (*u.f).0 = Vec::new() }; // explicit deref, this compiles
15+
unsafe { u.f.0 = Vec::new() }; //~ERROR not automatically applying `DerefMut` on `ManuallyDrop` union field
16+
unsafe { &mut (*u.f).0 }; // explicit deref, this compiles
17+
unsafe { &mut u.f.0 }; //~ERROR not automatically applying `DerefMut` on `ManuallyDrop` union field
18+
unsafe { (*u.f).0.push(0) }; // explicit deref, this compiles
19+
unsafe { u.f.0.push(0) }; //~ERROR not automatically applying `DerefMut` on `ManuallyDrop` union field
20+
21+
let mut u : U2<Vec<i32>> = U2 { x: () };
22+
unsafe { (*u.f.0).0 = Vec::new() }; // explicit deref, this compiles
23+
unsafe { u.f.0.0 = Vec::new() }; //~ERROR not automatically applying `DerefMut` on `ManuallyDrop` union field
24+
unsafe { &mut (*u.f.0).0 }; // explicit deref, this compiles
25+
unsafe { &mut u.f.0.0 }; //~ERROR not automatically applying `DerefMut` on `ManuallyDrop` union field
26+
unsafe { (*u.f.0).0.push(0) }; // explicit deref, this compiles
27+
unsafe { u.f.0.0.push(0) }; //~ERROR not automatically applying `DerefMut` on `ManuallyDrop` union field
28+
}

src/test/ui/union/union-deref.stderr

+56
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
error: not automatically applying `DerefMut` on `ManuallyDrop` union field
2+
--> $DIR/union-deref.rs:15:14
3+
|
4+
LL | unsafe { u.f.0 = Vec::new() };
5+
| ^^^
6+
|
7+
= help: writing to this reference calls the destructor for the old value
8+
= help: add an explicit `*` if that is desired, or call `ptr::write` to not run the destructor
9+
10+
error: not automatically applying `DerefMut` on `ManuallyDrop` union field
11+
--> $DIR/union-deref.rs:17:19
12+
|
13+
LL | unsafe { &mut u.f.0 };
14+
| ^^^
15+
|
16+
= help: writing to this reference calls the destructor for the old value
17+
= help: add an explicit `*` if that is desired, or call `ptr::write` to not run the destructor
18+
19+
error: not automatically applying `DerefMut` on `ManuallyDrop` union field
20+
--> $DIR/union-deref.rs:19:14
21+
|
22+
LL | unsafe { u.f.0.push(0) };
23+
| ^^^
24+
|
25+
= help: writing to this reference calls the destructor for the old value
26+
= help: add an explicit `*` if that is desired, or call `ptr::write` to not run the destructor
27+
28+
error: not automatically applying `DerefMut` on `ManuallyDrop` union field
29+
--> $DIR/union-deref.rs:23:14
30+
|
31+
LL | unsafe { u.f.0.0 = Vec::new() };
32+
| ^^^^^^^
33+
|
34+
= help: writing to this reference calls the destructor for the old value
35+
= help: add an explicit `*` if that is desired, or call `ptr::write` to not run the destructor
36+
37+
error: not automatically applying `DerefMut` on `ManuallyDrop` union field
38+
--> $DIR/union-deref.rs:25:19
39+
|
40+
LL | unsafe { &mut u.f.0.0 };
41+
| ^^^^^^^
42+
|
43+
= help: writing to this reference calls the destructor for the old value
44+
= help: add an explicit `*` if that is desired, or call `ptr::write` to not run the destructor
45+
46+
error: not automatically applying `DerefMut` on `ManuallyDrop` union field
47+
--> $DIR/union-deref.rs:27:14
48+
|
49+
LL | unsafe { u.f.0.0.push(0) };
50+
| ^^^^^^^
51+
|
52+
= help: writing to this reference calls the destructor for the old value
53+
= help: add an explicit `*` if that is desired, or call `ptr::write` to not run the destructor
54+
55+
error: aborting due to 6 previous errors
56+

0 commit comments

Comments
 (0)