Skip to content

Commit 45e5a85

Browse files
authored
Rollup merge of rust-lang#56100 - RalfJung:visiting-generators, r=oli-obk
generator fields are not necessarily initialized Looking at the MIR we generate for generators, I think we deliberately leave fields of the generator uninitialized in ways that would be illegal if this was a normal struct (or rather, one would have to use `MaybeUninit`). Consider [this example](https://play.rust-lang.org/?version=nightly&mode=debug&edition=2018&gist=417b4a2950421b726dd7b307e9ee3bec): ```rust #![feature(generators, generator_trait)] fn main() { let generator = || { let mut x = Box::new(5); { let y = &mut *x; *y = 5; yield *y; *y = 10; } *x }; let _gen = generator; } ``` It generates the MIR ``` fn main() -> (){ let mut _0: (); // return place scope 1 { scope 3 { } scope 4 { let _2: [generator@src/main.rs:4:21: 13:6 for<'r> {std::boxed::Box<i32>, i32, &'r mut i32, ()}]; // "_gen" in scope 4 at src/main.rs:14:9: 14:13 } } scope 2 { let _1: [generator@src/main.rs:4:21: 13:6 for<'r> {std::boxed::Box<i32>, i32, &'r mut i32, ()}]; // "generator" in scope 2 at src/main.rs:4:9: 4:18 } bb0: { StorageLive(_1); // bb0[0]: scope 0 at src/main.rs:4:9: 4:18 (_1.0: u32) = const 0u32; // bb0[1]: scope 0 at src/main.rs:4:21: 13:6 // ty::Const // + ty: u32 // + val: Scalar(Bits { size: 4, bits: 0 }) // mir::Constant // + span: src/main.rs:4:21: 13:6 // + ty: u32 // + literal: Const { ty: u32, val: Scalar(Bits { size: 4, bits: 0 }) } StorageLive(_2); // bb0[2]: scope 1 at src/main.rs:14:9: 14:13 _2 = move _1; // bb0[3]: scope 1 at src/main.rs:14:16: 14:25 drop(_2) -> bb1; // bb0[4]: scope 1 at src/main.rs:15:1: 15:2 } bb1: { StorageDead(_2); // bb1[0]: scope 1 at src/main.rs:15:1: 15:2 StorageDead(_1); // bb1[1]: scope 0 at src/main.rs:15:1: 15:2 return; // bb1[2]: scope 0 at src/main.rs:15:2: 15:2 } } ``` Notice how we only initialize the first field of `_1` (even though it contains a `Box`!), and then assign it to `_2`. This violates the rule "on assignment, all data must satisfy the validity invariant", and hence miri complains about this code. What this PR effectively does is to change the validity invariant for generators such that it says nothing about the fields of the generator. We behave as if every field of the generator was wrapped in a `MaybeUninit`. r? @oli-obk Cc @nikomatsakis @eddyb @cramertj @withoutboats @Zoxc
2 parents ab5e45a + 6befe67 commit 45e5a85

File tree

1 file changed

+42
-12
lines changed

1 file changed

+42
-12
lines changed

src/librustc_mir/interpret/visitor.rs

+42-12
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,9 @@ macro_rules! make_value_visitor {
158158
) -> EvalResult<'tcx> {
159159
self.walk_aggregate(v, fields)
160160
}
161-
/// Called each time we recurse down to a field, passing in old and new value.
161+
162+
/// Called each time we recurse down to a field of a "product-like" aggregate
163+
/// (structs, tuples, arrays and the like, but not enums), passing in old and new value.
162164
/// This gives the visitor the chance to track the stack of nested fields that
163165
/// we are descending through.
164166
#[inline(always)]
@@ -171,6 +173,19 @@ macro_rules! make_value_visitor {
171173
self.visit_value(new_val)
172174
}
173175

176+
/// Called for recursing into the field of a generator. These are not known to be
177+
/// initialized, so we treat them like unions.
178+
#[inline(always)]
179+
fn visit_generator_field(
180+
&mut self,
181+
_old_val: Self::V,
182+
_field: usize,
183+
new_val: Self::V,
184+
) -> EvalResult<'tcx> {
185+
self.visit_union(new_val)
186+
}
187+
188+
/// Called when recursing into an enum variant.
174189
#[inline(always)]
175190
fn visit_variant(
176191
&mut self,
@@ -291,17 +306,33 @@ macro_rules! make_value_visitor {
291306
// use that as an unambiguous signal for detecting primitives. Make sure
292307
// we did not miss any primitive.
293308
debug_assert!(fields > 0);
294-
self.visit_union(v)?;
309+
self.visit_union(v)
295310
},
296311
layout::FieldPlacement::Arbitrary { ref offsets, .. } => {
297-
// FIXME: We collect in a vec because otherwise there are lifetime errors:
298-
// Projecting to a field needs (mutable!) access to `ecx`.
299-
let fields: Vec<EvalResult<'tcx, Self::V>> =
300-
(0..offsets.len()).map(|i| {
301-
v.project_field(self.ecx(), i as u64)
302-
})
303-
.collect();
304-
self.visit_aggregate(v, fields.into_iter())?;
312+
// Special handling needed for generators: All but the first field
313+
// (which is the state) are actually implicitly `MaybeUninit`, i.e.,
314+
// they may or may not be initialized, so we cannot visit them.
315+
match v.layout().ty.sty {
316+
ty::Generator(..) => {
317+
let field = v.project_field(self.ecx(), 0)?;
318+
self.visit_aggregate(v, std::iter::once(Ok(field)))?;
319+
for i in 1..offsets.len() {
320+
let field = v.project_field(self.ecx(), i as u64)?;
321+
self.visit_generator_field(v, i, field)?;
322+
}
323+
Ok(())
324+
}
325+
_ => {
326+
// FIXME: We collect in a vec because otherwise there are lifetime
327+
// errors: Projecting to a field needs access to `ecx`.
328+
let fields: Vec<EvalResult<'tcx, Self::V>> =
329+
(0..offsets.len()).map(|i| {
330+
v.project_field(self.ecx(), i as u64)
331+
})
332+
.collect();
333+
self.visit_aggregate(v, fields.into_iter())
334+
}
335+
}
305336
},
306337
layout::FieldPlacement::Array { .. } => {
307338
// Let's get an mplace first.
@@ -317,10 +348,9 @@ macro_rules! make_value_visitor {
317348
.map(|f| f.and_then(|f| {
318349
Ok(Value::from_mem_place(f))
319350
}));
320-
self.visit_aggregate(v, iter)?;
351+
self.visit_aggregate(v, iter)
321352
}
322353
}
323-
Ok(())
324354
}
325355
}
326356
}

0 commit comments

Comments
 (0)