jvm_hprof/
heap_dump.rs

1//! Sub records contained in [crate::HeapDumpSegment] records.
2//!
3//! # Performance
4//!
5//! Since sub records do not have a length prefix like records do, they cannot be almost entirely
6//! lazily parsed -- the contents of the sub record must be parsed to know how long a sub record is.
7//! That said, it is still reasonably fast at approximately 1GiB/s per core, and there is room to
8//! make parsing lazier should the need arise.
9use getset::{CopyGetters, Getters};
10
11use crate::*;
12
13mod primitive_array;
14
15pub use primitive_array::PrimitiveArray;
16pub use primitive_array::PrimitiveArrayType;
17
18pub enum SubRecord<'a> {
19    /// Doesn't seem to ever be written, but it's documented as existing
20    GcRootUnknown(GcRootUnknown),
21    GcRootThreadObj(GcRootThreadObj),
22    GcRootJniGlobal(GcRootJniGlobal),
23    GcRootJniLocalRef(GcRootJniLocalRef),
24    GcRootJavaStackFrame(GcRootJavaStackFrame),
25    /// Doesn't seem to ever be written, but it's documented as existing
26    GcRootNativeStack(GcRootNativeStack),
27    GcRootSystemClass(GcRootSystemClass),
28    /// Doesn't seem to ever be written, but it's documented as existing
29    GcRootThreadBlock(GcRootThreadBlock),
30    GcRootBusyMonitor(GcRootBusyMonitor),
31    Class(Class<'a>),
32    Instance(Instance<'a>),
33    ObjectArray(ObjectArray<'a>),
34    PrimitiveArray(PrimitiveArray<'a>),
35}
36
37impl<'a> fmt::Debug for SubRecord<'a> {
38    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
39        write!(
40            f,
41            "{}",
42            match self {
43                SubRecord::GcRootUnknown(_) => "GcRootUnknown",
44                SubRecord::GcRootThreadObj(_) => "GcRootThreadObj",
45                SubRecord::GcRootJniGlobal(_) => "GcRootJniGlobal",
46                SubRecord::GcRootJniLocalRef(_) => "GcRootJniLocalRef",
47                SubRecord::GcRootJavaStackFrame(_) => "GcRootJavaStackFrame",
48                SubRecord::GcRootNativeStack(_) => "GcRootNativeStack",
49                SubRecord::GcRootSystemClass(_) => "GcRootSystemClass",
50                SubRecord::GcRootThreadBlock(_) => "GcRootThreadBlock",
51                SubRecord::GcRootBusyMonitor(_) => "GcRootBusyMonitor",
52                SubRecord::Class(_) => "Class",
53                SubRecord::Instance(_) => "Instance",
54                SubRecord::ObjectArray(_) => "ObjectArray",
55                SubRecord::PrimitiveArray(_) => "PrimitiveArray",
56            }
57        )
58    }
59}
60
61impl<'a> SubRecord<'a> {
62    pub(crate) fn parse(input: &[u8], id_size: IdSize) -> nom::IResult<&[u8], SubRecord> {
63        // https://github.com/openjdk/jdk/blob/08822b4e0526fe001c39fe08e241b849eddf481d/src/hotspot/share/services/heapDumper.cpp#L178
64        let (input, tag_byte) = number::be_u8(input)?;
65
66        // have to parse now since ClassObject, etc, have variable size
67
68        let (input, variant) = match tag_byte {
69            0xFF => GcRootUnknown::parse(input, id_size)
70                .map(|(input, r)| (input, SubRecord::GcRootUnknown(r))),
71            0x08 => GcRootThreadObj::parse(input, id_size)
72                .map(|(input, r)| (input, SubRecord::GcRootThreadObj(r))),
73            0x01 => GcRootJniGlobal::parse(input, id_size)
74                .map(|(input, r)| (input, SubRecord::GcRootJniGlobal(r))),
75            0x02 => GcRootJniLocalRef::parse(input, id_size)
76                .map(|(input, r)| (input, SubRecord::GcRootJniLocalRef(r))),
77            0x03 => GcRootJavaStackFrame::parse(input, id_size)
78                .map(|(input, r)| (input, SubRecord::GcRootJavaStackFrame(r))),
79            0x04 => GcRootNativeStack::parse(input, id_size)
80                .map(|(input, r)| (input, SubRecord::GcRootNativeStack(r))),
81            0x05 => GcRootSystemClass::parse(input, id_size)
82                .map(|(input, r)| (input, SubRecord::GcRootSystemClass(r))),
83            0x06 => GcRootThreadBlock::parse(input, id_size)
84                .map(|(input, r)| (input, SubRecord::GcRootThreadBlock(r))),
85            0x07 => GcRootBusyMonitor::parse(input, id_size)
86                .map(|(input, r)| (input, SubRecord::GcRootBusyMonitor(r))),
87            0x20 => Class::parse(input, id_size).map(|(input, r)| (input, SubRecord::Class(r))),
88            0x21 => {
89                Instance::parse(input, id_size).map(|(input, r)| (input, SubRecord::Instance(r)))
90            }
91            0x22 => ObjectArray::parse(input, id_size)
92                .map(|(input, r)| (input, SubRecord::ObjectArray(r))),
93            0x23 => PrimitiveArray::parse(input, id_size)
94                .map(|(input, r)| (input, SubRecord::PrimitiveArray(r))),
95            _ => panic!("Unexpected sub-record type {:#X}", tag_byte),
96        }?;
97
98        Ok((input, variant))
99    }
100}
101
102#[derive(CopyGetters, Copy, Clone, Debug)]
103pub struct GcRootUnknown {
104    #[get_copy = "pub"]
105    obj_id: Id,
106}
107
108impl GcRootUnknown {
109    fn parse(input: &[u8], id_size: IdSize) -> nom::IResult<&[u8], Self> {
110        // https://github.com/openjdk/jdk/blob/08822b4e0526fe001c39fe08e241b849eddf481d/src/hotspot/share/services/heapDumper.cpp#L180
111        let (input, id) = Id::parse(input, id_size)?;
112
113        Ok((input, GcRootUnknown { obj_id: id }))
114    }
115}
116
117#[derive(CopyGetters, Copy, Clone, Debug)]
118pub struct GcRootThreadObj {
119    /// May be missing for a thread newly attached through JNI
120    #[get_copy = "pub"]
121    thread_obj_id: Option<Id>,
122    #[get_copy = "pub"]
123    thread_serial: Serial,
124    #[get_copy = "pub"]
125    stack_trace_serial: Serial,
126}
127
128impl GcRootThreadObj {
129    fn parse(input: &[u8], id_size: IdSize) -> nom::IResult<&[u8], Self> {
130        // https://github.com/openjdk/jdk/blob/08822b4e0526fe001c39fe08e241b849eddf481d/src/hotspot/share/services/heapDumper.cpp#L184
131        let (input, thread_obj_id) = parse_optional_id(input, id_size)?;
132        let (input, thread_serial) = number::be_u32(input)?;
133        let (input, stack_trace_serial) = number::be_u32(input)?;
134
135        Ok((
136            input,
137            GcRootThreadObj {
138                thread_obj_id,
139                thread_serial: thread_serial.into(),
140                stack_trace_serial: stack_trace_serial.into(),
141            },
142        ))
143    }
144}
145
146#[derive(CopyGetters, Copy, Clone, Debug)]
147pub struct GcRootJniGlobal {
148    #[get_copy = "pub"]
149    obj_id: Id,
150    #[get_copy = "pub"]
151    jni_global_ref_id: Id,
152}
153
154impl GcRootJniGlobal {
155    fn parse(input: &[u8], id_size: IdSize) -> nom::IResult<&[u8], Self> {
156        // https://github.com/openjdk/jdk/blob/08822b4e0526fe001c39fe08e241b849eddf481d/src/hotspot/share/services/heapDumper.cpp#L191
157        let (input, obj_id) = Id::parse(input, id_size)?;
158        let (input, jni_global_ref_id) = Id::parse(input, id_size)?;
159
160        Ok((
161            input,
162            GcRootJniGlobal {
163                obj_id,
164                jni_global_ref_id,
165            },
166        ))
167    }
168}
169
170#[derive(CopyGetters, Copy, Clone, Debug)]
171pub struct GcRootJniLocalRef {
172    #[get_copy = "pub"]
173    obj_id: Id,
174    #[get_copy = "pub"]
175    thread_serial: Serial,
176    #[get_copy = "pub"]
177    frame_index: Option<u32>,
178}
179
180impl GcRootJniLocalRef {
181    fn parse(input: &[u8], id_size: IdSize) -> nom::IResult<&[u8], Self> {
182        // https://github.com/openjdk/jdk/blob/08822b4e0526fe001c39fe08e241b849eddf481d/src/hotspot/share/services/heapDumper.cpp#L196
183        let (input, obj_id) = Id::parse(input, id_size)?;
184        let (input, thread_serial) = number::be_u32(input)?;
185        let (input, frame_index) = parse_optional_u32(input)?;
186
187        Ok((
188            input,
189            GcRootJniLocalRef {
190                obj_id,
191                thread_serial: thread_serial.into(),
192                frame_index,
193            },
194        ))
195    }
196}
197
198#[derive(CopyGetters, Copy, Clone, Debug)]
199pub struct GcRootJavaStackFrame {
200    #[get_copy = "pub"]
201    obj_id: Id,
202    #[get_copy = "pub"]
203    thread_serial: Serial,
204    #[get_copy = "pub"]
205    frame_index: Option<u32>,
206}
207
208impl GcRootJavaStackFrame {
209    fn parse(input: &[u8], id_size: IdSize) -> nom::IResult<&[u8], Self> {
210        // https://github.com/openjdk/jdk/blob/08822b4e0526fe001c39fe08e241b849eddf481d/src/hotspot/share/services/heapDumper.cpp#L202
211        let (input, obj_id) = Id::parse(input, id_size)?;
212        let (input, thread_serial) = number::be_u32(input)?;
213        let (input, frame_index) = parse_optional_u32(input)?;
214
215        Ok((
216            input,
217            GcRootJavaStackFrame {
218                obj_id,
219                thread_serial: thread_serial.into(),
220                frame_index,
221            },
222        ))
223    }
224}
225
226#[derive(CopyGetters, Copy, Clone, Debug)]
227pub struct GcRootNativeStack {
228    #[get_copy = "pub"]
229    obj_id: Id,
230    #[get_copy = "pub"]
231    thread_serial: Serial,
232}
233
234impl GcRootNativeStack {
235    fn parse(input: &[u8], id_size: IdSize) -> nom::IResult<&[u8], Self> {
236        // https://github.com/openjdk/jdk/blob/08822b4e0526fe001c39fe08e241b849eddf481d/src/hotspot/share/services/heapDumper.cpp#L208
237        let (input, obj_id) = Id::parse(input, id_size)?;
238        let (input, thread_serial) = number::be_u32(input)?;
239
240        Ok((
241            input,
242            GcRootNativeStack {
243                obj_id,
244                thread_serial: thread_serial.into(),
245            },
246        ))
247    }
248}
249
250#[derive(CopyGetters, Copy, Clone, Debug)]
251pub struct GcRootSystemClass {
252    #[get_copy = "pub"]
253    obj_id: Id,
254}
255
256impl GcRootSystemClass {
257    fn parse(input: &[u8], id_size: IdSize) -> nom::IResult<&[u8], Self> {
258        // https://github.com/openjdk/jdk/blob/08822b4e0526fe001c39fe08e241b849eddf481d/src/hotspot/share/services/heapDumper.cpp#L213
259        let (input, obj_id) = Id::parse(input, id_size)?;
260
261        Ok((input, GcRootSystemClass { obj_id }))
262    }
263}
264
265#[derive(CopyGetters, Copy, Clone, Debug)]
266pub struct GcRootThreadBlock {
267    #[get_copy = "pub"]
268    obj_id: Id,
269    #[get_copy = "pub"]
270    thread_serial: Serial,
271}
272
273impl GcRootThreadBlock {
274    fn parse(input: &[u8], id_size: IdSize) -> nom::IResult<&[u8], Self> {
275        // https://github.com/openjdk/jdk/blob/08822b4e0526fe001c39fe08e241b849eddf481d/src/hotspot/share/services/heapDumper.cpp#L217
276        let (input, obj_id) = Id::parse(input, id_size)?;
277        let (input, thread_serial) = number::be_u32(input)?;
278
279        Ok((
280            input,
281            GcRootThreadBlock {
282                obj_id,
283                thread_serial: thread_serial.into(),
284            },
285        ))
286    }
287}
288
289#[derive(CopyGetters, Copy, Clone, Debug)]
290pub struct GcRootBusyMonitor {
291    #[get_copy = "pub"]
292    obj_id: Id,
293}
294
295impl GcRootBusyMonitor {
296    fn parse(input: &[u8], id_size: IdSize) -> nom::IResult<&[u8], Self> {
297        // https://github.com/openjdk/jdk/blob/08822b4e0526fe001c39fe08e241b849eddf481d/src/hotspot/share/services/heapDumper.cpp#L222
298        let (input, obj_id) = Id::parse(input, id_size)?;
299
300        Ok((input, GcRootBusyMonitor { obj_id }))
301    }
302}
303
304/// Analogous to a `java.lang.Class` object.
305///
306/// A notable absence from this is the class name, which is available via [crate::LoadClass]
307/// records.
308#[derive(CopyGetters)]
309pub struct Class<'a> {
310    id_size: IdSize,
311    #[get_copy = "pub"]
312    obj_id: Id,
313    #[get_copy = "pub"]
314    stack_trace_serial: Serial,
315    /// `None` when there is no superclass, e.g. for `java.lang.Object`.
316    #[get_copy = "pub"]
317    super_class_obj_id: Option<Id>,
318    #[get_copy = "pub"]
319    class_loader_obj_id: Option<Id>,
320    #[get_copy = "pub"]
321    signers_obj_id: Option<Id>,
322    // TODO optional
323    #[get_copy = "pub"]
324    protection_domain_obj_id: Option<Id>,
325    #[get_copy = "pub"]
326    instance_size_bytes: u32,
327    num_static_fields: u16,
328    static_fields: &'a [u8],
329    num_instance_fields: u16,
330    instance_fields: &'a [u8],
331}
332
333impl<'a> Class<'a> {
334    /// Iterate over [StaticFieldEntry] data.
335    pub fn static_fields(&self) -> StaticFieldEntries {
336        StaticFieldEntries {
337            iter: ParsingIterator::new_stateless_id_size(
338                self.id_size,
339                self.static_fields,
340                self.num_static_fields as u32,
341            ),
342        }
343    }
344
345    /// Iterate over [FieldDescriptor] data.
346    ///
347    /// Contains only the instance fields defined in this class, not in superclasses.
348    ///
349    /// An object's fields are serialized with the concrete type's fields first, then that class's
350    /// superclass, and so forth, up to the root of the hierarchy.
351    pub fn instance_field_descriptors(&self) -> FieldDescriptors {
352        FieldDescriptors {
353            iter: ParsingIterator::new_stateless_id_size(
354                self.id_size,
355                self.instance_fields,
356                self.num_instance_fields as u32,
357            ),
358        }
359    }
360
361    fn parse(input: &[u8], id_size: IdSize) -> nom::IResult<&[u8], Class> {
362        // https://github.com/openjdk/jdk/blob/08822b4e0526fe001c39fe08e241b849eddf481d/src/hotspot/share/services/heapDumper.cpp#L226
363        // dump_class_and_array_classes https://github.com/openjdk/jdk/blob/08822b4e0526fe001c39fe08e241b849eddf481d/src/hotspot/share/services/heapDumper.cpp#L995
364        let (input, obj_id) = Id::parse(input, id_size)?;
365        let (input, stack_trace_serial) = number::be_u32(input)?;
366        let (input, super_class_obj_id) = parse_optional_id(input, id_size)?;
367        let (input, class_loader_obj_id) = parse_optional_id(input, id_size)?;
368        let (input, signers_obj_id) = parse_optional_id(input, id_size)?;
369        let (input, protection_domain_obj_id) = parse_optional_id(input, id_size)?;
370        // 2x Id reserved
371        let (input, _) = Id::parse(input, id_size)?;
372        let (input, _) = Id::parse(input, id_size)?;
373        let (input, instance_size_bytes) = number::be_u32(input)?;
374        let (input, constant_pool_len) = number::be_u16(input)?;
375        // constant pool len always 0 as per
376        // https://github.com/openjdk/jdk/blob/08822b4e0526fe001c39fe08e241b849eddf481d/src/hotspot/share/services/heapDumper.cpp#L1031
377        // TODO parse failure
378        assert_eq!(0, constant_pool_len);
379
380        let (input, num_static_fields) = number::be_u16(input)?;
381
382        // since we get a _number of fields_ not a length in bytes, we have to parse now :(
383        // Fortunately, the number of classes << number of objects, so we only will have to do this
384        // tens of thousands of times, not billions.
385        // To save memory, we parse now to get the length, then just keep a slice and parse on
386        // demand later.
387
388        let input_before_static_fields = input;
389        // need to keep track of input outside the loop scope
390        let mut input_after_static_fields = input;
391        for _ in 0..num_static_fields {
392            let (input, _) = StaticFieldEntry::parse(input_after_static_fields, id_size)?;
393            input_after_static_fields = input;
394        }
395
396        let static_fields_byte_len =
397            input_before_static_fields.len() - input_after_static_fields.len();
398        let (_input, static_fields) = bytes::take(static_fields_byte_len)(input)?;
399
400        // instance field descriptors https://github.com/openjdk/jdk/blob/08822b4e0526fe001c39fe08e241b849eddf481d/src/hotspot/share/services/heapDumper.cpp#L964
401        let (input, num_instance_fields) = number::be_u16(input_after_static_fields)?;
402
403        // descriptors are a (name id, tag) pair, so we don't have to parse now
404        let instance_fields_byte_len = num_instance_fields as usize * (id_size.size_in_bytes() + 1);
405
406        let (input, instance_fields) = bytes::take(instance_fields_byte_len)(input)?;
407
408        Ok((
409            input,
410            Class {
411                id_size,
412                obj_id,
413                stack_trace_serial: stack_trace_serial.into(),
414                super_class_obj_id,
415                class_loader_obj_id,
416                signers_obj_id,
417                protection_domain_obj_id,
418                instance_size_bytes,
419                num_static_fields,
420                static_fields,
421                num_instance_fields,
422                instance_fields,
423            },
424        ))
425    }
426}
427
428/// Iterator over [StaticFieldEntry] for a [Class].
429pub struct StaticFieldEntries<'a> {
430    iter: ParsingIterator<'a, StaticFieldEntry, IdSizeParserWrapper<StaticFieldEntry>>,
431}
432
433impl<'a> Iterator for StaticFieldEntries<'a> {
434    type Item = ParseResult<'a, StaticFieldEntry>;
435
436    fn next(&mut self) -> Option<Self::Item> {
437        self.iter.next()
438    }
439}
440
441/// Iterator over [FieldDescriptor]s.
442pub struct FieldDescriptors<'a> {
443    iter: ParsingIterator<'a, FieldDescriptor, IdSizeParserWrapper<FieldDescriptor>>,
444}
445
446impl<'a> Iterator for FieldDescriptors<'a> {
447    type Item = ParseResult<'a, FieldDescriptor>;
448
449    fn next(&mut self) -> Option<Self::Item> {
450        self.iter.next()
451    }
452}
453
454/// An instance of a reference type (i.e. not a primitive).
455#[derive(CopyGetters, Getters)]
456pub struct Instance<'a> {
457    #[get_copy = "pub"]
458    obj_id: Id,
459    #[get_copy = "pub"]
460    stack_trace_serial: Serial,
461    #[get_copy = "pub"]
462    class_obj_id: Id,
463    /// Instance field values, root class's fields last.
464    ///
465    /// # Example
466    ///
467    /// Once you have assembled the class hierarchy and have accumulated all the field descriptors,
468    /// instance fields can be parsed like so (adapted from `print_field_val` in the examples):
469    ///
470    /// ```
471    /// use jvm_hprof::heap_dump::{FieldDescriptor, Instance, FieldValue};
472    /// use jvm_hprof::{Hprof, IdSize, Id};
473    /// use std::collections::HashMap;
474    ///
475    /// fn print_field_val(
476    ///     instance: Instance,
477    ///     field_descriptors: &[FieldDescriptor],
478    ///     id_size: IdSize,
479    ///     utf8: HashMap<Id, &str>
480    ///     ) {
481    ///    let mut field_val_input: &[u8] = instance.fields();
482    ///    for fd in field_descriptors.iter() {
483    ///        let (input, field_val) = fd
484    ///            .field_type()
485    ///            .parse_value(field_val_input, id_size)
486    ///            .unwrap();
487    ///        field_val_input = input;
488    ///        let field_name =
489    ///            utf8.get(&fd.name_id()).unwrap_or_else(|| &"missing");
490    ///
491    ///        match field_val {
492    ///            FieldValue::ObjectId(Some(id)) => println!("{}: ref to obj id {}", field_name, id),
493    ///            FieldValue::ObjectId(null) => println!("{}: ref to null", field_name),
494    ///            FieldValue::Int(i) => println!("{}: int {}", field_name, i),
495    ///            _ => { /* and so forth */ }
496    ///        }
497    ///    }
498    /// }
499    /// ```
500    #[get = "pub"]
501    fields: &'a [u8],
502}
503
504impl<'a> Instance<'a> {
505    fn parse(input: &[u8], id_size: IdSize) -> nom::IResult<&[u8], Instance> {
506        // https://github.com/openjdk/jdk/blob/08822b4e0526fe001c39fe08e241b849eddf481d/src/hotspot/share/services/heapDumper.cpp#L262
507        let (input, obj_id) = Id::parse(input, id_size)?;
508        let (input, stack_trace_serial) = number::be_u32(input)?;
509        let (input, class_obj_id) = Id::parse(input, id_size)?;
510        let (input, fields_byte_len) = number::be_u32(input)?;
511        let (input, fields) = bytes::take(fields_byte_len)(input)?;
512
513        Ok((
514            input,
515            Instance {
516                obj_id,
517                stack_trace_serial: stack_trace_serial.into(),
518                class_obj_id,
519                fields,
520            },
521        ))
522    }
523}
524
525/// An array of anything other than a primitive type.
526#[derive(CopyGetters)]
527pub struct ObjectArray<'a> {
528    #[get_copy = "pub"]
529    obj_id: Id,
530    #[get_copy = "pub"]
531    stack_trace_serial: Serial,
532    /// The obj id of the class that this is an array of
533    #[get_copy = "pub"]
534    array_class_obj_id: Id,
535    num_elements: u32,
536    contents: &'a [u8],
537}
538
539impl<'a> ObjectArray<'a> {
540    fn parse(input: &[u8], id_size: IdSize) -> nom::IResult<&[u8], ObjectArray> {
541        // https://github.com/openjdk/jdk/blob/08822b4e0526fe001c39fe08e241b849eddf481d/src/hotspot/share/services/heapDumper.cpp#L271
542        let (input, obj_id) = Id::parse(input, id_size)?;
543        let (input, stack_trace_serial) = number::be_u32(input)?;
544        let (input, num_elements) = number::be_u32(input)?;
545        let (input, array_class_id) = Id::parse(input, id_size)?;
546
547        let id_bytes_len = num_elements as usize * id_size.size_in_bytes();
548
549        let (input, contents) = bytes::take(id_bytes_len)(input)?;
550
551        Ok((
552            input,
553            ObjectArray {
554                obj_id,
555                stack_trace_serial: stack_trace_serial.into(),
556                array_class_obj_id: array_class_id,
557                num_elements,
558                contents,
559            },
560        ))
561    }
562
563    /// The obj ids of the objects in the array
564    pub fn elements(&self, id_size: IdSize) -> NullableIds {
565        NullableIds {
566            iter: ParsingIterator::new_stateless_id_size(id_size, self.contents, self.num_elements),
567        }
568    }
569}
570
571// TODO referenced in heapDumper.cpp, but not actually written?
572#[allow(unused)]
573enum ConstantPoolEntry {}
574
575/// The field type and value for a static field in a [Class].
576#[derive(CopyGetters, Clone, Copy, Debug)]
577pub struct StaticFieldEntry {
578    #[get_copy = "pub"]
579    name_id: Id,
580    #[get_copy = "pub"]
581    field_type: FieldType,
582    #[get_copy = "pub"]
583    value: FieldValue,
584}
585
586impl StatelessParserWithId for StaticFieldEntry {
587    fn parse(input: &[u8], id_size: IdSize) -> nom::IResult<&[u8], Self> {
588        let (input, name_id) = Id::parse(input, id_size)?;
589
590        let (input, field_type) = FieldType::parse(input)?;
591        let (input, value) = field_type.parse_value(input, id_size)?;
592
593        Ok((
594            input,
595            StaticFieldEntry {
596                name_id,
597                field_type,
598                value,
599            },
600        ))
601    }
602}
603
604#[derive(Clone, Copy, Debug)]
605pub enum FieldValue {
606    ObjectId(Option<Id>),
607    Boolean(bool),
608    Char(u16),
609    Float(f32),
610    Double(f64),
611    Byte(i8),
612    Short(i16),
613    Int(i32),
614    Long(i64),
615}
616
617/// The name and type of an instance field.
618#[derive(CopyGetters, Clone, Copy, Debug)]
619pub struct FieldDescriptor {
620    #[get_copy = "pub"]
621    name_id: Id,
622    #[get_copy = "pub"]
623    field_type: FieldType,
624}
625
626impl StatelessParserWithId for FieldDescriptor {
627    fn parse(input: &[u8], id_size: IdSize) -> nom::IResult<&[u8], Self> {
628        let (input, name_id) = Id::parse(input, id_size)?;
629
630        let (input, field_type) = FieldType::parse(input)?;
631
632        Ok((
633            input,
634            FieldDescriptor {
635                name_id,
636                field_type,
637            },
638        ))
639    }
640}
641
642#[derive(Clone, Copy, Debug)]
643pub enum FieldType {
644    ObjectId,
645    Boolean,
646    Char,
647    Float,
648    Double,
649    Byte,
650    Short,
651    Int,
652    Long,
653}
654
655impl FieldType {
656    fn parse(input: &[u8]) -> nom::IResult<&[u8], Self> {
657        let (input, type_byte) = number::be_u8(input)?;
658
659        // tags https://github.com/openjdk/jdk/blob/08822b4e0526fe001c39fe08e241b849eddf481d/src/hotspot/share/services/heapDumper.cpp#L709
660        let field_type = match type_byte {
661            // 0x01 is HPROF_ARRAY_OBJECT, which seems to never be used
662            0x02 => FieldType::ObjectId,
663            0x04 => FieldType::Boolean,
664            0x05 => FieldType::Char,
665            0x06 => FieldType::Float,
666            0x07 => FieldType::Double,
667            0x08 => FieldType::Byte,
668            0x09 => FieldType::Short,
669            0x0A => FieldType::Int,
670            0x0B => FieldType::Long,
671            _ => panic!("Unexpected field type {:#X}", type_byte),
672        };
673
674        Ok((input, field_type))
675    }
676
677    /// Returns the corresponding `FieldValue` variant
678    pub fn parse_value<'a>(
679        &self,
680        input: &'a [u8],
681        id_size: IdSize,
682    ) -> nom::IResult<&'a [u8], FieldValue> {
683        // dump_field_value https://github.com/openjdk/jdk/blob/08822b4e0526fe001c39fe08e241b849eddf481d/src/hotspot/share/services/heapDumper.cpp#L769
684        match self {
685            FieldType::ObjectId => parse_optional_id(input, id_size)
686                .map(|(input, id)| (input, FieldValue::ObjectId(id))),
687            FieldType::Boolean => {
688                bool::parse(input).map(|(input, b)| (input, FieldValue::Boolean(b)))
689            }
690            FieldType::Char => u16::parse(input).map(|(input, c)| (input, FieldValue::Char(c))),
691            FieldType::Float => f32::parse(input).map(|(input, f)| (input, FieldValue::Float(f))),
692            FieldType::Double => f64::parse(input).map(|(input, f)| (input, FieldValue::Double(f))),
693            FieldType::Byte => i8::parse(input).map(|(input, b)| (input, FieldValue::Byte(b))),
694            FieldType::Short => i16::parse(input).map(|(input, s)| (input, FieldValue::Short(s))),
695            FieldType::Int => i32::parse(input).map(|(input, i)| (input, FieldValue::Int(i))),
696            FieldType::Long => i64::parse(input).map(|(input, l)| (input, FieldValue::Long(l))),
697        }
698    }
699
700    pub fn java_type_name(&self) -> &'static str {
701        match self {
702            FieldType::ObjectId => "Object",
703            FieldType::Boolean => "boolean",
704            FieldType::Char => "char",
705            FieldType::Float => "float",
706            FieldType::Double => "double",
707            FieldType::Byte => "byte",
708            FieldType::Short => "short",
709            FieldType::Int => "int",
710            FieldType::Long => "long",
711        }
712    }
713}
714
715fn parse_optional_id(input: &[u8], id_size: IdSize) -> nom::IResult<&[u8], Option<Id>> {
716    Id::parse(input, id_size).map(|(input, id)| {
717        if id.id == 0 {
718            (input, None)
719        } else {
720            (input, Some(id))
721        }
722    })
723}
724
725fn parse_optional_u32(input: &[u8]) -> nom::IResult<&[u8], Option<u32>> {
726    number::be_u32(input).map(|(input, index)| {
727        if index == u32::max_value() {
728            (input, None)
729        } else {
730            (input, Some(index))
731        }
732    })
733}
734
735/// Iterator over object ids that may be null, represented as `Option<Id>`.
736pub struct NullableIds<'a> {
737    iter: ParsingIterator<'a, Option<Id>, IdSizeParserWrapper<Option<Id>>>,
738}
739
740impl<'a> Iterator for NullableIds<'a> {
741    type Item = ParseResult<'a, Option<Id>>;
742
743    fn next(&mut self) -> Option<Self::Item> {
744        self.iter.next()
745    }
746}
747
748impl StatelessParserWithId for Option<Id> {
749    fn parse(input: &[u8], id_size: IdSize) -> nom::IResult<&[u8], Self> {
750        parse_optional_id(input, id_size)
751    }
752}