use super::engine::{InferenceMethod, TypeKind};
use super::memory_view::{count_valid_pointers, is_valid_ptr, MemoryView};
#[derive(Default)]
pub(super) struct Score {
pub(super) vec: u16,
pub(super) string: u16,
pub(super) cstring: u16,
pub(super) pointer: u16,
pub(super) fat_ptr: u16,
pub(super) buffer: u16,
pub(super) cstruct: u16,
}
pub(super) fn size_heuristic(size: usize, score: &mut Score) {
match size {
8 => score.pointer += 30,
16 => score.fat_ptr += 25,
24 => {
score.vec += 15;
score.string += 15;
}
32 | 48 | 64 => score.cstruct += 10,
_ => {}
}
if size.is_power_of_two() && size >= 64 {
score.vec += 10;
score.buffer += 5;
}
}
pub(super) fn vec_string_layout(view: &MemoryView, score: &mut Score) -> bool {
let usize_size = std::mem::size_of::<usize>();
let min_len = usize_size * 3; if view.len() < min_len {
return false;
}
let ptr_val = view.read_usize(0);
let len = view.read_usize(usize_size);
let cap = view.read_usize(usize_size * 2);
let (Some(p), Some(l), Some(c)) = (ptr_val, len, cap) else {
return false;
};
if !is_valid_ptr(p) || c < l || c == 0 || c > 10_000_000 {
return false;
}
let spare = c.saturating_sub(l);
if spare < 16 && l > 0 {
score.string += 50;
score.vec += 20;
} else if spare > 0 {
score.vec += 60;
score.string += 15;
} else {
score.vec += 30;
score.string += 30;
}
if c.is_power_of_two() {
score.vec += 15;
}
true
}
pub(super) fn content_analysis(view: &MemoryView, score: &mut Score) {
let data = view.as_slice();
if data.is_empty() {
return;
}
utf8_validation(data, score);
cstring_enhanced(data, score);
if data.len() >= 32 && data.len() <= 4096 {
entropy_analysis(data, score);
} else if data.len() > 4096 {
score.buffer += 40;
}
zero_fill_detection(data, score);
}
fn utf8_validation(data: &[u8], score: &mut Score) {
if data.is_empty() {
return;
}
match std::str::from_utf8(data) {
Ok(s) => {
let total = s.chars().count();
if total == 0 {
return;
}
let printable = s.chars().filter(|c| !c.is_control()).count();
let ratio = printable as f32 / total as f32;
let has_nul = data.contains(&0);
if ratio > 0.8 && !has_nul {
score.string += 90;
} else if ratio > 0.5 {
score.string += 60;
score.vec += 20;
} else {
score.buffer += 10;
}
}
Err(_) => {
score.vec += 20;
}
}
}
fn cstring_enhanced(data: &[u8], score: &mut Score) {
let null_pos = match data.iter().position(|&b| b == 0) {
Some(pos) => pos,
None => return, };
if null_pos < 3 {
return;
}
let content = &data[..null_pos];
let printable_count = content
.iter()
.filter(|&&b| (0x20..=0x7E).contains(&b))
.count();
let printable_ratio = printable_count as f32 / content.len() as f32;
if printable_ratio > 0.9 {
score.cstring += 95;
} else if printable_ratio > 0.7 {
score.cstring += 60;
} else if printable_ratio > 0.5 {
score.cstring += 30;
}
let null_count = data.iter().filter(|&&b| b == 0).count();
if null_count > 1 {
score.cstring = score.cstring.saturating_sub(20);
score.buffer += 15;
}
}
fn entropy_analysis(data: &[u8], score: &mut Score) {
let entropy = shannon_entropy(data);
if entropy > 7.5 {
score.buffer += 30;
} else if entropy > 6.5 {
score.buffer += 15;
}
else if entropy < 3.0 {
score.cstruct += 5;
}
}
fn shannon_entropy(data: &[u8]) -> f64 {
if data.is_empty() {
return 0.0;
}
let mut freq = [0u32; 256];
for &b in data {
freq[b as usize] += 1;
}
let n = data.len() as f64;
let mut entropy = 0.0;
for &count in &freq {
if count > 0 {
let p = count as f64 / n;
entropy -= p * p.log2();
}
}
entropy
}
fn zero_fill_detection(data: &[u8], score: &mut Score) {
if data.len() < 16 {
return;
}
let zero_count = data.iter().filter(|&&b| b == 0).count();
let zero_ratio = zero_count as f32 / data.len() as f32;
if zero_ratio > 0.9 {
score.buffer += 15;
score.cstruct += 10;
}
}
pub(super) fn pointer_heuristic(view: &MemoryView, score: &mut Score) {
let ptr_count = count_valid_pointers(view);
if ptr_count == 0 && view.len() > 8 {
score.buffer += 40;
} else if ptr_count == 1 {
score.pointer += 10;
score.cstruct += 5;
} else if ptr_count >= 2 {
score.cstruct += 30;
}
}
pub(super) fn stack_trace_analysis(stack: Option<&[String]>, score: &mut Score) {
let Some(frames) = stack else {
return;
};
if frames.is_empty() {
return;
}
for frame in frames {
let f = frame.to_lowercase();
if f.contains("alloc::vec::vec") || f.contains("vec::from_elem") {
score.vec += 50;
}
if f.contains("alloc::string::string") || f.contains("from_utf8") {
score.string += 50;
}
if f.contains("alloc::boxed") {
score.pointer += 40;
}
if f.contains("ffi::c_str::cstring") || f.contains("from_bytes_with_nul") {
score.cstring += 60;
}
if f.contains("malloc") || f.contains("calloc") || f.contains("realloc") {
score.cstruct += 20;
score.buffer += 15;
}
if f.contains("libc::") || f.contains("std::ffi") {
score.cstring += 15;
score.cstruct += 10;
}
}
}
pub(super) fn lifetime_analysis(
alloc_time: Option<u64>,
dealloc_time: Option<u64>,
score: &mut Score,
) {
let Some(alloc) = alloc_time else {
return;
};
let Some(dealloc) = dealloc_time else {
return;
};
let lifetime_ns = dealloc.saturating_sub(alloc);
let lifetime_ms = lifetime_ns / 1_000_000;
match lifetime_ms {
0 => {
score.string += 10;
score.vec += 5;
}
1..=100 => {
score.cstruct += 5;
}
10000.. => {
score.buffer += 10;
}
_ => {}
}
}
pub(super) fn candidate_table(score: &Score) -> [(TypeKind, u16, InferenceMethod); 7] {
[
(TypeKind::Vec, score.vec, InferenceMethod::LayoutDetection),
(
TypeKind::String,
score.string,
InferenceMethod::Utf8Validation,
),
(
TypeKind::CString,
score.cstring,
InferenceMethod::ContentAnalysis,
),
(
TypeKind::Pointer,
score.pointer,
InferenceMethod::SizeHeuristic,
),
(
TypeKind::FatPtr,
score.fat_ptr,
InferenceMethod::SizeHeuristic,
),
(
TypeKind::Buffer,
score.buffer,
InferenceMethod::EntropyAnalysis,
),
(TypeKind::CStruct, score.cstruct, InferenceMethod::Combined),
]
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_entropy_calculation() {
let data = [0u8; 100];
assert!((shannon_entropy(&data) - 0.0).abs() < 0.01);
let data: Vec<u8> = (0..100).map(|i| if i % 2 == 0 { 0 } else { 255 }).collect();
assert!(shannon_entropy(&data) < 2.0);
let data: Vec<u8> = (0..100).map(|i| i as u8).collect();
assert!(shannon_entropy(&data) > 5.0);
}
}