Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions nll-repr/src/repr/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,15 @@ pub struct StructDecl {
pub fields: Vec<FieldDecl>,
}

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,
Expand Down Expand Up @@ -112,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<Ty>),
Expand Down Expand Up @@ -306,6 +321,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<RegionName>, Box<Constraint>),
Expand Down
273 changes: 272 additions & 1 deletion nll/src/borrowck.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ enum Mode {
impl<'cx> BorrowCheck<'cx> {
fn check_action(&self, action: &repr::Action) -> Result<(), Box<Error>> {
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)?;
Expand All @@ -65,7 +67,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)?;
Expand Down Expand Up @@ -133,6 +135,50 @@ 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<Error>> {
log!(
"check_drop of {:?} at {:?} with loans={:#?}",
path,
self.point,
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,
&loan.path,
loan.point,
)));
}
Ok(())
}

#[cfg(not_now)]
/// Cannot move from a path `p` if:
/// - `p` is borrowed;
/// - some subpath `p.foo` is borrowed;
Expand Down Expand Up @@ -273,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,
Expand All @@ -285,6 +538,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,
Expand Down
6 changes: 1 addition & 5 deletions nll/src/env.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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={:?}",
Expand Down
Loading