diff --git a/src/analyze/basic_block.rs b/src/analyze/basic_block.rs index 835a6623..3a444aad 100644 --- a/src/analyze/basic_block.rs +++ b/src/analyze/basic_block.rs @@ -184,7 +184,7 @@ impl<'tcx, 'ctx> Analyzer<'tcx, 'ctx> { } else { rty }; - if self.is_mut_local(local) || rty.ty.is_mut() { + if self.is_mut_local(local) || self.env.type_contains_mut(&rty.ty) { self.env.mut_bind(local, rty); } else { self.env.immut_bind(local, rty); @@ -1278,7 +1278,7 @@ impl<'tcx, 'ctx> Analyzer<'tcx, 'ctx> { match bb_ty.param_kind(param_idx) { BasicBlockTypeParamKind::Local(local, _) => { if bb_ty.mutbl_of_param(param_idx).unwrap().is_mut() - || param_unrefined_rty.ty.is_mut() + || self.env.type_contains_mut(¶m_unrefined_rty.ty) { self.env.mut_bind(local, param_unrefined_rty); } else { diff --git a/src/refine/env.rs b/src/refine/env.rs index d9c3dfbc..89742ffb 100644 --- a/src/refine/env.rs +++ b/src/refine/env.rs @@ -1,4 +1,4 @@ -use std::collections::{BTreeMap, HashMap}; +use std::collections::{BTreeMap, HashMap, HashSet}; use pretty::{termcolor, Pretty}; use rustc_abi::{FieldIdx, VariantIdx}; @@ -1013,6 +1013,52 @@ where } } + /// Returns `true` if a `&mut` can be reached from a place of this type by + /// following tuple/struct field, pointer, and enum-variant projections, so + /// that a sub-place may be mut-borrowed even when the enclosing local is not + /// reassignable. Such a local must be given flow-decomposed bindings. + /// + /// This is the "needs decomposition" notion, decided purely from the type + /// (resolving enum variants through the enum definitions). It is independent + /// of whether the local is reassignable/mut-borrowed: that is the separate + /// concern handled by box-elaboration before binding. A bare `Box`/`own` is + /// not itself reborrowable, so it only matters here when it wraps a `&mut`. + pub fn type_contains_mut(&self, ty: &rty::Type) -> bool { + self.type_contains_mut_rec(ty, &mut HashSet::new()) + } + + fn type_contains_mut_rec( + &self, + ty: &rty::Type, + visiting: &mut HashSet, + ) -> bool { + match ty { + rty::Type::Pointer(ty) => { + ty.is_mut() || self.type_contains_mut_rec(&ty.elem.ty, visiting) + } + rty::Type::Tuple(ty) => ty + .elems + .iter() + .any(|elem| self.type_contains_mut_rec(&elem.ty, visiting)), + rty::Type::Enum(ty) => { + // Guard against recursive enum types; a back edge reaches no new + // `&mut` (any reachable one is found along a finite path). + if !visiting.insert(ty.symbol.clone()) { + return false; + } + let def = self.enum_defs.enum_def(&ty.symbol); + let found = def.field_tys().any(|field_ty| { + let mut field_ty = rty::RefinedType::unrefined(field_ty.clone().vacuous()); + field_ty.instantiate_ty_params(ty.args.clone()); + self.type_contains_mut_rec(&field_ty.ty, visiting) + }); + visiting.remove(&ty.symbol); + found + } + _ => false, + } + } + fn locate_place(&self, place: Place<'_>) -> Var { let mut var = place.local.into(); diff --git a/tests/ui/fail/reborrow_mut_field_of_aggregate_param.rs b/tests/ui/fail/reborrow_mut_field_of_aggregate_param.rs new file mode 100644 index 00000000..01e88f73 --- /dev/null +++ b/tests/ui/fail/reborrow_mut_field_of_aggregate_param.rs @@ -0,0 +1,19 @@ +//@error-in-other-file: Unsat + +// Regression test for #125: reborrowing a `&mut`-typed field out of an +// aggregate (tuple/struct) parameter used to panic with "deref unbound var" +// because the aggregate parameter was bound without flow bindings. + +fn bump(r: &mut i64) { + *r = 1; +} + +fn f(w: (&mut i64,)) { + bump(w.0); +} + +fn main() { + let mut x = 0_i64; + f((&mut x,)); + assert!(x == 0); +} diff --git a/tests/ui/fail/reborrow_mut_field_of_enum_param.rs b/tests/ui/fail/reborrow_mut_field_of_enum_param.rs new file mode 100644 index 00000000..8e5f39ea --- /dev/null +++ b/tests/ui/fail/reborrow_mut_field_of_enum_param.rs @@ -0,0 +1,28 @@ +//@error-in-other-file: Unsat + +// Companion to reborrow_mut_field_of_aggregate_param for enums: a `&mut` +// reachable through an enum variant. Exercises the enum arm of the +// "needs decomposition" check; binding must flow-decompose so the `&mut` +// pulled out of the enum can be reborrowed. + +enum E<'a> { + A(&'a mut i64), + B, +} + +fn bump(r: &mut i64) { + *r = 1; +} + +fn f(w: E) { + match w { + E::A(r) => bump(r), + E::B => {} + } +} + +fn main() { + let mut x = 0_i64; + f(E::A(&mut x)); + assert!(x == 0); +} diff --git a/tests/ui/pass/reborrow_mut_field_of_aggregate_param.rs b/tests/ui/pass/reborrow_mut_field_of_aggregate_param.rs new file mode 100644 index 00000000..910a9ba6 --- /dev/null +++ b/tests/ui/pass/reborrow_mut_field_of_aggregate_param.rs @@ -0,0 +1,19 @@ +//@check-pass + +// Regression test for #125: reborrowing a `&mut`-typed field out of an +// aggregate (tuple/struct) parameter used to panic with "deref unbound var" +// because the aggregate parameter was bound without flow bindings. + +fn bump(r: &mut i64) { + *r = 1; +} + +fn f(w: (&mut i64,)) { + bump(w.0); +} + +fn main() { + let mut x = 0_i64; + f((&mut x,)); + assert!(x == 1); +} diff --git a/tests/ui/pass/reborrow_mut_field_of_enum_param.rs b/tests/ui/pass/reborrow_mut_field_of_enum_param.rs new file mode 100644 index 00000000..c3d0c616 --- /dev/null +++ b/tests/ui/pass/reborrow_mut_field_of_enum_param.rs @@ -0,0 +1,28 @@ +//@check-pass + +// Companion to reborrow_mut_field_of_aggregate_param for enums: a `&mut` +// reachable through an enum variant. Exercises the enum arm of the +// "needs decomposition" check; binding must flow-decompose so the `&mut` +// pulled out of the enum can be reborrowed. + +enum E<'a> { + A(&'a mut i64), + B, +} + +fn bump(r: &mut i64) { + *r = 1; +} + +fn f(w: E) { + match w { + E::A(r) => bump(r), + E::B => {} + } +} + +fn main() { + let mut x = 0_i64; + f(E::A(&mut x)); + assert!(x == 1); +}