use std::collections::HashMap;
use crate::{pass1::ClassInfo, types::HprofType};
pub(crate) fn align_up(n: usize, align: usize) -> usize {
n.div_ceil(align) * align
}
#[inline]
fn shallow_u32(n: usize) -> u32 {
n.min(u32::MAX as usize) as u32
}
pub(crate) fn own_prim_bytes(ci: &ClassInfo, _ref_size: usize) -> usize {
ci.fields
.iter()
.filter(|(_, t)| *t != HprofType::Object)
.map(|(_, t)| t.byte_size())
.sum()
}
pub(crate) fn own_obj_count(ci: &ClassInfo) -> usize {
ci.fields
.iter()
.filter(|(_, t)| *t == HprofType::Object)
.count()
}
pub(crate) fn calculate_size_recursive(
class_addr: u64,
class_map: &HashMap<u64, ClassInfo>,
ptr_size: usize,
ref_size: usize,
cache: &mut HashMap<u64, usize>,
) -> usize {
if let Some(&cached) = cache.get(&class_addr) {
return cached;
}
let result = match class_map.get(&class_addr) {
None => ptr_size + ref_size, Some(ci) => {
if ci.super_id == 0 {
ptr_size + ref_size
} else {
let own = own_obj_count(ci) * ref_size + own_prim_bytes(ci, ref_size);
let super_size =
calculate_size_recursive(ci.super_id, class_map, ptr_size, ref_size, cache);
align_up(own + super_size, ref_size)
}
}
};
cache.insert(class_addr, result);
result
}
pub(crate) fn instance_shallow_size(
class_addr: u64,
class_map: &HashMap<u64, ClassInfo>,
ptr_size: usize,
ref_size: usize,
cache: &mut HashMap<u64, usize>,
) -> u32 {
let inner = calculate_size_recursive(class_addr, class_map, ptr_size, ref_size, cache);
shallow_u32(align_up(inner, 8))
}
pub(crate) fn obj_array_shallow(num_elem: u64, ptr_size: usize, ref_size: usize) -> u32 {
let body = (num_elem as usize).saturating_mul(ref_size);
shallow_u32(align_up(
ptr_size
.saturating_add(ref_size)
.saturating_add(4)
.saturating_add(body),
8,
))
}
pub(crate) fn prim_array_shallow(
num_elem: u64,
elem_size: usize,
ptr_size: usize,
ref_size: usize,
) -> u32 {
let header = align_up(ptr_size + ref_size + 4, ref_size);
let body = (num_elem as usize).saturating_mul(elem_size);
shallow_u32(align_up(header.saturating_add(body), 8))
}
pub(crate) fn class_obj_shallow(ci: &ClassInfo, _ptr_size: usize, ref_size: usize) -> u32 {
let computed = ci.static_obj_count as usize * ref_size + ci.static_prim_bytes as usize;
shallow_u32(align_up(computed, 8))
}
pub type FieldPlan = Vec<(u32, bool)>;
pub type FieldPlanNamed = Vec<(u32, bool, String)>;
pub(crate) fn build_field_plans(
class_map: &HashMap<u64, ClassInfo>,
strings: &HashMap<u64, String>,
id_size: usize,
) -> HashMap<u64, FieldPlan> {
let mut plans: HashMap<u64, FieldPlan> = HashMap::with_capacity(class_map.len());
let mut chain: Vec<u64> = Vec::new();
for &class_addr in class_map.keys() {
chain.clear();
let mut cur = class_addr;
loop {
match class_map.get(&cur) {
None => break,
Some(ci) => {
chain.push(cur);
if ci.super_id == 0 {
break;
}
cur = ci.super_id;
}
}
}
let mut plan: FieldPlan = Vec::new();
let mut byte_offset = 0usize;
for &caddr in &chain {
let ci = match class_map.get(&caddr) {
Some(c) => c,
None => break,
};
let cname = strings.get(&ci.name_id).map(|s| s.as_str()).unwrap_or("");
for &(fname_id, t) in &ci.fields {
let fsize = if t == HprofType::Object {
id_size
} else {
t.byte_size()
};
if t == HprofType::Object {
let fname = strings.get(&fname_id).map(|s| s.as_str()).unwrap_or("");
let excluded = is_excluded_field(cname, fname);
plan.push((byte_offset as u32, excluded));
}
byte_offset += fsize;
}
}
plans.insert(class_addr, plan);
}
plans
}
pub(crate) fn build_field_plans_named(
class_map: &HashMap<u64, ClassInfo>,
strings: &HashMap<u64, String>,
id_size: usize,
) -> HashMap<u64, FieldPlanNamed> {
let mut plans: HashMap<u64, FieldPlanNamed> = HashMap::with_capacity(class_map.len());
let mut chain: Vec<u64> = Vec::new();
for &class_addr in class_map.keys() {
chain.clear();
let mut cur = class_addr;
loop {
match class_map.get(&cur) {
None => break,
Some(ci) => {
chain.push(cur);
if ci.super_id == 0 {
break;
}
cur = ci.super_id;
}
}
}
let mut plan: FieldPlanNamed = Vec::new();
let mut byte_offset = 0usize;
for &caddr in &chain {
let ci = match class_map.get(&caddr) {
Some(c) => c,
None => break,
};
let cname = strings.get(&ci.name_id).map(|s| s.as_str()).unwrap_or("");
for &(fname_id, t) in &ci.fields {
let fsize = if t == HprofType::Object {
id_size
} else {
t.byte_size()
};
if t == HprofType::Object {
let fname = strings.get(&fname_id).map(|s| s.as_str()).unwrap_or("");
let excluded = is_excluded_field(cname, fname);
plan.push((byte_offset as u32, excluded, fname.to_string()));
}
byte_offset += fsize;
}
}
plans.insert(class_addr, plan);
}
plans
}
pub(crate) fn is_excluded_field(class_name: &str, field_name: &str) -> bool {
matches!(
(class_name, field_name),
("java/lang/ref/Reference", "referent")
| ("java/lang/ref/Finalizer", "unfinalized")
| ("java/lang/Runtime", "<Unfinalized>")
)
}
pub(crate) fn detect_ref_size(id_size: u8, array_addr_counts: &[(u64, u64)]) -> u8 {
if id_size != 8 {
return id_size;
}
let mut sorted: Vec<(u64, u64)> = array_addr_counts.to_vec();
sorted.sort_unstable_by_key(|&(a, _)| a);
let mut prev_start = 0u64;
let mut prev_uncomp_end = 0u64;
for &(addr, count) in &sorted {
if prev_uncomp_end > 0 && addr > prev_start && addr < prev_uncomp_end {
return 4;
}
prev_start = addr;
prev_uncomp_end = addr
.saturating_add(16)
.saturating_add(count.saturating_mul(8));
}
id_size
}
pub(crate) fn prim_array_class_name(elem_type_code: u8) -> &'static str {
match elem_type_code {
4 => "[Z", 5 => "[C", 6 => "[F", 7 => "[D", 8 => "[B", 9 => "[S", 10 => "[I", 11 => "[J", _ => "[?",
}
}
pub(crate) fn prim_array_type_code(name: &str) -> Option<u8> {
if !is_primitive_array_class_name(name) {
return None;
}
Some(match name.as_bytes()[1] {
b'Z' => 4,
b'C' => 5,
b'F' => 6,
b'D' => 7,
b'B' => 8,
b'S' => 9,
b'I' => 10,
b'J' => 11,
_ => return None,
})
}
pub(crate) fn is_primitive_array_class_name(name: &str) -> bool {
name.len() == 2
&& name.as_bytes()[0] == b'['
&& matches!(
name.as_bytes()[1],
b'Z' | b'C' | b'F' | b'D' | b'S' | b'I' | b'J' | b'B'
)
}
pub(crate) fn should_add_system_class_root(
is_array: bool,
is_prim_array: bool,
has_sticky: bool,
) -> bool {
if is_prim_array {
return true;
}
if is_array {
return false;
}
!has_sticky
}
pub(crate) fn field_offset(
class_addr: u64,
field_name: &str,
owner_class: &str,
class_map: &HashMap<u64, ClassInfo>,
strings: &HashMap<u64, String>,
obj_ref_width: usize,
) -> Option<(u32, HprofType)> {
let mut chain: Vec<u64> = Vec::new();
let mut cur = class_addr;
loop {
match class_map.get(&cur) {
None => break,
Some(ci) => {
chain.push(cur);
if ci.super_id == 0 {
break;
}
cur = ci.super_id;
}
}
}
let mut byte_offset = 0usize;
for &caddr in chain.iter() {
let ci = class_map.get(&caddr)?;
let cname = strings.get(&ci.name_id).map(|s| s.as_str()).unwrap_or("");
let owner_matches = cname == owner_class;
for &(fname_id, t) in &ci.fields {
let fsize = if t == HprofType::Object {
obj_ref_width
} else {
t.byte_size()
};
let fname = strings.get(&fname_id).map(|s| s.as_str()).unwrap_or("");
if owner_matches && fname == field_name {
return Some((byte_offset as u32, t));
}
byte_offset += fsize;
}
}
None
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn array_shallow_saturates_instead_of_wrapping() {
let huge = 600_000_000u64; let sz = prim_array_shallow(huge, 8, 8, 8);
assert_eq!(sz, u32::MAX, "oversized prim array must saturate, not wrap");
let obj_sz = obj_array_shallow(u64::from(u32::MAX), 8, 8);
assert_eq!(obj_sz, u32::MAX, "oversized object array must saturate");
assert_eq!(prim_array_shallow(10, 4, 8, 8), 64);
}
}