ide/
view_memory_layout.rs

1use std::fmt;
2
3use hir::{DisplayTarget, Field, HirDisplay, Layout, Semantics, Type};
4use ide_db::{
5    RootDatabase,
6    defs::Definition,
7    helpers::{get_definition, pick_best_token},
8};
9use syntax::{AstNode, SyntaxKind};
10
11use crate::FilePosition;
12
13pub struct MemoryLayoutNode {
14    pub item_name: String,
15    pub typename: String,
16    pub size: u64,
17    pub alignment: u64,
18    pub offset: u64,
19    pub parent_idx: i64,
20    pub children_start: i64,
21    pub children_len: u64,
22}
23
24pub struct RecursiveMemoryLayout {
25    pub nodes: Vec<MemoryLayoutNode>,
26}
27
28// NOTE: this is currently strictly for testing and so isn't super useful as a visualization tool, however it could be adapted to become one?
29impl fmt::Display for RecursiveMemoryLayout {
30    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
31        fn process(
32            fmt: &mut fmt::Formatter<'_>,
33            nodes: &Vec<MemoryLayoutNode>,
34            idx: usize,
35            depth: usize,
36        ) -> fmt::Result {
37            let mut out = "\t".repeat(depth);
38            let node = &nodes[idx];
39            out += &format!(
40                "{}: {} (size: {}, align: {}, field offset: {})\n",
41                node.item_name, node.typename, node.size, node.alignment, node.offset
42            );
43            write!(fmt, "{out}")?;
44            if node.children_start != -1 {
45                for j in nodes[idx].children_start
46                    ..(nodes[idx].children_start + nodes[idx].children_len as i64)
47                {
48                    process(fmt, nodes, j as usize, depth + 1)?;
49                }
50            }
51            Ok(())
52        }
53
54        process(fmt, &self.nodes, 0, 0)
55    }
56}
57
58#[derive(Copy, Clone)]
59enum FieldOrTupleIdx {
60    Field(Field),
61    TupleIdx(usize),
62}
63
64impl FieldOrTupleIdx {
65    fn name(&self, db: &RootDatabase) -> String {
66        match *self {
67            FieldOrTupleIdx::Field(f) => f.name(db).as_str().to_owned(),
68            FieldOrTupleIdx::TupleIdx(i) => format!(".{i}"),
69        }
70    }
71}
72
73// Feature: View Memory Layout
74//
75// Displays the recursive memory layout of a datatype.
76//
77// | Editor  | Action Name |
78// |---------|-------------|
79// | VS Code | **rust-analyzer: View Memory Layout** |
80pub(crate) fn view_memory_layout(
81    db: &RootDatabase,
82    position: FilePosition,
83) -> Option<RecursiveMemoryLayout> {
84    let sema = Semantics::new(db);
85    let file = sema.parse_guess_edition(position.file_id);
86    let display_target = sema.first_crate(position.file_id)?.to_display_target(db);
87    let token =
88        pick_best_token(file.syntax().token_at_offset(position.offset), |kind| match kind {
89            SyntaxKind::IDENT => 3,
90            _ => 0,
91        })?;
92
93    let def = get_definition(&sema, token)?;
94
95    let ty = match def {
96        Definition::Adt(it) => it.ty(db),
97        Definition::TypeAlias(it) => it.ty(db),
98        Definition::BuiltinType(it) => it.ty(db),
99        Definition::SelfType(it) => it.self_ty(db),
100        Definition::Local(it) => it.ty(db),
101        Definition::Field(it) => it.ty(db).to_type(db),
102        Definition::Const(it) => it.ty(db),
103        Definition::Static(it) => it.ty(db),
104        _ => return None,
105    };
106
107    fn read_layout(
108        nodes: &mut Vec<MemoryLayoutNode>,
109        db: &RootDatabase,
110        ty: &Type<'_>,
111        layout: &Layout,
112        parent_idx: usize,
113        display_target: DisplayTarget,
114    ) {
115        let mut fields = ty
116            .fields(db)
117            .into_iter()
118            .map(|(f, ty)| (FieldOrTupleIdx::Field(f), ty))
119            .chain(
120                ty.tuple_fields(db)
121                    .into_iter()
122                    .enumerate()
123                    .map(|(i, ty)| (FieldOrTupleIdx::TupleIdx(i), ty)),
124            )
125            .collect::<Vec<_>>();
126
127        if fields.is_empty() {
128            return;
129        }
130
131        fields.sort_by_key(|&(f, _)| match f {
132            FieldOrTupleIdx::Field(f) => layout.field_offset(f).unwrap_or(0),
133            FieldOrTupleIdx::TupleIdx(f) => layout.tuple_field_offset(f).unwrap_or(0),
134        });
135
136        let children_start = nodes.len();
137        nodes[parent_idx].children_start = children_start as i64;
138        nodes[parent_idx].children_len = fields.len() as u64;
139
140        for (field, child_ty) in fields.iter() {
141            if let Ok(child_layout) = child_ty.layout(db) {
142                nodes.push(MemoryLayoutNode {
143                    item_name: field.name(db),
144                    typename: { child_ty.display(db, display_target).to_string() },
145                    size: child_layout.size(),
146                    alignment: child_layout.align(),
147                    offset: match *field {
148                        FieldOrTupleIdx::Field(f) => layout.field_offset(f).unwrap_or(0),
149                        FieldOrTupleIdx::TupleIdx(f) => layout.tuple_field_offset(f).unwrap_or(0),
150                    },
151                    parent_idx: parent_idx as i64,
152                    children_start: -1,
153                    children_len: 0,
154                });
155            } else {
156                nodes.push(MemoryLayoutNode {
157                    item_name: field.name(db)
158                        + format!("(no layout data: {:?})", child_ty.layout(db).unwrap_err())
159                            .as_ref(),
160                    typename: child_ty.display(db, display_target).to_string(),
161                    size: 0,
162                    offset: 0,
163                    alignment: 0,
164                    parent_idx: parent_idx as i64,
165                    children_start: -1,
166                    children_len: 0,
167                });
168            }
169        }
170
171        for (i, (_, child_ty)) in fields.iter().enumerate() {
172            if let Ok(child_layout) = child_ty.layout(db) {
173                read_layout(nodes, db, child_ty, &child_layout, children_start + i, display_target);
174            }
175        }
176    }
177
178    ty.layout(db)
179        .map(|layout| {
180            let item_name = match def {
181                // def is a datatype
182                Definition::Adt(_)
183                | Definition::TypeAlias(_)
184                | Definition::BuiltinType(_)
185                | Definition::SelfType(_) => "[ROOT]".to_owned(),
186
187                // def is an item
188                def => def.name(db).map(|n| n.as_str().to_owned()).unwrap_or("[ROOT]".to_owned()),
189            };
190
191            let typename = ty.display(db, display_target).to_string();
192
193            let mut nodes = vec![MemoryLayoutNode {
194                item_name,
195                typename,
196                size: layout.size(),
197                offset: 0,
198                alignment: layout.align(),
199                parent_idx: -1,
200                children_start: -1,
201                children_len: 0,
202            }];
203            read_layout(&mut nodes, db, &ty, &layout, 0, display_target);
204
205            RecursiveMemoryLayout { nodes }
206        })
207        .ok()
208}
209
210#[cfg(test)]
211mod tests {
212    use super::*;
213
214    use crate::fixture;
215    use expect_test::expect;
216
217    fn make_memory_layout(
218        #[rust_analyzer::rust_fixture] ra_fixture: &str,
219    ) -> Option<RecursiveMemoryLayout> {
220        let (analysis, position, _) = fixture::annotations(ra_fixture);
221
222        hir::attach_db(&analysis.db, || view_memory_layout(&analysis.db, position))
223    }
224
225    #[test]
226    fn view_memory_layout_none() {
227        assert!(make_memory_layout(r#"$0"#).is_none());
228        assert!(make_memory_layout(r#"stru$0ct Blah {}"#).is_none());
229    }
230
231    #[test]
232    fn view_memory_layout_primitive() {
233        expect![[r#"
234            foo: i32 (size: 4, align: 4, field offset: 0)
235        "#]]
236        .assert_eq(
237            &make_memory_layout(
238                r#"
239fn main() {
240    let foo$0 = 109; // default i32
241}
242"#,
243            )
244            .unwrap()
245            .to_string(),
246        );
247    }
248
249    #[test]
250    fn view_memory_layout_constant() {
251        expect![[r#"
252            BLAH: bool (size: 1, align: 1, field offset: 0)
253        "#]]
254        .assert_eq(
255            &make_memory_layout(
256                r#"
257const BLAH$0: bool = 0;
258"#,
259            )
260            .unwrap()
261            .to_string(),
262        );
263    }
264
265    #[test]
266    fn view_memory_layout_static() {
267        expect![[r#"
268            BLAH: bool (size: 1, align: 1, field offset: 0)
269        "#]]
270        .assert_eq(
271            &make_memory_layout(
272                r#"
273static BLAH$0: bool = 0;
274"#,
275            )
276            .unwrap()
277            .to_string(),
278        );
279    }
280
281    #[test]
282    fn view_memory_layout_tuple() {
283        expect![[r#"
284            x: (f64, u8, i64) (size: 24, align: 8, field offset: 0)
285            	.0: f64 (size: 8, align: 8, field offset: 0)
286            	.1: u8 (size: 1, align: 1, field offset: 8)
287            	.2: i64 (size: 8, align: 8, field offset: 16)
288        "#]]
289        .assert_eq(
290            &make_memory_layout(
291                r#"
292fn main() {
293    let x$0 = (101.0, 111u8, 119i64);
294}
295"#,
296            )
297            .unwrap()
298            .to_string(),
299        );
300    }
301
302    #[test]
303    fn view_memory_layout_c_struct() {
304        expect![[r#"
305            [ROOT]: Blah (size: 16, align: 4, field offset: 0)
306            	a: u32 (size: 4, align: 4, field offset: 0)
307            	b: (i32, u8) (size: 8, align: 4, field offset: 4)
308            		.0: i32 (size: 4, align: 4, field offset: 0)
309            		.1: u8 (size: 1, align: 1, field offset: 4)
310            	c: i8 (size: 1, align: 1, field offset: 12)
311        "#]]
312        .assert_eq(
313            &make_memory_layout(
314                r#"
315#[repr(C)]
316struct Blah$0 {
317    a: u32,
318    b: (i32, u8),
319    c: i8,
320}
321"#,
322            )
323            .unwrap()
324            .to_string(),
325        );
326    }
327
328    #[test]
329    fn view_memory_layout_struct() {
330        expect![[r#"
331            [ROOT]: Blah (size: 16, align: 4, field offset: 0)
332            	b: (i32, u8) (size: 8, align: 4, field offset: 0)
333            		.0: i32 (size: 4, align: 4, field offset: 0)
334            		.1: u8 (size: 1, align: 1, field offset: 4)
335            	a: u32 (size: 4, align: 4, field offset: 8)
336            	c: i8 (size: 1, align: 1, field offset: 12)
337        "#]]
338        .assert_eq(
339            &make_memory_layout(
340                r#"
341struct Blah$0 {
342    a: u32,
343    b: (i32, u8),
344    c: i8,
345}
346"#,
347            )
348            .unwrap()
349            .to_string(),
350        );
351    }
352
353    #[test]
354    fn view_memory_layout_member() {
355        expect![[r#"
356            a: bool (size: 1, align: 1, field offset: 0)
357        "#]]
358        .assert_eq(
359            &make_memory_layout(
360                r#"
361#[repr(C)]
362struct Oof {
363    a$0: bool,
364}
365"#,
366            )
367            .unwrap()
368            .to_string(),
369        );
370    }
371
372    #[test]
373    fn view_memory_layout_alias() {
374        let ml_a = make_memory_layout(
375            r#"
376struct X {
377    a: u32,
378    b: i8,
379    c: (f32, f32),
380}
381
382type Foo$0 = X;
383"#,
384        )
385        .unwrap();
386
387        let ml_b = make_memory_layout(
388            r#"
389struct X$0 {
390    a: u32,
391    b: i8,
392    c: (f32, f32),
393}
394"#,
395        )
396        .unwrap();
397
398        assert_eq!(ml_a.to_string(), ml_b.to_string());
399    }
400}