Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 19 additions & 1 deletion src/ci.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ struct CiArgs {
duration: Option<Duration>,
format: Option<FormatType>,
output: Option<String>,
diff_only: bool,
/// Bytes per second. Fail if the rolling-window growth rate exceeds this.
growth_rate: Option<u64>,
sample_interval: Option<u64>,
Expand Down Expand Up @@ -54,6 +55,11 @@ pub fn ci_main(args: &[String]) -> i32 {
None
};

if parsed.diff_only && !parsed.leak_check {
eprintln!("error: --diff-only requires --leak-check to be active");
return 1;
}

let start = Instant::now();
let poll_interval = Duration::from_millis(parsed.sample_interval.unwrap_or(1000));
let mut sys = System::new_all();
Expand Down Expand Up @@ -163,7 +169,13 @@ pub fn ci_main(args: &[String]) -> i32 {
// Export report if requested.
if let Some(ref format_type) = parsed.format {
if let Some(current_heap) = last_captured_heap {
let blocks = current_heap;
let mut blocks = current_heap;

if parsed.diff_only {
if let Some(ref prev) = baseline {
blocks = blocks.into_iter().filter(|b| !prev.contains(b)).collect();
}
}

let target_path = parsed.output.clone().unwrap_or_else(|| match format_type {
FormatType::Json => "heap_dump.json".to_string(),
Expand Down Expand Up @@ -227,6 +239,7 @@ fn resolve_target(target: &CiTarget) -> Result<(u32, Option<Child>), AppError> {
fn parse_ci_args(args: &[String]) -> Result<CiArgs, AppError> {
let mut max_memory = None;
let mut leak_check = false;
let mut diff_only = false;
let mut duration = None;
let mut target = None;
let mut format = None;
Expand All @@ -252,6 +265,10 @@ fn parse_ci_args(args: &[String]) -> Result<CiArgs, AppError> {
leak_check = true;
i += 1;
}
"--diff-only" => {
diff_only = true;
i += 1;
}
"--duration" => {
if i + 1 < args.len() {
let val = args[i + 1]
Expand Down Expand Up @@ -357,6 +374,7 @@ fn parse_ci_args(args: &[String]) -> Result<CiArgs, AppError> {
target,
max_memory,
leak_check,
diff_only,
duration,
format,
output,
Expand Down
6 changes: 4 additions & 2 deletions src/os/windows.rs
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ pub fn walk_regions(pid: u32) -> Vec<Region> {
/// A `Vec<HeapBlock>` with address, size, free status, and page protection
/// for each parsed heap block.
pub fn walk_heap(pid: u32) -> Vec<HeapBlock> {
use std::time::{Duration, Instant};
use windows::Win32::Foundation::CloseHandle;
use windows::Win32::System::Diagnostics::Debug::ReadProcessMemory;
use windows::Win32::System::Diagnostics::ToolHelp::{
Expand All @@ -150,7 +151,6 @@ pub fn walk_heap(pid: u32) -> Vec<HeapBlock> {
use windows::Win32::System::Threading::{
OpenProcess, PROCESS_QUERY_INFORMATION, PROCESS_VM_READ,
};
use std::time::{Duration, Instant};

// --- tunable safety limits ---
const MAX_BLOCKS_PER_HEAP: usize = 100_000;
Expand Down Expand Up @@ -218,7 +218,9 @@ pub fn walk_heap(pid: u32) -> Vec<HeapBlock> {
}
if blocks_this_heap >= MAX_BLOCKS_PER_HEAP {
// likely corrupted/hostile heap metadata — stop this heap, move to next
eprintln!("walk_heap: heap at {heap_base:#x} exceeded block cap, skipping rest");
eprintln!(
"walk_heap: heap at {heap_base:#x} exceeded block cap, skipping rest"
);
break;
}

Expand Down
2 changes: 1 addition & 1 deletion src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ pub enum RegionProtect {
Other,
}

#[derive(Clone, Debug, Serialize)]
#[derive(Clone, Debug, PartialEq, Serialize)]
pub struct HeapBlock {
pub address: usize,
pub size: usize,
Expand Down
Loading