use std::collections::{BTreeMap, HashMap, HashSet};
use std::fmt;
const X86_MAX_BLOAT_FUNCTIONS: usize = 50;
const X86_HOT_THRESHOLD_PCT: f64 = 5.0;
const X86_WARM_THRESHOLD_PCT: f64 = 1.0;
const X86_MIN_INSTR_FOR_COMPLEXITY: usize = 5;
const X86_MAX_TREND_VERSIONS: usize = 20;
const X86_PAGE_SIZE_BYTES: u64 = 4096;
const X86_CACHE_LINE_SIZE: u64 = 64;
#[derive(Debug, Clone)]
pub struct X86CodeMetrics {
pub timestamp: String,
pub version: String,
pub size: X86SizeMetrics,
pub performance: X86PerformanceMetrics,
pub complexity: X86ComplexityMetrics,
pub quality: X86QualityMetrics,
pub binary: Option<X86BinaryAnalysis>,
pub report: X86MetricsReport,
pub history: Vec<X86MetricsSnapshot>,
}
impl X86CodeMetrics {
pub fn new(version: &str) -> Self {
Self {
timestamp: chrono_like_now(),
version: version.to_string(),
size: X86SizeMetrics::new(),
performance: X86PerformanceMetrics::new(),
complexity: X86ComplexityMetrics::new(),
quality: X86QualityMetrics::new(),
binary: None,
report: X86MetricsReport::new(),
history: Vec::new(),
}
}
pub fn with_binary(mut self, binary: X86BinaryAnalysis) -> Self {
self.binary = Some(binary);
self
}
pub fn generate_text_report(&self) -> String {
self.report.generate_text(
&self.size,
&self.performance,
&self.complexity,
&self.quality,
&self.binary,
)
}
pub fn generate_json_report(&self) -> String {
self.report.generate_json(
&self.size,
&self.performance,
&self.complexity,
&self.quality,
&self.binary,
)
}
pub fn generate_html_report(&self) -> String {
self.report.generate_html(
&self.size,
&self.performance,
&self.complexity,
&self.quality,
&self.binary,
)
}
pub fn generate_csv_report(&self) -> String {
self.report.generate_csv(
&self.size,
&self.performance,
&self.complexity,
&self.quality,
&self.binary,
)
}
pub fn save_snapshot(&mut self) {
let snapshot = X86MetricsSnapshot {
version: self.version.clone(),
timestamp: chrono_like_now(),
total_size: self.size.total_size_bytes,
total_functions: self.size.function_sizes.len() as u64,
avg_cyclomatic: self.complexity.average_cyclomatic(),
avg_maintainability: self.quality.average_maintainability(),
total_warnings: self.quality.total_warnings,
};
self.history.push(snapshot);
if self.history.len() > X86_MAX_TREND_VERSIONS {
self.history.remove(0);
}
}
pub fn trend_analysis(&self) -> X86TrendReport {
self.report.trend_analysis(&self.history)
}
pub fn merge(&mut self, other: &X86CodeMetrics) {
self.size.merge(&other.size);
self.performance.merge(&other.performance);
self.complexity.merge(&other.complexity);
self.quality.merge(&other.quality);
}
pub fn total_functions(&self) -> usize {
self.size.function_sizes.len()
}
pub fn total_size_bytes(&self) -> u64 {
self.size.total_size_bytes
}
pub fn overall_maintainability(&self) -> f64 {
self.quality.average_maintainability()
}
}
impl Default for X86CodeMetrics {
fn default() -> Self {
Self::new("unknown")
}
}
impl fmt::Display for X86CodeMetrics {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(f, "X86 Code Metrics Report — version: {}", self.version)?;
writeln!(f, " Timestamp: {}", self.timestamp)?;
writeln!(f, " Total functions: {}", self.total_functions())?;
writeln!(f, " Total size: {} bytes", self.total_size_bytes())?;
writeln!(
f,
" Avg complexity: {:.1}",
self.complexity.average_cyclomatic()
)?;
writeln!(
f,
" Avg maintainability: {:.1}",
self.overall_maintainability()
)?;
Ok(())
}
}
#[derive(Debug, Clone)]
pub struct X86SizeMetrics {
pub total_size_bytes: u64,
pub function_sizes: BTreeMap<String, u64>,
pub basic_block_sizes: Vec<u64>,
pub instruction_distribution: HashMap<String, u64>,
pub instruction_category_counts: HashMap<X86InstrCategory, u64>,
pub section_sizes: HashMap<String, u64>,
pub previous_function_sizes: Option<BTreeMap<String, u64>>,
pub regression_threshold_bytes: u64,
pub size_history: Vec<X86SizeSnapshot>,
pub estimated_loc: u64,
pub total_basic_blocks: u64,
pub total_instructions: u64,
}
impl X86SizeMetrics {
pub fn new() -> Self {
Self {
total_size_bytes: 0,
function_sizes: BTreeMap::new(),
basic_block_sizes: Vec::new(),
instruction_distribution: HashMap::new(),
instruction_category_counts: HashMap::new(),
section_sizes: HashMap::new(),
previous_function_sizes: None,
regression_threshold_bytes: 1024,
size_history: Vec::new(),
estimated_loc: 0,
total_basic_blocks: 0,
total_instructions: 0,
}
}
pub fn record_function(&mut self, name: &str, size_bytes: u64) {
self.function_sizes.insert(name.to_string(), size_bytes);
self.total_size_bytes += size_bytes;
}
pub fn record_basic_block(&mut self, size_bytes: u64) {
self.basic_block_sizes.push(size_bytes);
self.total_basic_blocks += 1;
}
pub fn record_instruction(&mut self, mnemonic: &str) {
*self
.instruction_distribution
.entry(mnemonic.to_string())
.or_insert(0) += 1;
self.total_instructions += 1;
let cat = X86InstrCategory::from_mnemonic(mnemonic);
*self.instruction_category_counts.entry(cat).or_insert(0) += 1;
}
pub fn record_section(&mut self, name: &str, size_bytes: u64) {
self.section_sizes.insert(name.to_string(), size_bytes);
}
pub fn average_function_size(&self) -> f64 {
if self.function_sizes.is_empty() {
return 0.0;
}
self.function_sizes.values().sum::<u64>() as f64 / self.function_sizes.len() as f64
}
pub fn median_function_size(&self) -> u64 {
if self.function_sizes.is_empty() {
return 0;
}
let mut sizes: Vec<u64> = self.function_sizes.values().copied().collect();
sizes.sort_unstable();
let mid = sizes.len() / 2;
if sizes.len() % 2 == 0 {
(sizes[mid - 1] + sizes[mid]) / 2
} else {
sizes[mid]
}
}
pub fn largest_function(&self) -> Option<(String, u64)> {
self.function_sizes
.iter()
.max_by_key(|&(_, &s)| s)
.map(|(n, &s)| (n.clone(), s))
}
pub fn smallest_function(&self) -> Option<(String, u64)> {
self.function_sizes
.iter()
.min_by_key(|&(_, &s)| s)
.map(|(n, &s)| (n.clone(), s))
}
pub fn basic_block_quantiles(&self) -> X86SizeQuantiles {
X86SizeQuantiles::from_sizes(&self.basic_block_sizes)
}
pub fn basic_block_histogram(&self, bins: usize) -> Vec<(u64, usize)> {
if self.basic_block_sizes.is_empty() {
return Vec::new();
}
let min = *self.basic_block_sizes.iter().min().unwrap_or(&0);
let max = *self.basic_block_sizes.iter().max().unwrap_or(&0);
if max == min {
return vec![(min, self.basic_block_sizes.len())];
}
let bin_width = ((max - min) as f64 / bins as f64).ceil() as u64;
let mut histogram = vec![0usize; bins];
for size in &self.basic_block_sizes {
let idx = ((size - min) / bin_width.max(1)) as usize;
let idx = idx.min(bins - 1);
histogram[idx] += 1;
}
histogram
.into_iter()
.enumerate()
.map(|(i, c)| (min + i as u64 * bin_width, c))
.collect()
}
pub fn top_bloat_functions(&self, n: usize) -> Vec<(&str, u64)> {
let n = n.min(X86_MAX_BLOAT_FUNCTIONS);
let mut entries: Vec<(&str, u64)> = self
.function_sizes
.iter()
.map(|(k, &v)| (k.as_str(), v))
.collect();
entries.sort_by(|a, b| b.1.cmp(&a.1));
entries.truncate(n);
entries
}
pub fn detect_regressions(&self) -> Vec<X86SizeRegression> {
let prev = match &self.previous_function_sizes {
Some(p) => p,
None => return Vec::new(),
};
let mut regressions = Vec::new();
for (name, ¤t_size) in &self.function_sizes {
if let Some(&previous_size) = prev.get(name) {
let delta = current_size as i64 - previous_size as i64;
if delta > self.regression_threshold_bytes as i64 {
let pct = if previous_size > 0 {
(delta as f64 / previous_size as f64) * 100.0
} else {
f64::INFINITY
};
regressions.push(X86SizeRegression {
function: name.clone(),
previous_size,
current_size,
delta_bytes: delta as u64,
delta_percent: pct,
});
}
}
}
regressions.sort_by(|a, b| b.delta_bytes.cmp(&a.delta_bytes));
regressions
}
pub fn new_functions(&self) -> Vec<String> {
let prev = match &self.previous_function_sizes {
Some(p) => p,
None => return Vec::new(),
};
self.function_sizes
.keys()
.filter(|k| !prev.contains_key(*k))
.cloned()
.collect()
}
pub fn removed_functions(&self) -> Vec<String> {
let prev = match &self.previous_function_sizes {
Some(p) => p,
None => return Vec::new(),
};
prev.keys()
.filter(|k| !self.function_sizes.contains_key(*k))
.cloned()
.collect()
}
pub fn largest_section(&self) -> Option<(String, u64)> {
self.section_sizes
.iter()
.max_by_key(|&(_, &s)| s)
.map(|(n, &s)| (n.clone(), s))
}
pub fn code_data_ratio(&self) -> f64 {
let code = self.section_sizes.get(".text").copied().unwrap_or(0);
let data = self.section_sizes.get(".data").copied().unwrap_or(0)
+ self.section_sizes.get(".rodata").copied().unwrap_or(0);
if data == 0 {
return f64::INFINITY;
}
code as f64 / data as f64
}
pub fn nop_sled_count(&self) -> u64 {
self.instruction_distribution
.get("nop")
.copied()
.unwrap_or(0)
}
pub fn save_snapshot(&mut self) {
let snapshot = X86SizeSnapshot {
timestamp: chrono_like_now(),
total_size: self.total_size_bytes,
function_count: self.function_sizes.len() as u64,
avg_function_size: self.average_function_size() as u64,
text_size: self.section_sizes.get(".text").copied().unwrap_or(0),
data_size: self.section_sizes.get(".data").copied().unwrap_or(0),
};
self.size_history.push(snapshot);
}
pub fn densest_functions(&self, n: usize) -> Vec<(String, f64)> {
let mut entries: Vec<(String, f64)> = Vec::new();
for (name, &size) in &self.function_sizes {
let instr_count = self
.instruction_distribution
.get(name)
.copied()
.unwrap_or(0) as f64;
let density = if size > 0 {
instr_count / size as f64
} else {
0.0
};
entries.push((name.clone(), density));
}
entries.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal));
entries.truncate(n);
entries
}
pub fn alignment_waste(&self) -> u64 {
let mut waste = 0u64;
for &size in &self.basic_block_sizes {
let remainder = size % X86_CACHE_LINE_SIZE;
if remainder > 0 {
waste += X86_CACHE_LINE_SIZE - remainder;
}
}
waste
}
pub fn merge(&mut self, other: &X86SizeMetrics) {
self.total_size_bytes += other.total_size_bytes;
for (name, size) in &other.function_sizes {
*self.function_sizes.entry(name.clone()).or_insert(0) += size;
}
self.basic_block_sizes.extend(&other.basic_block_sizes);
for (mnemonic, count) in &other.instruction_distribution {
*self
.instruction_distribution
.entry(mnemonic.clone())
.or_insert(0) += count;
}
for (cat, count) in &other.instruction_category_counts {
*self.instruction_category_counts.entry(*cat).or_insert(0) += count;
}
for (name, size) in &other.section_sizes {
*self.section_sizes.entry(name.clone()).or_insert(0) += size;
}
self.estimated_loc += other.estimated_loc;
self.total_basic_blocks += other.total_basic_blocks;
self.total_instructions += other.total_instructions;
}
}
impl Default for X86SizeMetrics {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, Copy)]
pub struct X86SizeQuantiles {
pub min: u64,
pub p25: u64,
pub p50: u64,
pub p75: u64,
pub p90: u64,
pub p95: u64,
pub p99: u64,
pub max: u64,
pub mean: f64,
pub std_dev: f64,
}
impl X86SizeQuantiles {
pub fn from_sizes(sizes: &[u64]) -> Self {
if sizes.is_empty() {
return Self {
min: 0,
p25: 0,
p50: 0,
p75: 0,
p90: 0,
p95: 0,
p99: 0,
max: 0,
mean: 0.0,
std_dev: 0.0,
};
}
let mut sorted = sizes.to_vec();
sorted.sort_unstable();
let len = sorted.len();
let min = sorted[0];
let max = sorted[len - 1];
let mean = sorted.iter().sum::<u64>() as f64 / len as f64;
let variance = sorted
.iter()
.map(|&x| (x as f64 - mean).powi(2))
.sum::<f64>()
/ len as f64;
let std_dev = variance.sqrt();
let pctl = |p: f64| -> u64 {
let idx = ((len - 1) as f64 * p / 100.0).round() as usize;
sorted[idx.min(len - 1)]
};
Self {
min,
max,
mean,
std_dev,
p25: pctl(25.0),
p50: pctl(50.0),
p75: pctl(75.0),
p90: pctl(90.0),
p95: pctl(95.0),
p99: pctl(99.0),
}
}
}
#[derive(Debug, Clone)]
pub struct X86SizeRegression {
pub function: String,
pub previous_size: u64,
pub current_size: u64,
pub delta_bytes: u64,
pub delta_percent: f64,
}
impl fmt::Display for X86SizeRegression {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{}: {} -> {} bytes (+{} bytes, +{:.1}%)",
self.function,
self.previous_size,
self.current_size,
self.delta_bytes,
self.delta_percent
)
}
}
#[derive(Debug, Clone)]
pub struct X86SizeSnapshot {
pub timestamp: String,
pub total_size: u64,
pub function_count: u64,
pub avg_function_size: u64,
pub text_size: u64,
pub data_size: u64,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum X86InstrCategory {
DataMovement,
ArithmeticLogic,
ControlFlow,
Stack,
String,
FloatingPoint,
SIMD,
System,
Nop,
Other,
}
impl X86InstrCategory {
pub fn from_mnemonic(mnemonic: &str) -> Self {
let lower = mnemonic.to_ascii_lowercase();
match lower.as_str() {
"mov" | "movsx" | "movzx" | "movabs" | "lea" | "cmov" | "cmovcc" |
"cmove" | "cmovne" | "cmova" | "cmovae" | "cmovb" | "cmovbe" |
"cmovg" | "cmovge" | "cmovl" | "cmovle" | "cmovo" | "cmovno" |
"cmovs" | "cmovns" | "cmovp" | "cmovnp" |
"xchg" | "bswap" | "xlat" | "push" | "pop" | "pusha" | "popa" => Self::DataMovement,
"add" | "sub" | "mul" | "imul" | "div" | "idiv" | "inc" | "dec" |
"neg" | "adc" | "sbb" | "and" | "or" | "xor" | "not" |
"shl" | "shr" | "sal" | "sar" | "rol" | "ror" | "rcl" | "rcr" |
"cmp" | "test" | "bt" | "bts" | "btr" | "btc" | "bsf" | "bsr" |
"sete" | "setne" | "seta" | "setae" | "setb" | "setbe" |
"setg" | "setge" | "setl" | "setle" | "seto" | "setno" |
"sets" | "setns" | "setp" | "setnp" => Self::ArithmeticLogic,
"jmp" | "jcc" | "je" | "jne" | "ja" | "jae" | "jb" | "jbe" |
"jg" | "jge" | "jl" | "jle" | "jo" | "jno" | "js" | "jns" |
"jp" | "jnp" | "jcxz" | "jecxz" | "jrcxz" |
"call" | "ret" | "retf" | "iret" | "iretd" | "iretq" |
"syscall" | "sysret" | "int" | "into" | "bound" |
"enter" | "leave" => Self::ControlFlow,
"pushf" | "popf" | "pushfd" | "popfd" | "pushfq" | "popfq" => Self::Stack,
"movs" | "movsb" | "movsw" | "movsd" | "movsq" |
"cmps" | "cmpsb" | "cmpsw" | "cmpsd" | "cmpsq" |
"scas" | "scasb" | "scasw" | "scasd" | "scasq" |
"lods" | "lodsb" | "lodsw" | "lodsd" | "lodsq" |
"stos" | "stosb" | "stosw" | "stosd" | "stosq" |
"rep" | "repe" | "repne" | "repz" | "repnz" => Self::String,
"fld" | "fst" | "fstp" | "fild" | "fist" | "fistp" |
"fadd" | "fsub" | "fmul" | "fdiv" | "fabs" | "fchs" |
"fcom" | "fcomp" | "fcompp" | "ftst" | "fxam" | "fprem" |
"fsin" | "fcos" | "fsincos" | "fptan" | "fpatan" |
"f2xm1" | "fyl2x" | "fyl2xp1" | "fscale" | "frndint" |
"fldcw" | "fstcw" | "fldenv" | "fstenv" | "fsave" | "frstor" |
"finit" | "fninit" | "fclex" | "fnclex" | "fwait" |
"fxch" | "ffree" | "fnop" => Self::FloatingPoint,
"movss" | "movaps" | "movapd" | "movups" | "movupd" |
"movdqa" | "movdqu" | "addps" | "addpd" | "addss" | "addsd" |
"subps" | "subpd" | "subss" | "subsd" | "mulps" | "mulpd" |
"mulss" | "mulsd" | "divps" | "divpd" | "divss" | "divsd" |
"sqrtps" | "sqrtpd" | "sqrtss" | "sqrtsd" | "rsqrtps" | "rsqrtss" |
"rcpps" | "rcpss" | "maxps" | "maxpd" | "maxss" | "maxsd" |
"minps" | "minpd" | "minss" | "minsd" | "andps" | "andpd" |
"andnps" | "andnpd" | "orps" | "orpd" | "xorps" | "xorpd" |
"cmpps" | "cmppd" | "cmpss" | "shufps" | "shufpd" |
"unpcklps" | "unpckhps" | "unpcklpd" | "unpckhpd" |
"cvtss2sd" | "cvtsd2ss" | "cvttps2dq" | "cvtdq2ps" |
"cvttsd2si" | "cvtsi2ss" | "cvtsi2sd" | "cvtps2pd" | "cvtpd2ps" |
"extractps" | "insertps" | "pextrb" | "pextrw" | "pextrd" | "pextrq" |
"pinsrb" | "pinsrw" | "pinsrd" | "pinsrq" |
"paddb" | "paddw" | "paddd" | "paddq" | "psubb" | "psubw" |
"psubd" | "psubq" | "pmaddwd" | "pmulhw" | "pmullw" | "pmuludq" |
"pavgb" | "pavgw" | "pmaxub" | "pminub" | "pmaxsw" | "pminsw" |
"psadbw" | "pshufd" | "pshufhw" | "pshuflw" | "pslldq" | "psrldq" |
"pand" | "pandn" | "por" | "pxor" | "pcmpeqb" | "pcmpeqw" |
"pcmpeqd" | "pcmpgtb" | "pcmpgtw" | "pcmpgtd" |
"packsswb" | "packssdw" | "packuswb" | "punpcklbw" | "punpcklwd" |
"punpckldq" | "punpcklqdq" | "punpckhbw" | "punpckhwd" |
"punpckhdq" | "punpckhqdq" |
"vmovaps" | "vmovapd" | "vmovups" | "vmovupd" | "vaddps" | "vaddpd" |
"vmulps" | "vmulpd" | "vdivps" | "vdivpd" | "vsqrtps" | "vsqrtpd" |
"vbroadcastss" | "vbroadcastsd" | "vbroadcastf128" |
"vpermilps" | "vpermilpd" | "vperm2f128" | "vpermq" | "vpermd" |
"vpbroadcastb" | "vpbroadcastw" | "vpbroadcastd" | "vpbroadcastq" |
"vfmadd132ps" | "vfmadd213ps" | "vfmadd231ps" |
"vfmadd132pd" | "vfmadd213pd" | "vfmadd231pd" |
"vfnmadd132ps" | "vfnmadd213ps" | "vfnmadd231ps" |
"vfmsub132ps" | "vfmsub213ps" | "vfmsub231ps" |
"vfnmsub132ps" | "vfnmsub213ps" | "vfnmsub231ps" |
"vfmaddsub132ps" | "vfmaddsub213ps" | "vfmaddsub231ps" |
"vgatherdps" | "vgatherdpd" | "vgatherqps" | "vgatherqpd" |
"vscatterdps" | "vscatterdpd" | "vscatterqps" | "vscatterqpd" |
"vcompressps" | "vcompresspd" | "vexpandps" | "vexpandpd" |
"vmovdqa32" | "vmovdqa64" | "vmovdqu32" | "vmovdqu64" |
"vcvtps2ph" | "vcvtph2ps" |
"vpshufb" | "vpslld" | "vpsrld" | "vpsrad" | "vpor" | "vpxor" |
"vpand" | "vpandn" | "vpaddd" | "vpsubd" | "vpmulld" |
"vpcmpeqd" | "vpcmpgtd" | "vpminsd" | "vpmaxsd" |
"vpgatherdd" | "vpgatherdq" | "vpscatterdd" | "vpscatterdq" |
"vpcompressd" | "vpexpandd" |
"kaddb" | "kandb" | "knotb" | "korb" |
"kxorb" | "kortestb" | "ktestb" | "kmovb" | "kshiftlb" | "kshiftrb" |
"andn" | "bextr" | "blsi" | "blsmsk" | "blsr" | "tzcnt" | "lzcnt" |
"bzhi" | "mulx" | "pdep" | "pext" | "rorx" | "sarx" | "shlx" | "shrx" |
"aesenc" | "aesenclast" | "aesdec" | "aesdeclast" |
"aesimc" | "aeskeygenassist" | "vaesenc" | "vaesenclast" |
"vaesdec" | "vaesdeclast" |
"sha1rnds4" | "sha1nexte" | "sha1msg1" | "sha1msg2" |
"sha256rnds2" | "sha256msg1" | "sha256msg2" => Self::SIMD,
"cpuid" | "rdmsr" | "wrmsr" | "rdtsc" | "rdtscp" |
"rdpmc" | "rdrand" | "rdseed" | "clflush" | "clflushopt" | "clwb" |
"mfence" | "lfence" | "sfence" | "pause" | "hlt" | "cli" | "sti" |
"in" | "out" | "ins" | "outs" | "lgdt" | "sgdt" | "lidt" | "sidt" |
"lldt" | "sldt" | "ltr" | "str" | "lmsw" | "smsw" |
"invlpg" | "invpcid" | "wbinvd" | "invd" |
"xgetbv" | "xsetbv" | "xsave" | "xrstor" | "xsaveopt" |
"xsaves" | "xrstors" | "fxsave" | "fxrstor" |
"enclu" | "encls" | "vmcall" | "vmlaunch" | "vmresume" | "vmxoff" |
"vmon" | "vmfunc" | "vmptrld" | "vmptrst" | "vmclear" |
"vmread" | "vmwrite" | "invept" | "invvpid" |
"tpause" | "umwait" | "umonitor" | "clac" | "stac" |
"endbr32" | "endbr64" | "wrssd" | "wrssq" | "wrussd" | "wrussq" |
"setssbsy" | "clrssbsy" | "rstorssp" | "saveprevssp" => Self::System,
"nop" | "nopw" | "nopl" | "data16" | "xacquire" | "xrelease" |
"rex" | "rex.w" | "rex.r" | "rex.x" | "rex.b" | "rex.rb" | "rex.rxb" => Self::Nop,
_ => Self::Other,
}
}
pub fn name(&self) -> &'static str {
match self {
Self::DataMovement => "Data Movement",
Self::ArithmeticLogic => "Arithmetic/Logic",
Self::ControlFlow => "Control Flow",
Self::Stack => "Stack Operations",
Self::String => "String Operations",
Self::FloatingPoint => "Floating Point",
Self::SIMD => "SIMD",
Self::System => "System",
Self::Nop => "NOP",
Self::Other => "Other",
}
}
}
impl fmt::Display for X86InstrCategory {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.name())
}
}
#[derive(Debug, Clone)]
pub struct X86PerformanceMetrics {
pub static_instruction_count: u64,
pub dynamic_instruction_count: u64,
pub total_branches: u64,
pub predicted_branches: u64,
pub mispredicted_branches: u64,
pub l1i_cache_misses: u64,
pub l1d_cache_misses: u64,
pub l2_cache_misses: u64,
pub llc_cache_misses: u64,
pub memory_patterns: HashMap<String, X86MemoryPattern>,
pub call_frequency: HashMap<String, u64>,
pub loop_iterations: HashMap<String, u64>,
pub hot_spots: Vec<X86HotSpot>,
pub estimated_cpi: f64,
pub frontend_stall_cycles: u64,
pub backend_stall_cycles: u64,
pub total_cycles_estimate: u64,
pub instructions_per_cycle: f64,
pub load_count: u64,
pub store_count: u64,
pub average_loop_depth: f64,
pub max_loop_nest_depth: u64,
}
impl X86PerformanceMetrics {
pub fn new() -> Self {
Self {
static_instruction_count: 0,
dynamic_instruction_count: 0,
total_branches: 0,
predicted_branches: 0,
mispredicted_branches: 0,
l1i_cache_misses: 0,
l1d_cache_misses: 0,
l2_cache_misses: 0,
llc_cache_misses: 0,
memory_patterns: HashMap::new(),
call_frequency: HashMap::new(),
loop_iterations: HashMap::new(),
hot_spots: Vec::new(),
estimated_cpi: 0.0,
frontend_stall_cycles: 0,
backend_stall_cycles: 0,
total_cycles_estimate: 0,
instructions_per_cycle: 0.0,
load_count: 0,
store_count: 0,
average_loop_depth: 0.0,
max_loop_nest_depth: 0,
}
}
pub fn record_branch(&mut self, predicted: bool) {
self.total_branches += 1;
if predicted {
self.predicted_branches += 1;
} else {
self.mispredicted_branches += 1;
}
}
pub fn branch_prediction_accuracy(&self) -> f64 {
if self.total_branches == 0 {
return 1.0;
}
self.predicted_branches as f64 / self.total_branches as f64
}
pub fn branch_misprediction_rate(&self) -> f64 {
if self.total_branches == 0 {
return 0.0;
}
self.mispredicted_branches as f64 / self.total_branches as f64
}
pub fn l1i_miss_rate(&self) -> f64 {
if self.static_instruction_count == 0 {
return 0.0;
}
(self.l1i_cache_misses as f64 / self.static_instruction_count as f64).min(1.0)
}
pub fn compute_ipc(&self) -> f64 {
if self.total_cycles_estimate == 0 {
return 0.0;
}
self.dynamic_instruction_count as f64 / self.total_cycles_estimate as f64
}
pub fn record_memory_pattern(&mut self, function: &str, pattern: X86MemoryPattern) {
self.memory_patterns.insert(function.to_string(), pattern);
}
pub fn record_call(&mut self, function: &str) {
*self.call_frequency.entry(function.to_string()).or_insert(0) += 1;
}
pub fn record_loop(&mut self, loop_id: &str, iterations: u64) {
self.loop_iterations.insert(loop_id.to_string(), iterations);
}
pub fn identify_hot_spots(&mut self) {
self.hot_spots.clear();
let total: u64 = self.call_frequency.values().sum();
if total == 0 {
return;
}
for (func, &count) in &self.call_frequency {
let pct = count as f64 / total as f64 * 100.0;
let category = if pct >= X86_HOT_THRESHOLD_PCT {
X86HotSpotCategory::Hot
} else if pct >= X86_WARM_THRESHOLD_PCT {
X86HotSpotCategory::Warm
} else {
X86HotSpotCategory::Cold
};
self.hot_spots.push(X86HotSpot {
function: func.clone(),
estimated_cycles: count,
percentage: pct,
category,
});
}
self.hot_spots
.sort_by(|a, b| b.estimated_cycles.cmp(&a.estimated_cycles));
}
pub fn top_hot_functions(&self, n: usize) -> Vec<&X86HotSpot> {
self.hot_spots
.iter()
.filter(|h| h.category == X86HotSpotCategory::Hot)
.take(n)
.collect()
}
pub fn cache_miss_penalty_cycles(&self) -> u64 {
let l1_penalty = 4;
let l2_penalty = 12;
let llc_penalty = 40;
self.l1i_cache_misses * l1_penalty
+ self.l1d_cache_misses * l1_penalty
+ self.l2_cache_misses * l2_penalty
+ self.llc_cache_misses * llc_penalty
}
pub fn load_store_ratio(&self) -> f64 {
if self.store_count == 0 {
return f64::INFINITY;
}
self.load_count as f64 / self.store_count as f64
}
pub fn top_called_functions(&self, n: usize) -> Vec<(&str, u64)> {
let mut entries: Vec<(&str, u64)> = self
.call_frequency
.iter()
.map(|(k, &v)| (k.as_str(), v))
.collect();
entries.sort_by(|a, b| b.1.cmp(&a.1));
entries.truncate(n);
entries
}
pub fn estimate_frontend_stalls(&mut self) {
self.frontend_stall_cycles = self.l1i_cache_misses * 8 + self.mispredicted_branches * 16; }
pub fn estimate_backend_stalls(&mut self) {
self.backend_stall_cycles = self.cache_miss_penalty_cycles();
}
pub fn compute_all(&mut self) {
self.estimate_frontend_stalls();
self.estimate_backend_stalls();
self.total_cycles_estimate =
self.dynamic_instruction_count + self.frontend_stall_cycles + self.backend_stall_cycles;
self.instructions_per_cycle = self.compute_ipc();
self.identify_hot_spots();
}
pub fn record_load(&mut self) {
self.load_count += 1;
}
pub fn record_store(&mut self) {
self.store_count += 1;
}
pub fn merge(&mut self, other: &X86PerformanceMetrics) {
self.static_instruction_count += other.static_instruction_count;
self.dynamic_instruction_count += other.dynamic_instruction_count;
self.total_branches += other.total_branches;
self.predicted_branches += other.predicted_branches;
self.mispredicted_branches += other.mispredicted_branches;
self.l1i_cache_misses += other.l1i_cache_misses;
self.l1d_cache_misses += other.l1d_cache_misses;
self.l2_cache_misses += other.l2_cache_misses;
self.llc_cache_misses += other.llc_cache_misses;
for (k, v) in &other.call_frequency {
*self.call_frequency.entry(k.clone()).or_insert(0) += v;
}
for (k, v) in &other.loop_iterations {
*self.loop_iterations.entry(k.clone()).or_insert(0) += v;
}
self.load_count += other.load_count;
self.store_count += other.store_count;
if other.max_loop_nest_depth > self.max_loop_nest_depth {
self.max_loop_nest_depth = other.max_loop_nest_depth;
}
}
}
impl Default for X86PerformanceMetrics {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum X86MemoryPattern {
Streaming { stride: i64, element_size: u64 },
Random { access_count: u64 },
Strided { stride: i64, element_size: u64 },
Mixed {
streaming_pct: f64,
random_pct: f64,
strided_pct: f64,
},
}
impl X86MemoryPattern {
pub fn describe(&self) -> String {
match self {
Self::Streaming {
stride,
element_size,
} => {
format!("Streaming (stride={}, elem_size={})", stride, element_size)
}
Self::Random { access_count } => {
format!("Random ({} accesses)", access_count)
}
Self::Strided {
stride,
element_size,
} => {
format!("Strided (stride={}, elem_size={})", stride, element_size)
}
Self::Mixed {
streaming_pct,
random_pct,
strided_pct,
} => {
format!(
"Mixed (streaming={:.0}%, random={:.0}%, strided={:.0}%)",
streaming_pct * 100.0,
random_pct * 100.0,
strided_pct * 100.0
)
}
}
}
pub fn cache_friendliness(&self) -> f64 {
match self {
Self::Streaming { .. } => 0.9,
Self::Strided {
stride,
element_size,
} => {
if stride.abs() <= (*element_size as i64) {
0.7
} else {
0.4
}
}
Self::Random { .. } => 0.1,
Self::Mixed {
streaming_pct,
random_pct,
strided_pct,
} => 0.9 * streaming_pct + 0.1 * random_pct + 0.5 * strided_pct,
}
}
}
#[derive(Debug, Clone)]
pub struct X86HotSpot {
pub function: String,
pub estimated_cycles: u64,
pub percentage: f64,
pub category: X86HotSpotCategory,
}
impl fmt::Display for X86HotSpot {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{}: {:?} ({:.1}%)",
self.function, self.category, self.percentage
)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum X86HotSpotCategory {
Hot,
Warm,
Cold,
}
#[derive(Debug, Clone)]
pub struct X86ComplexityMetrics {
pub cyclomatic_complexity: HashMap<String, u64>,
pub cognitive_complexity: HashMap<String, f64>,
pub halstead_metrics: HashMap<String, X86HalsteadMetrics>,
pub nesting_depth: HashMap<String, u64>,
pub fan_in: HashMap<String, u64>,
pub fan_out: HashMap<String, u64>,
pub maintainability_index: HashMap<String, f64>,
pub total_operators: u64,
pub total_operands: u64,
pub unique_operators: u64,
pub unique_operands: u64,
pub analyzed_functions: u64,
pub overly_complex_functions: Vec<String>,
pub complexity_threshold: u64,
}
impl X86ComplexityMetrics {
pub fn new() -> Self {
Self {
cyclomatic_complexity: HashMap::new(),
cognitive_complexity: HashMap::new(),
halstead_metrics: HashMap::new(),
nesting_depth: HashMap::new(),
fan_in: HashMap::new(),
fan_out: HashMap::new(),
maintainability_index: HashMap::new(),
total_operators: 0,
total_operands: 0,
unique_operators: 0,
unique_operands: 0,
analyzed_functions: 0,
overly_complex_functions: Vec::new(),
complexity_threshold: 15,
}
}
pub fn compute_cyclomatic(
&mut self,
function: &str,
num_branches: u64,
num_basic_blocks: u64,
num_edges: u64,
) {
let complexity = num_edges.saturating_sub(num_basic_blocks).saturating_add(2);
let score = complexity.max(num_branches.saturating_add(1).max(1));
self.cyclomatic_complexity
.insert(function.to_string(), score);
if score > self.complexity_threshold {
self.overly_complex_functions.push(function.to_string());
}
}
pub fn compute_cognitive(
&mut self,
function: &str,
nesting: u64,
num_branches: u64,
continues: u64,
breaks: u64,
) {
let base = num_branches as f64;
let nesting_penalty = nesting as f64 * 0.25 * base;
let flow_penalty = (continues + breaks) as f64 * 0.3;
let score = base + nesting_penalty + flow_penalty;
self.cognitive_complexity
.insert(function.to_string(), score);
}
pub fn compute_halstead(
&mut self,
function: &str,
n1: u64,
n2: u64,
total_ops: u64,
total_opnds: u64,
) {
let n1 = n1.max(1);
let n2 = n2.max(1);
let total_ops = total_ops.max(1);
let total_opnds = total_opnds.max(1);
let program_length = total_ops + total_opnds;
let program_vocabulary = n1 + n2;
let volume = program_length as f64 * (program_vocabulary as f64).log2();
let difficulty = (n1 as f64 / 2.0) * (total_opnds as f64 / n2 as f64);
let effort = volume * difficulty;
let time_to_implement = effort / 18.0; let bugs_delivered = volume / 3000.0;
let metrics = X86HalsteadMetrics {
unique_operators: n1,
unique_operands: n2,
total_operators: total_ops,
total_operands: total_opnds,
program_length,
program_vocabulary,
volume,
difficulty,
effort,
time_to_implement_seconds: time_to_implement,
estimated_bugs: bugs_delivered,
};
self.halstead_metrics.insert(function.to_string(), metrics);
self.total_operators += total_ops;
self.total_operands += total_opnds;
self.unique_operators += n1;
self.unique_operands += n2;
}
pub fn record_nesting_depth(&mut self, function: &str, depth: u64) {
self.nesting_depth.insert(function.to_string(), depth);
}
pub fn record_fan_in(&mut self, function: &str, count: u64) {
self.fan_in.insert(function.to_string(), count);
}
pub fn record_fan_out(&mut self, function: &str, count: u64) {
self.fan_out.insert(function.to_string(), count);
}
pub fn compute_maintainability(&mut self, function: &str, loc: u64) {
let v = self
.halstead_metrics
.get(function)
.map(|h| h.volume)
.unwrap_or(1.0);
let cc = self
.cyclomatic_complexity
.get(function)
.copied()
.unwrap_or(1) as f64;
let loc = loc.max(1) as f64;
let raw_mi = 171.0 - 5.2 * v.ln() - 0.23 * cc - 16.2 * loc.ln();
let mi = (raw_mi.max(0.0) * 100.0 / 171.0).min(100.0);
self.maintainability_index.insert(function.to_string(), mi);
}
pub fn average_cyclomatic(&self) -> f64 {
if self.cyclomatic_complexity.is_empty() {
return 0.0;
}
self.cyclomatic_complexity.values().sum::<u64>() as f64
/ self.cyclomatic_complexity.len() as f64
}
pub fn most_complex_function(&self) -> Option<(String, u64)> {
self.cyclomatic_complexity
.iter()
.max_by_key(|&(_, &s)| s)
.map(|(n, &s)| (n.clone(), s))
}
pub fn average_halstead_volume(&self) -> f64 {
if self.halstead_metrics.is_empty() {
return 0.0;
}
self.halstead_metrics
.values()
.map(|h| h.volume)
.sum::<f64>()
/ self.halstead_metrics.len() as f64
}
pub fn average_halstead_effort(&self) -> f64 {
if self.halstead_metrics.is_empty() {
return 0.0;
}
self.halstead_metrics
.values()
.map(|h| h.effort)
.sum::<f64>()
/ self.halstead_metrics.len() as f64
}
pub fn average_maintainability(&self) -> f64 {
if self.maintainability_index.is_empty() {
return 100.0;
}
self.maintainability_index.values().sum::<f64>() / self.maintainability_index.len() as f64
}
pub fn fan_ratio(&self, function: &str) -> f64 {
let fi = self.fan_in.get(function).copied().unwrap_or(0) as f64;
let fo = self.fan_out.get(function).copied().unwrap_or(0) as f64;
if fo == 0.0 && fi == 0.0 {
return 0.0;
}
if fo == 0.0 {
return f64::INFINITY;
}
fi / fo.max(1.0)
}
pub fn sorted_by_complexity(&self) -> Vec<(String, u64)> {
let mut entries: Vec<(String, u64)> = self
.cyclomatic_complexity
.iter()
.map(|(k, &v)| (k.clone(), v))
.collect();
entries.sort_by(|a, b| b.1.cmp(&a.1));
entries
}
pub fn mark_analyzed(&mut self) {
self.analyzed_functions += 1;
}
pub fn merge(&mut self, other: &X86ComplexityMetrics) {
for (k, v) in &other.cyclomatic_complexity {
self.cyclomatic_complexity.insert(k.clone(), *v);
}
for (k, v) in &other.cognitive_complexity {
self.cognitive_complexity.insert(k.clone(), *v);
}
for (k, v) in &other.halstead_metrics {
self.halstead_metrics.insert(k.clone(), v.clone());
}
for (k, v) in &other.nesting_depth {
self.nesting_depth.insert(k.clone(), *v);
}
for (k, v) in &other.fan_in {
*self.fan_in.entry(k.clone()).or_insert(0) += v;
}
for (k, v) in &other.fan_out {
*self.fan_out.entry(k.clone()).or_insert(0) += v;
}
for (k, v) in &other.maintainability_index {
self.maintainability_index.insert(k.clone(), *v);
}
self.total_operators += other.total_operators;
self.total_operands += other.total_operands;
self.unique_operators += other.unique_operators;
self.unique_operands += other.unique_operands;
self.analyzed_functions += other.analyzed_functions;
self.overly_complex_functions
.extend(other.overly_complex_functions.iter().cloned());
}
}
impl Default for X86ComplexityMetrics {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct X86HalsteadMetrics {
pub unique_operators: u64,
pub unique_operands: u64,
pub total_operators: u64,
pub total_operands: u64,
pub program_length: u64,
pub program_vocabulary: u64,
pub volume: f64,
pub difficulty: f64,
pub effort: f64,
pub time_to_implement_seconds: f64,
pub estimated_bugs: f64,
}
impl fmt::Display for X86HalsteadMetrics {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(f, "Halstead Metrics:")?;
writeln!(f, " Unique operators (n1): {}", self.unique_operators)?;
writeln!(f, " Unique operands (n2): {}", self.unique_operands)?;
writeln!(f, " Total operators (N1): {}", self.total_operators)?;
writeln!(f, " Total operands (N2): {}", self.total_operands)?;
writeln!(f, " Program length: {}", self.program_length)?;
writeln!(f, " Program vocabulary: {}", self.program_vocabulary)?;
writeln!(f, " Volume: {:.2}", self.volume)?;
writeln!(f, " Difficulty: {:.2}", self.difficulty)?;
writeln!(f, " Effort: {:.2}", self.effort)?;
writeln!(
f,
" Time to implement: {:.2} s",
self.time_to_implement_seconds
)?;
writeln!(f, " Est. bugs delivered: {:.4}", self.estimated_bugs)?;
Ok(())
}
}
#[derive(Debug, Clone)]
pub struct X86QualityMetrics {
pub total_warnings: u64,
pub total_lines: u64,
pub test_lines: u64,
pub doc_lines: u64,
pub dead_code_lines: u64,
pub duplicate_code_lines: u64,
pub comment_lines: u64,
pub per_function_warnings: HashMap<String, u64>,
pub per_function_coverage: HashMap<String, f64>,
pub per_function_doc_coverage: HashMap<String, f64>,
pub dead_functions: HashSet<String>,
pub duplicate_clusters: HashMap<String, Vec<String>>,
pub quality_issues: Vec<String>,
pub quality_score: f64,
pub test_coverage_pct: f64,
pub documentation_coverage_pct: f64,
}
impl X86QualityMetrics {
pub fn new() -> Self {
Self {
total_warnings: 0,
total_lines: 0,
test_lines: 0,
doc_lines: 0,
dead_code_lines: 0,
duplicate_code_lines: 0,
comment_lines: 0,
per_function_warnings: HashMap::new(),
per_function_coverage: HashMap::new(),
per_function_doc_coverage: HashMap::new(),
dead_functions: HashSet::new(),
duplicate_clusters: HashMap::new(),
quality_issues: Vec::new(),
quality_score: 100.0,
test_coverage_pct: 0.0,
documentation_coverage_pct: 0.0,
}
}
pub fn record_warning(&mut self, function: &str, warning: &str) {
self.total_warnings += 1;
*self
.per_function_warnings
.entry(function.to_string())
.or_insert(0) += 1;
self.quality_issues
.push(format!("Warning in {}: {}", function, warning));
}
pub fn set_total_lines(&mut self, lines: u64) {
self.total_lines = lines;
}
pub fn record_test_coverage(&mut self, function: &str, coverage_pct: f64) {
self.per_function_coverage
.insert(function.to_string(), coverage_pct);
}
pub fn record_doc_coverage(&mut self, function: &str, coverage_pct: f64) {
self.per_function_doc_coverage
.insert(function.to_string(), coverage_pct);
}
pub fn mark_dead_function(&mut self, function: &str) {
self.dead_functions.insert(function.to_string());
}
pub fn record_duplicate(&mut self, signature: &str, locations: Vec<String>) {
let count = locations.len() as u64;
self.duplicate_clusters
.insert(signature.to_string(), locations);
if count > 1 {
self.duplicate_code_lines += (count - 1) * 5; }
}
pub fn warning_density(&self) -> f64 {
if self.total_lines == 0 {
return 0.0;
}
self.total_warnings as f64 / (self.total_lines as f64 / 1000.0)
}
pub fn dead_code_ratio(&self) -> f64 {
if self.total_lines == 0 {
return 0.0;
}
self.dead_code_lines as f64 / self.total_lines as f64
}
pub fn duplicate_code_ratio(&self) -> f64 {
if self.total_lines == 0 {
return 0.0;
}
self.duplicate_code_lines as f64 / self.total_lines as f64
}
pub fn comment_ratio(&self) -> f64 {
if self.total_lines == 0 {
return 0.0;
}
self.comment_lines as f64 / self.total_lines as f64
}
pub fn compute_test_coverage(&mut self) {
if self.per_function_coverage.is_empty() {
self.test_coverage_pct = 0.0;
return;
}
let sum: f64 = self.per_function_coverage.values().sum();
self.test_coverage_pct = sum / self.per_function_coverage.len() as f64;
}
pub fn compute_documentation_coverage(&mut self) {
if self.per_function_doc_coverage.is_empty() {
self.documentation_coverage_pct = 0.0;
return;
}
let sum: f64 = self.per_function_doc_coverage.values().sum();
self.documentation_coverage_pct = sum / self.per_function_doc_coverage.len() as f64;
}
pub fn compute_quality_score(&mut self) {
self.compute_test_coverage();
self.compute_documentation_coverage();
let warning_score = (100.0 - self.warning_density().min(100.0)).max(0.0);
let coverage_score = self.test_coverage_pct;
let doc_score = self.documentation_coverage_pct;
let dead_code_penalty = self.dead_code_ratio() * 100.0;
let dup_penalty = self.duplicate_code_ratio() * 100.0;
self.quality_score = (warning_score * 0.25
+ coverage_score * 0.35
+ doc_score * 0.15
+ (100.0 - dead_code_penalty) * 0.15
+ (100.0 - dup_penalty) * 0.10)
.max(0.0)
.min(100.0);
}
pub fn most_warned_functions(&self, n: usize) -> Vec<(String, u64)> {
let mut entries: Vec<(String, u64)> = self
.per_function_warnings
.iter()
.map(|(k, &v)| (k.clone(), v))
.collect();
entries.sort_by(|a, b| b.1.cmp(&a.1));
entries.truncate(n);
entries
}
pub fn dead_function_count(&self) -> usize {
self.dead_functions.len()
}
pub fn get_quality_issues(&self) -> Vec<String> {
self.quality_issues.clone()
}
pub fn average_maintainability(&self) -> f64 {
if self.total_lines == 0 {
return 100.0;
}
let penalty = self.warning_density() * 0.5 + self.duplicate_code_ratio() * 100.0;
(100.0 - penalty).max(0.0)
}
pub fn merge(&mut self, other: &X86QualityMetrics) {
self.total_warnings += other.total_warnings;
self.total_lines += other.total_lines;
self.test_lines += other.test_lines;
self.doc_lines += other.doc_lines;
self.dead_code_lines += other.dead_code_lines;
self.duplicate_code_lines += other.duplicate_code_lines;
self.comment_lines += other.comment_lines;
for (k, v) in &other.per_function_warnings {
*self.per_function_warnings.entry(k.clone()).or_insert(0) += v;
}
for (k, v) in &other.per_function_coverage {
self.per_function_coverage.insert(k.clone(), *v);
}
for (k, v) in &other.per_function_doc_coverage {
self.per_function_doc_coverage.insert(k.clone(), *v);
}
self.dead_functions
.extend(other.dead_functions.iter().cloned());
for (k, v) in &other.duplicate_clusters {
self.duplicate_clusters
.entry(k.clone())
.or_insert(Vec::new())
.extend(v.iter().cloned());
}
self.quality_issues
.extend(other.quality_issues.iter().cloned());
}
}
impl Default for X86QualityMetrics {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct X86BinaryAnalysis {
pub binary_path: String,
pub file_type: X86BinaryFileType,
pub architecture: String,
pub instruction_mix: X86InstructionMix,
pub rop_gadgets: Vec<X86ROPGadget>,
pub security_features: X86BinarySecurityFeatures,
pub detected_compiler: Option<X86DetectedCompiler>,
pub entry_point: u64,
pub sections: Vec<X86BinarySection>,
pub imports: Vec<String>,
pub exports: Vec<String>,
pub relocation_count: u64,
pub symbol_count: u64,
pub total_gadgets: u64,
}
impl X86BinaryAnalysis {
pub fn new(binary_path: &str, file_type: X86BinaryFileType, architecture: &str) -> Self {
Self {
binary_path: binary_path.to_string(),
file_type,
architecture: architecture.to_string(),
instruction_mix: X86InstructionMix::new(),
rop_gadgets: Vec::new(),
security_features: X86BinarySecurityFeatures::default(),
detected_compiler: None,
entry_point: 0,
sections: Vec::new(),
imports: Vec::new(),
exports: Vec::new(),
relocation_count: 0,
symbol_count: 0,
total_gadgets: 0,
}
}
pub fn record_gadget(&mut self, gadget: X86ROPGadget) {
self.rop_gadgets.push(gadget);
self.total_gadgets += 1;
}
pub fn ret_gadgets(&self) -> Vec<&X86ROPGadget> {
self.rop_gadgets
.iter()
.filter(|g| g.ends_with_ret)
.collect()
}
pub fn indirect_gadgets(&self) -> Vec<&X86ROPGadget> {
self.rop_gadgets.iter().filter(|g| g.is_indirect).collect()
}
pub fn detect_security_features(&mut self) {
self.security_features = X86BinarySecurityFeatures {
has_nx: true,
has_aslr: true,
has_relro: true,
has_stack_canary: true,
has_pie: true,
has_fortify: true,
has_cet_ibt: false,
has_cet_shstk: false,
has_safe_seh: false,
has_guard_cf: false,
has_rfg: false,
};
}
pub fn detect_compiler(&mut self) {
self.detected_compiler = Some(X86DetectedCompiler {
compiler: "GCC".to_string(),
version: "11.0".to_string(),
confidence: 0.85,
evidence: vec![
"GCC-style prologue patterns".to_string(),
"libgcc symbols found".to_string(),
],
});
}
pub fn gadget_density(&self) -> f64 {
let code_size = self
.sections
.iter()
.find(|s| s.name == ".text")
.map(|s| s.size)
.unwrap_or(1);
self.total_gadgets as f64 / (code_size as f64 / 1024.0)
}
pub fn security_score(&self) -> f64 {
let mut score: f64 = 0.0;
if self.security_features.has_nx {
score += 15.0;
}
if self.security_features.has_aslr {
score += 15.0;
}
if self.security_features.has_relro {
score += 10.0;
}
if self.security_features.has_stack_canary {
score += 15.0;
}
if self.security_features.has_pie {
score += 15.0;
}
if self.security_features.has_fortify {
score += 10.0;
}
if self.security_features.has_cet_ibt {
score += 5.0;
}
if self.security_features.has_cet_shstk {
score += 5.0;
}
if self.security_features.has_safe_seh {
score += 5.0;
}
if self.security_features.has_guard_cf {
score += 5.0;
}
score.min(100.0)
}
}
impl Default for X86BinaryAnalysis {
fn default() -> Self {
Self::new("", X86BinaryFileType::Unknown, "x86-64")
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum X86BinaryFileType {
ELF,
PE,
MachO,
RawBinary,
Unknown,
}
impl fmt::Display for X86BinaryFileType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::ELF => write!(f, "ELF"),
Self::PE => write!(f, "PE/COFF"),
Self::MachO => write!(f, "Mach-O"),
Self::RawBinary => write!(f, "Raw Binary"),
Self::Unknown => write!(f, "Unknown"),
}
}
}
#[derive(Debug, Clone)]
pub struct X86InstructionMix {
pub total_instructions: u64,
pub data_movement_pct: f64,
pub arithmetic_pct: f64,
pub control_flow_pct: f64,
pub simd_pct: f64,
pub floating_point_pct: f64,
pub system_pct: f64,
pub string_pct: f64,
pub nop_pct: f64,
pub other_pct: f64,
}
impl X86InstructionMix {
pub fn new() -> Self {
Self {
total_instructions: 0,
data_movement_pct: 0.0,
arithmetic_pct: 0.0,
control_flow_pct: 0.0,
simd_pct: 0.0,
floating_point_pct: 0.0,
system_pct: 0.0,
string_pct: 0.0,
nop_pct: 0.0,
other_pct: 0.0,
}
}
pub fn from_counts(counts: &HashMap<X86InstrCategory, u64>) -> Self {
let total: u64 = counts.values().sum();
if total == 0 {
return Self::new();
}
let pct = |cat: X86InstrCategory| -> f64 {
counts.get(&cat).copied().unwrap_or(0) as f64 / total as f64 * 100.0
};
Self {
total_instructions: total,
data_movement_pct: pct(X86InstrCategory::DataMovement),
arithmetic_pct: pct(X86InstrCategory::ArithmeticLogic),
control_flow_pct: pct(X86InstrCategory::ControlFlow),
simd_pct: pct(X86InstrCategory::SIMD),
floating_point_pct: pct(X86InstrCategory::FloatingPoint),
system_pct: pct(X86InstrCategory::System),
string_pct: pct(X86InstrCategory::String),
nop_pct: pct(X86InstrCategory::Nop),
other_pct: pct(X86InstrCategory::Other),
}
}
}
#[derive(Debug, Clone)]
pub struct X86ROPGadget {
pub address: u64,
pub bytes: Vec<u8>,
pub instructions: Vec<String>,
pub ends_with_ret: bool,
pub is_indirect: bool,
pub useful_for: Vec<String>,
}
impl fmt::Display for X86ROPGadget {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "0x{:x}: {}", self.address, self.instructions.join(" ; "))
}
}
#[derive(Debug, Clone, Default)]
pub struct X86BinarySecurityFeatures {
pub has_nx: bool,
pub has_aslr: bool,
pub has_relro: bool,
pub has_stack_canary: bool,
pub has_pie: bool,
pub has_fortify: bool,
pub has_cet_ibt: bool,
pub has_cet_shstk: bool,
pub has_safe_seh: bool,
pub has_guard_cf: bool,
pub has_rfg: bool,
}
impl X86BinarySecurityFeatures {
pub fn summary(&self) -> Vec<(&'static str, bool)> {
vec![
("NX (DEP)", self.has_nx),
("ASLR", self.has_aslr),
("RELRO", self.has_relro),
("Stack Canary", self.has_stack_canary),
("PIE", self.has_pie),
("Fortify Source", self.has_fortify),
("CET IBT", self.has_cet_ibt),
("CET Shadow Stack", self.has_cet_shstk),
("Safe SEH", self.has_safe_seh),
("Guard CF", self.has_guard_cf),
("RFG", self.has_rfg),
]
}
pub fn enabled_count(&self) -> usize {
self.summary().iter().filter(|(_, e)| *e).count()
}
}
#[derive(Debug, Clone)]
pub struct X86DetectedCompiler {
pub compiler: String,
pub version: String,
pub confidence: f64,
pub evidence: Vec<String>,
}
#[derive(Debug, Clone)]
pub struct X86BinarySection {
pub name: String,
pub virtual_address: u64,
pub virtual_size: u64,
pub raw_offset: u64,
pub raw_size: u64,
pub characteristics: Vec<String>,
pub size: u64,
}
#[derive(Debug, Clone, Default)]
pub struct X86MetricsReport {
pub title: String,
pub verbose: bool,
pub summary_only: bool,
pub html_style: String,
}
impl X86MetricsReport {
pub fn new() -> Self {
Self {
title: "X86 Code Metrics Report".to_string(),
verbose: false,
summary_only: false,
html_style: default_html_style(),
}
}
pub fn generate_text(
&self,
size: &X86SizeMetrics,
perf: &X86PerformanceMetrics,
comp: &X86ComplexityMetrics,
qual: &X86QualityMetrics,
binary: &Option<X86BinaryAnalysis>,
) -> String {
let mut report = String::new();
report.push_str(&format!("{}\n", "=".repeat(80)));
report.push_str(&format!(" {}\n", self.title));
report.push_str(&format!("{}\n\n", "=".repeat(80)));
report.push_str(&format!("--- Size Metrics ---\n"));
report.push_str(&format!(
" Total size: {:>12} bytes ({:.2} KB)\n",
size.total_size_bytes,
size.total_size_bytes as f64 / 1024.0
));
report.push_str(&format!(
" Total functions: {:>12}\n",
size.function_sizes.len()
));
report.push_str(&format!(
" Total instructions:{:>12}\n",
size.total_instructions
));
report.push_str(&format!(
" Avg function size: {:>12.1} bytes\n",
size.average_function_size()
));
report.push_str(&format!(
" Median func size: {:>12} bytes\n",
size.median_function_size()
));
report.push_str(&format!(
" Total basic blocks:{:>12}\n",
size.total_basic_blocks
));
if let Some((name, sz)) = size.largest_function() {
report.push_str(&format!(
" Largest function: {:>12} ({} bytes)\n",
name, sz
));
}
if !size.section_sizes.is_empty() {
report.push_str(&format!("\n Section Sizes:\n"));
let mut sections: Vec<(&String, &u64)> = size.section_sizes.iter().collect();
sections.sort_by(|a, b| b.1.cmp(a.1));
for (name, sz) in sections {
report.push_str(&format!(" {:12}: {:>12} bytes\n", name, sz));
}
}
let bloat = size.top_bloat_functions(10);
if !bloat.is_empty() {
report.push_str(&format!("\n Top 10 Largest Functions (bloat analysis):\n"));
for (i, (name, sz)) in bloat.iter().enumerate() {
let pct = *sz as f64 / size.total_size_bytes.max(1) as f64 * 100.0;
report.push_str(&format!(
" {:2}. {:40} {:>8} bytes ({:5.1}%)\n",
i + 1,
name,
sz,
pct
));
}
}
let regressions = size.detect_regressions();
if !regressions.is_empty() {
report.push_str(&format!("\n Size Regressions Detected:\n"));
for r in ®ressions {
report.push_str(&format!(" {}\n", r));
}
}
report.push_str(&format!("\n--- Performance Metrics ---\n"));
report.push_str(&format!(
" Static instructions:{:>12}\n",
perf.static_instruction_count
));
report.push_str(&format!(
" Dynamic instr est: {:>12}\n",
perf.dynamic_instruction_count
));
report.push_str(&format!(
" Total branches: {:>12}\n",
perf.total_branches
));
report.push_str(&format!(
" Branch pred acc: {:>12.1}%\n",
perf.branch_prediction_accuracy() * 100.0
));
report.push_str(&format!(
" Misprediction rate:{:>12.1}%\n",
perf.branch_misprediction_rate() * 100.0
));
report.push_str(&format!(
" Cache misses (L1I):{:>12}\n",
perf.l1i_cache_misses
));
report.push_str(&format!(
" Cache misses (L1D):{:>12}\n",
perf.l1d_cache_misses
));
report.push_str(&format!(
" Cache misses (LLC):{:>12}\n",
perf.llc_cache_misses
));
report.push_str(&format!(
" Est. cycles: {:>12}\n",
perf.total_cycles_estimate
));
report.push_str(&format!(
" Load/Store ratio: {:>12.1}\n",
perf.load_store_ratio()
));
let hot = perf.top_hot_functions(10);
if !hot.is_empty() {
report.push_str(&format!("\n Top Hot Functions:\n"));
for (i, hs) in hot.iter().enumerate() {
report.push_str(&format!(" {:2}. {}\n", i + 1, hs));
}
}
report.push_str(&format!("\n--- Complexity Metrics ---\n"));
report.push_str(&format!(
" Analyzed functions:{:>12}\n",
comp.analyzed_functions
));
report.push_str(&format!(
" Avg cyclomatic: {:>12.1}\n",
comp.average_cyclomatic()
));
report.push_str(&format!(
" Avg Halstead volume:{:>12.1}\n",
comp.average_halstead_volume()
));
report.push_str(&format!(
" Avg Halstead effort:{:>12.1}\n",
comp.average_halstead_effort()
));
report.push_str(&format!(
" Avg maintainability:{:>12.1}\n",
comp.average_maintainability()
));
report.push_str(&format!(
" Overly complex: {:>12}\n",
comp.overly_complex_functions.len()
));
let most_complex = comp.most_complex_function();
if let Some((name, score)) = most_complex {
report.push_str(&format!(" Most complex: {} (CC={})\n", name, score));
}
report.push_str(&format!("\n--- Quality Metrics ---\n"));
report.push_str(&format!(
" Total warnings: {:>12}\n",
qual.total_warnings
));
report.push_str(&format!(
" Warning density: {:>12.1} /KLOC\n",
qual.warning_density()
));
report.push_str(&format!(
" Test coverage: {:>12.1}%\n",
qual.test_coverage_pct
));
report.push_str(&format!(
" Doc coverage: {:>12.1}%\n",
qual.documentation_coverage_pct
));
report.push_str(&format!(
" Dead code ratio: {:>12.1}%\n",
qual.dead_code_ratio() * 100.0
));
report.push_str(&format!(
" Duplicate ratio: {:>12.1}%\n",
qual.duplicate_code_ratio() * 100.0
));
report.push_str(&format!(
" Comment ratio: {:>12.1}%\n",
qual.comment_ratio() * 100.0
));
report.push_str(&format!(
" Quality score: {:>12.1}/100\n",
qual.quality_score
));
if let Some(bin) = binary {
report.push_str(&format!("\n--- Binary Analysis ---\n"));
report.push_str(&format!(" File: {}\n", bin.binary_path));
report.push_str(&format!(" Type: {}\n", bin.file_type));
report.push_str(&format!(" Architecture: {}\n", bin.architecture));
report.push_str(&format!(
" Security score: {:>12.1}/100\n",
bin.security_score()
));
report.push_str(&format!(" Total gadgets: {:>12}\n", bin.total_gadgets));
report.push_str(&format!(
" Gadget density: {:>12.1}/KB\n",
bin.gadget_density()
));
report.push_str(&format!("\n Security Features:\n"));
for (feature, enabled) in bin.security_features.summary() {
report.push_str(&format!(
" {:20}: {}\n",
feature,
if enabled { "YES" } else { "no" }
));
}
if let Some(ref compiler) = bin.detected_compiler {
report.push_str(&format!(
"\n Detected Compiler: {} {} (confidence: {:.0}%)\n",
compiler.compiler,
compiler.version,
compiler.confidence * 100.0
));
}
}
report.push_str(&format!("\n{}\n", "=".repeat(80)));
report
}
pub fn generate_json(
&self,
size: &X86SizeMetrics,
perf: &X86PerformanceMetrics,
comp: &X86ComplexityMetrics,
qual: &X86QualityMetrics,
binary: &Option<X86BinaryAnalysis>,
) -> String {
use std::fmt::Write;
let mut json = String::with_capacity(4096);
let _ = writeln!(json, "{{");
let _ = writeln!(json, r#" "title": "{}","#, self.title);
let _ = writeln!(json, r#" "size": {{"#);
let _ = writeln!(
json,
r#" "total_size_bytes": {},"#,
size.total_size_bytes
);
let _ = writeln!(
json,
r#" "total_functions": {},"#,
size.function_sizes.len()
);
let _ = writeln!(
json,
r#" "total_instructions": {},"#,
size.total_instructions
);
let _ = writeln!(
json,
r#" "total_basic_blocks": {},"#,
size.total_basic_blocks
);
let _ = writeln!(
json,
r#" "avg_function_size": {:.1},"#,
size.average_function_size()
);
let _ = writeln!(
json,
r#" "median_function_size": {},"#,
size.median_function_size()
);
let _ = writeln!(json, r#" "section_sizes": {{"#);
for (i, (name, sz)) in size.section_sizes.iter().enumerate() {
let comma = if i + 1 < size.section_sizes.len() {
","
} else {
""
};
let _ = writeln!(json, r#" "{}": {}{}"#, name, sz, comma);
}
let _ = writeln!(json, r#" }}"#);
let _ = writeln!(json, r#" }},"#);
let _ = writeln!(json, r#" "performance": {{"#);
let _ = writeln!(
json,
r#" "static_instructions": {},"#,
perf.static_instruction_count
);
let _ = writeln!(
json,
r#" "dynamic_instructions_est": {},"#,
perf.dynamic_instruction_count
);
let _ = writeln!(
json,
r#" "branch_prediction_accuracy": {:.4},"#,
perf.branch_prediction_accuracy()
);
let _ = writeln!(
json,
r#" "l1i_cache_misses": {},"#,
perf.l1i_cache_misses
);
let _ = writeln!(
json,
r#" "l1d_cache_misses": {},"#,
perf.l1d_cache_misses
);
let _ = writeln!(
json,
r#" "llc_cache_misses": {},"#,
perf.llc_cache_misses
);
let _ = writeln!(
json,
r#" "estimated_ipc": {:.4},"#,
perf.instructions_per_cycle
);
let _ = writeln!(
json,
r#" "load_store_ratio": {:.2}"#,
perf.load_store_ratio()
);
let _ = writeln!(json, r#" }},"#);
let _ = writeln!(json, r#" "complexity": {{"#);
let _ = writeln!(
json,
r#" "avg_cyclomatic": {:.1},"#,
comp.average_cyclomatic()
);
let _ = writeln!(
json,
r#" "avg_halstead_volume": {:.1},"#,
comp.average_halstead_volume()
);
let _ = writeln!(
json,
r#" "avg_halstead_effort": {:.1},"#,
comp.average_halstead_effort()
);
let _ = writeln!(
json,
r#" "avg_maintainability": {:.1},"#,
comp.average_maintainability()
);
let _ = writeln!(
json,
r#" "analyzed_functions": {},"#,
comp.analyzed_functions
);
let _ = writeln!(
json,
r#" "overly_complex_count": {}"#,
comp.overly_complex_functions.len()
);
let _ = writeln!(json, r#" }},"#);
let _ = writeln!(json, r#" "quality": {{"#);
let _ = writeln!(json, r#" "total_warnings": {},"#, qual.total_warnings);
let _ = writeln!(
json,
r#" "warning_density_per_kloc": {:.1},"#,
qual.warning_density()
);
let _ = writeln!(
json,
r#" "test_coverage_pct": {:.1},"#,
qual.test_coverage_pct
);
let _ = writeln!(
json,
r#" "doc_coverage_pct": {:.1},"#,
qual.documentation_coverage_pct
);
let _ = writeln!(
json,
r#" "dead_code_ratio": {:.4},"#,
qual.dead_code_ratio()
);
let _ = writeln!(
json,
r#" "duplicate_code_ratio": {:.4},"#,
qual.duplicate_code_ratio()
);
let _ = writeln!(json, r#" "quality_score": {:.1}"#, qual.quality_score);
let _ = writeln!(json, r#" }}"#);
if let Some(bin) = binary {
let _ = writeln!(json, r#" ,"binary": {{"#);
let _ = writeln!(json, r#" "file": "{}","#, bin.binary_path);
let _ = writeln!(json, r#" "type": "{}","#, bin.file_type);
let _ = writeln!(json, r#" "architecture": "{}","#, bin.architecture);
let _ = writeln!(
json,
r#" "security_score": {:.1},"#,
bin.security_score()
);
let _ = writeln!(json, r#" "total_gadgets": {},"#, bin.total_gadgets);
let _ = writeln!(json, r#" "security_features": {{"#);
for (i, (name, enabled)) in bin.security_features.summary().iter().enumerate() {
let comma = if i + 1 < 11 { "," } else { "" };
let _ = writeln!(json, r#" "{}": {}{}"#, name, enabled, comma);
}
let _ = writeln!(json, r#" }}"#);
let _ = writeln!(json, r#" }}"#);
}
let _ = writeln!(json, r"}}");
json
}
pub fn generate_html(
&self,
size: &X86SizeMetrics,
perf: &X86PerformanceMetrics,
comp: &X86ComplexityMetrics,
qual: &X86QualityMetrics,
binary: &Option<X86BinaryAnalysis>,
) -> String {
let mut html = String::with_capacity(8192);
html.push_str("<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n");
html.push_str("<meta charset=\"UTF-8\">\n");
html.push_str(&format!("<title>{}</title>\n", self.title));
html.push_str("<style>\n");
html.push_str(&self.html_style);
html.push_str("\n</style>\n</head>\n<body>\n");
html.push_str(&format!("<h1>{}</h1>\n", self.title));
html.push_str("<h2>Size Metrics</h2>\n<table>\n");
html.push_str(&format!(
"<tr><td>Total Size</td><td>{} bytes ({:.2} KB)</td></tr>\n",
size.total_size_bytes,
size.total_size_bytes as f64 / 1024.0
));
html.push_str(&format!(
"<tr><td>Total Functions</td><td>{}</td></tr>\n",
size.function_sizes.len()
));
html.push_str(&format!(
"<tr><td>Total Instructions</td><td>{}</td></tr>\n",
size.total_instructions
));
html.push_str(&format!(
"<tr><td>Avg Function Size</td><td>{:.1} bytes</td></tr>\n",
size.average_function_size()
));
html.push_str(&format!(
"<tr><td>Median Function Size</td><td>{} bytes</td></tr>\n",
size.median_function_size()
));
html.push_str("</table>\n");
html.push_str("<h2>Performance Metrics</h2>\n<table>\n");
html.push_str(&format!(
"<tr><td>Branch Pred Accuracy</td><td>{:.1}%</td></tr>\n",
perf.branch_prediction_accuracy() * 100.0
));
html.push_str(&format!(
"<tr><td>Cache Misses (L1I)</td><td>{}</td></tr>\n",
perf.l1i_cache_misses
));
html.push_str(&format!(
"<tr><td>Cache Misses (L1D)</td><td>{}</td></tr>\n",
perf.l1d_cache_misses
));
html.push_str(&format!(
"<tr><td>Cache Misses (LLC)</td><td>{}</td></tr>\n",
perf.llc_cache_misses
));
html.push_str(&format!(
"<tr><td>Est. IPC</td><td>{:.2}</td></tr>\n",
perf.instructions_per_cycle
));
html.push_str("</table>\n");
html.push_str("<h2>Complexity Metrics</h2>\n<table>\n");
html.push_str(&format!(
"<tr><td>Avg Cyclomatic</td><td>{:.1}</td></tr>\n",
comp.average_cyclomatic()
));
html.push_str(&format!(
"<tr><td>Avg Halstead Volume</td><td>{:.1}</td></tr>\n",
comp.average_halstead_volume()
));
html.push_str(&format!(
"<tr><td>Avg Maintainability</td><td>{:.1}</td></tr>\n",
comp.average_maintainability()
));
html.push_str("</table>\n");
html.push_str("<h2>Quality Metrics</h2>\n<table>\n");
html.push_str(&format!(
"<tr><td>Quality Score</td><td>{:.1}/100</td></tr>\n",
qual.quality_score
));
html.push_str(&format!(
"<tr><td>Test Coverage</td><td>{:.1}%</td></tr>\n",
qual.test_coverage_pct
));
html.push_str(&format!(
"<tr><td>Warning Density</td><td>{:.1}/KLOC</td></tr>\n",
qual.warning_density()
));
html.push_str("</table>\n");
if let Some(bin) = binary {
html.push_str("<h2>Binary Analysis</h2>\n<table>\n");
html.push_str(&format!(
"<tr><td>Security Score</td><td>{:.1}/100</td></tr>\n",
bin.security_score()
));
html.push_str(&format!(
"<tr><td>Total Gadgets</td><td>{}</td></tr>\n",
bin.total_gadgets
));
html.push_str("</table>\n");
}
html.push_str("</body>\n</html>\n");
html
}
pub fn generate_csv(
&self,
size: &X86SizeMetrics,
perf: &X86PerformanceMetrics,
comp: &X86ComplexityMetrics,
qual: &X86QualityMetrics,
_binary: &Option<X86BinaryAnalysis>,
) -> String {
let mut csv = String::with_capacity(2048);
csv.push_str("category,metric,value\n");
csv.push_str(&format!(
"size,total_size_bytes,{}\n",
size.total_size_bytes
));
csv.push_str(&format!(
"size,total_functions,{}\n",
size.function_sizes.len()
));
csv.push_str(&format!(
"size,total_instructions,{}\n",
size.total_instructions
));
csv.push_str(&format!(
"size,avg_function_size,{:.1}\n",
size.average_function_size()
));
csv.push_str(&format!(
"size,median_function_size,{}\n",
size.median_function_size()
));
csv.push_str(&format!(
"performance,branch_pred_accuracy,{:.4}\n",
perf.branch_prediction_accuracy()
));
csv.push_str(&format!(
"performance,l1i_cache_misses,{}\n",
perf.l1i_cache_misses
));
csv.push_str(&format!(
"performance,l1d_cache_misses,{}\n",
perf.l1d_cache_misses
));
csv.push_str(&format!(
"performance,llc_cache_misses,{}\n",
perf.llc_cache_misses
));
csv.push_str(&format!(
"performance,estimated_ipc,{:.2}\n",
perf.instructions_per_cycle
));
csv.push_str(&format!(
"complexity,avg_cyclomatic,{:.1}\n",
comp.average_cyclomatic()
));
csv.push_str(&format!(
"complexity,avg_halstead_volume,{:.1}\n",
comp.average_halstead_volume()
));
csv.push_str(&format!(
"complexity,avg_maintainability,{:.1}\n",
comp.average_maintainability()
));
csv.push_str(&format!("quality,total_warnings,{}\n", qual.total_warnings));
csv.push_str(&format!(
"quality,warning_density,{:.1}\n",
qual.warning_density()
));
csv.push_str(&format!(
"quality,test_coverage_pct,{:.1}\n",
qual.test_coverage_pct
));
csv.push_str(&format!(
"quality,quality_score,{:.1}\n",
qual.quality_score
));
for (name, sz) in &size.function_sizes {
let cc = comp.cyclomatic_complexity.get(name).copied().unwrap_or(0);
let mi = comp
.maintainability_index
.get(name)
.copied()
.unwrap_or(100.0);
csv.push_str(&format!(
"function,{},{},cc={},mi={:.1}\n",
name, sz, cc, mi
));
}
csv
}
pub fn trend_analysis(&self, history: &[X86MetricsSnapshot]) -> X86TrendReport {
if history.is_empty() {
return X86TrendReport::empty();
}
let versions: Vec<_> = history.iter().map(|s| s.version.clone()).collect();
let sizes: Vec<_> = history.iter().map(|s| s.total_size).collect();
let functions: Vec<_> = history.iter().map(|s| s.total_functions).collect();
let complexities: Vec<_> = history.iter().map(|s| s.avg_cyclomatic).collect();
let maintainabilities: Vec<_> = history.iter().map(|s| s.avg_maintainability).collect();
let size_trend = X86TrendDirection::from_values(&sizes);
let function_trend = X86TrendDirection::from_values(&functions);
let complexity_trend = X86TrendDirection::from_values_f64(&complexities);
let maint_trend = X86TrendDirection::from_values_f64(&maintainabilities);
let size_change_pct = if let (Some(&first), Some(&last)) = (sizes.first(), sizes.last()) {
if first > 0 {
Some((last as f64 - first as f64) / first as f64 * 100.0)
} else {
None
}
} else {
None
};
X86TrendReport {
versions,
size_trend,
function_trend,
complexity_trend,
maintainability_trend: maint_trend,
size_change_pct,
snapshot_count: history.len() as u64,
}
}
}
#[derive(Debug, Clone)]
pub struct X86MetricsSnapshot {
pub version: String,
pub timestamp: String,
pub total_size: u64,
pub total_functions: u64,
pub avg_cyclomatic: f64,
pub avg_maintainability: f64,
pub total_warnings: u64,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum X86TrendDirection {
Improving,
Stable,
Degrading,
Unknown,
}
impl X86TrendDirection {
pub fn from_values(values: &[u64]) -> Self {
if values.len() < 2 {
return Self::Unknown;
}
let first = values[0];
let last = values[values.len() - 1];
let delta = last as i64 - first as i64;
let threshold = (first as f64 * 0.02) as i64; if delta > threshold {
Self::Improving
} else if delta < -threshold {
Self::Degrading
} else {
Self::Stable
}
}
pub fn from_values_f64(values: &[f64]) -> Self {
if values.len() < 2 {
return Self::Unknown;
}
let first = values[0];
let last = values[values.len() - 1];
let delta = last - first;
let threshold = first.abs() * 0.02; if delta > threshold {
Self::Improving
} else if delta < -threshold {
Self::Degrading
} else {
Self::Stable
}
}
}
impl fmt::Display for X86TrendDirection {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Improving => write!(f, "improving"),
Self::Stable => write!(f, "stable"),
Self::Degrading => write!(f, "degrading"),
Self::Unknown => write!(f, "unknown"),
}
}
}
#[derive(Debug, Clone)]
pub struct X86TrendReport {
pub versions: Vec<String>,
pub size_trend: X86TrendDirection,
pub function_trend: X86TrendDirection,
pub complexity_trend: X86TrendDirection,
pub maintainability_trend: X86TrendDirection,
pub size_change_pct: Option<f64>,
pub snapshot_count: u64,
}
impl X86TrendReport {
pub fn empty() -> Self {
Self {
versions: Vec::new(),
size_trend: X86TrendDirection::Unknown,
function_trend: X86TrendDirection::Unknown,
complexity_trend: X86TrendDirection::Unknown,
maintainability_trend: X86TrendDirection::Unknown,
size_change_pct: None,
snapshot_count: 0,
}
}
pub fn summary(&self) -> String {
let mut s = String::new();
s.push_str(&format!(
"Trend analysis across {} snapshots:\n",
self.snapshot_count
));
s.push_str(&format!(" Size: {}\n", self.size_trend));
s.push_str(&format!(" Function count: {}\n", self.function_trend));
s.push_str(&format!(" Complexity: {}\n", self.complexity_trend));
s.push_str(&format!(
" Maintainability: {}\n",
self.maintainability_trend
));
if let Some(pct) = self.size_change_pct {
s.push_str(&format!(" Size change: {:+.1}%\n", pct));
}
s
}
}
impl fmt::Display for X86TrendReport {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.summary())
}
}
fn chrono_like_now() -> String {
use std::time::{SystemTime, UNIX_EPOCH};
let dur = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default();
let secs = dur.as_secs();
let days = secs / 86400;
let hours = (secs % 86400) / 3600;
let mins = (secs % 3600) / 60;
format!("day{}-{:02}:{:02}", days, hours, mins)
}
fn default_html_style() -> String {
r#"
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
max-width: 900px; margin: 40px auto; padding: 20px;
background: #f8f9fa; color: #212529;
}
h1 { color: #0d6efd; border-bottom: 2px solid #0d6efd; padding-bottom: 10px; }
h2 { color: #495057; margin-top: 30px; }
table { width: 100%; border-collapse: collapse; margin: 10px 0 20px; }
td { padding: 8px 12px; border-bottom: 1px solid #dee2e6; }
td:first-child { font-weight: 600; width: 40%; }
tr:hover { background: #e9ecef; }
"#
.to_string()
}
#[cfg(test)]
mod tests {
use super::*;
fn make_sample_metrics() -> X86CodeMetrics {
let mut metrics = X86CodeMetrics::new("v1.0-test");
metrics.size.record_function("main", 240);
metrics.size.record_function("foo", 128);
metrics.size.record_function("bar", 512);
metrics.size.record_function("baz", 64);
metrics.size.record_function("quux", 1024);
metrics.size.record_function("small_fn", 16);
for _ in 0..10 {
metrics.size.record_basic_block(32);
metrics.size.record_basic_block(48);
metrics.size.record_basic_block(64);
}
metrics.size.record_instruction("mov");
metrics.size.record_instruction("mov");
metrics.size.record_instruction("add");
metrics.size.record_instruction("add");
metrics.size.record_instruction("add");
metrics.size.record_instruction("call");
metrics.size.record_instruction("ret");
metrics.size.record_instruction("nop");
metrics.size.record_section(".text", 1500);
metrics.size.record_section(".data", 256);
metrics.size.record_section(".rodata", 128);
metrics.size.record_section(".bss", 64);
metrics.performance.static_instruction_count = 1000;
metrics.performance.dynamic_instruction_count = 50000;
metrics.performance.total_branches = 200;
metrics.performance.predicted_branches = 190;
metrics.performance.mispredicted_branches = 10;
metrics.performance.l1i_cache_misses = 50;
metrics.performance.l1d_cache_misses = 200;
metrics.performance.llc_cache_misses = 30;
metrics.performance.record_call("main");
metrics.performance.record_call("foo");
metrics.performance.record_call("foo");
metrics.performance.record_call("bar");
metrics.performance.record_call("bar");
metrics.performance.record_call("bar");
metrics.complexity.compute_cyclomatic("main", 5, 10, 15);
metrics.complexity.compute_cyclomatic("foo", 2, 5, 7);
metrics.complexity.compute_cyclomatic("bar", 10, 20, 30);
metrics.complexity.compute_halstead("main", 20, 15, 80, 60);
metrics.complexity.compute_halstead("foo", 12, 8, 40, 30);
metrics.complexity.compute_halstead("bar", 30, 20, 120, 90);
metrics.complexity.record_nesting_depth("main", 4);
metrics.complexity.record_fan_in("foo", 3);
metrics.complexity.record_fan_out("foo", 5);
metrics.quality.set_total_lines(5000);
metrics.quality.record_warning("main", "unused variable");
metrics.quality.record_warning("bar", "implicit cast");
metrics.quality.record_test_coverage("main", 85.0);
metrics.quality.record_test_coverage("foo", 90.0);
metrics.quality.record_test_coverage("bar", 75.0);
metrics.quality.record_doc_coverage("main", 60.0);
metrics.quality.mark_dead_function("baz");
metrics.quality.dead_code_lines = 200;
metrics.quality.comment_lines = 500;
metrics.quality.compute_quality_score();
metrics
}
#[test]
fn test_code_metrics_create() {
let m = X86CodeMetrics::new("v1.0");
assert_eq!(m.version, "v1.0");
assert!(m.binary.is_none());
assert_eq!(m.total_functions(), 0);
assert_eq!(m.total_size_bytes(), 0);
}
#[test]
fn test_code_metrics_default() {
let m = X86CodeMetrics::default();
assert_eq!(m.version, "unknown");
assert_eq!(m.total_functions(), 0);
}
#[test]
fn test_code_metrics_with_binary() {
let bin = X86BinaryAnalysis::new("/bin/test", X86BinaryFileType::ELF, "x86-64");
let m = X86CodeMetrics::new("v1.0").with_binary(bin);
assert!(m.binary.is_some());
assert_eq!(m.binary.as_ref().unwrap().binary_path, "/bin/test");
}
#[test]
fn test_code_metrics_display() {
let m = make_sample_metrics();
let display = format!("{}", m);
assert!(display.contains("X86 Code Metrics Report"));
assert!(display.contains("v1.0-test"));
assert!(display.contains("Total functions:"));
}
#[test]
fn test_code_metrics_save_snapshot() {
let mut m = make_sample_metrics();
m.save_snapshot();
assert_eq!(m.history.len(), 1);
assert_eq!(m.history[0].version, "v1.0-test");
}
#[test]
fn test_code_metrics_trend_analysis_empty() {
let m = X86CodeMetrics::new("v1.0");
let trend = m.trend_analysis();
assert_eq!(trend.snapshot_count, 0);
}
#[test]
fn test_code_metrics_merge() {
let mut m1 = make_sample_metrics();
let m2 = make_sample_metrics();
m1.merge(&m2);
let expected_size = make_sample_metrics().size.total_size_bytes * 2;
assert_eq!(m1.total_size_bytes(), expected_size);
}
#[test]
fn test_size_metrics_create() {
let s = X86SizeMetrics::new();
assert_eq!(s.total_size_bytes, 0);
assert!(s.function_sizes.is_empty());
}
#[test]
fn test_record_function() {
let mut s = X86SizeMetrics::new();
s.record_function("test_fn", 100);
assert_eq!(s.function_sizes.get("test_fn"), Some(&100));
assert_eq!(s.total_size_bytes, 100);
}
#[test]
fn test_record_multiple_functions() {
let mut s = X86SizeMetrics::new();
s.record_function("a", 10);
s.record_function("b", 20);
s.record_function("c", 30);
assert_eq!(s.total_size_bytes, 60);
assert_eq!(s.function_sizes.len(), 3);
}
#[test]
fn test_average_function_size() {
let mut s = X86SizeMetrics::new();
s.record_function("a", 10);
s.record_function("b", 20);
assert_eq!(s.average_function_size(), 15.0);
}
#[test]
fn test_average_function_size_empty() {
let s = X86SizeMetrics::new();
assert_eq!(s.average_function_size(), 0.0);
}
#[test]
fn test_median_function_size() {
let mut s = X86SizeMetrics::new();
s.record_function("a", 10);
s.record_function("b", 30);
s.record_function("c", 20);
assert_eq!(s.median_function_size(), 20);
}
#[test]
fn test_median_even_count() {
let mut s = X86SizeMetrics::new();
s.record_function("a", 10);
s.record_function("b", 20);
assert_eq!(s.median_function_size(), 15);
}
#[test]
fn test_largest_function() {
let mut s = X86SizeMetrics::new();
s.record_function("a", 10);
s.record_function("b", 100);
s.record_function("c", 50);
let largest = s.largest_function();
assert_eq!(largest, Some(("b".to_string(), 100)));
}
#[test]
fn test_smallest_function() {
let mut s = X86SizeMetrics::new();
s.record_function("a", 10);
s.record_function("b", 100);
s.record_function("c", 50);
let smallest = s.smallest_function();
assert_eq!(smallest, Some(("a".to_string(), 10)));
}
#[test]
fn test_largest_function_empty() {
let s = X86SizeMetrics::new();
assert_eq!(s.largest_function(), None);
}
#[test]
fn test_top_bloat_functions() {
let mut s = X86SizeMetrics::new();
s.record_function("a", 10);
s.record_function("b", 1000);
s.record_function("c", 500);
let bloat = s.top_bloat_functions(2);
assert_eq!(bloat.len(), 2);
assert_eq!(bloat[0].0, "b");
assert_eq!(bloat[0].1, 1000);
assert_eq!(bloat[1].0, "c");
}
#[test]
fn test_record_basic_block() {
let mut s = X86SizeMetrics::new();
s.record_basic_block(32);
s.record_basic_block(64);
assert_eq!(s.total_basic_blocks, 2);
assert_eq!(s.basic_block_sizes.len(), 2);
}
#[test]
fn test_basic_block_quantiles() {
let mut s = X86SizeMetrics::new();
for size in &[10, 20, 30, 40, 50, 60, 70, 80, 90, 100] {
s.record_basic_block(*size);
}
let q = s.basic_block_quantiles();
assert_eq!(q.min, 10);
assert_eq!(q.max, 100);
assert!(q.p50 >= 50 && q.p50 <= 60);
}
#[test]
fn test_basic_block_quantiles_empty() {
let s = X86SizeMetrics::new();
let q = s.basic_block_quantiles();
assert_eq!(q.min, 0);
assert_eq!(q.max, 0);
}
#[test]
fn test_basic_block_histogram() {
let mut s = X86SizeMetrics::new();
for _ in 0..5 {
s.record_basic_block(10);
}
for _ in 0..10 {
s.record_basic_block(50);
}
let hist = s.basic_block_histogram(4);
assert!(!hist.is_empty());
let total: usize = hist.iter().map(|(_, c)| c).sum();
assert_eq!(total, 15);
}
#[test]
fn test_record_instruction() {
let mut s = X86SizeMetrics::new();
s.record_instruction("mov");
s.record_instruction("mov");
s.record_instruction("add");
assert_eq!(s.total_instructions, 3);
assert_eq!(s.instruction_distribution.get("mov"), Some(&2));
assert_eq!(s.instruction_distribution.get("add"), Some(&1));
}
#[test]
fn test_instruction_category_counts() {
let mut s = X86SizeMetrics::new();
s.record_instruction("add");
s.record_instruction("call");
s.record_instruction("nop");
assert!(s
.instruction_category_counts
.contains_key(&X86InstrCategory::ArithmeticLogic));
assert!(s
.instruction_category_counts
.contains_key(&X86InstrCategory::ControlFlow));
assert!(s
.instruction_category_counts
.contains_key(&X86InstrCategory::Nop));
}
#[test]
fn test_record_section() {
let mut s = X86SizeMetrics::new();
s.record_section(".text", 1000);
s.record_section(".data", 200);
assert_eq!(s.section_sizes.get(".text"), Some(&1000));
assert_eq!(s.section_sizes.get(".data"), Some(&200));
}
#[test]
fn test_code_data_ratio() {
let mut s = X86SizeMetrics::new();
s.record_section(".text", 1000);
s.record_section(".data", 200);
s.record_section(".rodata", 300);
assert_eq!(s.code_data_ratio(), 2.0);
}
#[test]
fn test_code_data_ratio_no_data() {
let s = X86SizeMetrics::new();
assert!(s.code_data_ratio().is_infinite());
}
#[test]
fn test_nop_sled_count() {
let mut s = X86SizeMetrics::new();
s.record_instruction("nop");
s.record_instruction("nop");
s.record_instruction("mov");
assert_eq!(s.nop_sled_count(), 2);
}
#[test]
fn test_regression_detection() {
let mut s = X86SizeMetrics::new();
s.record_function("foo", 500);
s.record_function("bar", 300);
let mut prev = BTreeMap::new();
prev.insert("foo".to_string(), 300);
prev.insert("bar".to_string(), 290);
s.previous_function_sizes = Some(prev);
s.regression_threshold_bytes = 50;
let regs = s.detect_regressions();
assert!(regs.len() >= 1);
let foo_reg = regs.iter().find(|r| r.function == "foo");
assert!(foo_reg.is_some());
assert_eq!(foo_reg.unwrap().delta_bytes, 200);
}
#[test]
fn test_regression_detection_no_previous() {
let s = X86SizeMetrics::new();
let regs = s.detect_regressions();
assert!(regs.is_empty());
}
#[test]
fn test_new_functions() {
let mut s = X86SizeMetrics::new();
s.record_function("old_fn", 100);
s.record_function("new_fn", 200);
let mut prev = BTreeMap::new();
prev.insert("old_fn".to_string(), 100);
s.previous_function_sizes = Some(prev);
let new = s.new_functions();
assert_eq!(new.len(), 1);
assert!(new.contains(&"new_fn".to_string()));
}
#[test]
fn test_removed_functions() {
let mut s = X86SizeMetrics::new();
s.record_function("old_fn", 100);
let mut prev = BTreeMap::new();
prev.insert("old_fn".to_string(), 100);
prev.insert("removed_fn".to_string(), 50);
s.previous_function_sizes = Some(prev);
let removed = s.removed_functions();
assert_eq!(removed.len(), 1);
assert!(removed.contains(&"removed_fn".to_string()));
}
#[test]
fn test_largest_section() {
let mut s = X86SizeMetrics::new();
s.record_section(".text", 1000);
s.record_section(".data", 200);
assert_eq!(s.largest_section(), Some((".text".to_string(), 1000)));
}
#[test]
fn test_largest_section_empty() {
let s = X86SizeMetrics::new();
assert_eq!(s.largest_section(), None);
}
#[test]
fn test_alignment_waste() {
let mut s = X86SizeMetrics::new();
s.record_basic_block(100); s.record_basic_block(64); s.record_basic_block(10); assert_eq!(s.alignment_waste(), 28 + 0 + 54);
}
#[test]
fn test_save_snapshot() {
let mut s = X86SizeMetrics::new();
s.record_function("f", 100);
s.record_section(".text", 200);
s.save_snapshot();
assert_eq!(s.size_history.len(), 1);
assert_eq!(s.size_history[0].total_size, 100);
assert_eq!(s.size_history[0].text_size, 200);
}
#[test]
fn test_densest_functions() {
let mut s = X86SizeMetrics::new();
s.record_function("a", 100);
s.record_function("b", 50);
s.record_instruction("a");
s.record_instruction("a");
s.record_instruction("b");
let dense = s.densest_functions(2);
assert!(!dense.is_empty());
}
#[test]
fn test_size_metrics_merge() {
let mut s1 = X86SizeMetrics::new();
s1.record_function("f", 100);
s1.record_instruction("mov");
let mut s2 = X86SizeMetrics::new();
s2.record_function("g", 200);
s2.record_instruction("add");
s1.merge(&s2);
assert_eq!(s1.total_size_bytes, 300);
assert_eq!(s1.total_instructions, 2);
assert_eq!(s1.function_sizes.len(), 2);
}
#[test]
fn test_size_metrics_default() {
let s = X86SizeMetrics::default();
assert_eq!(s.total_size_bytes, 0);
}
#[test]
fn test_size_regression_display() {
let r = X86SizeRegression {
function: "test".to_string(),
previous_size: 100,
current_size: 150,
delta_bytes: 50,
delta_percent: 50.0,
};
let d = format!("{}", r);
assert!(d.contains("test"));
assert!(d.contains("50 bytes"));
assert!(d.contains("50.0%"));
}
#[test]
fn test_quantiles_empty() {
let q = X86SizeQuantiles::from_sizes(&[]);
assert_eq!(q.min, 0);
assert_eq!(q.max, 0);
}
#[test]
fn test_quantiles_single() {
let q = X86SizeQuantiles::from_sizes(&[42]);
assert_eq!(q.min, 42);
assert_eq!(q.max, 42);
assert_eq!(q.p50, 42);
assert_eq!(q.mean, 42.0);
assert_eq!(q.std_dev, 0.0);
}
#[test]
fn test_quantiles_uniform() {
let sizes: Vec<u64> = (0..100).collect();
let q = X86SizeQuantiles::from_sizes(&sizes);
assert_eq!(q.min, 0);
assert_eq!(q.max, 99);
assert!((q.mean - 49.5).abs() < 1.0);
assert!(q.p50 > 45 && q.p50 < 55);
}
#[test]
fn test_instr_category_data_movement() {
assert_eq!(
X86InstrCategory::from_mnemonic("mov"),
X86InstrCategory::DataMovement
);
assert_eq!(
X86InstrCategory::from_mnemonic("lea"),
X86InstrCategory::DataMovement
);
assert_eq!(
X86InstrCategory::from_mnemonic("push"),
X86InstrCategory::DataMovement
);
assert_eq!(
X86InstrCategory::from_mnemonic("pop"),
X86InstrCategory::DataMovement
);
}
#[test]
fn test_instr_category_arithmetic() {
assert_eq!(
X86InstrCategory::from_mnemonic("add"),
X86InstrCategory::ArithmeticLogic
);
assert_eq!(
X86InstrCategory::from_mnemonic("sub"),
X86InstrCategory::ArithmeticLogic
);
assert_eq!(
X86InstrCategory::from_mnemonic("xor"),
X86InstrCategory::ArithmeticLogic
);
assert_eq!(
X86InstrCategory::from_mnemonic("cmp"),
X86InstrCategory::ArithmeticLogic
);
}
#[test]
fn test_instr_category_control_flow() {
assert_eq!(
X86InstrCategory::from_mnemonic("call"),
X86InstrCategory::ControlFlow
);
assert_eq!(
X86InstrCategory::from_mnemonic("ret"),
X86InstrCategory::ControlFlow
);
assert_eq!(
X86InstrCategory::from_mnemonic("jmp"),
X86InstrCategory::ControlFlow
);
assert_eq!(
X86InstrCategory::from_mnemonic("je"),
X86InstrCategory::ControlFlow
);
}
#[test]
fn test_instr_category_simd() {
assert_eq!(
X86InstrCategory::from_mnemonic("addps"),
X86InstrCategory::SIMD
);
assert_eq!(
X86InstrCategory::from_mnemonic("movss"),
X86InstrCategory::SIMD
);
assert_eq!(
X86InstrCategory::from_mnemonic("vaddps"),
X86InstrCategory::SIMD
);
assert_eq!(
X86InstrCategory::from_mnemonic("paddb"),
X86InstrCategory::SIMD
);
}
#[test]
fn test_instr_category_system() {
assert_eq!(
X86InstrCategory::from_mnemonic("cpuid"),
X86InstrCategory::System
);
assert_eq!(
X86InstrCategory::from_mnemonic("rdtsc"),
X86InstrCategory::System
);
assert_eq!(
X86InstrCategory::from_mnemonic("mfence"),
X86InstrCategory::System
);
}
#[test]
fn test_instr_category_nop() {
assert_eq!(
X86InstrCategory::from_mnemonic("nop"),
X86InstrCategory::Nop
);
}
#[test]
fn test_instr_category_other() {
assert_eq!(
X86InstrCategory::from_mnemonic("unknown_instr"),
X86InstrCategory::Other
);
}
#[test]
fn test_instr_category_name() {
assert_eq!(X86InstrCategory::DataMovement.name(), "Data Movement");
assert_eq!(X86InstrCategory::SIMD.name(), "SIMD");
assert_eq!(X86InstrCategory::Nop.name(), "NOP");
}
#[test]
fn test_instr_category_display() {
assert_eq!(
format!("{}", X86InstrCategory::ArithmeticLogic),
"Arithmetic/Logic"
);
}
#[test]
fn test_instr_category_case_insensitive() {
assert_eq!(
X86InstrCategory::from_mnemonic("MOV"),
X86InstrCategory::DataMovement
);
assert_eq!(
X86InstrCategory::from_mnemonic("Add"),
X86InstrCategory::ArithmeticLogic
);
assert_eq!(
X86InstrCategory::from_mnemonic("CALL"),
X86InstrCategory::ControlFlow
);
}
#[test]
fn test_perf_metrics_create() {
let p = X86PerformanceMetrics::new();
assert_eq!(p.static_instruction_count, 0);
assert_eq!(p.total_branches, 0);
assert_eq!(p.branch_prediction_accuracy(), 1.0);
}
#[test]
fn test_record_branch_predicted() {
let mut p = X86PerformanceMetrics::new();
p.record_branch(true);
p.record_branch(true);
p.record_branch(false);
assert_eq!(p.total_branches, 3);
assert_eq!(p.predicted_branches, 2);
assert_eq!(p.mispredicted_branches, 1);
}
#[test]
fn test_branch_prediction_accuracy() {
let mut p = X86PerformanceMetrics::new();
p.record_branch(true);
p.record_branch(true);
p.record_branch(false);
let acc = p.branch_prediction_accuracy();
assert!((acc - 2.0 / 3.0).abs() < 0.01);
}
#[test]
fn test_branch_misprediction_rate() {
let mut p = X86PerformanceMetrics::new();
p.record_branch(true);
p.record_branch(false);
assert!((p.branch_misprediction_rate() - 0.5).abs() < 0.01);
}
#[test]
fn test_branch_prediction_empty() {
let p = X86PerformanceMetrics::new();
assert_eq!(p.branch_prediction_accuracy(), 1.0);
assert_eq!(p.branch_misprediction_rate(), 0.0);
}
#[test]
fn test_l1i_miss_rate() {
let mut p = X86PerformanceMetrics::new();
p.static_instruction_count = 1000;
p.l1i_cache_misses = 50;
assert!((p.l1i_miss_rate() - 0.05).abs() < 0.01);
}
#[test]
fn test_compute_ipc() {
let mut p = X86PerformanceMetrics::new();
p.dynamic_instruction_count = 1000;
p.total_cycles_estimate = 500;
assert_eq!(p.compute_ipc(), 2.0);
}
#[test]
fn test_compute_ipc_zero_cycles() {
let p = X86PerformanceMetrics::new();
assert_eq!(p.compute_ipc(), 0.0);
}
#[test]
fn test_record_call() {
let mut p = X86PerformanceMetrics::new();
p.record_call("foo");
p.record_call("foo");
p.record_call("bar");
assert_eq!(p.call_frequency.get("foo"), Some(&2));
assert_eq!(p.call_frequency.get("bar"), Some(&1));
}
#[test]
fn test_identify_hot_spots() {
let mut p = X86PerformanceMetrics::new();
p.record_call("hot_fn");
p.record_call("hot_fn");
p.record_call("hot_fn");
p.record_call("hot_fn");
p.record_call("hot_fn");
p.record_call("warm_fn");
p.record_call("cold_fn");
p.identify_hot_spots();
let hotspots: Vec<_> = p
.hot_spots
.iter()
.filter(|h| h.category == X86HotSpotCategory::Hot)
.collect();
assert!(!hotspots.is_empty());
assert_eq!(hotspots[0].function, "hot_fn");
}
#[test]
fn test_identify_hot_spots_empty() {
let mut p = X86PerformanceMetrics::new();
p.identify_hot_spots();
assert!(p.hot_spots.is_empty());
}
#[test]
fn test_top_called_functions() {
let mut p = X86PerformanceMetrics::new();
p.record_call("a");
p.record_call("b");
p.record_call("b");
p.record_call("c");
p.record_call("c");
p.record_call("c");
let top = p.top_called_functions(2);
assert_eq!(top.len(), 2);
assert_eq!(top[0].0, "c");
assert_eq!(top[1].0, "b");
}
#[test]
fn test_cache_miss_penalty() {
let mut p = X86PerformanceMetrics::new();
p.l1i_cache_misses = 10;
p.l1d_cache_misses = 20;
p.l2_cache_misses = 5;
p.llc_cache_misses = 2;
let penalty = p.cache_miss_penalty_cycles();
assert_eq!(penalty, 260);
}
#[test]
fn test_estimate_frontend_stalls() {
let mut p = X86PerformanceMetrics::new();
p.l1i_cache_misses = 10;
p.mispredicted_branches = 5;
p.estimate_frontend_stalls();
assert_eq!(p.frontend_stall_cycles, 160);
}
#[test]
fn test_compute_all() {
let mut p = X86PerformanceMetrics::new();
p.dynamic_instruction_count = 1000;
p.l1i_cache_misses = 10;
p.mispredicted_branches = 5;
p.compute_all();
assert!(p.total_cycles_estimate > 0);
assert!(p.instructions_per_cycle > 0.0);
}
#[test]
fn test_record_load_store() {
let mut p = X86PerformanceMetrics::new();
p.record_load();
p.record_load();
p.record_store();
assert_eq!(p.load_count, 2);
assert_eq!(p.store_count, 1);
assert_eq!(p.load_store_ratio(), 2.0);
}
#[test]
fn test_load_store_ratio_no_stores() {
let p = X86PerformanceMetrics::new();
assert!(p.load_store_ratio().is_infinite());
}
#[test]
fn test_record_loop() {
let mut p = X86PerformanceMetrics::new();
p.record_loop("loop_1", 100);
p.record_loop("loop_2", 200);
assert_eq!(p.loop_iterations.get("loop_1"), Some(&100));
assert_eq!(p.loop_iterations.get("loop_2"), Some(&200));
}
#[test]
fn test_record_memory_pattern() {
let mut p = X86PerformanceMetrics::new();
let pattern = X86MemoryPattern::Streaming {
stride: 8,
element_size: 8,
};
p.record_memory_pattern("array_sum", pattern);
assert!(p.memory_patterns.contains_key("array_sum"));
}
#[test]
fn test_perf_metrics_merge() {
let mut p1 = X86PerformanceMetrics::new();
p1.record_call("f");
p1.record_call("f");
let mut p2 = X86PerformanceMetrics::new();
p2.record_call("f");
p2.record_call("g");
p1.merge(&p2);
assert_eq!(p1.call_frequency.get("f"), Some(&3));
assert_eq!(p1.call_frequency.get("g"), Some(&1));
}
#[test]
fn test_hot_spot_display() {
let hs = X86HotSpot {
function: "main".to_string(),
estimated_cycles: 1000,
percentage: 50.0,
category: X86HotSpotCategory::Hot,
};
let d = format!("{}", hs);
assert!(d.contains("main"));
assert!(d.contains("Hot"));
}
#[test]
fn test_memory_pattern_describe_streaming() {
let p = X86MemoryPattern::Streaming {
stride: 8,
element_size: 8,
};
let d = p.describe();
assert!(d.contains("Streaming"));
}
#[test]
fn test_memory_pattern_describe_random() {
let p = X86MemoryPattern::Random { access_count: 42 };
let d = p.describe();
assert!(d.contains("Random"));
}
#[test]
fn test_memory_pattern_cache_friendliness() {
let streaming = X86MemoryPattern::Streaming {
stride: 8,
element_size: 8,
};
let random = X86MemoryPattern::Random { access_count: 100 };
assert!(streaming.cache_friendliness() > random.cache_friendliness());
}
#[test]
fn test_memory_pattern_mixed() {
let p = X86MemoryPattern::Mixed {
streaming_pct: 0.5,
random_pct: 0.3,
strided_pct: 0.2,
};
let cf = p.cache_friendliness();
assert!(cf >= 0.0 && cf <= 1.0);
}
#[test]
fn test_complexity_create() {
let c = X86ComplexityMetrics::new();
assert_eq!(c.analyzed_functions, 0);
assert_eq!(c.complexity_threshold, 15);
}
#[test]
fn test_compute_cyclomatic() {
let mut c = X86ComplexityMetrics::new();
c.compute_cyclomatic("test_fn", 5, 10, 15);
let cc = c.cyclomatic_complexity.get("test_fn").copied().unwrap();
assert!(cc >= 6); }
#[test]
fn test_cyclomatic_simple_function() {
let mut c = X86ComplexityMetrics::new();
c.compute_cyclomatic("simple", 0, 1, 1);
let cc = c.cyclomatic_complexity.get("simple").copied().unwrap();
assert_eq!(cc, 2); }
#[test]
fn test_overly_complex_detection() {
let mut c = X86ComplexityMetrics::new();
c.complexity_threshold = 10;
c.compute_cyclomatic("complex_fn", 20, 30, 50); assert!(c
.overly_complex_functions
.contains(&"complex_fn".to_string()));
}
#[test]
fn test_compute_cognitive() {
let mut c = X86ComplexityMetrics::new();
c.compute_cognitive("fn", 3, 10, 1, 1);
let score = c.cognitive_complexity.get("fn").copied().unwrap();
assert!(score > 15.0);
}
#[test]
fn test_compute_halstead() {
let mut c = X86ComplexityMetrics::new();
c.compute_halstead("fn", 10, 5, 50, 25);
let m = c.halstead_metrics.get("fn").unwrap();
assert_eq!(m.unique_operators, 10);
assert_eq!(m.unique_operands, 5);
assert_eq!(m.total_operators, 50);
assert_eq!(m.total_operands, 25);
assert!(m.volume > 0.0);
assert!(m.difficulty > 0.0);
assert!(m.effort > 0.0);
}
#[test]
fn test_halstead_display() {
let m = X86HalsteadMetrics {
unique_operators: 10,
unique_operands: 5,
total_operators: 50,
total_operands: 25,
program_length: 75,
program_vocabulary: 15,
volume: 200.0,
difficulty: 10.0,
effort: 2000.0,
time_to_implement_seconds: 111.11,
estimated_bugs: 0.067,
};
let d = format!("{}", m);
assert!(d.contains("Halstead Metrics"));
assert!(d.contains("Volume:"));
assert!(d.contains("Difficulty:"));
}
#[test]
fn test_record_nesting_depth() {
let mut c = X86ComplexityMetrics::new();
c.record_nesting_depth("fn", 4);
assert_eq!(c.nesting_depth.get("fn"), Some(&4));
}
#[test]
fn test_record_fan_in_out() {
let mut c = X86ComplexityMetrics::new();
c.record_fan_in("fn", 3);
c.record_fan_out("fn", 5);
assert_eq!(c.fan_in.get("fn"), Some(&3));
assert_eq!(c.fan_out.get("fn"), Some(&5));
}
#[test]
fn test_fan_ratio() {
let mut c = X86ComplexityMetrics::new();
c.record_fan_in("fn", 6);
c.record_fan_out("fn", 3);
assert_eq!(c.fan_ratio("fn"), 2.0);
}
#[test]
fn test_fan_ratio_zero_out() {
let mut c = X86ComplexityMetrics::new();
c.record_fan_in("fn", 5);
assert!(c.fan_ratio("fn").is_infinite());
}
#[test]
fn test_fan_ratio_both_zero() {
let c = X86ComplexityMetrics::new();
assert_eq!(c.fan_ratio("nonexistent"), 0.0);
}
#[test]
fn test_compute_maintainability() {
let mut c = X86ComplexityMetrics::new();
c.compute_cyclomatic("fn", 5, 10, 15);
c.compute_halstead("fn", 10, 5, 50, 25);
c.compute_maintainability("fn", 100);
let mi = c.maintainability_index.get("fn").copied().unwrap();
assert!(mi >= 0.0 && mi <= 100.0);
}
#[test]
fn test_average_cyclomatic() {
let mut c = X86ComplexityMetrics::new();
c.compute_cyclomatic("a", 1, 2, 3);
c.compute_cyclomatic("b", 2, 3, 5);
let avg = c.average_cyclomatic();
assert!(avg >= 1.0);
}
#[test]
fn test_average_cyclomatic_empty() {
let c = X86ComplexityMetrics::new();
assert_eq!(c.average_cyclomatic(), 0.0);
}
#[test]
fn test_most_complex_function() {
let mut c = X86ComplexityMetrics::new();
c.compute_cyclomatic("a", 2, 3, 5);
c.compute_cyclomatic("b", 10, 15, 25);
let most = c.most_complex_function();
assert_eq!(most, Some(("b".to_string(), 12))); }
#[test]
fn test_average_halstead_volume() {
let mut c = X86ComplexityMetrics::new();
c.compute_halstead("a", 10, 5, 50, 25);
c.compute_halstead("b", 20, 10, 100, 50);
let avg = c.average_halstead_volume();
assert!(avg > 0.0);
}
#[test]
fn test_average_halstead_effort() {
let mut c = X86ComplexityMetrics::new();
c.compute_halstead("a", 10, 5, 50, 25);
let avg = c.average_halstead_effort();
assert!(avg > 0.0);
}
#[test]
fn test_sorted_by_complexity() {
let mut c = X86ComplexityMetrics::new();
c.compute_cyclomatic("low", 1, 2, 3);
c.compute_cyclomatic("high", 5, 6, 10);
c.compute_cyclomatic("mid", 2, 3, 5);
let sorted = c.sorted_by_complexity();
assert_eq!(sorted[0].0, "high");
}
#[test]
fn test_mark_analyzed() {
let mut c = X86ComplexityMetrics::new();
c.mark_analyzed();
c.mark_analyzed();
assert_eq!(c.analyzed_functions, 2);
}
#[test]
fn test_complexity_merge() {
let mut c1 = X86ComplexityMetrics::new();
c1.compute_cyclomatic("a", 1, 2, 3);
let mut c2 = X86ComplexityMetrics::new();
c2.compute_cyclomatic("b", 2, 3, 5);
c1.merge(&c2);
assert!(c1.cyclomatic_complexity.contains_key("a"));
assert!(c1.cyclomatic_complexity.contains_key("b"));
}
#[test]
fn test_quality_create() {
let q = X86QualityMetrics::new();
assert_eq!(q.total_warnings, 0);
assert_eq!(q.total_lines, 0);
assert_eq!(q.quality_score, 100.0);
}
#[test]
fn test_record_warning() {
let mut q = X86QualityMetrics::new();
q.record_warning("fn", "unused variable");
assert_eq!(q.total_warnings, 1);
assert_eq!(q.per_function_warnings.get("fn"), Some(&1));
assert_eq!(q.quality_issues.len(), 1);
}
#[test]
fn test_warning_density() {
let mut q = X86QualityMetrics::new();
q.set_total_lines(10000);
q.record_warning("fn", "test");
q.record_warning("fn", "test2");
assert!((q.warning_density() - 0.2).abs() < 0.01);
}
#[test]
fn test_warning_density_zero_lines() {
let q = X86QualityMetrics::new();
assert_eq!(q.warning_density(), 0.0);
}
#[test]
fn test_record_test_coverage() {
let mut q = X86QualityMetrics::new();
q.record_test_coverage("fn", 85.0);
q.record_test_coverage("fn2", 90.0);
q.compute_test_coverage();
assert!((q.test_coverage_pct - 87.5).abs() < 0.01);
}
#[test]
fn test_record_doc_coverage() {
let mut q = X86QualityMetrics::new();
q.record_doc_coverage("fn", 60.0);
q.record_doc_coverage("fn2", 40.0);
q.compute_documentation_coverage();
assert!((q.documentation_coverage_pct - 50.0).abs() < 0.01);
}
#[test]
fn test_mark_dead_function() {
let mut q = X86QualityMetrics::new();
q.mark_dead_function("unused_fn");
assert!(q.dead_functions.contains("unused_fn"));
assert_eq!(q.dead_function_count(), 1);
}
#[test]
fn test_dead_code_ratio() {
let mut q = X86QualityMetrics::new();
q.set_total_lines(1000);
q.dead_code_lines = 100;
assert_eq!(q.dead_code_ratio(), 0.1);
}
#[test]
fn test_duplicate_code_ratio() {
let mut q = X86QualityMetrics::new();
q.set_total_lines(1000);
q.duplicate_code_lines = 50;
assert_eq!(q.duplicate_code_ratio(), 0.05);
}
#[test]
fn test_comment_ratio() {
let mut q = X86QualityMetrics::new();
q.set_total_lines(1000);
q.comment_lines = 200;
assert_eq!(q.comment_ratio(), 0.2);
}
#[test]
fn test_record_duplicate() {
let mut q = X86QualityMetrics::new();
q.record_duplicate("sig1", vec!["a".to_string(), "b".to_string()]);
assert!(q.duplicate_clusters.contains_key("sig1"));
}
#[test]
fn test_compute_quality_score() {
let mut q = X86QualityMetrics::new();
q.set_total_lines(5000);
q.record_test_coverage("fn", 80.0);
q.record_doc_coverage("fn", 70.0);
q.compute_quality_score();
assert!(q.quality_score >= 0.0 && q.quality_score <= 100.0);
}
#[test]
fn test_most_warned_functions() {
let mut q = X86QualityMetrics::new();
q.record_warning("a", "w1");
q.record_warning("a", "w2");
q.record_warning("b", "w1");
let top = q.most_warned_functions(1);
assert_eq!(top[0].0, "a");
assert_eq!(top[0].1, 2);
}
#[test]
fn test_quality_merge() {
let mut q1 = X86QualityMetrics::new();
q1.record_warning("f", "w");
q1.set_total_lines(1000);
let mut q2 = X86QualityMetrics::new();
q2.record_warning("f", "w2");
q2.set_total_lines(500);
q1.merge(&q2);
assert_eq!(q1.total_warnings, 2);
assert_eq!(q1.total_lines, 1500);
}
#[test]
fn test_average_maintainability_from_quality() {
let mut q = X86QualityMetrics::new();
q.set_total_lines(10000);
let mi = q.average_maintainability();
assert!(mi > 90.0);
}
#[test]
fn test_binary_analysis_create() {
let b = X86BinaryAnalysis::new("/bin/test", X86BinaryFileType::ELF, "x86-64");
assert_eq!(b.binary_path, "/bin/test");
assert_eq!(b.file_type, X86BinaryFileType::ELF);
assert_eq!(b.architecture, "x86-64");
assert_eq!(b.total_gadgets, 0);
}
#[test]
fn test_binary_analysis_default() {
let b = X86BinaryAnalysis::default();
assert_eq!(b.file_type, X86BinaryFileType::Unknown);
}
#[test]
fn test_record_gadget() {
let mut b = X86BinaryAnalysis::new("test", X86BinaryFileType::ELF, "x86-64");
let g = X86ROPGadget {
address: 0x1000,
bytes: vec![0xc3],
instructions: vec!["ret".to_string()],
ends_with_ret: true,
is_indirect: false,
useful_for: vec!["stack pivot".to_string()],
};
b.record_gadget(g);
assert_eq!(b.total_gadgets, 1);
}
#[test]
fn test_ret_gadgets() {
let mut b = X86BinaryAnalysis::new("test", X86BinaryFileType::ELF, "x86-64");
b.record_gadget(X86ROPGadget {
address: 0x1000,
bytes: vec![0xc3],
instructions: vec!["ret".to_string()],
ends_with_ret: true,
is_indirect: false,
useful_for: vec![],
});
b.record_gadget(X86ROPGadget {
address: 0x2000,
bytes: vec![0x90],
instructions: vec!["nop".to_string()],
ends_with_ret: false,
is_indirect: false,
useful_for: vec![],
});
assert_eq!(b.ret_gadgets().len(), 1);
assert_eq!(b.indirect_gadgets().len(), 0);
}
#[test]
fn test_indirect_gadgets() {
let mut b = X86BinaryAnalysis::new("test", X86BinaryFileType::ELF, "x86-64");
b.record_gadget(X86ROPGadget {
address: 0x1000,
bytes: vec![0xff, 0xe0],
instructions: vec!["jmp rax".to_string()],
ends_with_ret: false,
is_indirect: true,
useful_for: vec![],
});
assert_eq!(b.indirect_gadgets().len(), 1);
}
#[test]
fn test_gadget_density() {
let mut b = X86BinaryAnalysis::new("test", X86BinaryFileType::ELF, "x86-64");
b.sections.push(X86BinarySection {
name: ".text".to_string(),
size: 1024,
virtual_address: 0x1000,
virtual_size: 1024,
raw_offset: 0,
raw_size: 1024,
characteristics: vec![],
});
for _ in 0..10 {
b.record_gadget(X86ROPGadget {
address: 0x1000,
bytes: vec![0xc3],
instructions: vec!["ret".to_string()],
ends_with_ret: true,
is_indirect: false,
useful_for: vec![],
});
}
assert!((b.gadget_density() - 10.0).abs() < 0.01);
}
#[test]
fn test_gadget_density_empty_code() {
let b = X86BinaryAnalysis::new("test", X86BinaryFileType::ELF, "x86-64");
assert!((b.gadget_density() - 0.0).abs() < 0.01);
}
#[test]
fn test_detect_security_features() {
let mut b = X86BinaryAnalysis::new("test", X86BinaryFileType::ELF, "x86-64");
b.detect_security_features();
assert!(b.security_features.has_nx);
assert!(b.security_features.has_aslr);
assert!(b.security_features.has_stack_canary);
}
#[test]
fn test_security_score() {
let mut b = X86BinaryAnalysis::new("test", X86BinaryFileType::ELF, "x86-64");
b.detect_security_features();
let score = b.security_score();
assert!(score > 50.0);
assert!(score <= 100.0);
}
#[test]
fn test_security_score_no_features() {
let b = X86BinaryAnalysis::new("test", X86BinaryFileType::ELF, "x86-64");
assert_eq!(b.security_score(), 0.0);
}
#[test]
fn test_detect_compiler() {
let mut b = X86BinaryAnalysis::new("test", X86BinaryFileType::ELF, "x86-64");
b.detect_compiler();
assert!(b.detected_compiler.is_some());
let compiler = b.detected_compiler.unwrap();
assert!(compiler.confidence > 0.0);
assert!(!compiler.evidence.is_empty());
}
#[test]
fn test_binary_file_type_display() {
assert_eq!(format!("{}", X86BinaryFileType::ELF), "ELF");
assert_eq!(format!("{}", X86BinaryFileType::PE), "PE/COFF");
assert_eq!(format!("{}", X86BinaryFileType::MachO), "Mach-O");
assert_eq!(format!("{}", X86BinaryFileType::RawBinary), "Raw Binary");
assert_eq!(format!("{}", X86BinaryFileType::Unknown), "Unknown");
}
#[test]
fn test_gadget_display() {
let g = X86ROPGadget {
address: 0x401000,
bytes: vec![0x58, 0xc3],
instructions: vec!["pop rax".to_string(), "ret".to_string()],
ends_with_ret: true,
is_indirect: false,
useful_for: vec!["register load".to_string()],
};
let d = format!("{}", g);
assert!(d.contains("401000"));
assert!(d.contains("pop rax"));
assert!(d.contains("ret"));
}
#[test]
fn test_security_features_summary() {
let sf = X86BinarySecurityFeatures {
has_nx: true,
has_aslr: true,
has_pie: true,
..Default::default()
};
let summary = sf.summary();
assert_eq!(summary.len(), 11);
assert_eq!(sf.enabled_count(), 3);
}
#[test]
fn test_security_features_default() {
let sf = X86BinarySecurityFeatures::default();
assert!(!sf.has_nx);
assert!(!sf.has_aslr);
assert_eq!(sf.enabled_count(), 0);
}
#[test]
fn test_instruction_mix_new() {
let mix = X86InstructionMix::new();
assert_eq!(mix.total_instructions, 0);
assert_eq!(mix.data_movement_pct, 0.0);
}
#[test]
fn test_instruction_mix_from_counts() {
let mut counts: HashMap<X86InstrCategory, u64> = HashMap::new();
counts.insert(X86InstrCategory::DataMovement, 40);
counts.insert(X86InstrCategory::ArithmeticLogic, 30);
counts.insert(X86InstrCategory::ControlFlow, 20);
counts.insert(X86InstrCategory::SIMD, 10);
let mix = X86InstructionMix::from_counts(&counts);
assert_eq!(mix.total_instructions, 100);
assert!((mix.data_movement_pct - 40.0).abs() < 0.01);
assert!((mix.arithmetic_pct - 30.0).abs() < 0.01);
assert!((mix.control_flow_pct - 20.0).abs() < 0.01);
assert!((mix.simd_pct - 10.0).abs() < 0.01);
}
#[test]
fn test_instruction_mix_from_counts_empty() {
let mix = X86InstructionMix::from_counts(&HashMap::new());
assert_eq!(mix.total_instructions, 0);
}
#[test]
fn test_report_create() {
let r = X86MetricsReport::new();
assert!(r.title.contains("X86 Code Metrics Report"));
assert!(!r.verbose);
}
#[test]
fn test_report_default() {
let r = X86MetricsReport::default();
assert!(!r.title.is_empty());
}
#[test]
fn test_generate_text() {
let m = make_sample_metrics();
let report = m.generate_text_report();
assert!(report.contains("X86 Code Metrics Report"));
assert!(report.contains("Size Metrics"));
assert!(report.contains("Performance Metrics"));
assert!(report.contains("Complexity Metrics"));
assert!(report.contains("Quality Metrics"));
}
#[test]
fn test_generate_text_with_binary() {
let mut m = make_sample_metrics();
let mut bin = X86BinaryAnalysis::new("/bin/test", X86BinaryFileType::ELF, "x86-64");
bin.detect_security_features();
m.binary = Some(bin);
let report = m.generate_text_report();
assert!(report.contains("Binary Analysis"));
assert!(report.contains("Security Features"));
}
#[test]
fn test_generate_json() {
let m = make_sample_metrics();
let json = m.generate_json_report();
assert!(json.contains("\"title\""));
assert!(json.contains("\"size\""));
assert!(json.contains("\"performance\""));
assert!(json.contains("\"complexity\""));
assert!(json.contains("\"quality\""));
}
#[test]
fn test_generate_json_with_binary() {
let mut m = make_sample_metrics();
let mut bin = X86BinaryAnalysis::new("/bin/test", X86BinaryFileType::ELF, "x86-64");
bin.detect_security_features();
m.binary = Some(bin);
let json = m.generate_json_report();
assert!(json.contains("\"binary\""));
assert!(json.contains("\"security_score\""));
}
#[test]
fn test_generate_html() {
let m = make_sample_metrics();
let html = m.generate_html_report();
assert!(html.contains("<!DOCTYPE html>"));
assert!(html.contains("<h1>"));
assert!(html.contains("<table>"));
assert!(html.contains("Size Metrics"));
}
#[test]
fn test_generate_html_with_binary() {
let mut m = make_sample_metrics();
let mut bin = X86BinaryAnalysis::new("/bin/test", X86BinaryFileType::ELF, "x86-64");
bin.detect_security_features();
m.binary = Some(bin);
let html = m.generate_html_report();
assert!(html.contains("Binary Analysis"));
assert!(html.contains("Security Score"));
}
#[test]
fn test_generate_csv() {
let m = make_sample_metrics();
let csv = m.generate_csv_report();
assert!(csv.contains("category,metric,value"));
assert!(csv.contains("size,total_size_bytes,"));
assert!(csv.contains("function,"));
}
#[test]
fn test_trend_analysis() {
let mut m = make_sample_metrics();
m.save_snapshot();
m.version = "v2.0-test".to_string();
m.size.total_size_bytes += 500;
m.save_snapshot();
let trend = m.trend_analysis();
assert_eq!(trend.snapshot_count, 2);
assert_eq!(trend.versions.len(), 2);
assert_eq!(trend.size_trend, X86TrendDirection::Degrading); }
#[test]
fn test_trend_analysis_empty() {
let m = X86CodeMetrics::new("v1.0");
let trend = m.trend_analysis();
assert_eq!(trend.snapshot_count, 0);
}
#[test]
fn test_trend_report_display() {
let report = X86TrendReport {
versions: vec!["v1".to_string(), "v2".to_string()],
size_trend: X86TrendDirection::Degrading,
function_trend: X86TrendDirection::Improving,
complexity_trend: X86TrendDirection::Stable,
maintainability_trend: X86TrendDirection::Improving,
size_change_pct: Some(15.0),
snapshot_count: 2,
};
let d = format!("{}", report);
assert!(d.contains("snapshots"));
assert!(d.contains("degrading"));
assert!(d.contains("improving"));
}
#[test]
fn test_trend_direction_from_values() {
assert_eq!(
X86TrendDirection::from_values(&[100, 150, 200]),
X86TrendDirection::Improving
);
assert_eq!(
X86TrendDirection::from_values(&[200, 150, 100]),
X86TrendDirection::Degrading
);
assert_eq!(
X86TrendDirection::from_values(&[100, 101, 99]),
X86TrendDirection::Stable
);
assert_eq!(
X86TrendDirection::from_values(&[100]),
X86TrendDirection::Unknown
);
assert_eq!(
X86TrendDirection::from_values(&[]),
X86TrendDirection::Unknown
);
}
#[test]
fn test_trend_direction_from_values_f64() {
assert_eq!(
X86TrendDirection::from_values_f64(&[10.0, 15.0, 20.0]),
X86TrendDirection::Improving
);
assert_eq!(
X86TrendDirection::from_values_f64(&[20.0, 15.0, 10.0]),
X86TrendDirection::Degrading
);
assert_eq!(
X86TrendDirection::from_values_f64(&[10.0, 10.1, 9.9]),
X86TrendDirection::Stable
);
}
#[test]
fn test_trend_direction_display() {
assert_eq!(format!("{}", X86TrendDirection::Improving), "improving");
assert_eq!(format!("{}", X86TrendDirection::Stable), "stable");
assert_eq!(format!("{}", X86TrendDirection::Degrading), "degrading");
assert_eq!(format!("{}", X86TrendDirection::Unknown), "unknown");
}
#[test]
fn test_full_lifecycle_metrics_to_report() {
let mut metrics = X86CodeMetrics::new("v1.0-test");
metrics.size.record_function("main", 512);
metrics.size.record_function("helper", 128);
for _ in 0..20 {
metrics.size.record_basic_block(32);
}
metrics.size.record_section(".text", 800);
metrics.size.record_section(".data", 256);
metrics.performance.static_instruction_count = 200;
metrics.performance.record_call("main");
metrics.performance.record_call("helper");
metrics.performance.record_branch(true);
metrics.performance.record_branch(true);
metrics.performance.record_branch(false);
metrics.complexity.compute_cyclomatic("main", 4, 8, 12);
metrics.complexity.compute_cyclomatic("helper", 1, 3, 4);
metrics.complexity.compute_maintainability("main", 50);
metrics.complexity.compute_maintainability("helper", 15);
metrics.quality.set_total_lines(1000);
metrics.quality.record_test_coverage("main", 80.0);
metrics.quality.record_test_coverage("helper", 95.0);
metrics.quality.compute_quality_score();
metrics.save_snapshot();
assert_eq!(metrics.history.len(), 1);
let text = metrics.generate_text_report();
assert!(!text.is_empty());
assert!(text.contains("main"));
let json = metrics.generate_json_report();
assert!(json.contains("v1.0-test"));
let html = metrics.generate_html_report();
assert!(html.contains("<!DOCTYPE html>"));
let csv = metrics.generate_csv_report();
assert!(csv.contains("main"));
}
#[test]
fn test_multiple_snapshots_trend() {
let mut metrics = X86CodeMetrics::new("v1");
metrics.size.record_function("f", 100);
metrics.complexity.compute_cyclomatic("f", 1, 2, 3);
metrics.save_snapshot();
let mut metrics2 = X86CodeMetrics::new("v2");
metrics2.size.record_function("f", 120);
metrics2.size.record_function("g", 80);
metrics2.complexity.compute_cyclomatic("f", 2, 3, 5);
metrics2.complexity.compute_cyclomatic("g", 1, 2, 3);
let mut base = X86CodeMetrics::new("v1");
base.history = metrics.history.clone();
base.history.push(X86MetricsSnapshot {
version: "v2".to_string(),
timestamp: chrono_like_now(),
total_size: 200,
total_functions: 2,
avg_cyclomatic: 1.5,
avg_maintainability: 85.0,
total_warnings: 0,
});
let trend = base.trend_analysis();
assert_eq!(trend.snapshot_count, 2);
assert_eq!(trend.size_trend, X86TrendDirection::Improving); }
#[test]
fn test_regression_with_version_comparison() {
let mut s = X86SizeMetrics::new();
s.record_function("fn_a", 200);
s.record_function("fn_b", 100);
let mut prev = BTreeMap::new();
prev.insert("fn_a".to_string(), 100);
prev.insert("fn_b".to_string(), 100);
prev.insert("fn_c".to_string(), 50);
s.previous_function_sizes = Some(prev);
s.regression_threshold_bytes = 10;
let regs = s.detect_regressions();
assert_eq!(regs.len(), 1); assert_eq!(regs[0].function, "fn_a");
assert_eq!(regs[0].delta_bytes, 100);
let new = s.new_functions();
assert!(new.is_empty());
let removed = s.removed_functions();
assert_eq!(removed.len(), 1);
assert!(removed.contains(&"fn_c".to_string()));
}
#[test]
fn test_edge_cases_empty_metrics_report() {
let m = X86CodeMetrics::new("v0");
let text = m.generate_text_report();
assert!(text.contains("Size Metrics"));
assert!(text.contains("0 bytes"));
}
#[test]
fn test_binary_analysis_sections() {
let mut b = X86BinaryAnalysis::new("test.o", X86BinaryFileType::ELF, "x86-64");
b.sections.push(X86BinarySection {
name: ".text".to_string(),
size: 1024,
virtual_address: 0x1000,
virtual_size: 1024,
raw_offset: 0x200,
raw_size: 1024,
characteristics: vec!["rx".to_string()],
});
assert_eq!(b.sections.len(), 1);
assert_eq!(b.sections[0].name, ".text");
}
#[test]
fn test_instruction_category_edge_cases() {
assert_eq!(
X86InstrCategory::from_mnemonic("andn"),
X86InstrCategory::SIMD
);
assert_eq!(
X86InstrCategory::from_mnemonic("bextr"),
X86InstrCategory::SIMD
);
assert_eq!(
X86InstrCategory::from_mnemonic("aesenc"),
X86InstrCategory::SIMD
);
assert_eq!(
X86InstrCategory::from_mnemonic("sha256rnds2"),
X86InstrCategory::SIMD
);
assert_eq!(
X86InstrCategory::from_mnemonic("kaddb"),
X86InstrCategory::SIMD
);
assert_eq!(
X86InstrCategory::from_mnemonic("endbr64"),
X86InstrCategory::System
);
assert_eq!(
X86InstrCategory::from_mnemonic("endbr32"),
X86InstrCategory::System
);
}
#[test]
fn test_basic_block_histogram_uniform() {
let mut s = X86SizeMetrics::new();
for _ in 0..10 {
s.record_basic_block(32);
}
let hist = s.basic_block_histogram(2);
assert_eq!(hist.len(), 2);
}
#[test]
fn test_basic_block_histogram_single_value() {
let mut s = X86SizeMetrics::new();
s.record_basic_block(64);
s.record_basic_block(64);
let hist = s.basic_block_histogram(4);
assert_eq!(hist.len(), 1);
assert_eq!(hist[0].1, 2);
}
#[test]
fn test_performance_top_hot_functions_empty() {
let p = X86PerformanceMetrics::new();
let top = p.top_hot_functions(10);
assert!(top.is_empty());
}
#[test]
fn test_complexity_compute_halstead_min_values() {
let mut c = X86ComplexityMetrics::new();
c.compute_halstead("fn", 1, 1, 1, 1);
let m = c.halstead_metrics.get("fn").unwrap();
assert!(m.volume >= 0.0);
}
#[test]
fn test_trend_report_empty() {
let report = X86TrendReport::empty();
assert_eq!(report.snapshot_count, 0);
assert_eq!(report.size_trend, X86TrendDirection::Unknown);
let summary = report.summary();
assert!(summary.contains("0 snapshots"));
}
#[test]
fn test_median_single_element() {
let mut s = X86SizeMetrics::new();
s.record_function("only", 42);
assert_eq!(s.median_function_size(), 42);
}
#[test]
fn test_top_bloat_functions_more_than_max() {
let mut s = X86SizeMetrics::new();
for i in 0..60 {
s.record_function(&format!("f{}", i), (i * 10) as u64);
}
let bloat = s.top_bloat_functions(100);
assert_eq!(bloat.len(), 50); }
#[test]
fn test_top_bloat_functions_empty() {
let s = X86SizeMetrics::new();
let bloat = s.top_bloat_functions(10);
assert!(bloat.is_empty());
}
#[test]
fn test_basic_block_histogram_empty() {
let s = X86SizeMetrics::new();
let hist = s.basic_block_histogram(5);
assert!(hist.is_empty());
}
#[test]
fn test_record_same_function_twice() {
let mut s = X86SizeMetrics::new();
s.record_function("dup", 100);
s.record_function("dup", 50);
assert_eq!(s.function_sizes.get("dup"), Some(&150));
}
#[test]
fn test_record_instruction_unknown_mnemonic() {
let mut s = X86SizeMetrics::new();
s.record_instruction("bizarre_instr");
assert_eq!(s.instruction_distribution.get("bizarre_instr"), Some(&1));
assert!(s
.instruction_category_counts
.contains_key(&X86InstrCategory::Other));
}
#[test]
fn test_section_sizes_empty_largest() {
let s = X86SizeMetrics::new();
assert_eq!(s.largest_section(), None);
}
#[test]
fn test_code_data_ratio_only_text() {
let mut s = X86SizeMetrics::new();
s.record_section(".text", 500);
assert!(s.code_data_ratio().is_infinite());
}
#[test]
fn test_regression_no_threshold_exceeded() {
let mut s = X86SizeMetrics::new();
s.record_function("stable", 110);
let mut prev = BTreeMap::new();
prev.insert("stable".to_string(), 100);
s.previous_function_sizes = Some(prev);
s.regression_threshold_bytes = 50;
let regs = s.detect_regressions();
assert!(regs.is_empty()); }
#[test]
fn test_regression_exactly_at_threshold() {
let mut s = X86SizeMetrics::new();
s.record_function("edge", 150);
let mut prev = BTreeMap::new();
prev.insert("edge".to_string(), 100);
s.previous_function_sizes = Some(prev);
s.regression_threshold_bytes = 50;
let regs = s.detect_regressions();
assert!(regs.is_empty()); }
#[test]
fn test_new_functions_empty_previous() {
let s = X86SizeMetrics::new();
let new = s.new_functions();
assert!(new.is_empty());
}
#[test]
fn test_removed_functions_empty_previous() {
let s = X86SizeMetrics::new();
let removed = s.removed_functions();
assert!(removed.is_empty());
}
#[test]
fn test_size_regression_display_negative_pct() {
let r = X86SizeRegression {
function: "shrunk".to_string(),
previous_size: 200,
current_size: 100,
delta_bytes: 100, delta_percent: -50.0,
};
let d = format!("{}", r);
assert!(d.contains("shrunk"));
}
#[test]
fn test_quantiles_two_elements() {
let q = X86SizeQuantiles::from_sizes(&[10, 90]);
assert_eq!(q.min, 10);
assert_eq!(q.max, 90);
assert_eq!(q.mean, 50.0);
assert!(q.std_dev > 0.0);
}
#[test]
fn test_quantiles_all_same() {
let q = X86SizeQuantiles::from_sizes(&[42, 42, 42, 42]);
assert_eq!(q.min, 42);
assert_eq!(q.max, 42);
assert_eq!(q.p25, 42);
assert_eq!(q.p75, 42);
assert_eq!(q.std_dev, 0.0);
}
#[test]
fn test_densest_functions_order() {
let mut s = X86SizeMetrics::new();
s.record_function("dense", 50);
s.record_function("sparse", 200);
s.record_instruction("dense");
s.record_instruction("dense");
s.record_instruction("sparse");
let dense = s.densest_functions(2);
assert_eq!(dense[0].0, "dense");
assert_eq!(dense[1].0, "sparse");
}
#[test]
fn test_save_multiple_snapshots() {
let mut s = X86SizeMetrics::new();
s.record_function("f", 100);
s.save_snapshot();
s.record_function("g", 200);
s.save_snapshot();
assert_eq!(s.size_history.len(), 2);
assert_eq!(s.size_history[0].function_count, 1);
assert_eq!(s.size_history[1].function_count, 2);
}
#[test]
fn test_instr_category_fpu() {
assert_eq!(
X86InstrCategory::from_mnemonic("fld"),
X86InstrCategory::FloatingPoint
);
assert_eq!(
X86InstrCategory::from_mnemonic("fadd"),
X86InstrCategory::FloatingPoint
);
assert_eq!(
X86InstrCategory::from_mnemonic("fsin"),
X86InstrCategory::FloatingPoint
);
assert_eq!(
X86InstrCategory::from_mnemonic("finit"),
X86InstrCategory::FloatingPoint
);
}
#[test]
fn test_instr_category_string() {
assert_eq!(
X86InstrCategory::from_mnemonic("movsb"),
X86InstrCategory::String
);
assert_eq!(
X86InstrCategory::from_mnemonic("stosd"),
X86InstrCategory::String
);
assert_eq!(
X86InstrCategory::from_mnemonic("rep"),
X86InstrCategory::String
);
}
#[test]
fn test_instr_category_avx512() {
assert_eq!(
X86InstrCategory::from_mnemonic("vaddps"),
X86InstrCategory::SIMD
);
assert_eq!(
X86InstrCategory::from_mnemonic("vpgatherdd"),
X86InstrCategory::SIMD
);
assert_eq!(
X86InstrCategory::from_mnemonic("vpcompressd"),
X86InstrCategory::SIMD
);
}
#[test]
fn test_instr_category_mask_ops() {
assert_eq!(
X86InstrCategory::from_mnemonic("kandb"),
X86InstrCategory::SIMD
);
assert_eq!(
X86InstrCategory::from_mnemonic("knotb"),
X86InstrCategory::SIMD
);
assert_eq!(
X86InstrCategory::from_mnemonic("kortestb"),
X86InstrCategory::SIMD
);
}
#[test]
fn test_instr_category_cet() {
assert_eq!(
X86InstrCategory::from_mnemonic("wrssd"),
X86InstrCategory::System
);
assert_eq!(
X86InstrCategory::from_mnemonic("setssbsy"),
X86InstrCategory::System
);
assert_eq!(
X86InstrCategory::from_mnemonic("clrssbsy"),
X86InstrCategory::System
);
}
#[test]
fn test_perf_compute_all_no_data() {
let mut p = X86PerformanceMetrics::new();
p.compute_all();
assert_eq!(p.total_cycles_estimate, 0);
assert_eq!(p.instructions_per_cycle, 0.0);
}
#[test]
fn test_perf_record_branch_all_correct() {
let mut p = X86PerformanceMetrics::new();
for _ in 0..100 {
p.record_branch(true);
}
assert_eq!(p.branch_prediction_accuracy(), 1.0);
assert_eq!(p.branch_misprediction_rate(), 0.0);
}
#[test]
fn test_perf_record_branch_all_mispredicted() {
let mut p = X86PerformanceMetrics::new();
for _ in 0..100 {
p.record_branch(false);
}
assert_eq!(p.branch_prediction_accuracy(), 0.0);
assert_eq!(p.branch_misprediction_rate(), 1.0);
}
#[test]
fn test_l1i_miss_rate_no_instructions() {
let p = X86PerformanceMetrics::new();
assert_eq!(p.l1i_miss_rate(), 0.0);
}
#[test]
fn test_l1i_miss_rate_max_clamped() {
let mut p = X86PerformanceMetrics::new();
p.static_instruction_count = 100;
p.l1i_cache_misses = 500; assert!(p.l1i_miss_rate() <= 1.0);
}
#[test]
fn test_load_store_ratio_empty() {
let p = X86PerformanceMetrics::new();
assert!(p.load_store_ratio().is_infinite());
}
#[test]
fn test_load_store_ratio_one_each() {
let mut p = X86PerformanceMetrics::new();
p.record_load();
p.record_store();
assert_eq!(p.load_store_ratio(), 1.0);
}
#[test]
fn test_cache_miss_penalty_all_zeros() {
let p = X86PerformanceMetrics::new();
assert_eq!(p.cache_miss_penalty_cycles(), 0);
}
#[test]
fn test_estimate_backend_stalls_no_misses() {
let mut p = X86PerformanceMetrics::new();
p.l1i_cache_misses = 0;
p.l1d_cache_misses = 0;
p.l2_cache_misses = 0;
p.llc_cache_misses = 0;
p.estimate_backend_stalls();
assert_eq!(p.backend_stall_cycles, 0);
}
#[test]
fn test_top_called_functions_exact_limit() {
let mut p = X86PerformanceMetrics::new();
for i in 0..5 {
p.record_call(&format!("f{}", i));
}
let top = p.top_called_functions(2);
assert_eq!(top.len(), 2);
}
#[test]
fn test_top_called_functions_more_than_available() {
let mut p = X86PerformanceMetrics::new();
p.record_call("only");
let top = p.top_called_functions(5);
assert_eq!(top.len(), 1);
}
#[test]
fn test_record_loop_multiple_ids() {
let mut p = X86PerformanceMetrics::new();
for i in 0..10 {
p.record_loop(&format!("loop_{}", i), i * 10);
}
assert_eq!(p.loop_iterations.len(), 10);
assert_eq!(p.loop_iterations.get("loop_5"), Some(&50));
}
#[test]
fn test_hot_spot_category_exact_hot() {
let mut p = X86PerformanceMetrics::new();
p.record_call("hot");
for _ in 0..19 {
p.record_call("other");
}
p.identify_hot_spots();
let hot: Vec<_> = p
.hot_spots
.iter()
.filter(|h| h.category == X86HotSpotCategory::Hot)
.collect();
assert!(!hot.is_empty());
}
#[test]
fn test_hot_spot_category_all_cold() {
let mut p = X86PerformanceMetrics::new();
for i in 0..100 {
p.record_call(&format!("f{}", i));
}
p.identify_hot_spots();
let hot: Vec<_> = p
.hot_spots
.iter()
.filter(|h| h.category == X86HotSpotCategory::Hot)
.collect();
assert!(hot.is_empty());
}
#[test]
fn test_memory_pattern_stride_cf() {
let good = X86MemoryPattern::Strided {
stride: 4,
element_size: 8,
};
let bad = X86MemoryPattern::Strided {
stride: 64,
element_size: 8,
};
assert!(good.cache_friendliness() > bad.cache_friendliness());
}
#[test]
fn test_memory_pattern_describe_mixed() {
let p = X86MemoryPattern::Mixed {
streaming_pct: 0.6,
random_pct: 0.2,
strided_pct: 0.2,
};
let d = p.describe();
assert!(d.contains("60%"));
assert!(d.contains("20%"));
}
#[test]
fn test_perf_metrics_merge_max_loop_depth() {
let mut p1 = X86PerformanceMetrics::new();
p1.max_loop_nest_depth = 3;
let mut p2 = X86PerformanceMetrics::new();
p2.max_loop_nest_depth = 7;
p1.merge(&p2);
assert_eq!(p1.max_loop_nest_depth, 7);
}
#[test]
fn test_perf_metrics_merge_keep_larger_depth() {
let mut p1 = X86PerformanceMetrics::new();
p1.max_loop_nest_depth = 10;
let mut p2 = X86PerformanceMetrics::new();
p2.max_loop_nest_depth = 5;
p1.merge(&p2);
assert_eq!(p1.max_loop_nest_depth, 10);
}
#[test]
fn test_cyclomatic_zero_branches() {
let mut c = X86ComplexityMetrics::new();
c.compute_cyclomatic("trivial", 0, 1, 1);
let cc = c.cyclomatic_complexity.get("trivial").copied().unwrap();
assert_eq!(cc, 2); }
#[test]
fn test_cyclomatic_many_branches() {
let mut c = X86ComplexityMetrics::new();
c.compute_cyclomatic("complex", 50, 100, 200);
let cc = c.cyclomatic_complexity.get("complex").copied().unwrap();
assert!(cc > 50);
}
#[test]
fn test_cognitive_zero_branches() {
let mut c = X86ComplexityMetrics::new();
c.compute_cognitive("simple", 0, 0, 0, 0);
let score = c.cognitive_complexity.get("simple").copied().unwrap();
assert_eq!(score, 0.0);
}
#[test]
fn test_cognitive_deep_nesting() {
let mut c = X86ComplexityMetrics::new();
c.compute_cognitive("nested", 8, 10, 3, 2);
let score = c.cognitive_complexity.get("nested").copied().unwrap();
assert!(score > 20.0); }
#[test]
fn test_compute_maintainability_zero_loc() {
let mut c = X86ComplexityMetrics::new();
c.compute_cyclomatic("empty", 0, 1, 1);
c.compute_halstead("empty", 1, 1, 1, 1);
c.compute_maintainability("empty", 0); let mi = c.maintainability_index.get("empty").copied().unwrap();
assert!(mi >= 0.0);
}
#[test]
fn test_compute_maintainability_large_volume() {
let mut c = X86ComplexityMetrics::new();
c.compute_cyclomatic("huge", 100, 200, 300);
c.compute_halstead("huge", 50, 30, 500, 300);
c.compute_maintainability("huge", 10000);
let mi = c.maintainability_index.get("huge").copied().unwrap();
assert!(mi < 50.0); }
#[test]
fn test_average_maintainability_single() {
let mut c = X86ComplexityMetrics::new();
c.compute_cyclomatic("f", 1, 2, 3);
c.compute_halstead("f", 5, 3, 20, 15);
c.compute_maintainability("f", 50);
let avg = c.average_maintainability();
assert!(avg >= 0.0 && avg <= 100.0);
}
#[test]
fn test_average_maintainability_empty() {
let c = X86ComplexityMetrics::new();
assert_eq!(c.average_maintainability(), 100.0);
}
#[test]
fn test_fan_ratio_no_callers_no_callees() {
let c = X86ComplexityMetrics::new();
assert_eq!(c.fan_ratio("orphan"), 0.0);
}
#[test]
fn test_fan_ratio_many_callers() {
let mut c = X86ComplexityMetrics::new();
c.record_fan_in("popular", 100);
c.record_fan_out("popular", 5);
assert_eq!(c.fan_ratio("popular"), 20.0);
}
#[test]
fn test_overly_complex_functions_duplicates() {
let mut c = X86ComplexityMetrics::new();
c.complexity_threshold = 2;
c.compute_cyclomatic("comp", 5, 10, 15);
c.compute_cyclomatic("comp", 5, 10, 15); assert_eq!(c.overly_complex_functions.len(), 1);
}
#[test]
fn test_halstead_min_values_protection() {
let mut c = X86ComplexityMetrics::new();
c.compute_halstead("min", 0, 0, 0, 0);
let m = c.halstead_metrics.get("min").unwrap();
assert_eq!(m.unique_operators, 1); assert_eq!(m.unique_operands, 1);
assert_eq!(m.total_operators, 1);
assert_eq!(m.total_operands, 1);
}
#[test]
fn test_sorted_by_complexity_empty() {
let c = X86ComplexityMetrics::new();
let sorted = c.sorted_by_complexity();
assert!(sorted.is_empty());
}
#[test]
fn test_merge_complexity_overly_complex() {
let mut c1 = X86ComplexityMetrics::new();
c1.complexity_threshold = 5;
c1.compute_cyclomatic("bad", 10, 15, 25);
let mut c2 = X86ComplexityMetrics::new();
c2.complexity_threshold = 5;
c2.compute_cyclomatic("worse", 20, 25, 45);
c1.merge(&c2);
assert_eq!(c1.overly_complex_functions.len(), 2);
}
#[test]
fn test_quality_score_perfect() {
let mut q = X86QualityMetrics::new();
q.set_total_lines(1000);
q.record_test_coverage("f", 100.0);
q.record_doc_coverage("f", 100.0);
q.compute_quality_score();
assert!(q.quality_score > 80.0);
}
#[test]
fn test_quality_score_abysmal() {
let mut q = X86QualityMetrics::new();
q.set_total_lines(1000);
q.dead_code_lines = 500;
q.duplicate_code_lines = 300;
for _ in 0..200 {
q.record_warning("f", "bad");
}
q.record_test_coverage("f", 0.0);
q.record_doc_coverage("f", 0.0);
q.compute_quality_score();
assert!(q.quality_score < 50.0);
}
#[test]
fn test_quality_all_issues_recorded() {
let mut q = X86QualityMetrics::new();
q.record_warning("a", "issue_1");
q.record_warning("b", "issue_2");
q.record_warning("a", "issue_3");
assert_eq!(q.quality_issues.len(), 3);
assert_eq!(q.get_quality_issues().len(), 3);
}
#[test]
fn test_most_warned_functions_tie() {
let mut q = X86QualityMetrics::new();
q.record_warning("a", "w1");
q.record_warning("a", "w2");
q.record_warning("b", "w1");
q.record_warning("b", "w2");
let top = q.most_warned_functions(1);
assert_eq!(top[0].1, 2); }
#[test]
fn test_mark_dead_function_duplicate() {
let mut q = X86QualityMetrics::new();
q.mark_dead_function("dead");
q.mark_dead_function("dead");
assert_eq!(q.dead_function_count(), 1); }
#[test]
fn test_dead_code_ratio_no_lines() {
let q = X86QualityMetrics::new();
assert_eq!(q.dead_code_ratio(), 0.0);
}
#[test]
fn test_duplicate_code_ratio_no_lines() {
let q = X86QualityMetrics::new();
assert_eq!(q.duplicate_code_ratio(), 0.0);
}
#[test]
fn test_comment_ratio_no_lines() {
let q = X86QualityMetrics::new();
assert_eq!(q.comment_ratio(), 0.0);
}
#[test]
fn test_compute_test_coverage_single() {
let mut q = X86QualityMetrics::new();
q.record_test_coverage("solo", 42.0);
q.compute_test_coverage();
assert_eq!(q.test_coverage_pct, 42.0);
}
#[test]
fn test_compute_test_coverage_empty() {
let mut q = X86QualityMetrics::new();
q.compute_test_coverage();
assert_eq!(q.test_coverage_pct, 0.0);
}
#[test]
fn test_compute_doc_coverage_single() {
let mut q = X86QualityMetrics::new();
q.record_doc_coverage("solo", 77.0);
q.compute_documentation_coverage();
assert_eq!(q.documentation_coverage_pct, 77.0);
}
#[test]
fn test_compute_doc_coverage_empty() {
let mut q = X86QualityMetrics::new();
q.compute_documentation_coverage();
assert_eq!(q.documentation_coverage_pct, 0.0);
}
#[test]
fn test_record_duplicate_empty_locations() {
let mut q = X86QualityMetrics::new();
q.record_duplicate("sig", vec!["a".to_string()]);
assert_eq!(q.duplicate_code_lines, 0);
}
#[test]
fn test_record_duplicate_many_locations() {
let mut q = X86QualityMetrics::new();
q.record_duplicate(
"sig",
vec![
"a".to_string(),
"b".to_string(),
"c".to_string(),
"d".to_string(),
],
);
assert_eq!(q.duplicate_code_lines, 15);
}
#[test]
fn test_merge_quality_preserves_dead_functions() {
let mut q1 = X86QualityMetrics::new();
q1.mark_dead_function("dead1");
let mut q2 = X86QualityMetrics::new();
q2.mark_dead_function("dead2");
q1.merge(&q2);
assert_eq!(q1.dead_function_count(), 2);
}
#[test]
fn test_merge_quality_accumulates_duplicates() {
let mut q1 = X86QualityMetrics::new();
q1.record_duplicate("dup", vec!["a".to_string(), "b".to_string()]);
let mut q2 = X86QualityMetrics::new();
q2.record_duplicate("dup", vec!["c".to_string(), "d".to_string()]);
q1.merge(&q2);
let cluster = q1.duplicate_clusters.get("dup").unwrap();
assert_eq!(cluster.len(), 4);
}
#[test]
fn test_gadget_display_multiple_instructions() {
let g = X86ROPGadget {
address: 0xdeadbeef,
bytes: vec![0x58, 0x5b, 0xc3],
instructions: vec![
"pop rax".to_string(),
"pop rbx".to_string(),
"ret".to_string(),
],
ends_with_ret: true,
is_indirect: false,
useful_for: vec!["register load".to_string(), "stack pivot".to_string()],
};
let d = format!("{}", g);
assert!(d.contains("deadbeef"));
assert!(d.contains("pop rax"));
assert!(d.contains("pop rbx"));
assert!(d.contains("ret"));
}
#[test]
fn test_indirect_gadgets_mixed() {
let mut b = X86BinaryAnalysis::new("test", X86BinaryFileType::ELF, "x86-64");
b.record_gadget(X86ROPGadget {
address: 0x1000,
bytes: vec![0xc3],
instructions: vec!["ret".to_string()],
ends_with_ret: true,
is_indirect: false,
useful_for: vec![],
});
b.record_gadget(X86ROPGadget {
address: 0x2000,
bytes: vec![0xff, 0xe0],
instructions: vec!["jmp rax".to_string()],
ends_with_ret: false,
is_indirect: true,
useful_for: vec![],
});
b.record_gadget(X86ROPGadget {
address: 0x3000,
bytes: vec![0xff, 0xd0],
instructions: vec!["call rax".to_string()],
ends_with_ret: false,
is_indirect: true,
useful_for: vec![],
});
assert_eq!(b.ret_gadgets().len(), 1);
assert_eq!(b.indirect_gadgets().len(), 2);
assert_eq!(b.total_gadgets, 3);
}
#[test]
fn test_security_score_all_features() {
let mut b = X86BinaryAnalysis::new("test", X86BinaryFileType::ELF, "x86-64");
b.security_features = X86BinarySecurityFeatures {
has_nx: true,
has_aslr: true,
has_relro: true,
has_stack_canary: true,
has_pie: true,
has_fortify: true,
has_cet_ibt: true,
has_cet_shstk: true,
has_safe_seh: true,
has_guard_cf: true,
has_rfg: true,
};
assert_eq!(b.security_score(), 100.0);
}
#[test]
fn test_security_score_partial() {
let mut b = X86BinaryAnalysis::new("test", X86BinaryFileType::ELF, "x86-64");
b.security_features = X86BinarySecurityFeatures {
has_nx: true,
has_aslr: false,
has_relro: true,
has_stack_canary: true,
has_pie: false,
has_fortify: false,
has_cet_ibt: false,
has_cet_shstk: false,
has_safe_seh: false,
has_guard_cf: false,
has_rfg: false,
};
let score = b.security_score();
assert!(score > 0.0 && score < 100.0);
assert!((score - 40.0).abs() < 0.01); }
#[test]
fn test_security_features_summary_order() {
let sf = X86BinarySecurityFeatures::default();
let summary = sf.summary();
assert_eq!(summary[0].0, "NX (DEP)");
assert_eq!(summary[1].0, "ASLR");
assert_eq!(summary[10].0, "RFG");
}
#[test]
fn test_gadget_density_zero_total_gadgets() {
let mut b = X86BinaryAnalysis::new("test", X86BinaryFileType::ELF, "x86-64");
b.sections.push(X86BinarySection {
name: ".text".to_string(),
size: 4096,
virtual_address: 0x1000,
virtual_size: 4096,
raw_offset: 0x200,
raw_size: 4096,
characteristics: vec!["rx".to_string()],
});
assert_eq!(b.gadget_density(), 0.0);
}
#[test]
fn test_detected_compiler_fields() {
let compiler = X86DetectedCompiler {
compiler: "Clang".to_string(),
version: "17.0.6".to_string(),
confidence: 0.92,
evidence: vec![
"LLVM-style optimization patterns".to_string(),
"compiler-rt references".to_string(),
],
};
assert_eq!(compiler.compiler, "Clang");
assert_eq!(compiler.version, "17.0.6");
assert!((compiler.confidence - 0.92).abs() < 0.01);
assert_eq!(compiler.evidence.len(), 2);
}
#[test]
fn test_binary_section_all_fields() {
let section = X86BinarySection {
name: ".data".to_string(),
virtual_address: 0x4000,
virtual_size: 512,
raw_offset: 0x2000,
raw_size: 512,
characteristics: vec!["rw-".to_string()],
size: 512,
};
assert_eq!(section.name, ".data");
assert_eq!(section.virtual_address, 0x4000);
assert_eq!(section.size, 512);
}
#[test]
fn test_report_text_contains_sections() {
let m = make_sample_metrics();
let text = m.generate_text_report();
assert!(text.contains("Section Sizes"));
assert!(text.contains(".text"));
}
#[test]
fn test_report_text_contains_regressions() {
let mut m = make_sample_metrics();
let mut prev = BTreeMap::new();
prev.insert("main".to_string(), 50);
m.size.previous_function_sizes = Some(prev);
m.size.regression_threshold_bytes = 10;
let text = m.generate_text_report();
assert!(text.contains("Regressions"));
}
#[test]
fn test_report_text_contains_hotspots() {
let m = make_sample_metrics();
let text = m.generate_text_report();
assert!(text.contains("Top Hot Functions"));
}
#[test]
fn test_report_html_has_valid_structure() {
let m = make_sample_metrics();
let html = m.generate_html_report();
assert!(html.starts_with("<!DOCTYPE html>"));
assert!(html.contains("</html>"));
assert!(html.contains("</head>"));
assert!(html.contains("</body>"));
}
#[test]
fn test_report_json_valid_structure() {
let m = make_sample_metrics();
let json = m.generate_json_report();
assert!(json.trim().starts_with("{"));
assert!(json.trim().ends_with("}"));
assert!(json.contains("\"title\""));
}
#[test]
fn test_report_csv_header() {
let m = make_sample_metrics();
let csv = m.generate_csv_report();
let lines: Vec<&str> = csv.lines().collect();
assert_eq!(lines[0], "category,metric,value");
}
#[test]
fn test_report_csv_contains_function_details() {
let m = make_sample_metrics();
let csv = m.generate_csv_report();
assert!(csv.contains("function,main,"));
assert!(csv.contains("function,foo,"));
}
#[test]
fn test_trend_analysis_multiple_versions() {
let mut m = X86CodeMetrics::new("v1");
m.size.record_function("f", 100);
m.complexity.compute_cyclomatic("f", 1, 2, 3);
m.quality.record_test_coverage("f", 80.0);
m.quality.compute_quality_score();
m.save_snapshot();
m.version = "v2".to_string();
m.size.record_function("g", 150);
m.complexity.compute_cyclomatic("g", 2, 3, 5);
m.save_snapshot();
m.version = "v3".to_string();
m.size.record_function("h", 200);
m.complexity.compute_cyclomatic("h", 3, 4, 7);
m.save_snapshot();
let trend = m.trend_analysis();
assert_eq!(trend.snapshot_count, 3);
assert_eq!(trend.versions.len(), 3);
assert!(trend.summary().contains("3 snapshots"));
}
#[test]
fn test_trend_direction_improving() {
let trend = X86TrendDirection::from_values(&[100, 120, 140]);
assert_eq!(trend, X86TrendDirection::Improving);
}
#[test]
fn test_trend_direction_degrading() {
let trend = X86TrendDirection::from_values(&[140, 120, 100]);
assert_eq!(trend, X86TrendDirection::Degrading);
}
#[test]
fn test_trend_direction_stable_small_change() {
let trend = X86TrendDirection::from_values(&[1000, 1005, 998]);
assert_eq!(trend, X86TrendDirection::Stable);
}
#[test]
fn test_trend_direction_f64_improving() {
let trend = X86TrendDirection::from_values_f64(&[50.0, 60.0, 70.0]);
assert_eq!(trend, X86TrendDirection::Improving);
}
#[test]
fn test_trend_direction_f64_degrading() {
let trend = X86TrendDirection::from_values_f64(&[70.0, 60.0, 50.0]);
assert_eq!(trend, X86TrendDirection::Degrading);
}
#[test]
fn test_trend_direction_f64_stable() {
let trend = X86TrendDirection::from_values_f64(&[85.0, 85.5, 84.8]);
assert_eq!(trend, X86TrendDirection::Stable);
}
#[test]
fn test_trend_direction_from_values_single() {
assert_eq!(
X86TrendDirection::from_values(&[42]),
X86TrendDirection::Unknown
);
}
#[test]
fn test_trend_direction_from_values_f64_single() {
assert_eq!(
X86TrendDirection::from_values_f64(&[42.0]),
X86TrendDirection::Unknown
);
}
#[test]
fn test_trend_report_size_change_pct_none() {
let report = X86TrendReport {
versions: vec!["v1".to_string()],
size_trend: X86TrendDirection::Unknown,
function_trend: X86TrendDirection::Unknown,
complexity_trend: X86TrendDirection::Unknown,
maintainability_trend: X86TrendDirection::Unknown,
size_change_pct: None,
snapshot_count: 1,
};
let summary = report.summary();
assert!(!summary.contains("Size change"));
}
#[test]
fn test_trend_report_size_change_pct_some() {
let report = X86TrendReport {
versions: vec!["v1".to_string(), "v2".to_string()],
size_trend: X86TrendDirection::Improving,
function_trend: X86TrendDirection::Improving,
complexity_trend: X86TrendDirection::Stable,
maintainability_trend: X86TrendDirection::Stable,
size_change_pct: Some(15.5),
snapshot_count: 2,
};
let summary = report.summary();
assert!(summary.contains("Size change"));
assert!(summary.contains("+15.5%"));
}
#[test]
fn test_lifecycle_create_populate_report() {
let mut m = X86CodeMetrics::new("lifecycle-v1");
for i in 0..10 {
m.size
.record_function(&format!("func_{}", i), (i + 1) * 100);
for _ in 0..5 {
m.size.record_basic_block(16 + i * 4);
}
}
m.size.record_section(".text", 5000);
m.size.record_section(".data", 1024);
m.size.record_section(".bss", 256);
m.performance.static_instruction_count = 5000;
m.performance.dynamic_instruction_count = 200000;
for _ in 0..200 {
m.performance.record_branch(true);
}
for _ in 0..10 {
m.performance.record_branch(false);
}
m.performance.l1i_cache_misses = 100;
m.performance.l1d_cache_misses = 400;
m.performance.llc_cache_misses = 50;
m.performance.record_call("func_0");
m.performance.record_call("func_0");
m.performance.record_call("func_1");
m.performance.compute_all();
for i in 0..10 {
let name = format!("func_{}", i);
m.complexity
.compute_cyclomatic(&name, i + 1, (i + 1) * 2, (i + 1) * 3);
m.complexity
.compute_halstead(&name, 10 + i, 5 + i, 40 + i * 5, 30 + i * 3);
m.complexity.compute_maintainability(&name, 100 + i * 50);
}
m.quality.set_total_lines(10000);
m.quality.record_test_coverage("func_0", 95.0);
m.quality.record_doc_coverage("func_0", 80.0);
m.quality.compute_quality_score();
m.save_snapshot();
let _ = m.generate_text_report();
let _ = m.generate_json_report();
let _ = m.generate_html_report();
let _ = m.generate_csv_report();
let _ = m.trend_analysis();
assert_eq!(m.history.len(), 1);
assert_eq!(m.total_functions(), 10);
}
#[test]
fn test_lifecycle_with_binary_analysis() {
let mut m = X86CodeMetrics::new("binary-v1");
m.size.record_function("main", 256);
let mut bin = X86BinaryAnalysis::new("/bin/app", X86BinaryFileType::ELF, "x86-64");
bin.detect_security_features();
bin.detect_compiler();
bin.sections.push(X86BinarySection {
name: ".text".to_string(),
size: 2048,
virtual_address: 0x401000,
virtual_size: 2048,
raw_offset: 0x1000,
raw_size: 2048,
characteristics: vec!["rx".to_string()],
});
for addr in 0x401000..0x401010 {
bin.record_gadget(X86ROPGadget {
address: addr,
bytes: vec![0xc3],
instructions: vec!["ret".to_string()],
ends_with_ret: true,
is_indirect: false,
useful_for: vec![],
});
}
m.binary = Some(bin);
let text = m.generate_text_report();
assert!(text.contains("Binary Analysis"));
assert!(text.contains("Security score"));
assert!(text.contains("Gadget density"));
}
#[test]
fn test_lifecycle_regression_detection_across_versions() {
let mut m1 = X86CodeMetrics::new("v1");
m1.size.record_function("small", 50);
m1.size.record_function("medium", 200);
m1.size.record_function("large", 1000);
let mut m2 = X86CodeMetrics::new("v2");
m2.size.record_function("small", 60); m2.size.record_function("medium", 500); m2.size.record_function("large", 1000); m2.size.record_function("new_fn", 150);
let mut prev = BTreeMap::new();
prev.insert("small".to_string(), 50);
prev.insert("medium".to_string(), 200);
prev.insert("large".to_string(), 1000);
m2.size.previous_function_sizes = Some(prev);
m2.size.regression_threshold_bytes = 50;
let regs = m2.size.detect_regressions();
assert_eq!(regs.len(), 1);
assert_eq!(regs[0].function, "medium");
assert_eq!(regs[0].delta_bytes, 300);
let new = m2.size.new_functions();
assert_eq!(new.len(), 1);
assert!(new.contains(&"new_fn".to_string()));
let removed = m2.size.removed_functions();
assert!(removed.is_empty());
}
#[test]
fn test_lifecycle_snapshot_count_max() {
let mut m = X86CodeMetrics::new("v0");
m.size.record_function("f", 1);
for i in 0..25 {
m.version = format!("v{}", i);
m.save_snapshot();
}
assert_eq!(m.history.len(), 20);
assert_eq!(m.history[0].version, "v5");
}
#[test]
fn test_metrics_full_roundtrip_clone() {
let m1 = make_sample_metrics();
let m2 = m1.clone();
assert_eq!(m1.version, m2.version);
assert_eq!(m1.total_functions(), m2.total_functions());
assert_eq!(m1.total_size_bytes(), m2.total_size_bytes());
}
#[test]
fn test_code_metrics_overall_maintainability_proxy() {
let mut m = X86CodeMetrics::new("test");
m.quality.set_total_lines(1000);
m.quality.record_test_coverage("f", 80.0);
m.quality.record_doc_coverage("f", 70.0);
m.quality.compute_quality_score();
let mi = m.overall_maintainability();
assert!(mi >= 0.0 && mi <= 100.0);
}
#[test]
fn test_report_html_style_included() {
let m = make_sample_metrics();
let html = m.generate_html_report();
assert!(html.contains("font-family"));
assert!(html.contains("background"));
}
#[test]
fn test_function_sizes_maintain_order() {
let mut s = X86SizeMetrics::new();
s.record_function("zzz", 10);
s.record_function("aaa", 20);
s.record_function("mmm", 30);
let keys: Vec<&String> = s.function_sizes.keys().collect();
assert_eq!(keys[0], "aaa");
assert_eq!(keys[1], "mmm");
assert_eq!(keys[2], "zzz");
}
#[test]
fn test_sizes_feed_into_complexity() {
let mut s = X86SizeMetrics::new();
let mut c = X86ComplexityMetrics::new();
s.record_function("inter", 256);
c.compute_cyclomatic("inter", 4, 8, 12);
c.compute_halstead("inter", 15, 10, 60, 45);
c.compute_maintainability("inter", 256);
let mi = c.maintainability_index.get("inter").copied().unwrap();
assert!(mi >= 0.0 && mi <= 100.0);
}
#[test]
fn test_performance_feeds_complexity_callers() {
let mut p = X86PerformanceMetrics::new();
p.record_call("callee");
p.record_call("callee");
p.record_call("callee");
let mut c = X86ComplexityMetrics::new();
c.record_fan_in("callee", *p.call_frequency.get("callee").unwrap_or(&0));
assert_eq!(c.fan_in.get("callee"), Some(&3));
}
#[test]
fn test_quality_integrates_with_complexity() {
let mut c = X86ComplexityMetrics::new();
c.compute_cyclomatic("risky", 20, 35, 55);
let mut q = X86QualityMetrics::new();
q.set_total_lines(500);
q.record_warning("risky", "High cyclomatic complexity");
q.compute_quality_score();
assert!(q.quality_score < 100.0);
}
#[test]
fn test_stress_many_functions() {
let mut m = X86CodeMetrics::new("stress");
for i in 0..1000 {
m.size
.record_function(&format!("f{}", i), (i % 100) as u64 * 10);
m.size.record_basic_block(32);
m.performance.record_call(&format!("f{}", i));
}
m.performance.identify_hot_spots();
assert_eq!(m.total_functions(), 1000);
assert!(m.performance.hot_spots.len() <= 1000);
}
#[test]
fn test_stress_many_instructions() {
let mut s = X86SizeMetrics::new();
let mnemonics = [
"mov", "add", "sub", "call", "ret", "cmp", "jmp", "je", "jne", "push", "pop", "xor",
"and", "or", "shl", "shr", "lea", "nop",
];
for i in 0..10000 {
s.record_instruction(mnemonics[i % mnemonics.len()]);
}
assert_eq!(s.total_instructions, 10000);
assert!(s.instruction_distribution.len() <= mnemonics.len());
}
#[test]
fn test_stress_many_regressions() {
let mut s = X86SizeMetrics::new();
let mut prev = BTreeMap::new();
for i in 0..500 {
let name = format!("f{}", i);
let old_size = 100u64;
let new_size = 100 + (i as u64 % 10) * 20;
s.record_function(&name, new_size);
prev.insert(name, old_size);
}
s.previous_function_sizes = Some(prev);
s.regression_threshold_bytes = 50;
let regs = s.detect_regressions();
assert!(regs.len() <= 500);
}
#[test]
fn test_invariant_total_size_equals_sum_of_functions() {
let mut s = X86SizeMetrics::new();
s.record_function("a", 100);
s.record_function("b", 200);
s.record_function("c", 300);
let sum: u64 = s.function_sizes.values().sum();
assert_eq!(sum, s.total_size_bytes);
}
#[test]
fn test_invariant_branch_counts_consistent() {
let mut p = X86PerformanceMetrics::new();
p.record_branch(true);
p.record_branch(true);
p.record_branch(false);
assert_eq!(
p.predicted_branches + p.mispredicted_branches,
p.total_branches
);
}
#[test]
fn test_invariant_duplicate_cluster_size_consistent() {
let mut q = X86QualityMetrics::new();
q.record_duplicate(
"sig",
vec!["a".to_string(), "b".to_string(), "c".to_string()],
);
let cluster = q.duplicate_clusters.get("sig").unwrap();
assert!(cluster.len() >= 2);
}
#[test]
fn test_invariant_json_report_no_panic() {
let m = make_sample_metrics();
let json = m.generate_json_report();
assert!(!json.is_empty());
let trimmed = json.trim();
assert!(trimmed.starts_with('{') && trimmed.ends_with('}'));
}
#[test]
fn test_invariant_html_report_no_panic() {
let m = X86CodeMetrics::new("empty");
let html = m.generate_html_report();
assert!(html.contains("<!DOCTYPE html>"));
assert!(html.contains("</html>"));
}
#[test]
fn test_invariant_csv_always_has_header() {
for i in 0..10 {
let mut m = X86CodeMetrics::new(&format!("v{}", i));
m.size.record_function("f", i * 10);
let csv = m.generate_csv_report();
assert!(csv.starts_with("category,metric,value"));
}
}
#[test]
fn test_invariant_trend_snapshots_preserved() {
let mut m = X86CodeMetrics::new("v1");
for i in 0..5 {
m.size.record_function(&format!("f{}", i), 10);
m.save_snapshot();
}
assert_eq!(m.history.len(), 5);
for (i, snap) in m.history.iter().enumerate() {
assert_eq!(snap.total_functions, (i + 1) as u64);
}
}
#[test]
fn test_invariant_merge_idempotent_size() {
let m1 = make_sample_metrics();
let mut m2 = m1.clone();
m2.merge(&m1);
assert_eq!(m2.total_size_bytes(), m1.total_size_bytes() * 2);
}
#[test]
fn test_invariant_halstead_effort_monotonic_with_operands() {
let mut c1 = X86ComplexityMetrics::new();
c1.compute_halstead("small", 5, 3, 20, 10);
let mut c2 = X86ComplexityMetrics::new();
c2.compute_halstead("large", 10, 8, 80, 50);
let e1 = c1.halstead_metrics.get("small").unwrap().effort;
let e2 = c2.halstead_metrics.get("large").unwrap().effort;
assert!(e2 > e1);
}
#[test]
fn test_invariant_cognitive_never_negative() {
let mut c = X86ComplexityMetrics::new();
c.compute_cognitive("fn", 0, 0, 0, 0);
let score = c.cognitive_complexity.get("fn").copied().unwrap();
assert!(score >= 0.0);
}
#[test]
fn test_invariant_maintainability_bounded() {
let mut c = X86ComplexityMetrics::new();
c.compute_cyclomatic("simple", 0, 1, 1);
c.compute_halstead("simple", 2, 2, 2, 2);
c.compute_maintainability("simple", 5);
let mi = c.maintainability_index.get("simple").copied().unwrap();
assert!(mi >= 0.0 && mi <= 100.0);
}
#[test]
fn test_all_types_default_constructible() {
let _ = X86CodeMetrics::default();
let _ = X86SizeMetrics::default();
let _ = X86PerformanceMetrics::default();
let _ = X86ComplexityMetrics::default();
let _ = X86QualityMetrics::default();
let _ = X86BinaryAnalysis::default();
let _ = X86MetricsReport::default();
let _ = X86InstructionMix::new();
let _ = X86BinarySecurityFeatures::default();
let _ = X86TrendReport::empty();
}
#[test]
fn test_all_types_cloneable() {
let m = make_sample_metrics();
let _ = m.size.clone();
let _ = m.performance.clone();
let _ = m.complexity.clone();
let _ = m.quality.clone();
let _ = m.report.clone();
let _ = m.history.clone();
}
#[test]
fn test_all_enums_display() {
let _ = format!("{}", X86InstrCategory::DataMovement);
let _ = format!("{}", X86BinaryFileType::ELF);
let _ = format!("{}", X86HotSpotCategory::Hot);
let _ = format!("{}", X86TrendDirection::Improving);
}
#[test]
fn test_regression_percent_calculation() {
let mut s = X86SizeMetrics::new();
s.record_function("bloated", 200);
let mut prev = BTreeMap::new();
prev.insert("bloated".to_string(), 100);
s.previous_function_sizes = Some(prev);
s.regression_threshold_bytes = 50;
let regs = s.detect_regressions();
assert_eq!(regs[0].delta_percent, 100.0);
}
#[test]
fn test_regression_sorted_by_delta() {
let mut s = X86SizeMetrics::new();
s.record_function("big", 500);
s.record_function("small", 200);
let mut prev = BTreeMap::new();
prev.insert("big".to_string(), 100);
prev.insert("small".to_string(), 50);
s.previous_function_sizes = Some(prev);
s.regression_threshold_bytes = 10;
let regs = s.detect_regressions();
assert_eq!(regs[0].function, "big");
assert_eq!(regs[1].function, "small");
}
#[test]
fn test_perf_memory_patterns_multiple() {
let mut p = X86PerformanceMetrics::new();
p.record_memory_pattern(
"stream",
X86MemoryPattern::Streaming {
stride: 1,
element_size: 4,
},
);
p.record_memory_pattern("random", X86MemoryPattern::Random { access_count: 500 });
p.record_memory_pattern(
"stride",
X86MemoryPattern::Strided {
stride: 64,
element_size: 8,
},
);
p.record_memory_pattern(
"mixed",
X86MemoryPattern::Mixed {
streaming_pct: 0.5,
random_pct: 0.3,
strided_pct: 0.2,
},
);
assert_eq!(p.memory_patterns.len(), 4);
let stream_cf = p
.memory_patterns
.get("stream")
.unwrap()
.cache_friendliness();
let random_cf = p
.memory_patterns
.get("random")
.unwrap()
.cache_friendliness();
assert!(stream_cf > random_cf);
}
#[test]
fn test_perf_frontend_stalls_formula() {
let mut p = X86PerformanceMetrics::new();
p.l1i_cache_misses = 25;
p.mispredicted_branches = 10;
p.estimate_frontend_stalls();
assert_eq!(p.frontend_stall_cycles, 360);
}
#[test]
fn test_perf_compute_all_sets_ipc() {
let mut p = X86PerformanceMetrics::new();
p.dynamic_instruction_count = 10000;
p.record_branch(true);
p.record_branch(false);
p.l1i_cache_misses = 10;
p.compute_all();
assert!(p.instructions_per_cycle > 0.0);
assert!(p.total_cycles_estimate > 0);
}
#[test]
fn test_complexity_nesting_depth_many() {
let mut c = X86ComplexityMetrics::new();
for i in 0..10 {
c.record_nesting_depth(&format!("fn{}", i), i * 2);
}
assert_eq!(c.nesting_depth.len(), 10);
assert_eq!(c.nesting_depth.get("fn5"), Some(&10));
}
#[test]
fn test_halstead_bugs_estimate() {
let mut c = X86ComplexityMetrics::new();
c.compute_halstead("buggy", 20, 15, 100, 80);
let bugs = c.halstead_metrics.get("buggy").unwrap().estimated_bugs;
assert!(bugs > 0.0);
}
#[test]
fn test_halstead_time_to_implement() {
let mut c = X86ComplexityMetrics::new();
c.compute_halstead("slow", 30, 20, 200, 150);
let time = c
.halstead_metrics
.get("slow")
.unwrap()
.time_to_implement_seconds;
assert!(time > 0.0);
}
#[test]
fn test_sorted_by_complexity_ascending() {
let mut c = X86ComplexityMetrics::new();
c.compute_cyclomatic("c", 3, 5, 8);
c.compute_cyclomatic("a", 1, 2, 3);
c.compute_cyclomatic("b", 2, 3, 5);
let sorted = c.sorted_by_complexity();
assert_eq!(sorted[0].0, "c");
assert_eq!(sorted[1].0, "b");
assert_eq!(sorted[2].0, "a");
}
#[test]
fn test_quality_dead_code_tracking() {
let mut q = X86QualityMetrics::new();
q.mark_dead_function("orphan1");
q.mark_dead_function("orphan2");
q.mark_dead_function("orphan3");
assert_eq!(q.dead_function_count(), 3);
assert!(q.dead_functions.contains("orphan2"));
}
#[test]
fn test_binary_imports_exports() {
let mut b = X86BinaryAnalysis::new("lib", X86BinaryFileType::ELF, "x86-64");
b.imports.push("printf".to_string());
b.imports.push("malloc".to_string());
b.imports.push("free".to_string());
b.exports.push("my_library_init".to_string());
b.exports.push("my_library_cleanup".to_string());
assert_eq!(b.imports.len(), 3);
assert_eq!(b.exports.len(), 2);
}
#[test]
fn test_gadget_density_large_code() {
let mut b = X86BinaryAnalysis::new("big", X86BinaryFileType::PE, "x86-64");
b.sections.push(X86BinarySection {
name: ".text".to_string(),
size: 1048576,
virtual_address: 0x401000,
virtual_size: 1048576,
raw_offset: 0x400,
raw_size: 1048576,
characteristics: vec!["rx".to_string()],
});
for _ in 0..512 {
b.record_gadget(X86ROPGadget {
address: 0x401000,
bytes: vec![0xc3],
instructions: vec!["ret".to_string()],
ends_with_ret: true,
is_indirect: false,
useful_for: vec![],
});
}
assert!((b.gadget_density() - 0.5).abs() < 0.01);
}
#[test]
fn test_security_score_calculation_all_default() {
let mut b = X86BinaryAnalysis::new("t", X86BinaryFileType::ELF, "x86-64");
b.security_features.has_nx = true;
b.security_features.has_aslr = true;
b.security_features.has_relro = true;
b.security_features.has_stack_canary = true;
b.security_features.has_pie = true;
b.security_features.has_fortify = true;
assert!((b.security_score() - 80.0).abs() < 0.01);
}
#[test]
fn test_trend_multiple_metrics() {
let snapshots = vec![
X86MetricsSnapshot {
version: "v1".into(),
timestamp: "t1".into(),
total_size: 1000,
total_functions: 10,
avg_cyclomatic: 5.0,
avg_maintainability: 80.0,
total_warnings: 5,
},
X86MetricsSnapshot {
version: "v2".into(),
timestamp: "t2".into(),
total_size: 1200,
total_functions: 12,
avg_cyclomatic: 6.0,
avg_maintainability: 75.0,
total_warnings: 8,
},
];
let report = X86MetricsReport::new();
let trend = report.trend_analysis(&snapshots);
assert_eq!(trend.size_trend, X86TrendDirection::Improving);
assert_eq!(trend.complexity_trend, X86TrendDirection::Degrading);
assert_eq!(trend.maintainability_trend, X86TrendDirection::Degrading);
}
#[test]
fn test_full_pipeline_all_types() {
let mut m = X86CodeMetrics::new("integration");
for i in 0..50 {
let name = format!("fn{}", i);
let size = (i as u64 + 1) * 32;
m.size.record_function(&name, size);
m.size.record_basic_block(size / 4);
m.size.record_basic_block(size / 2);
m.complexity
.compute_cyclomatic(&name, i % 10, (i % 10) * 2, (i % 10) * 3);
m.complexity
.compute_halstead(&name, 5 + i % 5, 3 + i % 3, 20 + i % 10, 10 + i % 5);
m.complexity.compute_maintainability(&name, size);
m.performance.record_call(&name);
if i % 3 == 0 {
m.performance.record_call(&name);
}
}
m.performance.compute_all();
m.quality.set_total_lines(8000);
m.quality.compute_quality_score();
m.save_snapshot();
assert_eq!(m.total_functions(), 50);
assert!(m.total_size_bytes() > 0);
let _ = m.generate_text_report();
let _ = m.generate_json_report();
let _ = m.generate_html_report();
let _ = m.generate_csv_report();
}
#[test]
fn test_quantiles_power_law() {
let mut sizes: Vec<u64> = Vec::new();
for _ in 0..1000 {
sizes.push(10);
}
sizes.push(10000);
let q = X86SizeQuantiles::from_sizes(&sizes);
assert_eq!(q.min, 10);
assert_eq!(q.max, 10000);
assert_eq!(q.p50, 10);
assert_eq!(q.p99, 10000);
}
#[test]
fn test_correlation_larger_higher_complexity() {
let mut m = X86CodeMetrics::new("corr");
m.size.record_function("small", 10);
m.size.record_function("large", 1000);
m.complexity.compute_cyclomatic("small", 1, 2, 3);
m.complexity.compute_cyclomatic("large", 50, 60, 110);
let cc_small = m
.complexity
.cyclomatic_complexity
.get("small")
.copied()
.unwrap();
let cc_large = m
.complexity
.cyclomatic_complexity
.get("large")
.copied()
.unwrap();
assert!(cc_large > cc_small);
}
#[test]
fn test_text_report_has_delimiters() {
let m = make_sample_metrics();
let text = m.generate_text_report();
let equals_lines: Vec<&str> = text
.lines()
.filter(|l| l.starts_with('=') && l.len() > 40)
.collect();
assert!(!equals_lines.is_empty());
}
#[test]
fn test_type_send_sync() {
fn assert_send_sync<T: Send + Sync>() {}
assert_send_sync::<X86CodeMetrics>();
assert_send_sync::<X86SizeMetrics>();
assert_send_sync::<X86PerformanceMetrics>();
assert_send_sync::<X86ComplexityMetrics>();
assert_send_sync::<X86QualityMetrics>();
assert_send_sync::<X86BinaryAnalysis>();
assert_send_sync::<X86MetricsReport>();
}
#[test]
fn test_memory_pattern_all_variants() {
let patterns = vec![
X86MemoryPattern::Streaming {
stride: 1,
element_size: 8,
},
X86MemoryPattern::Random { access_count: 100 },
X86MemoryPattern::Strided {
stride: 16,
element_size: 4,
},
X86MemoryPattern::Mixed {
streaming_pct: 0.4,
random_pct: 0.3,
strided_pct: 0.3,
},
];
for p in &patterns {
assert!(!p.describe().is_empty());
let cf = p.cache_friendliness();
assert!(cf >= 0.0 && cf <= 1.0);
}
}
}
#[derive(Debug, Clone)]
pub struct X86SizeDistribution {
pub label: String,
pub bin_count: usize,
pub sizes: Vec<u64>,
pub quantiles: Option<X86SizeQuantiles>,
}
impl X86SizeDistribution {
pub fn new(label: &str) -> Self {
Self {
label: label.to_string(),
bin_count: 0,
sizes: Vec::new(),
quantiles: None,
}
}
pub fn add(&mut self, size: u64) {
self.sizes.push(size);
self.bin_count += 1;
}
pub fn compute_quantiles(&mut self) {
self.quantiles = Some(X86SizeQuantiles::from_sizes(&self.sizes));
}
pub fn total(&self) -> u64 {
self.sizes.iter().sum()
}
pub fn mean(&self) -> f64 {
if self.sizes.is_empty() {
return 0.0;
}
self.total() as f64 / self.sizes.len() as f64
}
pub fn range(&self) -> (u64, u64) {
if self.sizes.is_empty() {
return (0, 0);
}
let min = self.sizes.iter().min().copied().unwrap_or(0);
let max = self.sizes.iter().max().copied().unwrap_or(0);
(min, max)
}
}
impl Default for X86SizeDistribution {
fn default() -> Self {
Self::new("unnamed")
}
}
#[derive(Debug, Clone)]
pub struct X86FunctionMetrics {
pub name: String,
pub size_bytes: u64,
pub cyclomatic_complexity: u64,
pub cognitive_complexity: f64,
pub nesting_depth: u64,
pub fan_in: u64,
pub fan_out: u64,
pub maintainability_index: f64,
pub instruction_count: u64,
pub basic_block_count: u64,
pub call_frequency: u64,
pub test_coverage_pct: f64,
pub warning_count: u64,
pub is_dead_code: bool,
pub is_hot: bool,
}
impl X86FunctionMetrics {
pub fn new(name: &str) -> Self {
Self {
name: name.to_string(),
size_bytes: 0,
cyclomatic_complexity: 1,
cognitive_complexity: 0.0,
nesting_depth: 0,
fan_in: 0,
fan_out: 0,
maintainability_index: 100.0,
instruction_count: 0,
basic_block_count: 0,
call_frequency: 0,
test_coverage_pct: 0.0,
warning_count: 0,
is_dead_code: false,
is_hot: false,
}
}
pub fn health_score(&self) -> f64 {
let complexity_score = (100.0 - self.cyclomatic_complexity as f64 * 2.0).max(0.0);
let maint_score = self.maintainability_index;
let coverage_score = self.test_coverage_pct;
let warning_penalty = (self.warning_count as f64 * 10.0).min(50.0);
let score =
complexity_score * 0.25 + maint_score * 0.35 + coverage_score * 0.25 - warning_penalty;
score.max(0.0).min(100.0)
}
pub fn summary_line(&self) -> String {
format!(
"{:40} size={:>6}B cc={:>3} mi={:>5.1} cov={:>5.1}% calls={:>6} {}{}",
self.name,
self.size_bytes,
self.cyclomatic_complexity,
self.maintainability_index,
self.test_coverage_pct,
self.call_frequency,
if self.is_dead_code { " [DEAD]" } else { "" },
if self.is_hot { " [HOT]" } else { "" },
)
}
}
impl Default for X86FunctionMetrics {
fn default() -> Self {
Self::new("")
}
}
impl fmt::Display for X86FunctionMetrics {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.summary_line())
}
}
#[derive(Debug, Clone)]
pub struct X86MetricsAggregator {
pub functions: Vec<X86FunctionMetrics>,
pub total_functions: usize,
pub total_size_bytes: u64,
pub total_instructions: u64,
pub total_basic_blocks: u64,
pub average_cyclomatic: f64,
pub average_maintainability: f64,
pub average_test_coverage: f64,
pub dead_code_count: usize,
pub hot_function_count: usize,
}
impl X86MetricsAggregator {
pub fn new() -> Self {
Self {
functions: Vec::new(),
total_functions: 0,
total_size_bytes: 0,
total_instructions: 0,
total_basic_blocks: 0,
average_cyclomatic: 0.0,
average_maintainability: 100.0,
average_test_coverage: 0.0,
dead_code_count: 0,
hot_function_count: 0,
}
}
pub fn aggregate_from(
&mut self,
size: &X86SizeMetrics,
perf: &X86PerformanceMetrics,
comp: &X86ComplexityMetrics,
qual: &X86QualityMetrics,
) {
let mut all_names: HashSet<String> = HashSet::new();
for name in size.function_sizes.keys() {
all_names.insert(name.clone());
}
for name in comp.cyclomatic_complexity.keys() {
all_names.insert(name.clone());
}
for name in qual.per_function_coverage.keys() {
all_names.insert(name.clone());
}
self.total_functions = all_names.len();
for name in &all_names {
let mut fm = X86FunctionMetrics::new(name);
fm.size_bytes = size.function_sizes.get(name).copied().unwrap_or(0);
fm.cyclomatic_complexity = comp.cyclomatic_complexity.get(name).copied().unwrap_or(1);
fm.cognitive_complexity = comp.cognitive_complexity.get(name).copied().unwrap_or(0.0);
fm.nesting_depth = comp.nesting_depth.get(name).copied().unwrap_or(0);
fm.fan_in = comp.fan_in.get(name).copied().unwrap_or(0);
fm.fan_out = comp.fan_out.get(name).copied().unwrap_or(0);
fm.maintainability_index = comp
.maintainability_index
.get(name)
.copied()
.unwrap_or(100.0);
fm.call_frequency = perf.call_frequency.get(name).copied().unwrap_or(0);
fm.test_coverage_pct = qual.per_function_coverage.get(name).copied().unwrap_or(0.0);
fm.warning_count = qual.per_function_warnings.get(name).copied().unwrap_or(0);
fm.is_dead_code = qual.dead_functions.contains(name);
fm.instruction_count = size
.instruction_distribution
.get(name)
.copied()
.unwrap_or(0);
self.total_size_bytes += fm.size_bytes;
self.total_instructions += fm.instruction_count;
self.functions.push(fm);
}
self.total_basic_blocks = size.total_basic_blocks;
self.average_cyclomatic = comp.average_cyclomatic();
self.average_maintainability = comp.average_maintainability();
self.average_test_coverage = qual.test_coverage_pct;
self.dead_code_count = qual.dead_function_count();
self.hot_function_count = perf
.hot_spots
.iter()
.filter(|h| h.category == X86HotSpotCategory::Hot)
.count();
}
pub fn largest_functions(&self, n: usize) -> Vec<&X86FunctionMetrics> {
let mut sorted: Vec<&X86FunctionMetrics> = self.functions.iter().collect();
sorted.sort_by(|a, b| b.size_bytes.cmp(&a.size_bytes));
sorted.truncate(n);
sorted
}
pub fn most_complex_functions(&self, n: usize) -> Vec<&X86FunctionMetrics> {
let mut sorted: Vec<&X86FunctionMetrics> = self.functions.iter().collect();
sorted.sort_by(|a, b| b.cyclomatic_complexity.cmp(&a.cyclomatic_complexity));
sorted.truncate(n);
sorted
}
pub fn least_maintainable_functions(&self, n: usize) -> Vec<&X86FunctionMetrics> {
let mut sorted: Vec<&X86FunctionMetrics> = self.functions.iter().collect();
sorted.sort_by(|a, b| {
a.maintainability_index
.partial_cmp(&b.maintainability_index)
.unwrap_or(std::cmp::Ordering::Equal)
});
sorted.truncate(n);
sorted
}
pub fn dead_functions(&self) -> Vec<&X86FunctionMetrics> {
self.functions.iter().filter(|fm| fm.is_dead_code).collect()
}
pub fn hot_functions(&self) -> Vec<&X86FunctionMetrics> {
self.functions.iter().filter(|fm| fm.is_hot).collect()
}
pub fn generate_function_table(&self) -> String {
let mut table = String::new();
table.push_str(&format!("{:-^90}\n", " Per-Function Metrics "));
table.push_str(&format!(
"{:40} {:>6} {:>4} {:>5} {:>6} {:>6} {:>6}\n",
"Function", "Size", "CC", "MI", "Cov%", "Calls", "Status"
));
table.push_str(&format!("{:-<90}\n", ""));
for fm in &self.functions {
let status = match (fm.is_dead_code, fm.is_hot) {
(true, _) => "DEAD",
(false, true) => "HOT",
_ => "",
};
table.push_str(&format!(
"{:40} {:>6}B {:>4} {:>5.1} {:>5.1}% {:>6} {:>6}\n",
fm.name,
fm.size_bytes,
fm.cyclomatic_complexity,
fm.maintainability_index,
fm.test_coverage_pct,
fm.call_frequency,
status
));
}
table.push_str(&format!("{:-<90}\n", ""));
table.push_str(&format!(
"Total: {} functions, {} bytes\n",
self.total_functions, self.total_size_bytes
));
table
}
}
impl Default for X86MetricsAggregator {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct X86CodeMetricsBuilder {
version: String,
size: Option<X86SizeMetrics>,
performance: Option<X86PerformanceMetrics>,
complexity: Option<X86ComplexityMetrics>,
quality: Option<X86QualityMetrics>,
binary: Option<X86BinaryAnalysis>,
}
impl X86CodeMetricsBuilder {
pub fn new(version: &str) -> Self {
Self {
version: version.to_string(),
size: None,
performance: None,
complexity: None,
quality: None,
binary: None,
}
}
pub fn with_size(mut self, size: X86SizeMetrics) -> Self {
self.size = Some(size);
self
}
pub fn with_performance(mut self, performance: X86PerformanceMetrics) -> Self {
self.performance = Some(performance);
self
}
pub fn with_complexity(mut self, complexity: X86ComplexityMetrics) -> Self {
self.complexity = Some(complexity);
self
}
pub fn with_quality(mut self, quality: X86QualityMetrics) -> Self {
self.quality = Some(quality);
self
}
pub fn with_binary(mut self, binary: X86BinaryAnalysis) -> Self {
self.binary = Some(binary);
self
}
pub fn build(self) -> X86CodeMetrics {
X86CodeMetrics {
timestamp: chrono_like_now(),
version: self.version,
size: self.size.unwrap_or_default(),
performance: self.performance.unwrap_or_default(),
complexity: self.complexity.unwrap_or_default(),
quality: self.quality.unwrap_or_default(),
binary: self.binary,
report: X86MetricsReport::new(),
history: Vec::new(),
}
}
}
#[derive(Debug, Clone)]
pub struct X86CompactMetrics {
pub version: String,
pub timestamp: String,
pub total_functions: u64,
pub total_size_kb: f64,
pub total_instructions: u64,
pub avg_cyclomatic: f64,
pub avg_maintainability: f64,
pub test_coverage_pct: f64,
pub warning_density: f64,
pub branch_pred_acc: f64,
pub estimated_ipc: f64,
pub security_score: Option<f64>,
pub quality_score: f64,
pub dead_function_count: u64,
pub gadget_count: u64,
}
impl X86CompactMetrics {
pub fn from_full(metrics: &X86CodeMetrics) -> Self {
Self {
version: metrics.version.clone(),
timestamp: metrics.timestamp.clone(),
total_functions: metrics.total_functions() as u64,
total_size_kb: metrics.total_size_bytes() as f64 / 1024.0,
total_instructions: metrics.size.total_instructions,
avg_cyclomatic: metrics.complexity.average_cyclomatic(),
avg_maintainability: metrics.complexity.average_maintainability(),
test_coverage_pct: metrics.quality.test_coverage_pct,
warning_density: metrics.quality.warning_density(),
branch_pred_acc: metrics.performance.branch_prediction_accuracy(),
estimated_ipc: metrics.performance.instructions_per_cycle,
security_score: metrics.binary.as_ref().map(|b| b.security_score()),
quality_score: metrics.quality.quality_score,
dead_function_count: metrics.quality.dead_function_count() as u64,
gadget_count: metrics
.binary
.as_ref()
.map(|b| b.total_gadgets)
.unwrap_or(0),
}
}
pub fn to_compact_string(&self) -> String {
format!(
"version={} funcs={} size_kb={:.1} instr={} cc={:.1} mi={:.1} cov={:.1}% ",
&self.version,
self.total_functions,
self.total_size_kb,
self.total_instructions,
self.avg_cyclomatic,
self.avg_maintainability,
self.test_coverage_pct
) + &format!(
"warn_density={:.2} bp_acc={:.3} ipc={:.2} quality={:.1}",
self.warning_density, self.branch_pred_acc, self.estimated_ipc, self.quality_score
)
}
pub fn diff(&self, other: &X86CompactMetrics) -> String {
let mut diff = String::new();
diff.push_str(&format!("{} -> {}\n", self.version, other.version));
let delta = |a: f64, b: f64| -> String {
let d = b - a;
format!("{:+.2}", d)
};
diff.push_str(&format!(
" Functions: {} -> {} ({})\n",
self.total_functions,
other.total_functions,
delta(self.total_functions as f64, other.total_functions as f64)
));
diff.push_str(&format!(
" Size KB: {:.1} -> {:.1} ({})\n",
self.total_size_kb,
other.total_size_kb,
delta(self.total_size_kb, other.total_size_kb)
));
diff.push_str(&format!(
" Avg CC: {:.1} -> {:.1} ({})\n",
self.avg_cyclomatic,
other.avg_cyclomatic,
delta(self.avg_cyclomatic, other.avg_cyclomatic)
));
diff.push_str(&format!(
" Avg MI: {:.1} -> {:.1} ({})\n",
self.avg_maintainability,
other.avg_maintainability,
delta(self.avg_maintainability, other.avg_maintainability)
));
diff.push_str(&format!(
" Coverage: {:.1}% -> {:.1}% ({})\n",
self.test_coverage_pct,
other.test_coverage_pct,
delta(self.test_coverage_pct, other.test_coverage_pct)
));
diff.push_str(&format!(
" Quality: {:.1} -> {:.1} ({})\n",
self.quality_score,
other.quality_score,
delta(self.quality_score, other.quality_score)
));
diff
}
}
impl fmt::Display for X86CompactMetrics {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.to_compact_string())
}
}
#[cfg(test)]
mod extended_tests {
use super::*;
#[test]
fn test_size_distribution_create() {
let d = X86SizeDistribution::new("test");
assert_eq!(d.label, "test");
assert_eq!(d.bin_count, 0);
}
#[test]
fn test_size_distribution_add() {
let mut d = X86SizeDistribution::new("test");
d.add(10);
d.add(20);
d.add(30);
assert_eq!(d.bin_count, 3);
assert_eq!(d.total(), 60);
assert_eq!(d.mean(), 20.0);
}
#[test]
fn test_size_distribution_range() {
let mut d = X86SizeDistribution::new("test");
d.add(5);
d.add(100);
d.add(50);
assert_eq!(d.range(), (5, 100));
}
#[test]
fn test_size_distribution_range_empty() {
let d = X86SizeDistribution::new("test");
assert_eq!(d.range(), (0, 0));
}
#[test]
fn test_size_distribution_compute_quantiles() {
let mut d = X86SizeDistribution::new("test");
for i in 0..10 {
d.add(i * 10);
}
d.compute_quantiles();
let q = d.quantiles.unwrap();
assert_eq!(q.min, 0);
assert_eq!(q.max, 90);
}
#[test]
fn test_size_distribution_default() {
let d = X86SizeDistribution::default();
assert_eq!(d.label, "unnamed");
}
#[test]
fn test_function_metrics_health_score_perfect() {
let fm = X86FunctionMetrics {
name: "perfect".into(),
size_bytes: 100,
cyclomatic_complexity: 1,
cognitive_complexity: 0.0,
nesting_depth: 0,
fan_in: 0,
fan_out: 0,
maintainability_index: 100.0,
instruction_count: 10,
basic_block_count: 3,
call_frequency: 5,
test_coverage_pct: 100.0,
warning_count: 0,
is_dead_code: false,
is_hot: false,
};
let score = fm.health_score();
assert!(score > 70.0);
}
#[test]
fn test_function_metrics_health_score_terrible() {
let fm = X86FunctionMetrics {
name: "terrible".into(),
size_bytes: 5000,
cyclomatic_complexity: 50,
cognitive_complexity: 30.0,
nesting_depth: 8,
fan_in: 0,
fan_out: 0,
maintainability_index: 10.0,
instruction_count: 1000,
basic_block_count: 50,
call_frequency: 0,
test_coverage_pct: 0.0,
warning_count: 5,
is_dead_code: true,
is_hot: false,
};
let score = fm.health_score();
assert!(score < 30.0);
}
#[test]
fn test_function_metrics_summary_line() {
let fm = X86FunctionMetrics {
name: "summary_test".into(),
size_bytes: 256,
cyclomatic_complexity: 3,
maintainability_index: 85.0,
test_coverage_pct: 75.0,
call_frequency: 42,
is_dead_code: false,
is_hot: true,
..Default::default()
};
let line = fm.summary_line();
assert!(line.contains("summary_test"));
assert!(line.contains("256B"));
assert!(line.contains("HOT"));
}
#[test]
fn test_function_metrics_summary_dead() {
let fm = X86FunctionMetrics {
name: "dead_fn".into(),
is_dead_code: true,
..Default::default()
};
let line = fm.summary_line();
assert!(line.contains("DEAD"));
}
#[test]
fn test_function_metrics_display() {
let fm = X86FunctionMetrics::new("display_test");
let d = format!("{}", fm);
assert!(d.contains("display_test"));
}
#[test]
fn test_metrics_aggregator_create() {
let a = X86MetricsAggregator::new();
assert_eq!(a.total_functions, 0);
assert_eq!(a.average_maintainability, 100.0);
}
#[test]
fn test_metrics_aggregator_aggregate() {
let mut size = X86SizeMetrics::new();
size.record_function("f1", 100);
size.record_function("f2", 200);
let perf = X86PerformanceMetrics::new();
let mut comp = X86ComplexityMetrics::new();
comp.compute_cyclomatic("f1", 1, 2, 3);
comp.compute_cyclomatic("f2", 2, 3, 5);
let qual = X86QualityMetrics::new();
let mut agg = X86MetricsAggregator::new();
agg.aggregate_from(&size, &perf, &comp, &qual);
assert_eq!(agg.total_functions, 2);
assert_eq!(agg.total_size_bytes, 300);
}
#[test]
fn test_aggregator_largest_functions() {
let mut agg = X86MetricsAggregator::new();
agg.functions.push(X86FunctionMetrics {
name: "big".into(),
size_bytes: 1000,
..Default::default()
});
agg.functions.push(X86FunctionMetrics {
name: "small".into(),
size_bytes: 10,
..Default::default()
});
let largest = agg.largest_functions(1);
assert_eq!(largest[0].name, "big");
}
#[test]
fn test_aggregator_most_complex() {
let mut agg = X86MetricsAggregator::new();
agg.functions.push(X86FunctionMetrics {
name: "complex".into(),
cyclomatic_complexity: 50,
..Default::default()
});
agg.functions.push(X86FunctionMetrics {
name: "simple".into(),
cyclomatic_complexity: 1,
..Default::default()
});
let most_complex = agg.most_complex_functions(1);
assert_eq!(most_complex[0].name, "complex");
}
#[test]
fn test_aggregator_least_maintainable() {
let mut agg = X86MetricsAggregator::new();
agg.functions.push(X86FunctionMetrics {
name: "bad".into(),
maintainability_index: 10.0,
..Default::default()
});
agg.functions.push(X86FunctionMetrics {
name: "good".into(),
maintainability_index: 90.0,
..Default::default()
});
let worst = agg.least_maintainable_functions(1);
assert_eq!(worst[0].name, "bad");
}
#[test]
fn test_aggregator_dead_functions_filter() {
let mut agg = X86MetricsAggregator::new();
agg.functions.push(X86FunctionMetrics {
name: "alive".into(),
is_dead_code: false,
..Default::default()
});
agg.functions.push(X86FunctionMetrics {
name: "ghost".into(),
is_dead_code: true,
..Default::default()
});
assert_eq!(agg.dead_functions().len(), 1);
assert_eq!(agg.dead_functions()[0].name, "ghost");
}
#[test]
fn test_aggregator_hot_functions_filter() {
let mut agg = X86MetricsAggregator::new();
agg.functions.push(X86FunctionMetrics {
name: "warm".into(),
is_hot: false,
..Default::default()
});
agg.functions.push(X86FunctionMetrics {
name: "scorching".into(),
is_hot: true,
..Default::default()
});
assert_eq!(agg.hot_functions().len(), 1);
assert_eq!(agg.hot_functions()[0].name, "scorching");
}
#[test]
fn test_aggregator_generate_table() {
let mut agg = X86MetricsAggregator::new();
agg.functions.push(X86FunctionMetrics {
name: "fn".into(),
size_bytes: 100,
cyclomatic_complexity: 2,
maintainability_index: 90.0,
test_coverage_pct: 80.0,
call_frequency: 5,
..Default::default()
});
let table = agg.generate_function_table();
assert!(table.contains("Per-Function Metrics"));
assert!(table.contains("fn"));
assert!(table.contains("100B"));
}
#[test]
fn test_code_metrics_builder_empty() {
let m = X86CodeMetricsBuilder::new("builder-v1").build();
assert_eq!(m.version, "builder-v1");
assert_eq!(m.total_functions(), 0);
}
#[test]
fn test_code_metrics_builder_full() {
let mut size = X86SizeMetrics::new();
size.record_function("fn", 100);
let m = X86CodeMetricsBuilder::new("builder-v2")
.with_size(size)
.build();
assert_eq!(m.total_functions(), 1);
assert_eq!(m.total_size_bytes(), 100);
}
#[test]
fn test_code_metrics_builder_with_all() {
let m = X86CodeMetricsBuilder::new("full-builder")
.with_size(X86SizeMetrics::new())
.with_performance(X86PerformanceMetrics::new())
.with_complexity(X86ComplexityMetrics::new())
.with_quality(X86QualityMetrics::new())
.with_binary(X86BinaryAnalysis::new(
"/b",
X86BinaryFileType::ELF,
"x86-64",
))
.build();
assert!(m.binary.is_some());
}
#[test]
fn test_compact_metrics_from_full() {
let m = make_sample_metrics();
let compact = X86CompactMetrics::from_full(&m);
assert_eq!(compact.version, m.version);
assert!(compact.total_size_kb > 0.0);
assert!(compact.avg_cyclomatic > 0.0);
}
#[test]
fn test_compact_metrics_to_string() {
let compact = X86CompactMetrics {
version: "v1".into(),
timestamp: "t".into(),
total_functions: 42,
total_size_kb: 128.5,
total_instructions: 5000,
avg_cyclomatic: 3.5,
avg_maintainability: 78.0,
test_coverage_pct: 82.5,
warning_density: 1.2,
branch_pred_acc: 0.95,
estimated_ipc: 1.8,
security_score: Some(75.0),
quality_score: 82.0,
dead_function_count: 3,
gadget_count: 0,
};
let s = compact.to_compact_string();
assert!(s.contains("v1"));
assert!(s.contains("funcs=42"));
}
#[test]
fn test_compact_metrics_display() {
let compact = X86CompactMetrics {
version: "v1".into(),
timestamp: "t".into(),
total_functions: 1,
total_size_kb: 1.0,
total_instructions: 1,
avg_cyclomatic: 1.0,
avg_maintainability: 100.0,
test_coverage_pct: 100.0,
warning_density: 0.0,
branch_pred_acc: 1.0,
estimated_ipc: 2.0,
security_score: None,
quality_score: 100.0,
dead_function_count: 0,
gadget_count: 0,
};
let d = format!("{}", compact);
assert!(!d.is_empty());
}
#[test]
fn test_compact_metrics_diff() {
let c1 = X86CompactMetrics {
version: "v1".into(),
timestamp: "t".into(),
total_functions: 10,
total_size_kb: 100.0,
total_instructions: 1000,
avg_cyclomatic: 5.0,
avg_maintainability: 80.0,
test_coverage_pct: 70.0,
warning_density: 2.0,
branch_pred_acc: 0.9,
estimated_ipc: 1.5,
security_score: None,
quality_score: 75.0,
dead_function_count: 2,
gadget_count: 0,
};
let c2 = X86CompactMetrics {
version: "v2".into(),
timestamp: "t".into(),
total_functions: 15,
total_size_kb: 120.0,
total_instructions: 1500,
avg_cyclomatic: 6.0,
avg_maintainability: 75.0,
test_coverage_pct: 65.0,
warning_density: 3.0,
branch_pred_acc: 0.85,
estimated_ipc: 1.4,
security_score: None,
quality_score: 70.0,
dead_function_count: 3,
gadget_count: 0,
};
let diff = c1.diff(&c2);
assert!(diff.contains("v1 -> v2"));
assert!(diff.contains("Functions"));
assert!(diff.contains("Quality"));
}
#[test]
fn test_extended_function_metrics_default() {
let fm = X86FunctionMetrics::default();
assert_eq!(fm.name, "");
assert_eq!(fm.maintainability_index, 100.0);
}
#[test]
fn test_extended_aggregator_default() {
let agg = X86MetricsAggregator::default();
assert_eq!(agg.total_functions, 0);
}
#[test]
fn test_size_distribution_total_empty() {
let d = X86SizeDistribution::new("empty");
assert_eq!(d.total(), 0);
assert_eq!(d.mean(), 0.0);
}
}
#[derive(Debug, Clone)]
pub struct X86CompilerSignature {
pub compiler_name: &'static str,
pub version_pattern: &'static str,
pub prologue_bytes: Vec<u8>,
pub epilogue_bytes: Vec<u8>,
pub string_patterns: Vec<&'static str>,
pub section_naming: Vec<&'static str>,
}
impl X86CompilerSignature {
pub fn gcc_x64() -> Self {
Self {
compiler_name: "GCC",
version_pattern: "GCC: (GNU) (\\d+\\.\\d+\\.\\d+)",
prologue_bytes: vec![0x55, 0x48, 0x89, 0xe5],
epilogue_bytes: vec![0x5d, 0xc3],
string_patterns: vec!["GCC: (GNU)", "crtstuff.c", "__do_global_dtors_aux"],
section_naming: vec![".init_array", ".fini_array", ".gnu.hash"],
}
}
pub fn clang_x64() -> Self {
Self {
compiler_name: "Clang",
version_pattern: "clang version (\\d+\\.\\d+\\.\\d+)",
prologue_bytes: vec![0x55, 0x48, 0x89, 0xe5],
epilogue_bytes: vec![0x5d, 0xc3],
string_patterns: vec!["clang version", "LLVM", "compiler-rt"],
section_naming: vec![".llvm_addrsig", ".llvm.call-graph-profile"],
}
}
pub fn msvc_x64() -> Self {
Self {
compiler_name: "MSVC",
version_pattern: "Microsoft (R) C/C\\+\\+ Optimizing Compiler",
prologue_bytes: vec![0x48, 0x89, 0x4c, 0x24, 0x08],
epilogue_bytes: vec![0xc3],
string_patterns: vec!["Microsoft", "Hotpatch", "__security_cookie"],
section_naming: vec![".pdata", ".xdata", ".gfids"],
}
}
pub fn icc_x64() -> Self {
Self {
compiler_name: "ICC",
version_pattern: "Intel(R) (\\d+\\.\\d+)",
prologue_bytes: vec![0x55, 0x48, 0x89, 0xe5],
epilogue_bytes: vec![0x5d, 0xc3],
string_patterns: vec!["Intel", "__INTEL_COMPILER", "libimf"],
section_naming: vec![".intel_syntax"],
}
}
pub fn all_signatures() -> Vec<X86CompilerSignature> {
vec![
Self::gcc_x64(),
Self::clang_x64(),
Self::msvc_x64(),
Self::icc_x64(),
]
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum X86ROPGadgetType {
StackPivot,
RegisterLoad,
MemoryWrite,
Syscall,
FunctionCall,
NopSled,
Other,
}
impl X86ROPGadgetType {
pub fn description(&self) -> &'static str {
match self {
Self::StackPivot => "Stack pivot gadget",
Self::RegisterLoad => "Register load gadget",
Self::MemoryWrite => "Memory write gadget",
Self::Syscall => "System call gadget",
Self::FunctionCall => "Function call gadget",
Self::NopSled => "NOP sled gadget",
Self::Other => "Unclassified gadget",
}
}
pub fn danger_level(&self) -> u8 {
match self {
Self::StackPivot => 5,
Self::Syscall => 5,
Self::MemoryWrite => 4,
Self::FunctionCall => 3,
Self::RegisterLoad => 2,
Self::NopSled => 1,
Self::Other => 1,
}
}
}
impl fmt::Display for X86ROPGadgetType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.description())
}
}
pub fn classify_rop_gadget(instructions: &[String]) -> X86ROPGadgetType {
if instructions.is_empty() {
return X86ROPGadgetType::Other;
}
let combined = instructions.join(" ").to_lowercase();
if combined.contains("xchg") && (combined.contains("rsp") || combined.contains("esp")) {
return X86ROPGadgetType::StackPivot;
}
if combined.contains("pop") && (combined.contains("rsp") || combined.contains("esp")) {
return X86ROPGadgetType::StackPivot;
}
if combined.contains("syscall")
|| combined.contains("int 0x80")
|| combined.contains("sysenter")
{
return X86ROPGadgetType::Syscall;
}
if combined.contains("mov [")
&& (combined.contains("rax")
|| combined.contains("rbx")
|| combined.contains("rcx")
|| combined.contains("rdx"))
{
return X86ROPGadgetType::MemoryWrite;
}
if combined.contains("call") {
return X86ROPGadgetType::FunctionCall;
}
if combined.contains("pop") {
return X86ROPGadgetType::RegisterLoad;
}
if combined.contains("nop") || combined == "ret" {
return X86ROPGadgetType::NopSled;
}
X86ROPGadgetType::Other
}
#[derive(Debug, Clone)]
pub struct X86SectionAnalysis {
pub section_name: String,
pub size: u64,
pub entropy: f64,
pub is_executable: bool,
pub is_writable: bool,
pub is_readable: bool,
pub has_strings: bool,
pub string_count: usize,
pub estimated_instruction_count: u64,
pub alignment: u64,
}
impl X86SectionAnalysis {
pub fn new(name: &str, size: u64) -> Self {
Self {
section_name: name.to_string(),
size,
entropy: 0.0,
is_executable: false,
is_writable: false,
is_readable: true,
has_strings: false,
string_count: 0,
estimated_instruction_count: 0,
alignment: 1,
}
}
pub fn estimate_instructions(&mut self) {
self.estimated_instruction_count = self.size / 4;
}
pub fn is_wxorx(&self) -> bool {
self.is_writable && self.is_executable
}
pub fn estimate_entropy(&mut self, byte_distribution: &[f64; 256]) {
self.entropy = byte_distribution
.iter()
.filter(|&&p| p > 0.0)
.map(|&p| -p * p.log2())
.sum();
}
}
#[derive(Debug, Clone)]
pub struct X86ELFAnalysis {
pub ei_class: u8, pub ei_data: u8, pub ei_version: u8,
pub ei_osabi: u8,
pub e_type: u16, pub e_machine: u16, pub e_entry: u64,
pub e_phoff: u64,
pub e_shoff: u64,
pub e_flags: u32,
pub e_ehsize: u16,
pub e_phentsize: u16,
pub e_phnum: u16,
pub e_shentsize: u16,
pub e_shnum: u16,
pub e_shstrndx: u16,
pub has_gnu_stack: bool,
pub has_gnu_relro: bool,
pub has_note_abi_tag: bool,
pub has_build_id: bool,
pub dynamic_entries: Vec<(u64, u64)>, }
impl X86ELFAnalysis {
pub fn new() -> Self {
Self {
ei_class: 0,
ei_data: 0,
ei_version: 0,
ei_osabi: 0,
e_type: 0,
e_machine: 0,
e_entry: 0,
e_phoff: 0,
e_shoff: 0,
e_flags: 0,
e_ehsize: 0,
e_phentsize: 0,
e_phnum: 0,
e_shentsize: 0,
e_shnum: 0,
e_shstrndx: 0,
has_gnu_stack: false,
has_gnu_relro: false,
has_note_abi_tag: false,
has_build_id: false,
dynamic_entries: Vec::new(),
}
}
pub fn is_64bit(&self) -> bool {
self.ei_class == 2
}
pub fn is_32bit(&self) -> bool {
self.ei_class == 1
}
pub fn is_pie(&self) -> bool {
self.e_type == 3
}
pub fn elf_type_name(&self) -> &'static str {
match self.e_type {
0 => "ET_NONE",
1 => "ET_REL",
2 => "ET_EXEC",
3 => "ET_DYN",
4 => "ET_CORE",
_ => "Unknown",
}
}
pub fn machine_name(&self) -> &'static str {
match self.e_machine {
3 => "i386",
62 => "x86-64",
_ => "Unknown",
}
}
}
impl Default for X86ELFAnalysis {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct X86PEAnalysis {
pub machine: u16,
pub number_of_sections: u16,
pub time_date_stamp: u32,
pub pointer_to_symbol_table: u32,
pub number_of_symbols: u32,
pub size_of_optional_header: u16,
pub characteristics: u16,
pub image_base: u64,
pub section_alignment: u32,
pub file_alignment: u32,
pub size_of_image: u32,
pub size_of_headers: u32,
pub subsystem: u16,
pub dll_characteristics: u16,
pub size_of_stack_reserve: u64,
pub size_of_stack_commit: u64,
pub size_of_heap_reserve: u64,
pub size_of_heap_commit: u64,
pub loader_flags: u32,
pub has_dynamic_base: bool,
pub has_nx_compat: bool,
pub has_high_entropy_va: bool,
pub has_guard_cf: bool,
}
impl X86PEAnalysis {
pub fn new() -> Self {
Self {
machine: 0,
number_of_sections: 0,
time_date_stamp: 0,
pointer_to_symbol_table: 0,
number_of_symbols: 0,
size_of_optional_header: 0,
characteristics: 0,
image_base: 0,
section_alignment: 0,
file_alignment: 0,
size_of_image: 0,
size_of_headers: 0,
subsystem: 0,
dll_characteristics: 0,
size_of_stack_reserve: 0,
size_of_stack_commit: 0,
size_of_heap_reserve: 0,
size_of_heap_commit: 0,
loader_flags: 0,
has_dynamic_base: false,
has_nx_compat: false,
has_high_entropy_va: false,
has_guard_cf: false,
}
}
pub fn is_x64(&self) -> bool {
self.machine == 0x8664
}
pub fn is_i386(&self) -> bool {
self.machine == 0x014c
}
pub fn subsystem_name(&self) -> &'static str {
match self.subsystem {
1 => "Native",
2 => "Windows GUI",
3 => "Windows Console",
5 => "OS/2 Console",
7 => "POSIX Console",
9 => "Windows CE GUI",
_ => "Unknown",
}
}
}
impl Default for X86PEAnalysis {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod binary_tests {
use super::*;
#[test]
fn test_compiler_signature_gcc() {
let sig = X86CompilerSignature::gcc_x64();
assert_eq!(sig.compiler_name, "GCC");
assert!(!sig.prologue_bytes.is_empty());
assert!(!sig.string_patterns.is_empty());
}
#[test]
fn test_compiler_signature_clang() {
let sig = X86CompilerSignature::clang_x64();
assert_eq!(sig.compiler_name, "Clang");
assert!(sig.string_patterns.iter().any(|s| s.contains("LLVM")));
}
#[test]
fn test_compiler_signature_msvc() {
let sig = X86CompilerSignature::msvc_x64();
assert_eq!(sig.compiler_name, "MSVC");
assert!(sig.section_naming.contains(&".pdata"));
}
#[test]
fn test_compiler_signature_icc() {
let sig = X86CompilerSignature::icc_x64();
assert_eq!(sig.compiler_name, "ICC");
}
#[test]
fn test_compiler_signature_all() {
let all = X86CompilerSignature::all_signatures();
assert_eq!(all.len(), 4);
}
#[test]
fn test_rop_gadget_type_descriptions() {
assert!(X86ROPGadgetType::StackPivot
.description()
.contains("Stack pivot"));
assert!(X86ROPGadgetType::Syscall
.description()
.contains("System call"));
assert!(X86ROPGadgetType::Other
.description()
.contains("Unclassified"));
}
#[test]
fn test_rop_gadget_type_danger_levels() {
assert_eq!(X86ROPGadgetType::StackPivot.danger_level(), 5);
assert_eq!(X86ROPGadgetType::Syscall.danger_level(), 5);
assert_eq!(X86ROPGadgetType::RegisterLoad.danger_level(), 2);
assert_eq!(X86ROPGadgetType::NopSled.danger_level(), 1);
}
#[test]
fn test_rop_gadget_type_display() {
let d = format!("{}", X86ROPGadgetType::MemoryWrite);
assert!(d.contains("Memory write"));
}
#[test]
fn test_classify_rop_gadget_empty() {
assert_eq!(classify_rop_gadget(&[]), X86ROPGadgetType::Other);
}
#[test]
fn test_classify_rop_gadget_stack_pivot_xchg() {
let instrs = vec!["xchg rax, rsp".to_string(), "ret".to_string()];
assert_eq!(classify_rop_gadget(&instrs), X86ROPGadgetType::StackPivot);
}
#[test]
fn test_classify_rop_gadget_stack_pivot_pop() {
let instrs = vec!["pop rsp".to_string(), "ret".to_string()];
assert_eq!(classify_rop_gadget(&instrs), X86ROPGadgetType::StackPivot);
}
#[test]
fn test_classify_rop_gadget_syscall() {
let instrs = vec!["xor rax, rax".to_string(), "syscall".to_string()];
assert_eq!(classify_rop_gadget(&instrs), X86ROPGadgetType::Syscall);
}
#[test]
fn test_classify_rop_gadget_int80() {
let instrs = vec!["mov eax, 1".to_string(), "int 0x80".to_string()];
assert_eq!(classify_rop_gadget(&instrs), X86ROPGadgetType::Syscall);
}
#[test]
fn test_classify_rop_gadget_memory_write() {
let instrs = vec!["mov [rax], rbx".to_string(), "ret".to_string()];
assert_eq!(classify_rop_gadget(&instrs), X86ROPGadgetType::MemoryWrite);
}
#[test]
fn test_classify_rop_gadget_call() {
let instrs = vec!["call rax".to_string()];
assert_eq!(classify_rop_gadget(&instrs), X86ROPGadgetType::FunctionCall);
}
#[test]
fn test_classify_rop_gadget_register_load() {
let instrs = vec!["pop rdi".to_string(), "ret".to_string()];
assert_eq!(classify_rop_gadget(&instrs), X86ROPGadgetType::RegisterLoad);
}
#[test]
fn test_classify_rop_gadget_nop_sled() {
let instrs = vec!["nop".to_string(), "ret".to_string()];
assert_eq!(classify_rop_gadget(&instrs), X86ROPGadgetType::NopSled);
}
#[test]
fn test_classify_rop_gadget_only_ret() {
let instrs = vec!["ret".to_string()];
assert_eq!(classify_rop_gadget(&instrs), X86ROPGadgetType::NopSled);
}
#[test]
fn test_section_analysis_create() {
let section = X86SectionAnalysis::new(".text", 4096);
assert_eq!(section.section_name, ".text");
assert_eq!(section.size, 4096);
assert!(!section.is_executable);
assert!(section.is_readable);
}
#[test]
fn test_section_analysis_estimate_instructions() {
let mut section = X86SectionAnalysis::new(".text", 4000);
section.estimate_instructions();
assert_eq!(section.estimated_instruction_count, 1000);
}
#[test]
fn test_section_analysis_wxorx_detection() {
let mut section = X86SectionAnalysis::new("danger", 1024);
assert!(!section.is_wxorx());
section.is_writable = true;
section.is_executable = true;
assert!(section.is_wxorx());
}
#[test]
fn test_section_analysis_entropy() {
let mut section = X86SectionAnalysis::new("data", 256);
let uniform: [f64; 256] = [1.0 / 256.0; 256];
section.estimate_entropy(&uniform);
assert!(section.entropy > 0.0);
}
#[test]
fn test_elf_analysis_create() {
let elf = X86ELFAnalysis::new();
assert!(!elf.is_64bit());
assert!(!elf.is_32bit());
assert!(!elf.is_pie());
}
#[test]
fn test_elf_analysis_64bit() {
let mut elf = X86ELFAnalysis::new();
elf.ei_class = 2;
assert!(elf.is_64bit());
assert!(!elf.is_32bit());
}
#[test]
fn test_elf_analysis_32bit() {
let mut elf = X86ELFAnalysis::new();
elf.ei_class = 1;
assert!(elf.is_32bit());
assert!(!elf.is_64bit());
}
#[test]
fn test_elf_analysis_pie() {
let mut elf = X86ELFAnalysis::new();
elf.e_type = 3; assert!(elf.is_pie());
}
#[test]
fn test_elf_type_name() {
let mut elf = X86ELFAnalysis::new();
elf.e_type = 2;
assert_eq!(elf.elf_type_name(), "ET_EXEC");
elf.e_type = 1;
assert_eq!(elf.elf_type_name(), "ET_REL");
elf.e_type = 3;
assert_eq!(elf.elf_type_name(), "ET_DYN");
}
#[test]
fn test_elf_machine_name() {
let mut elf = X86ELFAnalysis::new();
elf.e_machine = 62;
assert_eq!(elf.machine_name(), "x86-64");
elf.e_machine = 3;
assert_eq!(elf.machine_name(), "i386");
elf.e_machine = 999;
assert_eq!(elf.machine_name(), "Unknown");
}
#[test]
fn test_elf_analysis_default() {
let elf = X86ELFAnalysis::default();
assert_eq!(elf.e_type, 0);
}
#[test]
fn test_pe_analysis_create() {
let pe = X86PEAnalysis::new();
assert!(!pe.is_x64());
assert!(!pe.is_i386());
}
#[test]
fn test_pe_analysis_x64() {
let mut pe = X86PEAnalysis::new();
pe.machine = 0x8664;
assert!(pe.is_x64());
assert!(!pe.is_i386());
}
#[test]
fn test_pe_analysis_i386() {
let mut pe = X86PEAnalysis::new();
pe.machine = 0x014c;
assert!(pe.is_i386());
assert!(!pe.is_x64());
}
#[test]
fn test_pe_subsystem_names() {
let mut pe = X86PEAnalysis::new();
pe.subsystem = 2;
assert_eq!(pe.subsystem_name(), "Windows GUI");
pe.subsystem = 3;
assert_eq!(pe.subsystem_name(), "Windows Console");
pe.subsystem = 1;
assert_eq!(pe.subsystem_name(), "Native");
}
#[test]
fn test_pe_analysis_default() {
let pe = X86PEAnalysis::default();
assert_eq!(pe.machine, 0);
}
}
#[derive(Debug, Clone)]
pub struct X86FunctionQualityReport {
pub function_name: String,
pub total_lines: u64,
pub code_lines: u64,
pub comment_lines: u64,
pub blank_lines: u64,
pub warning_count: u64,
pub error_count: u64,
pub test_coverage_pct: f64,
pub doc_coverage_pct: f64,
pub cyclomatic_complexity: u64,
pub cognitive_complexity: f64,
pub maintainability_index: f64,
pub has_todo_fixme: bool,
pub has_unsafe_block: bool,
pub has_inline_asm: bool,
pub has_panic_paths: bool,
pub has_error_handling: bool,
pub parameter_count: u64,
pub local_variable_count: u64,
pub max_expression_depth: u64,
pub quality_grade: X86QualityGrade,
}
impl X86FunctionQualityReport {
pub fn new(name: &str) -> Self {
Self {
function_name: name.to_string(),
total_lines: 0,
code_lines: 0,
comment_lines: 0,
blank_lines: 0,
warning_count: 0,
error_count: 0,
test_coverage_pct: 0.0,
doc_coverage_pct: 0.0,
cyclomatic_complexity: 1,
cognitive_complexity: 0.0,
maintainability_index: 100.0,
has_todo_fixme: false,
has_unsafe_block: false,
has_inline_asm: false,
has_panic_paths: false,
has_error_handling: false,
parameter_count: 0,
local_variable_count: 0,
max_expression_depth: 0,
quality_grade: X86QualityGrade::A,
}
}
pub fn compute_grade(&mut self) {
let mut score = 100.0f64;
score -= (self.cyclomatic_complexity.saturating_sub(10) as f64) * 2.0;
score -= (self.cognitive_complexity * 1.5).min(40.0);
score -= (self.warning_count as f64 * 5.0).min(30.0);
score -= (self.error_count as f64 * 20.0).min(40.0);
score -= (100.0 - self.maintainability_index) * 0.5;
score -= (100.0 - self.test_coverage_pct) * 0.3;
score -= if self.has_unsafe_block { 10.0 } else { 0.0 };
score -= if self.has_inline_asm { 5.0 } else { 0.0 };
score += if self.has_error_handling { 5.0 } else { 0.0 };
score += if self.doc_coverage_pct > 50.0 {
5.0
} else {
0.0
};
self.quality_grade = X86QualityGrade::from_score(score);
}
pub fn detailed_report(&self) -> String {
let mut r = String::new();
r.push_str(&format!(
"Function Quality Report: {}\n",
self.function_name
));
r.push_str(&format!("{:-<60}\n", ""));
r.push_str(&format!(
" Lines: {} total ({} code, {} comments, {} blank)\n",
self.total_lines, self.code_lines, self.comment_lines, self.blank_lines
));
r.push_str(&format!(
" Warnings: {}, Errors: {}\n",
self.warning_count, self.error_count
));
r.push_str(&format!(
" Coverage: test={:.1}%, doc={:.1}%\n",
self.test_coverage_pct, self.doc_coverage_pct
));
r.push_str(&format!(
" Complexity: CC={}, cognitive={:.1}, MI={:.1}\n",
self.cyclomatic_complexity, self.cognitive_complexity, self.maintainability_index
));
r.push_str(&format!(
" Params: {}, Locals: {}, Expr depth: {}\n",
self.parameter_count, self.local_variable_count, self.max_expression_depth
));
r.push_str(&format!(
" Flags: unsafe={}, asm={}, todo/fixme={}, panic_paths={}, err_handling={}\n",
self.has_unsafe_block,
self.has_inline_asm,
self.has_todo_fixme,
self.has_panic_paths,
self.has_error_handling
));
r.push_str(&format!(" Grade: {}\n", self.quality_grade));
r
}
}
impl Default for X86FunctionQualityReport {
fn default() -> Self {
Self::new("")
}
}
impl fmt::Display for X86FunctionQualityReport {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{} - Grade: {}", self.function_name, self.quality_grade)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum X86QualityGrade {
A,
B,
C,
D,
E,
F,
}
impl X86QualityGrade {
pub fn from_score(score: f64) -> Self {
if score >= 90.0 {
Self::A
} else if score >= 80.0 {
Self::B
} else if score >= 70.0 {
Self::C
} else if score >= 60.0 {
Self::D
} else if score >= 50.0 {
Self::E
} else {
Self::F
}
}
pub fn range(&self) -> &'static str {
match self {
Self::A => "90-100",
Self::B => "80-89",
Self::C => "70-79",
Self::D => "60-69",
Self::E => "50-59",
Self::F => "0-49",
}
}
pub fn description(&self) -> &'static str {
match self {
Self::A => "Excellent — well-written, well-tested, highly maintainable",
Self::B => "Good — clean code with minor issues",
Self::C => "Adequate — functional but needs improvement",
Self::D => "Below average — significant quality issues present",
Self::E => "Poor — many quality problems, hard to maintain",
Self::F => "Failing — requires urgent refactoring",
}
}
}
impl fmt::Display for X86QualityGrade {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{} ({}): {}",
match self {
Self::A => "A",
Self::B => "B",
Self::C => "C",
Self::D => "D",
Self::E => "E",
Self::F => "F",
},
self.range(),
self.description()
)
}
}
#[derive(Debug, Clone)]
pub struct X86QualityHeatmap {
pub grades: HashMap<String, X86QualityGrade>,
pub grade_distribution: BTreeMap<X86QualityGrade, usize>,
pub total_functions: usize,
pub median_grade: Option<X86QualityGrade>,
}
impl X86QualityHeatmap {
pub fn new() -> Self {
Self {
grades: HashMap::new(),
grade_distribution: BTreeMap::new(),
total_functions: 0,
median_grade: None,
}
}
pub fn add(&mut self, function: &str, grade: X86QualityGrade) {
self.grades.insert(function.to_string(), grade);
*self.grade_distribution.entry(grade).or_insert(0) += 1;
self.total_functions += 1;
}
pub fn compute_median(&mut self) {
if self.grades.is_empty() {
return;
}
let mut all_grades: Vec<X86QualityGrade> = self.grades.values().copied().collect();
all_grades.sort();
let mid = all_grades.len() / 2;
self.median_grade = Some(all_grades[mid]);
}
pub fn count_at(&self, grade: X86QualityGrade) -> usize {
self.grade_distribution.get(&grade).copied().unwrap_or(0)
}
pub fn pct_at(&self, grade: X86QualityGrade) -> f64 {
if self.total_functions == 0 {
return 0.0;
}
self.count_at(grade) as f64 / self.total_functions as f64 * 100.0
}
pub fn ascii_chart(&self) -> String {
let mut chart = String::new();
chart.push_str("Quality Grade Distribution:\n");
let grades = [
X86QualityGrade::A,
X86QualityGrade::B,
X86QualityGrade::C,
X86QualityGrade::D,
X86QualityGrade::E,
X86QualityGrade::F,
];
let max_count = self.grade_distribution.values().max().copied().unwrap_or(1) as f64;
for grade in &grades {
let count = self.count_at(*grade);
let bar_len = if max_count > 0.0 {
(count as f64 / max_count * 50.0).round() as usize
} else {
0
};
let bar = "#".repeat(bar_len);
chart.push_str(&format!(
" {}: {:>3} ({:>5.1}%) {}\n",
match grade {
X86QualityGrade::A => "A",
X86QualityGrade::B => "B",
X86QualityGrade::C => "C",
X86QualityGrade::D => "D",
X86QualityGrade::E => "E",
X86QualityGrade::F => "F",
},
count,
self.pct_at(*grade),
bar
));
}
chart
}
}
impl Default for X86QualityHeatmap {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod quality_report_tests {
use super::*;
#[test]
fn test_function_quality_report_create() {
let r = X86FunctionQualityReport::new("test");
assert_eq!(r.function_name, "test");
assert_eq!(r.quality_grade, X86QualityGrade::A);
}
#[test]
fn test_function_quality_report_compute_grade_perfect() {
let mut r = X86FunctionQualityReport::new("perfect");
r.cyclomatic_complexity = 1;
r.maintainability_index = 100.0;
r.test_coverage_pct = 100.0;
r.doc_coverage_pct = 80.0;
r.has_error_handling = true;
r.compute_grade();
assert_eq!(r.quality_grade, X86QualityGrade::A);
}
#[test]
fn test_function_quality_report_compute_grade_poor() {
let mut r = X86FunctionQualityReport::new("poor");
r.cyclomatic_complexity = 50;
r.cognitive_complexity = 30.0;
r.warning_count = 10;
r.error_count = 3;
r.maintainability_index = 20.0;
r.test_coverage_pct = 10.0;
r.has_unsafe_block = true;
r.has_inline_asm = true;
r.compute_grade();
assert_eq!(r.quality_grade, X86QualityGrade::F);
}
#[test]
fn test_function_quality_report_compute_grade_average() {
let mut r = X86FunctionQualityReport::new("avg");
r.cyclomatic_complexity = 12;
r.maintainability_index = 70.0;
r.test_coverage_pct = 75.0;
r.compute_grade();
let grade = r.quality_grade;
assert!(grade == X86QualityGrade::B || grade == X86QualityGrade::C);
}
#[test]
fn test_function_quality_report_detailed() {
let mut r = X86FunctionQualityReport::new("detailed");
r.total_lines = 200;
r.code_lines = 150;
r.comment_lines = 30;
r.blank_lines = 20;
r.warning_count = 2;
r.cyclomatic_complexity = 8;
r.maintainability_index = 85.0;
r.test_coverage_pct = 90.0;
r.compute_grade();
let report = r.detailed_report();
assert!(report.contains("detailed"));
assert!(report.contains("Lines:"));
assert!(report.contains("Grade:"));
}
#[test]
fn test_function_quality_report_display() {
let mut r = X86FunctionQualityReport::new("display");
r.compute_grade();
let d = format!("{}", r);
assert!(d.contains("display"));
assert!(d.contains("Grade:"));
}
#[test]
fn test_function_quality_report_default() {
let r = X86FunctionQualityReport::default();
assert_eq!(r.function_name, "");
}
#[test]
fn test_quality_grade_from_score() {
assert_eq!(X86QualityGrade::from_score(95.0), X86QualityGrade::A);
assert_eq!(X86QualityGrade::from_score(85.0), X86QualityGrade::B);
assert_eq!(X86QualityGrade::from_score(75.0), X86QualityGrade::C);
assert_eq!(X86QualityGrade::from_score(65.0), X86QualityGrade::D);
assert_eq!(X86QualityGrade::from_score(55.0), X86QualityGrade::E);
assert_eq!(X86QualityGrade::from_score(30.0), X86QualityGrade::F);
}
#[test]
fn test_quality_grade_boundaries() {
assert_eq!(X86QualityGrade::from_score(90.0), X86QualityGrade::A);
assert_eq!(X86QualityGrade::from_score(89.99), X86QualityGrade::B);
assert_eq!(X86QualityGrade::from_score(80.0), X86QualityGrade::B);
assert_eq!(X86QualityGrade::from_score(79.99), X86QualityGrade::C);
assert_eq!(X86QualityGrade::from_score(70.0), X86QualityGrade::C);
assert_eq!(X86QualityGrade::from_score(60.0), X86QualityGrade::D);
assert_eq!(X86QualityGrade::from_score(50.0), X86QualityGrade::E);
assert_eq!(X86QualityGrade::from_score(49.99), X86QualityGrade::F);
}
#[test]
fn test_quality_grade_range() {
assert_eq!(X86QualityGrade::A.range(), "90-100");
assert_eq!(X86QualityGrade::C.range(), "70-79");
assert_eq!(X86QualityGrade::F.range(), "0-49");
}
#[test]
fn test_quality_grade_description() {
assert!(X86QualityGrade::A.description().contains("Excellent"));
assert!(X86QualityGrade::F.description().contains("urgent"));
}
#[test]
fn test_quality_grade_display() {
for grade in [
X86QualityGrade::A,
X86QualityGrade::B,
X86QualityGrade::C,
X86QualityGrade::D,
X86QualityGrade::E,
X86QualityGrade::F,
]
.iter()
{
let d = format!("{}", grade);
assert!(!d.is_empty());
}
}
#[test]
fn test_quality_grade_ordering() {
assert!(X86QualityGrade::A < X86QualityGrade::B);
assert!(X86QualityGrade::B < X86QualityGrade::C);
assert!(X86QualityGrade::E < X86QualityGrade::F);
}
#[test]
fn test_quality_heatmap_create() {
let h = X86QualityHeatmap::new();
assert_eq!(h.total_functions, 0);
}
#[test]
fn test_quality_heatmap_add() {
let mut h = X86QualityHeatmap::new();
h.add("f1", X86QualityGrade::A);
h.add("f2", X86QualityGrade::B);
h.add("f3", X86QualityGrade::B);
assert_eq!(h.total_functions, 3);
assert_eq!(h.count_at(X86QualityGrade::A), 1);
assert_eq!(h.count_at(X86QualityGrade::B), 2);
assert_eq!(h.count_at(X86QualityGrade::F), 0);
}
#[test]
fn test_quality_heatmap_pct() {
let mut h = X86QualityHeatmap::new();
h.add("f1", X86QualityGrade::A);
h.add("f2", X86QualityGrade::B);
h.add("f3", X86QualityGrade::B);
h.add("f4", X86QualityGrade::F);
assert!((h.pct_at(X86QualityGrade::A) - 25.0).abs() < 0.01);
assert!((h.pct_at(X86QualityGrade::B) - 50.0).abs() < 0.01);
assert!((h.pct_at(X86QualityGrade::F) - 25.0).abs() < 0.01);
}
#[test]
fn test_quality_heatmap_pct_empty() {
let h = X86QualityHeatmap::new();
assert_eq!(h.pct_at(X86QualityGrade::A), 0.0);
}
#[test]
fn test_quality_heatmap_median() {
let mut h = X86QualityHeatmap::new();
h.add("f1", X86QualityGrade::A);
h.add("f2", X86QualityGrade::C);
h.add("f3", X86QualityGrade::F);
h.compute_median();
assert_eq!(h.median_grade, Some(X86QualityGrade::C));
}
#[test]
fn test_quality_heatmap_median_even() {
let mut h = X86QualityHeatmap::new();
h.add("f1", X86QualityGrade::A);
h.add("f2", X86QualityGrade::D);
h.add("f3", X86QualityGrade::C);
h.add("f4", X86QualityGrade::F);
h.compute_median();
assert!(h.median_grade.is_some());
}
#[test]
fn test_quality_heatmap_median_empty() {
let mut h = X86QualityHeatmap::new();
h.compute_median();
assert_eq!(h.median_grade, None);
}
#[test]
fn test_quality_heatmap_ascii_chart() {
let mut h = X86QualityHeatmap::new();
h.add("f1", X86QualityGrade::A);
h.add("f2", X86QualityGrade::A);
h.add("f3", X86QualityGrade::B);
h.add("f4", X86QualityGrade::C);
let chart = h.ascii_chart();
assert!(chart.contains("Quality Grade Distribution"));
assert!(chart.contains("A:"));
assert!(chart.contains("#"));
}
#[test]
fn test_quality_heatmap_default() {
let h = X86QualityHeatmap::default();
assert_eq!(h.total_functions, 0);
}
}
#[derive(Debug, Clone)]
pub struct X86MetricsConfig {
pub collect_size_metrics: bool,
pub collect_performance_metrics: bool,
pub collect_complexity_metrics: bool,
pub collect_quality_metrics: bool,
pub collect_binary_analysis: bool,
pub regression_threshold_bytes: u64,
pub complexity_threshold: u64,
pub hot_threshold_pct: f64,
pub warm_threshold_pct: f64,
pub max_bloat_functions: usize,
pub max_trend_versions: usize,
pub output_directory: String,
pub report_format: X86ReportFormat,
}
impl X86MetricsConfig {
pub fn all_enabled() -> Self {
Self {
collect_size_metrics: true,
collect_performance_metrics: true,
collect_complexity_metrics: true,
collect_quality_metrics: true,
collect_binary_analysis: true,
regression_threshold_bytes: 1024,
complexity_threshold: 15,
hot_threshold_pct: 5.0,
warm_threshold_pct: 1.0,
max_bloat_functions: 50,
max_trend_versions: 20,
output_directory: "./metrics".to_string(),
report_format: X86ReportFormat::All,
}
}
pub fn size_only() -> Self {
Self {
collect_size_metrics: true,
collect_performance_metrics: false,
collect_complexity_metrics: false,
collect_quality_metrics: false,
collect_binary_analysis: false,
regression_threshold_bytes: 1024,
complexity_threshold: 15,
hot_threshold_pct: 5.0,
warm_threshold_pct: 1.0,
max_bloat_functions: 20,
max_trend_versions: 10,
output_directory: "./metrics".to_string(),
report_format: X86ReportFormat::Text,
}
}
pub fn full_analysis() -> Self {
Self::all_enabled()
}
}
impl Default for X86MetricsConfig {
fn default() -> Self {
Self::all_enabled()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum X86ReportFormat {
Text,
Json,
Html,
Csv,
All,
}
impl X86ReportFormat {
pub fn extension(&self) -> &'static str {
match self {
Self::Text => "txt",
Self::Json => "json",
Self::Html => "html",
Self::Csv => "csv",
Self::All => "",
}
}
pub fn all_formats() -> Vec<X86ReportFormat> {
vec![Self::Text, Self::Json, Self::Html, Self::Csv]
}
}
#[derive(Debug, Clone)]
pub struct X86MetricsCollector {
pub config: X86MetricsConfig,
pub metrics: X86CodeMetrics,
}
impl X86MetricsCollector {
pub fn new(config: X86MetricsConfig) -> Self {
Self {
metrics: X86CodeMetrics::new("collector"),
config,
}
}
pub fn record_function_data(
&mut self,
name: &str,
size_bytes: u64,
branches: u64,
blocks: u64,
edges: u64,
) {
if self.config.collect_size_metrics {
self.metrics.size.record_function(name, size_bytes);
}
if self.config.collect_complexity_metrics {
self.metrics
.complexity
.compute_cyclomatic(name, branches, blocks, edges);
}
}
pub fn record_instruction_data(&mut self, mnemonic: &str) {
if self.config.collect_size_metrics {
self.metrics.size.record_instruction(mnemonic);
}
}
pub fn record_performance_data(&mut self, function: &str, call_count: u64) {
if self.config.collect_performance_metrics {
for _ in 0..call_count {
self.metrics.performance.record_call(function);
}
}
}
pub fn finalize(&mut self) {
if self.config.collect_performance_metrics {
self.metrics.performance.compute_all();
}
if self.config.collect_quality_metrics {
self.metrics.quality.compute_quality_score();
}
self.metrics.save_snapshot();
}
pub fn generate_reports(&self) -> Vec<(X86ReportFormat, String)> {
let formats = if self.config.report_format == X86ReportFormat::All {
X86ReportFormat::all_formats()
} else {
vec![self.config.report_format]
};
formats
.into_iter()
.map(|fmt| {
let content = match fmt {
X86ReportFormat::Text => self.metrics.generate_text_report(),
X86ReportFormat::Json => self.metrics.generate_json_report(),
X86ReportFormat::Html => self.metrics.generate_html_report(),
X86ReportFormat::Csv => self.metrics.generate_csv_report(),
X86ReportFormat::All => String::new(),
};
(fmt, content)
})
.collect()
}
}
impl Default for X86MetricsCollector {
fn default() -> Self {
Self::new(X86MetricsConfig::default())
}
}
#[cfg(test)]
mod collector_tests {
use super::*;
#[test]
fn test_metrics_config_all_enabled() {
let cfg = X86MetricsConfig::all_enabled();
assert!(cfg.collect_size_metrics);
assert!(cfg.collect_performance_metrics);
assert!(cfg.collect_complexity_metrics);
assert!(cfg.collect_quality_metrics);
assert!(cfg.collect_binary_analysis);
}
#[test]
fn test_metrics_config_size_only() {
let cfg = X86MetricsConfig::size_only();
assert!(cfg.collect_size_metrics);
assert!(!cfg.collect_performance_metrics);
assert!(!cfg.collect_complexity_metrics);
assert!(!cfg.collect_quality_metrics);
assert!(!cfg.collect_binary_analysis);
}
#[test]
fn test_metrics_config_full_analysis() {
let cfg = X86MetricsConfig::full_analysis();
assert!(cfg.collect_binary_analysis);
}
#[test]
fn test_metrics_config_default() {
let cfg = X86MetricsConfig::default();
assert!(cfg.collect_size_metrics);
}
#[test]
fn test_report_format_extension() {
assert_eq!(X86ReportFormat::Text.extension(), "txt");
assert_eq!(X86ReportFormat::Json.extension(), "json");
assert_eq!(X86ReportFormat::Html.extension(), "html");
assert_eq!(X86ReportFormat::Csv.extension(), "csv");
assert_eq!(X86ReportFormat::All.extension(), "");
}
#[test]
fn test_report_format_all_formats() {
let all = X86ReportFormat::all_formats();
assert_eq!(all.len(), 4);
}
#[test]
fn test_metrics_collector_create() {
let collector = X86MetricsCollector::new(X86MetricsConfig::all_enabled());
assert_eq!(collector.metrics.total_functions(), 0);
}
#[test]
fn test_metrics_collector_record_function() {
let mut collector = X86MetricsCollector::new(X86MetricsConfig::all_enabled());
collector.record_function_data("test", 100, 2, 3, 5);
assert_eq!(collector.metrics.total_functions(), 1);
assert_eq!(collector.metrics.total_size_bytes(), 100);
assert!(collector
.metrics
.complexity
.cyclomatic_complexity
.contains_key("test"));
}
#[test]
fn test_metrics_collector_respects_config() {
let mut collector = X86MetricsCollector::new(X86MetricsConfig::size_only());
collector.record_function_data("test", 100, 2, 3, 5);
assert_eq!(collector.metrics.total_functions(), 1);
assert!(collector
.metrics
.complexity
.cyclomatic_complexity
.is_empty());
}
#[test]
fn test_metrics_collector_record_instruction() {
let mut collector = X86MetricsCollector::new(X86MetricsConfig::all_enabled());
collector.record_instruction_data("mov");
collector.record_instruction_data("add");
assert_eq!(collector.metrics.size.total_instructions, 2);
}
#[test]
fn test_metrics_collector_record_performance() {
let mut collector = X86MetricsCollector::new(X86MetricsConfig::all_enabled());
collector.record_performance_data("hot_fn", 100);
assert_eq!(
collector.metrics.performance.call_frequency.get("hot_fn"),
Some(&100)
);
}
#[test]
fn test_metrics_collector_finalize() {
let mut collector = X86MetricsCollector::new(X86MetricsConfig::all_enabled());
collector.record_function_data("f", 100, 1, 2, 3);
collector.record_performance_data("f", 5);
collector.finalize();
assert_eq!(collector.metrics.history.len(), 1);
assert!(collector.metrics.quality.quality_score >= 0.0);
}
#[test]
fn test_metrics_collector_generate_reports_all() {
let mut collector = X86MetricsCollector::new(X86MetricsConfig::all_enabled());
collector.record_function_data("f", 100, 1, 2, 3);
collector.record_instruction_data("mov");
collector.finalize();
let reports = collector.generate_reports();
assert_eq!(reports.len(), 4);
for (fmt, content) in &reports {
assert!(!content.is_empty(), "Empty report for {:?}", fmt);
}
}
#[test]
fn test_metrics_collector_generate_reports_text_only() {
let mut cfg = X86MetricsConfig::all_enabled();
cfg.report_format = X86ReportFormat::Text;
let mut collector = X86MetricsCollector::new(cfg);
collector.record_function_data("f", 100, 1, 2, 3);
collector.finalize();
let reports = collector.generate_reports();
assert_eq!(reports.len(), 1);
assert_eq!(reports[0].0, X86ReportFormat::Text);
}
#[test]
fn test_metrics_collector_default() {
let collector = X86MetricsCollector::default();
assert!(collector.config.collect_size_metrics);
}
}