#![allow(dead_code)]
use std::io::{self, ErrorKind};
use crate::{
reader::{HEAP_DUMP_END_KIND, HprofReader},
types::{HprofType, heap, tags},
};
#[inline]
pub(crate) fn sub_remaining(remaining: &mut u64, n: u64) -> io::Result<()> {
*remaining = remaining
.checked_sub(n)
.ok_or_else(|| io::Error::new(ErrorKind::InvalidData, "heap segment sub-record overrun"))?;
Ok(())
}
pub(crate) fn scan_prim_arrays<O, F>(
open: O,
id_size: u8,
wanted: &std::collections::HashSet<u64>,
mut f: F,
) -> io::Result<()>
where
O: Fn() -> io::Result<HprofReader>,
F: FnMut(u64, &[u8]),
{
let ids = id_size as u64;
let mut r = open()?;
let mut scratch: Vec<u8> = Vec::with_capacity(256);
loop {
let (tag, length) = match r.next_record()? {
None => break,
Some(h) => h,
};
let result: io::Result<()> = (|| match tag {
tags::HEAP_DUMP | tags::HEAP_DUMP_SEGMENT => {
let mut remaining = length;
while remaining > 0 {
let sub_tag = r.u1()?;
sub_remaining(&mut remaining, 1)?;
match sub_tag {
heap::ROOT_SYSTEM_CLASS
| heap::ROOT_UNKNOWN
| heap::ROOT_MONITOR_USED
| heap::ROOT_STICKY_CLASS
| heap::ROOT_INTERNED_STRING
| heap::ROOT_DEBUGGER
| heap::ROOT_VM_INTERNAL => {
r.skip(ids)?;
sub_remaining(&mut remaining, ids)?;
}
heap::ROOT_JNI_GLOBAL => {
r.skip(2 * ids)?;
sub_remaining(&mut remaining, 2 * ids)?;
}
heap::ROOT_JNI_LOCAL
| heap::ROOT_JAVA_FRAME
| heap::ROOT_JNI_MONITOR
| heap::ROOT_THREAD_OBJ => {
r.skip(ids + 8)?;
sub_remaining(&mut remaining, ids + 8)?;
}
heap::ROOT_NATIVE_STACK | heap::ROOT_THREAD_BLOCK => {
r.skip(ids + 4)?;
sub_remaining(&mut remaining, ids + 4)?;
}
heap::HEAP_DUMP_INFO => {
r.skip(4 + ids)?;
sub_remaining(&mut remaining, 4 + ids)?;
}
heap::CLASS_DUMP => {
let consumed = skip_class_dump(&mut r, id_size)?;
sub_remaining(&mut remaining, consumed)?;
}
heap::INSTANCE_DUMP => {
r.skip(ids + 4)?;
let _class_id = r.id()?;
let data_len = r.u4()? as u64;
r.skip(data_len)?;
sub_remaining(&mut remaining, ids + 4 + ids + 4 + data_len)?;
}
heap::OBJ_ARRAY_DUMP => {
r.skip(ids + 4)?;
let count = r.u4()? as u64;
r.skip(ids)?;
let byte_len = count.saturating_mul(ids);
r.skip(byte_len)?;
sub_remaining(&mut remaining, ids + 4 + 4 + ids + byte_len)?;
}
heap::PRIM_ARRAY_NODATA_DUMP => {
r.skip(ids + 4 + 4 + 1)?;
sub_remaining(&mut remaining, ids + 4 + 4 + 1)?;
}
heap::PRIM_ARRAY_DUMP => {
let addr = r.id()?;
r.skip(4)?;
let count = r.u4()? as u64;
let elem_type = r.u1()?;
let esz = HprofType::from_code(elem_type)
.map(|t| t.byte_size() as u64)
.unwrap_or(1);
let byte_len = count.saturating_mul(esz);
sub_remaining(&mut remaining, ids + 4 + 4 + 1 + byte_len)?;
if wanted.contains(&addr) {
r.read_bytes_reuse(&mut scratch, byte_len as usize)?;
f(addr, &scratch);
} else {
r.skip(byte_len)?;
}
}
other => {
return Err(io::Error::new(
ErrorKind::InvalidData,
format!("unknown heap sub-tag 0x{other:02x} in thread-name scan"),
));
}
}
}
Ok(())
}
tags::HEAP_DUMP_END => Err(io::Error::new(HEAP_DUMP_END_KIND, "heap_dump_end")),
_ => r.skip(length),
})();
match result {
Ok(()) => {}
Err(e) if e.kind() == HEAP_DUMP_END_KIND => break,
Err(e)
if e.kind() == ErrorKind::UnexpectedEof || e.kind() == ErrorKind::InvalidData =>
{
break;
}
Err(e) => return Err(e),
}
}
Ok(())
}
pub(crate) enum Record<'a> {
Instance(u64, u64, &'a [u8]),
PrimArray(u64, u8, u64, &'a [u8]),
ObjArray(u64, u64, u64, &'a [u8]),
}
pub(crate) fn scan_all_records<O, F>(open: O, id_size: u8, mut f: F) -> io::Result<()>
where
O: Fn() -> io::Result<HprofReader>,
F: FnMut(Record<'_>),
{
let ids = id_size as u64;
let mut r = open()?;
let mut inst_scratch: Vec<u8> = Vec::with_capacity(256);
let mut prim_scratch: Vec<u8> = Vec::with_capacity(256);
let mut obj_scratch: Vec<u8> = Vec::with_capacity(256);
loop {
let (tag, length) = match r.next_record()? {
None => break,
Some(h) => h,
};
let result: io::Result<()> = (|| match tag {
tags::HEAP_DUMP | tags::HEAP_DUMP_SEGMENT => {
let mut remaining = length;
while remaining > 0 {
let sub_tag = r.u1()?;
sub_remaining(&mut remaining, 1)?;
match sub_tag {
heap::ROOT_SYSTEM_CLASS
| heap::ROOT_UNKNOWN
| heap::ROOT_MONITOR_USED
| heap::ROOT_STICKY_CLASS
| heap::ROOT_INTERNED_STRING
| heap::ROOT_DEBUGGER
| heap::ROOT_VM_INTERNAL => {
r.skip(ids)?;
sub_remaining(&mut remaining, ids)?;
}
heap::ROOT_JNI_GLOBAL => {
r.skip(2 * ids)?;
sub_remaining(&mut remaining, 2 * ids)?;
}
heap::ROOT_JNI_LOCAL
| heap::ROOT_JAVA_FRAME
| heap::ROOT_JNI_MONITOR
| heap::ROOT_THREAD_OBJ => {
r.skip(ids + 8)?;
sub_remaining(&mut remaining, ids + 8)?;
}
heap::ROOT_NATIVE_STACK | heap::ROOT_THREAD_BLOCK => {
r.skip(ids + 4)?;
sub_remaining(&mut remaining, ids + 4)?;
}
heap::HEAP_DUMP_INFO => {
r.skip(4 + ids)?;
sub_remaining(&mut remaining, 4 + ids)?;
}
heap::CLASS_DUMP => {
let consumed = skip_class_dump(&mut r, id_size)?;
sub_remaining(&mut remaining, consumed)?;
}
heap::INSTANCE_DUMP => {
let addr = r.id()?;
r.skip(4)?;
let class_id = r.id()?;
let data_len = r.u4()? as u64;
sub_remaining(&mut remaining, ids + 4 + ids + 4 + data_len)?;
r.read_bytes_reuse(&mut inst_scratch, data_len as usize)?;
f(Record::Instance(addr, class_id, &inst_scratch));
}
heap::OBJ_ARRAY_DUMP => {
let addr = r.id()?;
r.skip(4)?; let count = r.u4()? as u64;
let array_class_id = r.id()?; let byte_len = count.saturating_mul(ids);
sub_remaining(&mut remaining, ids + 4 + 4 + ids + byte_len)?;
r.read_bytes_reuse(&mut obj_scratch, byte_len as usize)?;
f(Record::ObjArray(addr, array_class_id, count, &obj_scratch));
}
heap::PRIM_ARRAY_NODATA_DUMP => {
r.skip(ids + 4 + 4 + 1)?;
sub_remaining(&mut remaining, ids + 4 + 4 + 1)?;
}
heap::PRIM_ARRAY_DUMP => {
let addr = r.id()?;
r.skip(4)?;
let count = r.u4()? as u64;
let elem_type = r.u1()?;
let esz = HprofType::from_code(elem_type)
.map(|t| t.byte_size() as u64)
.unwrap_or(1);
let byte_len = count.saturating_mul(esz);
sub_remaining(&mut remaining, ids + 4 + 4 + 1 + byte_len)?;
r.read_bytes_reuse(&mut prim_scratch, byte_len as usize)?;
f(Record::PrimArray(addr, elem_type, count, &prim_scratch));
}
other => {
return Err(io::Error::new(
ErrorKind::InvalidData,
format!("unknown heap sub-tag 0x{other:02x} in fused record scan"),
));
}
}
}
Ok(())
}
tags::HEAP_DUMP_END => Err(io::Error::new(HEAP_DUMP_END_KIND, "heap_dump_end")),
_ => r.skip(length),
})();
match result {
Ok(()) => {}
Err(e) if e.kind() == HEAP_DUMP_END_KIND => break,
Err(e)
if e.kind() == ErrorKind::UnexpectedEof || e.kind() == ErrorKind::InvalidData =>
{
break;
}
Err(e) => return Err(e),
}
}
Ok(())
}
pub(crate) fn scan_class_dumps<O, F>(open: O, id_size: u8, mut f: F) -> io::Result<()>
where
O: Fn() -> io::Result<HprofReader>,
F: FnMut(u64, &[(u64, u8, u64)]),
{
let ids = id_size as u64;
let mut r = open()?;
let mut statics: Vec<(u64, u8, u64)> = Vec::new();
let mut vbuf: Vec<u8> = Vec::with_capacity(8);
loop {
let (tag, length) = match r.next_record()? {
None => break,
Some(h) => h,
};
let result: io::Result<()> = (|| match tag {
tags::HEAP_DUMP | tags::HEAP_DUMP_SEGMENT => {
let mut remaining = length;
while remaining > 0 {
let sub_tag = r.u1()?;
sub_remaining(&mut remaining, 1)?;
match sub_tag {
heap::ROOT_SYSTEM_CLASS
| heap::ROOT_UNKNOWN
| heap::ROOT_MONITOR_USED
| heap::ROOT_STICKY_CLASS
| heap::ROOT_INTERNED_STRING
| heap::ROOT_DEBUGGER
| heap::ROOT_VM_INTERNAL => {
r.skip(ids)?;
sub_remaining(&mut remaining, ids)?;
}
heap::ROOT_JNI_GLOBAL => {
r.skip(2 * ids)?;
sub_remaining(&mut remaining, 2 * ids)?;
}
heap::ROOT_JNI_LOCAL
| heap::ROOT_JAVA_FRAME
| heap::ROOT_JNI_MONITOR
| heap::ROOT_THREAD_OBJ => {
r.skip(ids + 8)?;
sub_remaining(&mut remaining, ids + 8)?;
}
heap::ROOT_NATIVE_STACK | heap::ROOT_THREAD_BLOCK => {
r.skip(ids + 4)?;
sub_remaining(&mut remaining, ids + 4)?;
}
heap::HEAP_DUMP_INFO => {
r.skip(4 + ids)?;
sub_remaining(&mut remaining, 4 + ids)?;
}
heap::CLASS_DUMP => {
let mut consumed = 0u64;
let class_obj_id = r.id()?;
r.skip(4)?; r.skip(ids * 6)?; r.skip(4)?; consumed += ids + 4 + ids * 6 + 4;
let cp_count = r.u2()?;
consumed += 2;
for _ in 0..cp_count {
r.skip(2)?;
let type_code = r.u1()?;
let vs = value_size(type_code, id_size);
r.skip(vs)?;
consumed += 2 + 1 + vs;
}
statics.clear();
let static_count = r.u2()?;
consumed += 2;
for _ in 0..static_count {
let name_id = r.id()?;
let type_code = r.u1()?;
let vs = value_size(type_code, id_size);
let value = if vs == 0 {
0
} else {
r.read_bytes_reuse(&mut vbuf, vs as usize)?;
let mut acc = 0u64;
for &b in vbuf.iter() {
acc = (acc << 8) | b as u64;
}
acc
};
consumed += ids + 1 + vs;
statics.push((name_id, type_code, value));
}
let inst_count = r.u2()?;
consumed += 2;
for _ in 0..inst_count {
r.skip(ids)?;
r.skip(1)?;
consumed += ids + 1;
}
f(class_obj_id, &statics);
sub_remaining(&mut remaining, consumed)?;
}
heap::INSTANCE_DUMP => {
r.skip(ids + 4)?;
let _class_id = r.id()?;
let data_len = r.u4()? as u64;
r.skip(data_len)?;
sub_remaining(&mut remaining, ids + 4 + ids + 4 + data_len)?;
}
heap::OBJ_ARRAY_DUMP => {
r.skip(ids + 4)?;
let count = r.u4()? as u64;
r.skip(ids)?;
let byte_len = count.saturating_mul(ids);
r.skip(byte_len)?;
sub_remaining(&mut remaining, ids + 4 + 4 + ids + byte_len)?;
}
heap::PRIM_ARRAY_NODATA_DUMP => {
r.skip(ids + 4 + 4 + 1)?;
sub_remaining(&mut remaining, ids + 4 + 4 + 1)?;
}
heap::PRIM_ARRAY_DUMP => {
r.skip(ids + 4)?;
let count = r.u4()? as u64;
let elem_type = r.u1()?;
let esz = HprofType::from_code(elem_type)
.map(|t| t.byte_size() as u64)
.unwrap_or(1);
r.skip(count.saturating_mul(esz))?;
sub_remaining(
&mut remaining,
ids + 4 + 4 + 1 + count.saturating_mul(esz),
)?;
}
other => {
return Err(io::Error::new(
ErrorKind::InvalidData,
format!("unknown heap sub-tag 0x{other:02x} in class-dump scan"),
));
}
}
}
Ok(())
}
tags::HEAP_DUMP_END => Err(io::Error::new(HEAP_DUMP_END_KIND, "heap_dump_end")),
_ => r.skip(length),
})();
match result {
Ok(()) => {}
Err(e) if e.kind() == HEAP_DUMP_END_KIND => break,
Err(e)
if e.kind() == ErrorKind::UnexpectedEof || e.kind() == ErrorKind::InvalidData =>
{
break;
}
Err(e) => return Err(e),
}
}
Ok(())
}
pub(crate) fn skip_class_dump(r: &mut HprofReader, id_size: u8) -> io::Result<u64> {
let ids = id_size as u64;
let mut consumed = 0u64;
r.skip(ids)?; r.skip(4)?; r.skip(ids * 6)?; r.skip(4)?; consumed += ids + 4 + ids * 6 + 4;
let cp_count = r.u2()?;
consumed += 2;
for _ in 0..cp_count {
r.skip(2)?; let type_code = r.u1()?;
let vs = value_size(type_code, id_size);
r.skip(vs)?;
consumed += 2 + 1 + vs;
}
let static_count = r.u2()?;
consumed += 2;
for _ in 0..static_count {
r.skip(ids)?; let type_code = r.u1()?;
let vs = value_size(type_code, id_size);
r.skip(vs)?;
consumed += ids + 1 + vs;
}
let inst_count = r.u2()?;
consumed += 2;
for _ in 0..inst_count {
r.skip(ids)?; r.skip(1)?; consumed += ids + 1;
}
Ok(consumed)
}
pub(crate) fn read_ref(data: &[u8], ref_size: usize) -> u64 {
if ref_size == 4 {
if data.len() >= 4 {
u32::from_be_bytes([data[0], data[1], data[2], data[3]]) as u64
} else {
0
}
} else if data.len() >= 8 {
u64::from_be_bytes([
data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7],
])
} else {
0
}
}
pub(crate) fn read_id(chunk: &[u8], id_size: u8) -> u64 {
if id_size == 4 {
if chunk.len() >= 4 {
u32::from_be_bytes([chunk[0], chunk[1], chunk[2], chunk[3]]) as u64
} else {
0
}
} else if chunk.len() >= 8 {
u64::from_be_bytes([
chunk[0], chunk[1], chunk[2], chunk[3], chunk[4], chunk[5], chunk[6], chunk[7],
])
} else {
0
}
}
pub(crate) fn read_id_from_reader(r: &mut HprofReader, _id_size: u8) -> io::Result<u64> {
r.id()
}
pub(crate) fn value_size(type_code: u8, id_size: u8) -> u64 {
match HprofType::from_code(type_code) {
Some(HprofType::Object) => id_size as u64,
Some(t) => t.byte_size() as u64,
None => 0,
}
}
#[allow(clippy::type_complexity)]
pub(crate) fn collect_blobs<O>(
open: O,
id_size: u8,
wanted_inst: &std::collections::HashSet<u64>,
wanted_prim: &std::collections::HashSet<u64>,
wanted_obj: &std::collections::HashSet<u64>,
) -> io::Result<(
std::collections::HashMap<u64, (u64, Vec<u8>)>,
std::collections::HashMap<u64, Vec<u8>>,
std::collections::HashMap<u64, Vec<u8>>,
)>
where
O: Fn() -> io::Result<HprofReader>,
{
use crate::types::{HprofType, heap, tags};
let ids = id_size as u64;
let mut inst_blobs: std::collections::HashMap<u64, (u64, Vec<u8>)> =
std::collections::HashMap::new();
let mut prim_blobs: std::collections::HashMap<u64, Vec<u8>> = std::collections::HashMap::new();
let mut obj_blobs: std::collections::HashMap<u64, Vec<u8>> = std::collections::HashMap::new();
if wanted_inst.is_empty() && wanted_prim.is_empty() && wanted_obj.is_empty() {
return Ok((inst_blobs, prim_blobs, obj_blobs));
}
let mut r = open()?;
let mut scratch: Vec<u8> = Vec::with_capacity(256);
loop {
let (tag, length) = match r.next_record()? {
None => break,
Some(h) => h,
};
let result: io::Result<()> = (|| match tag {
tags::HEAP_DUMP | tags::HEAP_DUMP_SEGMENT => {
let mut remaining = length;
while remaining > 0 {
let sub_tag = r.u1()?;
sub_remaining(&mut remaining, 1)?;
match sub_tag {
heap::ROOT_SYSTEM_CLASS
| heap::ROOT_UNKNOWN
| heap::ROOT_MONITOR_USED
| heap::ROOT_STICKY_CLASS
| heap::ROOT_INTERNED_STRING
| heap::ROOT_DEBUGGER
| heap::ROOT_VM_INTERNAL => {
r.skip(ids)?;
sub_remaining(&mut remaining, ids)?;
}
heap::ROOT_JNI_GLOBAL => {
r.skip(2 * ids)?;
sub_remaining(&mut remaining, 2 * ids)?;
}
heap::ROOT_JNI_LOCAL
| heap::ROOT_JAVA_FRAME
| heap::ROOT_JNI_MONITOR
| heap::ROOT_THREAD_OBJ => {
r.skip(ids + 8)?;
sub_remaining(&mut remaining, ids + 8)?;
}
heap::ROOT_NATIVE_STACK | heap::ROOT_THREAD_BLOCK => {
r.skip(ids + 4)?;
sub_remaining(&mut remaining, ids + 4)?;
}
heap::HEAP_DUMP_INFO => {
r.skip(4 + ids)?;
sub_remaining(&mut remaining, 4 + ids)?;
}
heap::CLASS_DUMP => {
let consumed = skip_class_dump(&mut r, id_size)?;
sub_remaining(&mut remaining, consumed)?;
}
heap::INSTANCE_DUMP => {
let addr = r.id()?;
r.skip(4)?;
let class_id = r.id()?;
let data_len = r.u4()? as u64;
sub_remaining(&mut remaining, ids + 4 + ids + 4 + data_len)?;
if wanted_inst.contains(&addr) {
r.read_bytes_reuse(&mut scratch, data_len as usize)?;
inst_blobs.insert(addr, (class_id, scratch.clone()));
} else {
r.skip(data_len)?;
}
}
heap::OBJ_ARRAY_DUMP => {
let addr = r.id()?;
r.skip(4)?;
let count = r.u4()? as u64;
r.skip(ids)?;
let byte_len = count.saturating_mul(ids);
sub_remaining(&mut remaining, ids + 4 + 4 + ids + byte_len)?;
if wanted_obj.contains(&addr) {
r.read_bytes_reuse(&mut scratch, byte_len as usize)?;
obj_blobs.insert(addr, scratch.clone());
} else {
r.skip(byte_len)?;
}
}
heap::PRIM_ARRAY_NODATA_DUMP => {
r.skip(ids + 4 + 4 + 1)?;
sub_remaining(&mut remaining, ids + 4 + 4 + 1)?;
}
heap::PRIM_ARRAY_DUMP => {
let addr = r.id()?;
r.skip(4)?;
let count = r.u4()? as u64;
let elem_type = r.u1()?;
let esz = HprofType::from_code(elem_type)
.map(|t| t.byte_size() as u64)
.unwrap_or(1);
let byte_len = count.saturating_mul(esz);
sub_remaining(&mut remaining, ids + 4 + 4 + 1 + byte_len)?;
if wanted_prim.contains(&addr) {
r.read_bytes_reuse(&mut scratch, byte_len as usize)?;
prim_blobs.insert(addr, scratch.clone());
} else {
r.skip(byte_len)?;
}
}
other => {
return Err(io::Error::new(
ErrorKind::InvalidData,
format!("unknown heap sub-tag 0x{other:02x} in collect_blobs"),
));
}
}
}
Ok(())
}
tags::HEAP_DUMP_END => Err(io::Error::new(HEAP_DUMP_END_KIND, "heap_dump_end")),
_ => r.skip(length),
})();
match result {
Ok(()) => {}
Err(e) if e.kind() == HEAP_DUMP_END_KIND => break,
Err(e)
if e.kind() == ErrorKind::UnexpectedEof || e.kind() == ErrorKind::InvalidData =>
{
break;
}
Err(e) => return Err(e),
}
}
Ok((inst_blobs, prim_blobs, obj_blobs))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn sub_remaining_errors_on_underflow_instead_of_wrapping() {
let mut rem = 100u64;
sub_remaining(&mut rem, 40).unwrap();
assert_eq!(rem, 60);
sub_remaining(&mut rem, 60).unwrap();
assert_eq!(rem, 0);
let mut rem = 3u64;
let err = sub_remaining(&mut rem, 4).unwrap_err();
assert_eq!(err.kind(), ErrorKind::InvalidData);
assert_eq!(rem, 3);
}
}