borrow_graph/
graph.rs

1// Copyright (c) The Diem Core Contributors
2// SPDX-License-Identifier: Apache-2.0
3
4use crate::{
5    paths::{self, Path, PathSlice},
6    references::*,
7};
8use mirai_annotations::{debug_checked_postcondition, debug_checked_precondition};
9use std::collections::{BTreeMap, BTreeSet};
10
11//**************************************************************************************************
12// Definitions
13//**************************************************************************************************
14
15#[derive(Clone, Debug, Default, PartialEq)]
16pub struct BorrowGraph<Loc: Copy, Lbl: Clone + Ord>(BTreeMap<RefID, Ref<Loc, Lbl>>);
17
18//**************************************************************************************************
19// Impls
20//**************************************************************************************************
21
22impl<Loc: Copy, Lbl: Clone + Ord> BorrowGraph<Loc, Lbl> {
23    /// creates an empty borrow graph
24    #[allow(clippy::new_without_default)]
25    pub fn new() -> Self {
26        Self(BTreeMap::new())
27    }
28
29    /// checks if the given reference is mutable or not
30    pub fn is_mutable(&self, id: RefID) -> bool {
31        self.0.get(&id).unwrap().mutable
32    }
33
34    /// Adds a new reference to the borrow graph
35    /// Fails if the id is already in use
36    pub fn new_ref(&mut self, id: RefID, mutable: bool) {
37        assert!(
38            self.0.insert(id, Ref::new(mutable)).is_none(),
39            "ref {} exists",
40            id.0
41        )
42    }
43
44    /// Return the references borrowing the `id` reference
45    /// The borrows are collected by first label in the borrow edge
46    /// `BTreeMap<RefID, Loc>` represents all of the "full" or "epsilon" borrows (non field borrows)
47    /// `BTreeMap<Lbl, BTreeMap<RefID, Loc>>)` represents the field borrows, collected over the
48    /// first label
49    pub fn borrowed_by(
50        &self,
51        id: RefID,
52    ) -> (BTreeMap<RefID, Loc>, BTreeMap<Lbl, BTreeMap<RefID, Loc>>) {
53        let borrowed_by = &self.0.get(&id).unwrap().borrowed_by;
54        let mut full_borrows: BTreeMap<RefID, Loc> = BTreeMap::new();
55        let mut field_borrows: BTreeMap<Lbl, BTreeMap<RefID, Loc>> = BTreeMap::new();
56        for (borrower, edges) in &borrowed_by.0 {
57            let borrower = *borrower;
58            for edge in edges {
59                match edge.path.get(0) {
60                    None => full_borrows.insert(borrower, edge.loc),
61                    Some(f) => field_borrows
62                        .entry(f.clone())
63                        .or_insert_with(BTreeMap::new)
64                        .insert(borrower, edge.loc),
65                };
66            }
67        }
68        (full_borrows, field_borrows)
69    }
70
71    /// Return the edges between parent and child
72    pub fn between_edges(&self, parent: RefID, child: RefID) -> Vec<(Loc, Path<Lbl>, bool)> {
73        let edges = &self.0.get(&parent).unwrap().borrowed_by.0[&child];
74        edges
75            .iter()
76            .map(|edge| (edge.loc, edge.path.clone(), edge.strong))
77            .collect()
78    }
79
80    /// Return the outgoing edges from id
81    pub fn out_edges(&self, id: RefID) -> Vec<(Loc, Path<Lbl>, bool, RefID)> {
82        let mut returned_edges = vec![];
83        let borrowed_by = &self.0.get(&id).unwrap().borrowed_by;
84        for (borrower, edges) in &borrowed_by.0 {
85            let borrower = *borrower;
86            for edge in edges {
87                returned_edges.push((edge.loc, edge.path.clone(), edge.strong, borrower));
88            }
89        }
90        returned_edges
91    }
92
93    /// Return the incoming edges into id
94    pub fn in_edges(&self, id: RefID) -> Vec<(Loc, RefID, Path<Lbl>, bool)> {
95        let mut returned_edges = vec![];
96        let borrows_from = &self.0.get(&id).unwrap().borrows_from;
97        for src in borrows_from {
98            for edge in self.between_edges(*src, id) {
99                returned_edges.push((edge.0, *src, edge.1, edge.2));
100            }
101        }
102        returned_edges
103    }
104
105    //**********************************************************************************************
106    // Edges/Borrows
107    //**********************************************************************************************
108
109    /// Add a strong (exact) epsilon borrow from `parent_id` to `child_id`
110    pub fn add_strong_borrow(&mut self, loc: Loc, parent_id: RefID, child_id: RefID) {
111        self.factor(parent_id, loc, vec![], child_id)
112    }
113
114    /// Add a strong (exact) field borrow from `parent_id` to `child_id` at field `field`
115    pub fn add_strong_field_borrow(
116        &mut self,
117        loc: Loc,
118        parent_id: RefID,
119        field: Lbl,
120        child_id: RefID,
121    ) {
122        self.factor(parent_id, loc, vec![field], child_id)
123    }
124
125    /// Add a weak (prefix) epsilon borrow from `parent_id` to `child_id`
126    /// i.e. `child_id` might be borrowing from ANY field in `parent_id`
127    pub fn add_weak_borrow(&mut self, loc: Loc, parent_id: RefID, child_id: RefID) {
128        self.add_path(parent_id, loc, false, vec![], child_id)
129    }
130
131    /// Add a weak (prefix) field borrow from `parent_id` to `child_id` at field `field`
132    /// i.e. `child_id` might be borrowing from ANY field in `parent_id` rooted at `field`
133    pub fn add_weak_field_borrow(
134        &mut self,
135        loc: Loc,
136        parent_id: RefID,
137        field: Lbl,
138        child_id: RefID,
139    ) {
140        self.add_path(parent_id, loc, false, vec![field], child_id)
141    }
142
143    fn add_edge(&mut self, parent_id: RefID, edge: BorrowEdge<Loc, Lbl>, child_id: RefID) {
144        assert!(parent_id != child_id);
145        let parent = self.0.get_mut(&parent_id).unwrap();
146        parent
147            .borrowed_by
148            .0
149            .entry(child_id)
150            .or_insert_with(BTreeSet::new)
151            .insert(edge);
152        let child = self.0.get_mut(&child_id).unwrap();
153        child.borrows_from.insert(parent_id);
154    }
155
156    fn add_path(
157        &mut self,
158        parent_id: RefID,
159        loc: Loc,
160        strong: bool,
161        path: Path<Lbl>,
162        child_id: RefID,
163    ) {
164        let edge = BorrowEdge { strong, path, loc };
165        self.add_edge(parent_id, edge, child_id)
166    }
167
168    fn factor(&mut self, parent_id: RefID, loc: Loc, path: Path<Lbl>, intermediate_id: RefID) {
169        debug_checked_precondition!(self.check_invariant());
170        let parent = self.0.get_mut(&parent_id).unwrap();
171        let mut needs_factored = vec![];
172        for (child_id, parent_to_child_edges) in &parent.borrowed_by.0 {
173            for parent_to_child_edge in parent_to_child_edges {
174                if paths::leq(&path, &parent_to_child_edge.path) {
175                    let factored_edge = (*child_id, parent_to_child_edge.clone());
176                    needs_factored.push(factored_edge);
177                }
178            }
179        }
180
181        let mut cleanup_ids = BTreeSet::new();
182        for (child_id, parent_to_child_edge) in &needs_factored {
183            let parent_to_child_edges = parent.borrowed_by.0.get_mut(child_id).unwrap();
184            assert!(parent_to_child_edges.remove(parent_to_child_edge));
185            if parent_to_child_edges.is_empty() {
186                assert!(parent.borrowed_by.0.remove(child_id).is_some());
187                cleanup_ids.insert(child_id);
188            }
189        }
190
191        for child_id in cleanup_ids {
192            assert!(self
193                .0
194                .get_mut(child_id)
195                .unwrap()
196                .borrows_from
197                .remove(&parent_id));
198        }
199
200        for (child_id, parent_to_child_edge) in needs_factored {
201            let (_, intermediate_to_child_suffix) = paths::factor(&path, parent_to_child_edge.path);
202            self.add_path(
203                intermediate_id,
204                parent_to_child_edge.loc,
205                parent_to_child_edge.strong,
206                intermediate_to_child_suffix,
207                child_id,
208            )
209        }
210        self.add_path(
211            parent_id,
212            loc,
213            /* strong */ true,
214            path,
215            intermediate_id,
216        );
217        debug_checked_postcondition!(self.check_invariant());
218    }
219
220    //**********************************************************************************************
221    // Release
222    //**********************************************************************************************
223
224    /// Remove reference `id` from the graph
225    /// Fixes any transitive borrows, so if `parent` borrowed by `id` borrowed by `child`
226    /// After the release, `parent` borrowed by `child`
227    pub fn release(&mut self, id: RefID) {
228        debug_checked_precondition!(self.check_invariant());
229        let Ref {
230            borrowed_by,
231            borrows_from,
232            ..
233        } = self.0.remove(&id).unwrap();
234        for parent_ref_id in borrows_from.into_iter() {
235            let parent = self.0.get_mut(&parent_ref_id).unwrap();
236            let parent_edges = parent.borrowed_by.0.remove(&id).unwrap();
237            for parent_edge in parent_edges {
238                for (child_ref_id, child_edges) in &borrowed_by.0 {
239                    for child_edge in child_edges {
240                        self.splice_out_intermediate(
241                            parent_ref_id,
242                            &parent_edge,
243                            *child_ref_id,
244                            child_edge,
245                        )
246                    }
247                }
248            }
249        }
250        for child_ref_id in borrowed_by.0.keys() {
251            let child = self.0.get_mut(&child_ref_id).unwrap();
252            child.borrows_from.remove(&id);
253        }
254        debug_checked_postcondition!(self.check_invariant());
255    }
256
257    fn splice_out_intermediate(
258        &mut self,
259        parent_id: RefID,
260        parent_to_intermediate: &BorrowEdge<Loc, Lbl>,
261        child_id: RefID,
262        intermediate_to_child: &BorrowEdge<Loc, Lbl>,
263    ) {
264        // dont add in an edge if releasing from a cycle
265        if parent_id == child_id {
266            return;
267        }
268
269        let path = if parent_to_intermediate.strong {
270            paths::append(&parent_to_intermediate.path, &intermediate_to_child.path)
271        } else {
272            parent_to_intermediate.path.clone()
273        };
274        let strong = parent_to_intermediate.strong && intermediate_to_child.strong;
275        let loc = intermediate_to_child.loc;
276        let parent_to_child = BorrowEdge { strong, path, loc };
277        self.add_edge(parent_id, parent_to_child, child_id)
278    }
279
280    //**********************************************************************************************
281    // Subsumes/weakens
282    //**********************************************************************************************
283
284    /// checks if `self` covers `other`
285    pub fn leq(&self, other: &Self) -> bool {
286        self.unmatched_edges(other).is_empty()
287    }
288
289    fn unmatched_edges(&self, other: &Self) -> BTreeMap<RefID, BorrowEdges<Loc, Lbl>> {
290        let mut unmatched_edges = BTreeMap::new();
291        for (parent_id, other_ref) in &other.0 {
292            let self_ref = &self.0[parent_id];
293            let self_borrowed_by = &self_ref.borrowed_by.0;
294            for (child_id, other_edges) in &other_ref.borrowed_by.0 {
295                for other_edge in other_edges {
296                    let found_match = self_borrowed_by
297                        .get(child_id)
298                        .map(|parent_to_child| {
299                            parent_to_child
300                                .iter()
301                                .any(|self_edge| self_edge.leq(other_edge))
302                        })
303                        .unwrap_or(false);
304                    if !found_match {
305                        assert!(parent_id != child_id);
306                        unmatched_edges
307                            .entry(*parent_id)
308                            .or_insert_with(BorrowEdges::new)
309                            .0
310                            .entry(*child_id)
311                            .or_insert_with(BTreeSet::new)
312                            .insert(other_edge.clone());
313                    }
314                }
315            }
316        }
317        unmatched_edges
318    }
319
320    //**********************************************************************************************
321    // Remap
322    //**********************************************************************************************
323
324    /// Utility for remapping the reference ids according the `id_map` provided
325    /// If it is not in the map, the id remains the same
326    pub fn remap_refs(&mut self, id_map: &BTreeMap<RefID, RefID>) {
327        debug_checked_precondition!(self.check_invariant());
328        for info in self.0.values_mut() {
329            info.remap_refs(id_map);
330        }
331        for (old, new) in id_map {
332            if let Some(info) = self.0.remove(old) {
333                self.0.insert(*new, info);
334            }
335        }
336        debug_checked_postcondition!(self.check_invariant());
337    }
338
339    //**********************************************************************************************
340    // Joins
341    //**********************************************************************************************
342
343    /// Joins other into self
344    /// It adds only 'unmatched' edges from other into self, i.e. for any edge in other, if there
345    /// is an edge in self that is <= than that edge, it is not added.
346    pub fn join(&self, other: &Self) -> Self {
347        debug_checked_precondition!(self.check_invariant());
348        debug_checked_precondition!(other.check_invariant());
349        debug_checked_precondition!(self.0.keys().all(|id| other.0.contains_key(id)));
350        debug_checked_precondition!(other.0.keys().all(|id| self.0.contains_key(id)));
351
352        let mut joined = self.clone();
353        for (parent_id, unmatched_borrowed_by) in self.unmatched_edges(other) {
354            for (child_id, unmatched_edges) in unmatched_borrowed_by.0 {
355                for unmatched_edge in unmatched_edges {
356                    joined.add_edge(parent_id, unmatched_edge, child_id);
357                }
358            }
359        }
360        debug_checked_postcondition!(joined.check_invariant());
361        joined
362    }
363
364    //**********************************************************************************************
365    // Consistency/Invariants
366    //**********************************************************************************************
367
368    fn check_invariant(&self) -> bool {
369        self.id_consistency() && self.edge_consistency() && self.no_self_loops()
370    }
371
372    /// Checks at all ids in edges are contained in the borrow map itself, i.e. that each id
373    /// corresponds to a reference
374    fn id_consistency(&self) -> bool {
375        let contains_id = |id| self.0.contains_key(id);
376        self.0.values().all(|r| {
377            r.borrowed_by.0.keys().all(contains_id) && r.borrows_from.iter().all(contains_id)
378        })
379    }
380
381    /// Checks that for every edge in borrowed_by there is a flipped edge in borrows_from
382    /// And vice versa
383    //// i.e. verifies the "back edges" in the borrow graph
384    fn edge_consistency(&self) -> bool {
385        let parent_to_child_consistency =
386            |cur_parent, child| self.0[child].borrows_from.contains(cur_parent);
387        let child_to_parent_consistency =
388            |cur_child, parent| self.0[parent].borrowed_by.0.contains_key(cur_child);
389        self.0.iter().all(|(id, r)| {
390            r.borrowed_by
391                .0
392                .keys()
393                .all(|c| parent_to_child_consistency(id, c))
394                && r.borrows_from
395                    .iter()
396                    .all(|p| child_to_parent_consistency(id, p))
397        })
398    }
399
400    /// Checks that no reference borrows from itself
401    fn no_self_loops(&self) -> bool {
402        self.0.iter().all(|(id, r)| {
403            r.borrowed_by.0.keys().all(|to_id| id != to_id)
404                && r.borrows_from.iter().all(|from_id| id != from_id)
405        })
406    }
407
408    //**********************************************************************************************
409    // Util
410    //**********************************************************************************************
411
412    /// Checks if the current reference is in the graph
413    pub fn contains_id(&self, ref_id: RefID) -> bool {
414        self.0.contains_key(&ref_id)
415    }
416
417    /// Returns all ref ids in the map
418    pub fn all_refs(&self) -> BTreeSet<RefID> {
419        self.0.keys().cloned().collect()
420    }
421
422    /// Prints out a view of the borrow graph
423    #[allow(dead_code)]
424    pub fn display(&self)
425    where
426        Lbl: std::fmt::Display,
427    {
428        fn path_to_string<Lbl: std::fmt::Display>(p: &PathSlice<Lbl>) -> String {
429            p.iter()
430                .map(|l| l.to_string())
431                .collect::<Vec<_>>()
432                .join(".")
433        }
434
435        for (id, ref_info) in &self.0 {
436            if ref_info.borrowed_by.0.is_empty() && ref_info.borrows_from.is_empty() {
437                println!("{}", id.0);
438            }
439            for (borrower, edges) in &ref_info.borrowed_by.0 {
440                for edge in edges {
441                    let edisp = if edge.strong { "=" } else { "-" };
442                    println!(
443                        "{} {}{}{}> {}",
444                        id.0,
445                        edisp,
446                        path_to_string(&edge.path),
447                        edisp,
448                        borrower.0,
449                    );
450                }
451            }
452            for parent in &ref_info.borrows_from {
453                println!("{} <- {}", parent.0, id.0);
454            }
455        }
456    }
457}