v8_heap_parser/
graph.rs

1use std::{
2    cell::UnsafeCell,
3    collections::{HashMap, HashSet, VecDeque},
4    rc::Rc,
5};
6
7use crate::{decoder::*, perf::PerfCounter};
8use petgraph::visit::EdgeRef;
9
10/// Maps node indices to and back from the DFS order.
11struct PostOrder {
12    // Node index to the order in which they're iterated
13    index_to_order: Vec<usize>,
14    // Order in which nodes are iterated to the node index
15    order_to_index: Vec<usize>,
16}
17
18struct DominatedNodes {
19    first_dom_node_index: Vec<usize>,
20    dominated_nodes: Vec<usize>,
21}
22
23struct Retaining {
24    nodes: Vec<usize>,
25    edges: Vec<usize>,
26    first_retainer: Vec<usize>,
27    first_edge: Vec<usize>,
28}
29
30#[derive(Clone)]
31pub struct ClassGroup {
32    pub index: usize,
33    pub self_size: u64,
34    pub retained_size: u64,
35    pub nodes: Vec<usize>,
36}
37
38#[cfg(target_arch = "wasm32")]
39use wasm_bindgen::prelude::*;
40
41#[cfg_attr(target_arch = "wasm32", wasm_bindgen)]
42pub struct Graph {
43    // Much of the code in the Graph is based on that in the Chrome DevTools
44    // (see LICENSE). We used Github Copilot to do the bulk of the translation from
45    // TypeScript to Rust, with ample hand-editing afterwards. Chrome DevTools
46    // prefers to store all node/edge information in equivalently-lengthed arrays,
47    // which makes V8 very happy. In native code, we can do things a little more
48    // efficiently, but there's still chunks of the graph that mirror devtools and
49    // could be made more idiomatic.
50    pub(crate) inner: Rc<PetGraph>,
51    pub root_index: usize,
52    strings: Rc<Vec<String>>,
53    dominators: UnsafeCell<Option<Vec<usize>>>,
54    retained_sizes: UnsafeCell<Option<Vec<u64>>>,
55    flags: UnsafeCell<Option<Flags>>,
56    retainers: UnsafeCell<Option<Retaining>>,
57    post_order: UnsafeCell<Option<PostOrder>>,
58    dominated_nodes: UnsafeCell<Option<DominatedNodes>>,
59    class_groups: UnsafeCell<Option<Vec<ClassGroup>>>,
60}
61
62mod flag {
63    pub const CAN_BE_QUERIED: u8 = 1 << 0;
64    pub const DETACHED_DOM_TREE_NODE: u8 = 1 << 1;
65    pub const PAGE_OBJECT: u8 = 1 << 2;
66}
67
68struct Flags(Vec<u8>);
69
70impl Flags {
71    pub fn test(&self, index: usize, flag: u8) -> bool {
72        (self.0[index] & flag) != 0
73    }
74}
75
76impl Graph {
77    pub(crate) fn new(inner: PetGraph, root_index: usize, strings: Rc<Vec<String>>) -> Self {
78        Self {
79            inner: Rc::new(inner),
80            root_index,
81            strings,
82            dominators: UnsafeCell::new(None),
83            retained_sizes: UnsafeCell::new(None),
84            retainers: UnsafeCell::new(None),
85            post_order: UnsafeCell::new(None),
86            flags: UnsafeCell::new(None),
87            dominated_nodes: UnsafeCell::new(None),
88            class_groups: UnsafeCell::new(None),
89        }
90    }
91
92    /// Gets a list of all nodes in the graph.
93    pub fn nodes(&self) -> &[petgraph::graph::Node<Node>] {
94        self.inner.raw_nodes()
95    }
96
97    /// Gets a node by its index.
98    pub fn get_node(&self, index: usize) -> Option<&Node> {
99        self.inner.raw_nodes().get(index).map(|n| &n.weight)
100    }
101
102    /// Gets a string by its index. Indexed strings are exposed in the 'Other'
103    /// type in Node and Edge types.
104    pub fn get_indexed_string(&self, index: usize) -> Option<&str> {
105        self.strings.get(index).map(|s| s.as_str())
106    }
107
108    pub(crate) fn graph(&self) -> &PetGraph {
109        &self.inner
110    }
111
112    /// Gets an iterator over the graph nodes.
113    pub fn iter(&self) -> NodeIterator<'_> {
114        NodeIterator {
115            graph: self.graph(),
116            index: 0,
117        }
118    }
119
120    /// Gets a list children of the node at the given index.
121    pub fn children(&self, parent: usize) -> Vec<usize> {
122        let graph = self.graph();
123
124        graph
125            .neighbors(petgraph::graph::NodeIndex::new(parent))
126            .map(|n| n.index())
127            .collect()
128    }
129
130    /// Gets the retained size of a node in the graph. This is the size this
131    /// node specifically requires the program to retain., i.e. the nodes
132    /// the given node dominates <https://en.wikipedia.org/wiki/Dominator_(graph_theory)>
133    pub fn retained_size(&self, node_index: usize) -> u64 {
134        let retained_sizes = unsafe { &mut *self.retained_sizes.get() };
135
136        if retained_sizes.is_none() {
137            let graph = self.graph();
138            let post_order = &self.get_post_order().order_to_index;
139            let dom = self.get_dominators();
140            let _perf = PerfCounter::new("build_retained_sizes");
141
142            let mut rs = Vec::with_capacity(graph.node_count());
143            for n in self.graph().raw_nodes() {
144                rs.push(n.weight.self_size);
145            }
146
147            for i in post_order.iter() {
148                if *i != self.root_index {
149                    rs[dom[*i]] += rs[*i];
150                }
151            }
152
153            *retained_sizes = Some(rs);
154        }
155
156        retained_sizes.as_ref().unwrap()[node_index]
157    }
158
159    fn get_dominated_nodes(&self) -> &DominatedNodes {
160        let dominated_nodes_o = unsafe { &mut *self.dominated_nodes.get() };
161
162        if dominated_nodes_o.is_none() {
163            let dominators_tree = self.get_dominators();
164            let _perf = PerfCounter::new("build_dominated_nodes");
165            let graph = self.graph();
166            let mut dominated_nodes = vec![0; graph.node_count()];
167            let mut first_dom_node_index = vec![0; graph.node_count() + 1];
168
169            let range = match self.root_index {
170                0 => 1..graph.node_count(),
171                i if i == graph.node_count() - 1 => 0..graph.node_count() - 1,
172                i => panic!("expected root index to be first or last, was {}", i),
173            };
174
175            // clone the range as it's used again later
176            for node_index in range.clone() {
177                first_dom_node_index[dominators_tree[node_index]] += 1;
178            }
179
180            let mut first_dominated_node_index = 0;
181            #[allow(clippy::needless_range_loop)]
182            for i in 0..graph.node_count() {
183                let dominated_count = first_dom_node_index[i];
184                if i < graph.node_count() - 1 {
185                    dominated_nodes[first_dominated_node_index] = dominated_count;
186                }
187                first_dom_node_index[i] = first_dominated_node_index;
188                first_dominated_node_index += dominated_count;
189            }
190            first_dom_node_index[graph.node_count()] = dominated_nodes.len() - 1;
191
192            for node_index in range {
193                let dominator_ordinal = dominators_tree[node_index];
194                let mut dominated_ref_index = first_dom_node_index[dominator_ordinal];
195                dominated_nodes[dominated_ref_index] -= 1;
196                dominated_ref_index += dominated_nodes[dominated_ref_index];
197                dominated_nodes[dominated_ref_index] = node_index;
198            }
199
200            *dominated_nodes_o = Some(DominatedNodes {
201                dominated_nodes,
202                first_dom_node_index,
203            });
204        }
205
206        dominated_nodes_o.as_ref().unwrap()
207    }
208
209    /// Gets top-level groups for classes to show in a summary view. Sorted by
210    /// retained size descending by default.
211    pub fn get_class_groups(&self, no_retained: bool) -> &[ClassGroup] {
212        let class_groups = unsafe { &mut *self.class_groups.get() };
213
214        if class_groups.is_none() {
215            let nodes = self.nodes();
216            let mut groups = HashMap::new();
217            for (index, node) in nodes.iter().enumerate() {
218                let name = node.weight.class_name();
219                let group = groups.entry(name).or_insert(ClassGroup {
220                    index,
221                    self_size: 0,
222                    retained_size: 0,
223                    nodes: vec![],
224                });
225
226                group.self_size += node.weight.self_size;
227                group.nodes.push(index);
228            }
229
230            if !no_retained {
231                let dominators = self.get_dominated_nodes();
232                let _perf = PerfCounter::new("build_class_groups");
233                let mut queue = VecDeque::new();
234                let mut sizes = vec![-1];
235                let mut classes = vec![];
236                queue.push_back(self.root_index);
237
238                let mut seen_groups: HashSet<&str> = HashSet::new();
239                while let Some(node_index) = queue.pop_back() {
240                    let node = &nodes[node_index];
241                    let name = node.weight.class_name();
242                    let seen = seen_groups.contains(name);
243
244                    let dominated_index_from = dominators.first_dom_node_index[node_index];
245                    let dominated_index_to = dominators.first_dom_node_index[node_index + 1];
246
247                    if !seen
248                        && (node.weight.self_size != 0
249                            || matches!(node.weight.typ, NodeType::Native))
250                    {
251                        // group must eexist from built groups above
252                        let group = groups.get_mut(name).unwrap();
253                        group.retained_size += self.retained_size(node_index);
254                        if dominated_index_from != dominated_index_to {
255                            seen_groups.insert(name);
256                            sizes.push(queue.len() as isize);
257                            classes.push(name);
258                        }
259                    }
260
261                    for i in dominated_index_from..dominated_index_to {
262                        queue.push_back(dominators.dominated_nodes[i]);
263                    }
264
265                    let l = queue.len();
266                    while sizes.last().copied() == Some(l as isize) {
267                        sizes.pop();
268                        seen_groups.remove(classes.pop().unwrap());
269                    }
270                }
271            }
272
273            let mut groups: Vec<_> = groups.into_values().collect();
274            groups.sort_by_key(|g| std::cmp::Reverse(g.retained_size));
275            *class_groups = Some(groups);
276        }
277
278        class_groups.as_ref().unwrap()
279    }
280
281    fn get_retainers(&self) -> &Retaining {
282        let retaining = unsafe { &mut *self.retainers.get() };
283
284        if retaining.is_none() {
285            let _perf = PerfCounter::new("build_retainers");
286            let graph = self.graph();
287            let mut r = Retaining {
288                edges: vec![0; graph.edge_count()],
289                nodes: vec![0; graph.edge_count()],
290                first_retainer: vec![0; graph.node_count() + 1],
291                first_edge: vec![0; graph.node_count() + 1],
292            };
293
294            r.first_edge[graph.node_count()] = graph.edge_count();
295            let mut edge_index = 0;
296            for (i, node) in graph.raw_nodes().iter().enumerate() {
297                r.first_edge[i] = edge_index;
298                edge_index += node.weight.edge_count;
299            }
300
301            for edge in graph.raw_edges() {
302                r.first_retainer[edge.target().index()] += 1;
303            }
304
305            let mut first_unused_retainer_slot = 0;
306            for i in 0..graph.node_count() {
307                let retainers_count = r.first_retainer[i];
308                r.first_retainer[i] = first_unused_retainer_slot;
309                r.nodes[first_unused_retainer_slot] = retainers_count;
310                first_unused_retainer_slot += retainers_count;
311            }
312            r.first_retainer[graph.node_count()] = r.nodes.len();
313
314            for node in graph.node_indices() {
315                for edge in graph.edges(node) {
316                    let first_retainer_slot_index = r.first_retainer[edge.target().index()];
317                    r.nodes[first_retainer_slot_index] -= 1;
318                    let next_unused_retainer_slot_index =
319                        first_retainer_slot_index + r.nodes[first_retainer_slot_index];
320                    r.nodes[next_unused_retainer_slot_index] = node.index();
321                    r.edges[next_unused_retainer_slot_index] = edge.id().index();
322                }
323            }
324
325            *retaining = Some(r);
326        }
327
328        retaining.as_ref().unwrap()
329    }
330
331    fn get_dominators(&self) -> &Vec<usize> {
332        let dominators = unsafe { &mut *self.dominators.get() };
333        if dominators.is_none() {
334            *dominators = Some(self.build_dominators());
335        }
336
337        dominators.as_ref().unwrap()
338    }
339
340    pub(crate) fn is_essential_edge(&self, index: usize, typ: &EdgeType) -> bool {
341        if let EdgeType::Weak = typ {
342            return false;
343        }
344        if let EdgeType::Shortcut = typ {
345            return index == self.root_index;
346        }
347
348        true
349    }
350
351    /// Gets the flags of metadata for the graph.
352    fn get_flags(&self) -> &Flags {
353        let flags = unsafe { &mut *self.flags.get() };
354
355        if flags.is_none() {
356            let _perf = PerfCounter::new("build_flags");
357            let mut f = vec![0; self.graph().node_count()];
358            self.mark_detached_dom_tree_nodes(&mut f);
359            self.mark_queriable_heap_objects(&mut f);
360            self.mark_page_owned_nodes(&mut f);
361            *flags = Some(Flags(f));
362        }
363
364        flags.as_ref().unwrap()
365    }
366
367    fn mark_detached_dom_tree_nodes(&self, flags: &mut [u8]) {
368        for (index, node) in self.graph().raw_nodes().iter().enumerate() {
369            if let NodeType::Native = node.weight.typ {
370                if node.weight.name().starts_with("Detached ") {
371                    flags[index] |= flag::DETACHED_DOM_TREE_NODE;
372                }
373            }
374        }
375    }
376
377    fn mark_queriable_heap_objects(&self, flags: &mut [u8]) {
378        let graph = self.graph();
379        let mut list = VecDeque::new();
380        let retained = self.get_retainers();
381
382        while let Some(node_index) = list.pop_front() {
383            if flags[node_index] & flag::CAN_BE_QUERIED != 0 {
384                continue;
385            }
386            flags[node_index] |= flag::CAN_BE_QUERIED;
387            let begin_edge = retained.first_edge[node_index];
388            let end_edge = retained.first_edge[node_index + 1];
389            for edge in graph.raw_edges()[begin_edge..end_edge].iter() {
390                if flags[edge.target().index()] & flag::CAN_BE_QUERIED != 0 {
391                    continue;
392                }
393                let typ = edge.weight.typ;
394                if matches!(
395                    typ,
396                    EdgeType::Hidden | EdgeType::Internal | EdgeType::Invisible | EdgeType::Weak
397                ) {
398                    continue;
399                }
400                list.push_back(edge.target().index());
401            }
402        }
403    }
404
405    fn mark_page_owned_nodes(&self, flags: &mut [u8]) {
406        let retainers = self.get_retainers();
407        let graph = self.graph();
408        let node_count = graph.raw_nodes().len();
409        let mut nodes_to_visit = vec![0; node_count];
410        let mut nodes_to_visit_length = 0;
411
412        // Populate the entry points. They are Window objects and DOM Tree Roots.
413        for edge_index in
414            retainers.first_edge[self.root_index]..retainers.first_edge[self.root_index + 1]
415        {
416            let edge = &graph.raw_edges()[edge_index];
417            if edge.weight.typ == EdgeType::Element {
418                if !graph[edge.target()].is_document_dom_trees_root() {
419                    continue;
420                }
421            } else if edge.weight.typ != EdgeType::Shortcut {
422                continue;
423            }
424
425            nodes_to_visit[nodes_to_visit_length] = edge.target().index();
426            flags[edge.target().index()] |= flag::PAGE_OBJECT;
427            nodes_to_visit_length += 1;
428        }
429
430        // Mark everything reachable with the pageObject flag.
431        while nodes_to_visit_length > 0 {
432            let node_ordinal = nodes_to_visit[nodes_to_visit_length - 1];
433            nodes_to_visit_length -= 1;
434
435            let edge_begin = retainers.first_edge[node_ordinal];
436            let edge_end = retainers.first_edge[node_ordinal + 1];
437            for edge in graph.raw_edges()[edge_begin..edge_end].iter() {
438                let child_index = edge.target().index();
439                if flags[child_index] & flag::PAGE_OBJECT != 0 {
440                    continue;
441                }
442                if edge.weight.typ == EdgeType::Weak {
443                    continue;
444                }
445                nodes_to_visit[nodes_to_visit_length] = child_index;
446                flags[child_index] |= flag::PAGE_OBJECT;
447                nodes_to_visit_length += 1;
448            }
449        }
450    }
451
452    /// Gets information about the DFS order of nodes in the tree.
453    fn get_post_order(&self) -> &PostOrder {
454        let post_order = unsafe { &mut *self.post_order.get() };
455        if post_order.is_none() {
456            *post_order = Some(self.build_post_order());
457        }
458
459        post_order.as_ref().unwrap()
460    }
461
462    fn build_post_order(&self) -> PostOrder {
463        let retainers = self.get_retainers();
464        let _perf = PerfCounter::new("build_post_order");
465        let graph = self.graph();
466        let node_count = graph.raw_nodes().len();
467        let flags = self.get_flags();
468        let mut stack_nodes = vec![0; node_count];
469        let mut stack_current_edge = vec![0; node_count];
470        let mut order_to_index = vec![0; node_count];
471        let mut index_to_order = vec![0; node_count];
472        let mut visited = vec![false; node_count];
473        let mut post_order_index = 0;
474
475        let mut stack_top = 0;
476        stack_nodes[0] = self.root_index;
477        stack_current_edge[0] = retainers.first_edge[self.root_index];
478        visited[self.root_index] = true;
479
480        let mut iteration = 0;
481        loop {
482            iteration += 1;
483            loop {
484                let node_index = stack_nodes[stack_top];
485                let edge_index = stack_current_edge[stack_top];
486                let edges_end = retainers.first_edge[node_index + 1];
487
488                if edge_index < edges_end {
489                    stack_current_edge[stack_top] += 1;
490                    let edge = &graph.raw_edges()[edge_index];
491                    if !self.is_essential_edge(node_index, &edge.weight.typ) {
492                        continue;
493                    }
494                    let child_node_index = edge.target().index();
495                    if visited[child_node_index] {
496                        continue;
497                    }
498
499                    if node_index != self.root_index
500                        && flags.test(child_node_index, flag::PAGE_OBJECT)
501                        && !flags.test(node_index, flag::PAGE_OBJECT)
502                    {
503                        continue;
504                    }
505
506                    stack_top += 1;
507                    stack_nodes[stack_top] = child_node_index;
508                    stack_current_edge[stack_top] = retainers.first_edge[child_node_index];
509                    visited[child_node_index] = true;
510                } else {
511                    index_to_order[node_index] = post_order_index;
512                    order_to_index[post_order_index] = node_index;
513                    post_order_index += 1;
514
515                    if stack_top == 0 {
516                        break;
517                    }
518
519                    stack_top -= 1;
520                }
521            }
522
523            if post_order_index == node_count || iteration > 1 {
524                break;
525            }
526
527            post_order_index -= 1;
528            stack_top = 0;
529            stack_nodes[0] = self.root_index;
530            stack_current_edge[0] = retainers.first_edge[self.root_index + 1];
531
532            for (i, did_visit) in visited.iter_mut().enumerate() {
533                if *did_visit || !self.has_only_weak_retainers(i) {
534                    continue;
535                }
536
537                stack_top += 1;
538                stack_nodes[stack_top] = i;
539                stack_current_edge[stack_top] = retainers.first_edge[i];
540                *did_visit = true;
541            }
542        }
543
544        if post_order_index != node_count {
545            post_order_index -= 1;
546            for i in 0..node_count {
547                if visited[i] {
548                    continue;
549                }
550                index_to_order[i] = post_order_index;
551                order_to_index[post_order_index] = i;
552                post_order_index += 1;
553            }
554            index_to_order[self.root_index] = post_order_index;
555            order_to_index[post_order_index] = self.root_index;
556        }
557
558        PostOrder {
559            index_to_order,
560            order_to_index,
561        }
562    }
563
564    fn build_dominators(&self) -> Vec<usize> {
565        let post_order = self.get_post_order();
566        let graph = self.graph();
567
568        let flags = self.get_flags();
569        let retaining = self.get_retainers();
570        let nodes_count = self.nodes().len();
571        let root_post_ordered_index = nodes_count - 1;
572        let no_entry = nodes_count;
573        let mut dominators = vec![no_entry; nodes_count];
574        dominators[root_post_ordered_index] = root_post_ordered_index;
575        let _perf = PerfCounter::new("build_dominators");
576
577        // The affected vector is used to mark entries which dominators
578        // have to be recalculated because of changes in their retainers.
579        let mut affected: Vec<u8> = vec![0; nodes_count];
580
581        // Mark the root direct children as affected.
582        {
583            let begin_index = retaining.first_edge[self.root_index];
584            let end_index = retaining.first_edge[self.root_index + 1];
585            for edge in graph.raw_edges()[begin_index..end_index].iter() {
586                if !self.is_essential_edge(self.root_index, &edge.weight.typ) {
587                    continue;
588                }
589
590                let index = post_order.index_to_order[edge.target().index()];
591                affected[index] = 1;
592            }
593        }
594
595        let mut changed = true;
596        while changed {
597            changed = false;
598            for i in 0..root_post_ordered_index {
599                let post_order_index = root_post_ordered_index - (i + 1);
600                if affected[post_order_index] == 0 {
601                    continue;
602                }
603                affected[post_order_index] = 0;
604                if dominators[post_order_index] == root_post_ordered_index {
605                    continue;
606                }
607                let node_index = post_order.order_to_index[post_order_index];
608                let mut new_dominator_index = no_entry;
609                let begin_retainer_index = retaining.first_retainer[node_index];
610                let end_retainer_index = retaining.first_retainer[node_index + 1];
611                let mut orphan_node = true;
612                for retainer_index in begin_retainer_index..end_retainer_index {
613                    let retainer_edge_index = retaining.edges[retainer_index];
614                    let retainer_edge_type = &graph.raw_edges()[retainer_edge_index].weight.typ;
615                    let retainer_node_index = retaining.nodes[retainer_index];
616                    if !self.is_essential_edge(retainer_node_index, retainer_edge_type) {
617                        continue;
618                    }
619                    orphan_node = false;
620                    if retainer_node_index != self.root_index
621                        && flags.test(node_index, flag::PAGE_OBJECT)
622                        && !flags.test(retainer_node_index, flag::PAGE_OBJECT)
623                    {
624                        continue;
625                    }
626                    let mut retainer_post_order_index =
627                        post_order.index_to_order[retainer_node_index];
628                    if dominators[retainer_post_order_index] != no_entry {
629                        if new_dominator_index == no_entry {
630                            new_dominator_index = retainer_post_order_index;
631                        } else {
632                            while retainer_post_order_index != new_dominator_index {
633                                while retainer_post_order_index < new_dominator_index {
634                                    retainer_post_order_index =
635                                        dominators[retainer_post_order_index];
636                                }
637                                while new_dominator_index < retainer_post_order_index {
638                                    new_dominator_index = dominators[new_dominator_index];
639                                }
640                            }
641                        }
642                        if new_dominator_index == root_post_ordered_index {
643                            break;
644                        }
645                    }
646                }
647                if orphan_node {
648                    new_dominator_index = root_post_ordered_index;
649                }
650                if new_dominator_index != no_entry
651                    && dominators[post_order_index] != new_dominator_index
652                {
653                    dominators[post_order_index] = new_dominator_index;
654                    let node_index = post_order.order_to_index[post_order_index];
655                    changed = true;
656                    let begin_index = retaining.first_edge[node_index];
657                    let end_index = retaining.first_edge[node_index + 1];
658                    for edge in graph.raw_edges()[begin_index..end_index].iter() {
659                        affected[post_order.index_to_order[edge.target().index()]] = 1;
660                    }
661                }
662            }
663        }
664
665        let mut dominators_tree = vec![0; nodes_count];
666        for (post_order_index, dominated_by) in dominators.into_iter().enumerate() {
667            let node_index = post_order.order_to_index[post_order_index];
668            // detached nodes are not in the dominators tree, so have them be
669            // dominated solely by the root.
670            dominators_tree[node_index] = post_order
671                .order_to_index
672                .get(dominated_by)
673                .copied()
674                .unwrap_or(self.root_index);
675        }
676
677        dominators_tree
678    }
679
680    fn has_only_weak_retainers(&self, node_index: usize) -> bool {
681        let ret = self.get_retainers();
682        let graph = self.graph();
683        for retainer in ret.first_retainer[node_index]..ret.first_retainer[node_index + 1] {
684            if let Some(e) = graph.edge_weight(petgraph::graph::EdgeIndex::new(ret.edges[retainer]))
685            {
686                if !matches!(e.typ, EdgeType::Weak | EdgeType::Shortcut) {
687                    return false;
688                }
689            }
690        }
691
692        true
693    }
694}
695
696pub struct NodeIterator<'graph> {
697    graph: &'graph PetGraph,
698    index: usize,
699}
700
701impl<'graph> Iterator for NodeIterator<'graph> {
702    type Item = &'graph Node;
703
704    fn next(&mut self) -> Option<Self::Item> {
705        let n = self.graph.raw_nodes().get(self.index).map(|n| &n.weight);
706        self.index += 1;
707        n
708    }
709}
710
711#[cfg(test)]
712mod tests {
713    use super::*;
714    use std::fs;
715
716    #[test]
717    fn test_retained_sizes() {
718        let contents = fs::read("test/basic.heapsnapshot").unwrap();
719        let graph = decode_slice(&contents).expect("expect no errors");
720
721        let mut groups = graph.get_class_groups(false).to_vec();
722        groups.sort_by_key(|g| std::cmp::Reverse(g.retained_size));
723
724        let mut actual = String::new();
725        for group in groups.iter().take(10) {
726            actual.push_str(&format!(
727                "{} {} {}\n",
728                group.retained_size,
729                group.nodes.len(),
730                graph.get_node(group.index).unwrap().class_name()
731            ));
732        }
733
734        assert_eq!(
735            actual,
736            "3413384 18983 (compiled code)
7372347408 13774 (string)
7382301824 6714 (closure)
7391555120 1186 (array)
7401396568 811 Object
7411266788 757 system / Context
742985128 12345 (system)
743651840 29 Map
744340240 322 BuiltinModule
745325872 3 global
746"
747        );
748    }
749}