use crate::models::Result;
pub struct BinaryOptimizer {
merge_strings: bool,
gc_sections: bool,
pack_rodata: bool,
}
impl Default for BinaryOptimizer {
fn default() -> Self {
Self {
merge_strings: true,
gc_sections: true,
pack_rodata: true,
}
}
}
impl BinaryOptimizer {
pub fn new() -> Self {
Self::default()
}
pub fn optimize(&self, elf_data: &mut Vec<u8>) -> Result<()> {
if self.merge_strings {
self.merge_duplicate_strings(elf_data.as_mut_slice())?;
}
if self.gc_sections {
self.garbage_collect_sections(elf_data.as_mut_slice())?;
}
if self.pack_rodata {
self.compress_rodata(elf_data.as_mut_slice())?;
}
Ok(())
}
fn merge_duplicate_strings(&self, _elf_data: &mut [u8]) -> Result<()> {
Ok(())
}
fn garbage_collect_sections(&self, _elf_data: &mut [u8]) -> Result<()> {
Ok(())
}
fn compress_rodata(&self, _elf_data: &mut [u8]) -> Result<()> {
Ok(())
}
pub fn estimate_size_reduction(&self, original_size: usize) -> usize {
let mut reduction = 0;
if self.merge_strings {
reduction += original_size / 20; }
if self.gc_sections {
reduction += original_size / 4; }
if self.pack_rodata {
reduction += original_size / 10; }
reduction
}
}
pub fn size_optimization_flags() -> Vec<&'static str> {
vec![
"-Os", "-ffunction-sections", "-fdata-sections", "-fno-unwind-tables", "-fno-asynchronous-unwind-tables",
"-fno-ident", "-fomit-frame-pointer", "-fmerge-all-constants", "-fno-exceptions", "-fno-rtti", ]
}
pub fn size_linker_flags() -> Vec<&'static str> {
vec![
"-Wl,--gc-sections", "-Wl,--as-needed", "-Wl,--strip-all", "-Wl,-O2", "-Wl,--hash-style=gnu", "-Wl,--build-id=none", "-Wl,-z,norelro", "-static", ]
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_size_estimation() {
let optimizer = BinaryOptimizer::new();
let original = 1000000; let reduction = optimizer.estimate_size_reduction(original);
assert!(reduction > 350000);
assert!(reduction < 450000);
}
#[test]
fn test_optimization_flags() {
let flags = size_optimization_flags();
assert!(flags.contains(&"-Os"));
assert!(flags.contains(&"-ffunction-sections"));
let linker_flags = size_linker_flags();
assert!(linker_flags.contains(&"-Wl,--gc-sections"));
assert!(linker_flags.contains(&"-static"));
}
#[test]
fn test_binary_optimizer_default() {
let optimizer = BinaryOptimizer::default();
assert!(optimizer.merge_strings);
assert!(optimizer.gc_sections);
assert!(optimizer.pack_rodata);
}
#[test]
fn test_binary_optimizer_new() {
let optimizer = BinaryOptimizer::new();
assert!(optimizer.merge_strings);
assert!(optimizer.gc_sections);
assert!(optimizer.pack_rodata);
}
#[test]
fn test_optimize_empty_data() {
let optimizer = BinaryOptimizer::new();
let mut data = Vec::new();
let result = optimizer.optimize(&mut data);
assert!(result.is_ok());
}
#[test]
fn test_optimize_with_data() {
let optimizer = BinaryOptimizer::new();
let mut data = vec![0u8; 1000];
let result = optimizer.optimize(&mut data);
assert!(result.is_ok());
}
#[test]
fn test_merge_duplicate_strings() {
let optimizer = BinaryOptimizer::new();
let mut data = b"hello world hello".to_vec();
let result = optimizer.merge_duplicate_strings(&mut data);
assert!(result.is_ok());
}
#[test]
fn test_garbage_collect_sections() {
let optimizer = BinaryOptimizer::new();
let mut data = vec![0u8; 100];
let result = optimizer.garbage_collect_sections(&mut data);
assert!(result.is_ok());
}
#[test]
fn test_compress_rodata() {
let optimizer = BinaryOptimizer::new();
let mut data = vec![0u8; 100];
let result = optimizer.compress_rodata(&mut data);
assert!(result.is_ok());
}
#[test]
fn test_estimate_size_reduction_zero() {
let optimizer = BinaryOptimizer::new();
let reduction = optimizer.estimate_size_reduction(0);
assert_eq!(reduction, 0);
}
#[test]
fn test_estimate_size_reduction_small() {
let optimizer = BinaryOptimizer::new();
let reduction = optimizer.estimate_size_reduction(100);
assert_eq!(reduction, 40);
}
#[test]
fn test_size_optimization_flags_count() {
let flags = size_optimization_flags();
assert_eq!(flags.len(), 10);
}
#[test]
fn test_size_optimization_flags_contains_all() {
let flags = size_optimization_flags();
assert!(flags.contains(&"-Os"));
assert!(flags.contains(&"-ffunction-sections"));
assert!(flags.contains(&"-fdata-sections"));
assert!(flags.contains(&"-fno-unwind-tables"));
assert!(flags.contains(&"-fno-asynchronous-unwind-tables"));
assert!(flags.contains(&"-fno-ident"));
assert!(flags.contains(&"-fomit-frame-pointer"));
assert!(flags.contains(&"-fmerge-all-constants"));
assert!(flags.contains(&"-fno-exceptions"));
assert!(flags.contains(&"-fno-rtti"));
}
#[test]
fn test_size_linker_flags_count() {
let flags = size_linker_flags();
assert_eq!(flags.len(), 8);
}
#[test]
fn test_size_linker_flags_contains_all() {
let flags = size_linker_flags();
assert!(flags.contains(&"-Wl,--gc-sections"));
assert!(flags.contains(&"-Wl,--as-needed"));
assert!(flags.contains(&"-Wl,--strip-all"));
assert!(flags.contains(&"-Wl,-O2"));
assert!(flags.contains(&"-Wl,--hash-style=gnu"));
assert!(flags.contains(&"-Wl,--build-id=none"));
assert!(flags.contains(&"-Wl,-z,norelro"));
assert!(flags.contains(&"-static"));
}
}