From 51faae1c8bd02c1dfa0a345f92788934cc62cd8c Mon Sep 17 00:00:00 2001 From: "Felix S. Klock II" Date: Wed, 6 Sep 2017 18:46:28 +0200 Subject: [PATCH 1/8] Encoded versions of dangly-path examples. --- test/dangly-path-1.nll | 37 +++++++++++++++++++++++++++++++++++++ test/dangly-path-2a.nll | 38 ++++++++++++++++++++++++++++++++++++++ test/dangly-path-2b.nll | 36 ++++++++++++++++++++++++++++++++++++ test/dangly-path-3a.nll | 40 ++++++++++++++++++++++++++++++++++++++++ test/dangly-path-3b.nll | 29 +++++++++++++++++++++++++++++ test/dangly-path-4.nll | 41 +++++++++++++++++++++++++++++++++++++++++ test/dangly-path-5.nll | 37 +++++++++++++++++++++++++++++++++++++ 7 files changed, 258 insertions(+) create mode 100644 test/dangly-path-1.nll create mode 100644 test/dangly-path-2a.nll create mode 100644 test/dangly-path-2b.nll create mode 100644 test/dangly-path-3a.nll create mode 100644 test/dangly-path-3b.nll create mode 100644 test/dangly-path-4.nll create mode 100644 test/dangly-path-5.nll diff --git a/test/dangly-path-1.nll b/test/dangly-path-1.nll new file mode 100644 index 0000000..784cf64 --- /dev/null +++ b/test/dangly-path-1.nll @@ -0,0 +1,37 @@ +// fn allowed_and_sound<'long>(a_box: Box<&'long mut Type>) { +// let borrow: &'long mut Type; +// { +// let tmp = a_box; +// borrow = &mut **tmp; // Know `borrow` must have sole access to `**tmp` here +// +// } // `tmp` dropped here +// +// // `*borrow` is valid (since `'long` outlives invocation), and we know +// // destroying `tmp` (a Box) did not touch data behind the reference. +// println!("borrow; {:?}", borrow); +// } + +struct Type { } + +struct Box { + dummy: 0 +} + +let a_box: Box<&'long mut Type>; +let borrow: &'func mut Type; + +block START { + borrow = &'_ mut *a_box.dummy; + + drop(a_box); + StorageDead(a_box); + + goto REBORROW_USE; +} + +block REBORROW_USE { + use(*borrow); +} + +assert borrow live at REBORROW_USE; +assert a_box not live at REBORROW_USE; diff --git a/test/dangly-path-2a.nll b/test/dangly-path-2a.nll new file mode 100644 index 0000000..2d9048b --- /dev/null +++ b/test/dangly-path-2a.nll @@ -0,0 +1,38 @@ +// struct PoisonOnDrop<'a>(&'a mut Type); +// +// fn should_be_disallowed_1<'long>(impure_drop: PoisonOnDrop<'long>) { +// let borrow: &'long mut Type; +// { +// let tmp = impure_drop; +// borrow = &mut *tmp.0; // `borrow` must have sole access to `*tmp.0` here +// +// } // but now dropping `tmp` *does* access `*tmp.0` +// println!("borrow: {:?}", borrow); +// } + +struct Type { } + +// Notes: +// * invariant ('=) because its `&mut` (probably irrelevant to our concerns here) +// * lack of may_dangle implies destructor may dereference self.dummy + +struct PoisonOnDrop<'=> { + ptr: &'0 mut Type +} + +let impure_drop: PoisonOnDrop<'long>; +let borrow: &'func mut Type; + +block START { + borrow = &'_ mut *impure_drop.ptr; + + drop(impure_drop); //! ERROR + StorageDead(impure_drop); + + goto REBORROW_USE; +} + +block REBORROW_USE { + use(*borrow); + StorageDead(borrow); +} diff --git a/test/dangly-path-2b.nll b/test/dangly-path-2b.nll new file mode 100644 index 0000000..bc9ac96 --- /dev/null +++ b/test/dangly-path-2b.nll @@ -0,0 +1,36 @@ +// fn should_be_disallowed_2<'long>(impure_drop: PoisonOnDrop<'long>) { +// let borrow: &'long mut Type; +// { +// let tmp = impure_drop; +// borrow = &*tmp.0; // `borrow` wants shared access to `*tmp.0` +// +// } // but dropping `tmp` mutates `*tmp.0` +// println!("borrow: {:?}", borrow); +// } + +struct Type { } + +// Notes: +// * invariant ('=) because its `&mut` (probably irrelevant to our concerns here) +// * lack of may_dangle implies destructor may dereference self.dummy + +struct PoisonOnDrop<'=> { + dummy: &'0 mut Type +} + +let impure_drop: PoisonOnDrop<'long>; +let borrow: &'func Type; + +block START { + borrow = &'_ *impure_drop.dummy; + + drop(impure_drop); //! ERROR + StorageDead(impure_drop); + + goto REBORROW_USE; +} + +block REBORROW_USE { + use(*borrow); + StorageDead(borrow); +} diff --git a/test/dangly-path-3a.nll b/test/dangly-path-3a.nll new file mode 100644 index 0000000..7b854f1 --- /dev/null +++ b/test/dangly-path-3a.nll @@ -0,0 +1,40 @@ +// struct MessageOnDrop1<'a>(&'a mut Type); +// +// unsafe impl<#[may_dangle] 'a> Drop for MessageOnDrop1<'a> { +// fn drop(&mut self) { +// println!("dropping but not derefing"); +// } +// } +// +// fn but_allow_dangling_lifetimes<'long>(pure_drop: MessageOnDrop1<'long>) { +// let borrow: &'long mut Type; +// { +// let tmp = pure_drop; +// borrow = &mut *tmp.0; // `borrow` must have sole access to `*tmp.0` here +// +// } // dropping `tmp` will not access `*tmp.0` +// println!("borrow: {:?}", borrow); +// } + +struct Type { } + +struct MessageOnDrop { + ptr: &'0 mut Type +} + +let pure_drop: MessageOnDrop<'long>; +let borrow: &'func mut Type; + +block START { + borrow = &'_ mut *pure_drop.ptr; + + drop(pure_drop); + StorageDead(pure_drop); + + goto REBORROW_USE; +} + +block REBORROW_USE { + use(*borrow); + StorageDead(borrow); +} diff --git a/test/dangly-path-3b.nll b/test/dangly-path-3b.nll new file mode 100644 index 0000000..598a1ee --- /dev/null +++ b/test/dangly-path-3b.nll @@ -0,0 +1,29 @@ +// This is a variation on dangly-path-3a that has a use of `pure_drop` +// instead of a drop. This is just a double-check that whatever I do +// to let the drops through does not also let the uses through too, +// since uses do not provide the reasoning about "purity" +// (parametricity? dangliness?) that is provided to drop via +// #[may_dangle]. + +struct Type { } + +struct MessageOnDrop { + ptr: &'0 mut Type +} + +let pure_drop: MessageOnDrop<'long>; +let borrow: &'func mut Type; + +block START { + borrow = &'_ mut *pure_drop.ptr; + + use(pure_drop); //! ERROR + StorageDead(pure_drop); + + goto REBORROW_USE; +} + +block REBORROW_USE { + use(*borrow); + StorageDead(borrow); +} diff --git a/test/dangly-path-4.nll b/test/dangly-path-4.nll new file mode 100644 index 0000000..aa68078 --- /dev/null +++ b/test/dangly-path-4.nll @@ -0,0 +1,41 @@ +// struct MessageOnDrop2(T); +// +// unsafe impl<#[may_dangle] T> Drop for MessageOnDrop { +// fn drop(&mut self) { +// println!("dropping but not derefing"); +// } +// } +// +// fn and_also_allow_dangling_types<'long>(pure_drop: MessageOnDrop2<&'long mut Type>) { +// let borrow: &'long mut Type; +// { +// let tmp = pure_drop; +// borrow = &mut *tmp.0; // `borrow` must have sole access to `*tmp.0` here +// +// } // dropping `tmp` will not access `*tmp.0` +// println!("borrow: {:?}", borrow); +// } + +struct Type { } + +// FIXME: should this be invariant instead of co-variant? +struct MessageOnDrop { + val: 0 +} + +let pure_drop: MessageOnDrop<&'long mut Type>; +let borrow: &'func mut Type; + +block START { + borrow = &'_ mut *pure_drop.val; + + drop(pure_drop); + StorageDead(pure_drop); + + goto REBORROW_USE; +} + +block REBORROW_USE { + use(*borrow); + StorageDead(borrow); +} diff --git a/test/dangly-path-5.nll b/test/dangly-path-5.nll new file mode 100644 index 0000000..9f3e320 --- /dev/null +++ b/test/dangly-path-5.nll @@ -0,0 +1,37 @@ +// fn still_reject_this<'long>(eventually_impure: MessageOnDrop2) { +// let borrow: &'long mut Type; +// { +// let tmp = pure_drop; +// borrow = &mut *(tmp.0).0; // `borrow` must have sole access to `*(tmp.0).0` here +// +// } // dropping `tmp` mutates `*(tmp.0).0` +// println!("borrow: {:?}", borrow); +// } + +struct Type { } + +// FIXME: should this be invariant instead of co-variant? +struct MessageOnDrop { + val: 0 +} + +struct PoisonOnDrop<'=> { + ptr: &'0 mut Type +} + +let eventually_impure: MessageOnDrop>; +let borrow: &'func mut Type; + +block START { + borrow = &'_ mut *(eventually_impure.val).ptr; + + drop(eventually_impure); //! ERROR + StorageDead(eventually_impure); + + goto REBORROW_USE; +} + +block REBORROW_USE { + use(*borrow); + StorageDead(borrow); +} From b82acf77df59cce26d70820a29102cc0295ab187 Mon Sep 17 00:00:00 2001 From: "Felix S. Klock II" Date: Wed, 6 Sep 2017 20:03:07 +0200 Subject: [PATCH 2/8] Refactoring: cloned old `fn check_move` method to `fn check_drop`, and call *that* from the `drop` NLL statement. No observable behavior change otherwise (apart from error messages now say "cannot drop" instead of "cannot move"), but the doc comment indicates the plans I have (in terms of incorporating `may_dangle` support). --- nll/src/borrowck.rs | 61 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 60 insertions(+), 1 deletion(-) diff --git a/nll/src/borrowck.rs b/nll/src/borrowck.rs index 05b6acc..bf79a7b 100644 --- a/nll/src/borrowck.rs +++ b/nll/src/borrowck.rs @@ -65,7 +65,7 @@ impl<'cx> BorrowCheck<'cx> { self.check_read(p)?; } repr::ActionKind::Drop(ref p) => { - self.check_move(p)?; + self.check_drop(p)?; } repr::ActionKind::StorageDead(p) => { self.check_storage_dead(p)?; @@ -133,6 +133,47 @@ impl<'cx> BorrowCheck<'cx> { Ok(()) } + /// Cannot drop (*) for a path `p` if: + /// - `p` is borrowed; + /// - some subpath `p.foo` is borrowed (unless *every* projection + /// for the subpath is may_dangle) + /// - some prefix of `p` is borrowed + /// + /// Note that the above disjunction is stricter than both *writes* + /// and *storage-dead*. In particular, you **can** write to a variable + /// `x` that contains an `&mut value when `*x` is borrowed, but you + /// **cannot** drop `x`. This is because the drop may run a destructor + /// that could subsequently access `*x` via the variable. + /// + /// (On the other hand, `may_dangle` throws a wrench into the + /// reasoning above. Namely, even if `*x` is borrowed, you still + /// **can** drop `x` that contains a `&'l mut value` where + /// `may_dangle 'l`, because that serves as a flag that the + /// destructor is not allowed to access the data behind any + /// reference of lifetime `'l`.) + /// + /// (*): to drop is to check the initialization-flag (be it static + /// or dynamic), and run all destructors recursively if + /// initialized) + fn check_drop(&self, path: &repr::Path) -> Result<(), Box> { + log!( + "check_drop of {:?} at {:?} with loans={:#?}", + path, + self.point, + self.loans + ); + for loan in self.find_loans_that_intersect(path) { + return Err(Box::new(BorrowError::for_drop( + self.point, + path, + &loan.path, + loan.point, + ))); + } + Ok(()) + } + + #[cfg(not_now)] /// Cannot move from a path `p` if: /// - `p` is borrowed; /// - some subpath `p.foo` is borrowed; @@ -285,6 +326,24 @@ impl BorrowError { } } + fn for_drop( + point: Point, + path: &repr::Path, + loan_path: &repr::Path, + loan_point: Point, + ) -> Self { + BorrowError { + description: format!( + "point {:?} cannot drop {:?} because {:?} is borrowed (at point `{:?}`)", + point, + path, + loan_path, + loan_point + ), + } + } + + #[cfg(not_now)] fn for_move( point: Point, path: &repr::Path, From 0a7af36269b7e636415c79449d8202a447667225 Mon Sep 17 00:00:00 2001 From: "Felix S. Klock II" Date: Wed, 6 Sep 2017 20:35:38 +0200 Subject: [PATCH 3/8] Added `impl fmt::Display for Path` so path formats can resemble source code. --- nll-repr/src/repr/mod.rs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/nll-repr/src/repr/mod.rs b/nll-repr/src/repr/mod.rs index 34e03ae..fda141a 100644 --- a/nll-repr/src/repr/mod.rs +++ b/nll-repr/src/repr/mod.rs @@ -306,6 +306,21 @@ impl Path { } } +impl fmt::Display for Path { + fn fmt(&self, w: &mut fmt::Formatter) -> fmt::Result { + match *self { + Path::Var(ref var) => + write!(w, "{}", var.name), + Path::Extension(ref path, ref field_name) => + if field_name.name == intern::intern("*") { + write!(w, "*{}", path) + } else { + write!(w, "{}.{}", path, field_name.name) + }, + } + } +} + #[derive(Clone, Debug, Hash, PartialEq, Eq)] pub enum Constraint { ForAll(Vec, Box), From f107cbec7fd7702afcb0a6e34ee264eb360e6510 Mon Sep 17 00:00:00 2001 From: "Felix S. Klock II" Date: Wed, 6 Sep 2017 20:40:42 +0200 Subject: [PATCH 4/8] Switch to `fmt::Display` formatting of paths in errors emitted by check_drop. --- nll/src/borrowck.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nll/src/borrowck.rs b/nll/src/borrowck.rs index bf79a7b..0296a0c 100644 --- a/nll/src/borrowck.rs +++ b/nll/src/borrowck.rs @@ -334,7 +334,7 @@ impl BorrowError { ) -> Self { BorrowError { description: format!( - "point {:?} cannot drop {:?} because {:?} is borrowed (at point `{:?}`)", + "point {:?} cannot drop `{}` because `{}` is borrowed (at point `{:?}`)", point, path, loan_path, From 5cefe1a8d612898f91e36cf39218bc7b97773d26 Mon Sep 17 00:00:00 2001 From: "Felix S. Klock II" Date: Mon, 11 Sep 2017 15:03:50 +0200 Subject: [PATCH 5/8] Factor out a `StructDecl::field_decl` method for `FieldDecl` lookup. --- nll-repr/src/repr/mod.rs | 9 +++++++++ nll/src/env.rs | 6 +----- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/nll-repr/src/repr/mod.rs b/nll-repr/src/repr/mod.rs index fda141a..019aeae 100644 --- a/nll-repr/src/repr/mod.rs +++ b/nll-repr/src/repr/mod.rs @@ -57,6 +57,15 @@ pub struct StructDecl { pub fields: Vec, } +impl StructDecl { + pub fn field_decl(&self, field_name: &FieldName) -> &FieldDecl { + self.fields + .iter() + .find(|fd| fd.name == *field_name) + .unwrap_or_else(|| panic!("no field named `{:?}` in `{:?}`", field_name, self)) + } +} + #[derive(Clone, Debug, Hash, PartialEq, Eq)] pub struct FieldDecl { pub name: FieldName, diff --git a/nll/src/env.rs b/nll/src/env.rs index 7430b87..50b0136 100644 --- a/nll/src/env.rs +++ b/nll/src/env.rs @@ -139,11 +139,7 @@ impl<'func> Environment<'func> { repr::Ty::Struct(n, ref parameters) => { let struct_decl = self.struct_map[&n]; - let field_decl = struct_decl - .fields - .iter() - .find(|fd| fd.name == field_name) - .unwrap_or_else(|| panic!("no field named `{:?}` in `{:?}`", field_name, n)); + let field_decl = struct_decl.field_decl(&field_name); let field_ty = &field_decl.ty; log!( "field_ty: field_ty={:?} parameters={:?}", From 560e9e54aac64004943dd6776cf8ce5860c1af57 Mon Sep 17 00:00:00 2001 From: "Felix S. Klock II" Date: Tue, 12 Sep 2017 12:03:36 +0200 Subject: [PATCH 6/8] `fmt::Display` impl for `StructName` that just writes name (like `RegionName` and `FieldName`). --- nll-repr/src/repr/mod.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/nll-repr/src/repr/mod.rs b/nll-repr/src/repr/mod.rs index 019aeae..17f450d 100644 --- a/nll-repr/src/repr/mod.rs +++ b/nll-repr/src/repr/mod.rs @@ -121,6 +121,12 @@ pub struct StructName { name: InternedString } +impl fmt::Display for StructName { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + write!(fmt, "{}", self.name) + } +} + #[derive(Clone, Debug, Hash, PartialEq, Eq)] pub enum Ty { Ref(Region, BorrowKind, Box), From 09acae9610967e6db4bcad0d24f009492ee18876 Mon Sep 17 00:00:00 2001 From: "Felix S. Klock II" Date: Tue, 12 Sep 2017 12:38:01 +0200 Subject: [PATCH 7/8] A fixme to investigate in the future. --- nll/src/borrowck.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/nll/src/borrowck.rs b/nll/src/borrowck.rs index 0296a0c..b9805cd 100644 --- a/nll/src/borrowck.rs +++ b/nll/src/borrowck.rs @@ -41,6 +41,8 @@ enum Mode { impl<'cx> BorrowCheck<'cx> { fn check_action(&self, action: &repr::Action) -> Result<(), Box> { log!("check_action({:?}) at {:?}", action, self.point); + // FIXME: should check_read in Init and Assign use check_move + // instead of (or in addition to) check_read? match action.kind { repr::ActionKind::Init(ref a, ref bs) => { self.check_shallow_write(a)?; From 2d603856b53873e4432834b9f512d7e4c1bd6d4e Mon Sep 17 00:00:00 2001 From: "Felix S. Klock II" Date: Tue, 12 Sep 2017 12:38:36 +0200 Subject: [PATCH 8/8] The actual dangly-path analysis. With this in place, all tests pass. --- nll/src/borrowck.rs | 210 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 210 insertions(+) diff --git a/nll/src/borrowck.rs b/nll/src/borrowck.rs index b9805cd..7f175b9 100644 --- a/nll/src/borrowck.rs +++ b/nll/src/borrowck.rs @@ -165,6 +165,9 @@ impl<'cx> BorrowCheck<'cx> { self.loans ); for loan in self.find_loans_that_intersect(path) { + if loan.path.may_dangle_with_respect_to(self.env, path) { + continue; + } return Err(Box::new(BorrowError::for_drop( self.point, path, @@ -316,6 +319,213 @@ impl<'cx> BorrowCheck<'cx> { } } +// Extension trait to attach dangly-path analysis. +// +// `p` *may dangle with respect to* `r` when a borrow of p can remain +// valid across drop(r), because the destructor for `r` guarantees it +// will not touch the data behind such a borrow. +// +// (Methods are documented with its impl.) +trait MayDangle { + fn may_dangle_with_respect_to(&self, + env: &Environment, + root: &repr::Path) + -> bool; + + fn inner_deref_nearest<'a>(&'a self, + root: &repr::Path) + -> Option<&'a repr::Path>; + + fn all_allow_dangling_up_to(&self, + env: &Environment, + root: &repr::Path) + -> bool; +} + +impl MayDangle for repr::Path { + // Main entry point; true only if this path may dangle with + // respect to `root`. + fn may_dangle_with_respect_to(&self, + env: &Environment, + root: &repr::Path) + -> bool + { + let path = self; + + // Determine if `p` may dangle w.r.t. `root`: identify path + // `loc` such that `p` extends `*loc`, `*loc` extends `root`, + // and all structs on path from `root` to `*loc` allow + // dangling. + + // Since we require all structured data along path to `*loc` + // to allow dangling, we can without loss of generality + // *solely* consider the `*loc` closest to the root. + // + // For example, given root `r` and path `(*(*r.f1).f2).f3` + // (aka `p`), if we determine that `*r.f1` (= `*loc`) may + // dangle with respect to `r`, then dropping `r` is guaranteed + // not to access state behind `*r.f1`, which will include all + // of `(*r.f1).f2`, `*(*r.f1).f2`, and `(*(*r.f1).f2).f3`. + // + // Therefore need only consider the loc `r.f1` to know `p` may + // dangle with respect to `r`. + let loc = match path.inner_deref_nearest(root) { + // This is nearest deref we saw before we hit root. + Some(loc) => loc, + + // Either (1.) we never saw deref or (2.) `path` is not an + // extension of `root`; cannot be a dangly path. + None => return false, + }; + + // At this point we have a candidate `*loc` where + // `p` extends `*loc` and `*loc` extends `r`. + // + // Need to confirm that every destructor up to and including + // the destructor of root allows `*loc` to dangle. + + return loc.all_allow_dangling_up_to(env, root); + } + + // Remainder are helper methods for doing dangly-path analysis. + + // `p.inner_deref_nearest(r)` returns `loc` such that `p` extends + // `*loc`, `*loc` extends `r`, and there are no remaining derefs + // between `*loc` and `r`. + fn inner_deref_nearest<'a>(&'a self, + root: &repr::Path) + -> Option<&'a repr::Path> { + let mut path = self; + let mut cand_loc: Option<&repr::Path> = None; + loop { + if path == root { + return cand_loc; + } else { + match *path { + repr::Path::Extension(ref p, ref field_name) => { + if field_name == &repr::FieldName::star() { + cand_loc = Some(p); + } + path = p; + continue; + } + repr::Path::Var(_) => { + // If we hit `Var`, then `path` does not extend `root`. + return None; + } + } + } + } + } + + // Here, `self` is the candidate loc. + // + // `loc.all_allow_dangling_up_to(env, r)` is true only if path + // from `r` to `loc` has no destructors that could access state of + // `loc`. + fn all_allow_dangling_up_to(&self, + env: &Environment, + root: &repr::Path) + -> bool { + let loc = self; + let mut b = loc.clone(); + loop { + let next_b; // (lexical lifetimes force updating `b` outside `match`) + match b { + repr::Path::Var(_) => { + // all destructors we encountered allowed `loc` to + // dangle. As a sanity check, ensure `loc` extends root. + assert_eq!(b, *root); + return true; + } + repr::Path::Extension(ref p, ref field_name) => { + // We know this is a non-deref extension, since + // `*loc` is the deref extension nearest to + // `root`. + assert_ne!(field_name, &repr::FieldName::star()); + + // On any extension, we need to determine if the + // destructor for `p` allows this field to dangle. + let p_ty = env.path_ty(p); + let struct_name = if let repr::Ty::Struct(s_n, _params) = *p_ty { + s_n + } else { + panic!("how can we have non-deref extension {:?} of non-struct type {:?}", + p, p_ty); + }; + let struct_decl = env.struct_map[&struct_name]; + let field_decl = struct_decl.field_decl(field_name); + match *field_decl.ty { + repr::Ty::Ref(rgn, _borrow_kind, ref _base_ty) => { + match rgn { + repr::Region::Free(region_name) => { + // free regions are never struct + // params and thus can never be + // marked "may_dangle". (Or if you + // prefer another POV, we assume + // free regions appearing within + // struct declaration itself can + // always be referenced from the + // destructor.) + log!("root `{T:?}` might access loc `{L:?}` \ + via &'{R} in {S} destructor", + T=root, L=loc, S=struct_decl.name, R=region_name); + return false; + } + repr::Region::Bound(idx) => { + let struct_param = struct_decl.parameters[idx]; + if !struct_param.may_dangle { + log!("root `{T:?}` might access loc `{L:?}` \ + via `&'{R}` in {S} destructor", + T=root, L=loc, S=struct_decl.name, R=idx); + return false; + } else { + log!("struct {S} says region `'{R}` may dangle during \ + destructor access to loc `{L:?}` from root `{T:?}`", + T=root, L=loc, S=struct_decl.name, R=idx); + } + } + } + } + repr::Ty::Unit => { + panic!("found unit type during the dangly path walk?"); + } + repr::Ty::Struct(struct_name, ref _params) => { + // we must have already confirmed this + // struct's destructor (with respect to a + // particular field of the path) earlier + // in the walk. Keep going. + log!("struct {S2} presumed innocent of access to \ + loc `{L:?}` from root `{T:?}` in {S} destructor", + T=root, L=loc, S=struct_decl.name, S2=struct_name); + } + repr::Ty::Bound(idx) => { + let struct_param = struct_decl.parameters[idx]; + if !struct_param.may_dangle { + log!("root `{T:?}` might access loc `{L:?}` \ + via type param {P} in {S} destructor", + T=root, L=loc, S=struct_decl.name, P=idx); + return false; + } else { + log!("struct {S} says type param {P} may dangle \ + during destructor access to loc `{L:?}` from root `{T:?}`", + T=root, L=loc, S=struct_decl.name, P=idx); + } + } + } + + if b == *root { + return true; + } + + next_b = (**p).clone(); + } + }; + b = next_b; + } + } +} + #[derive(Debug)] pub struct BorrowError { description: String,