use alloc::{ format, rc::{Rc, Weak}, str::FromStr, string::{String, ToString}, vec, vec::Vec, }; use core::cell::RefCell; use objdiff_core::{diff, obj}; use regex::{Regex, RegexBuilder}; use xxhash_rust::xxh3::xxh3_64; use super::logging; wit_bindgen::generate!({ world: "api", with: { "wasi:logging/logging@0.1.0-draft": logging::wasi_logging, }, }); use exports::objdiff::core::{ diff::{ DiffConfigBorrow, DiffResult, Guest as GuestDiff, GuestDiffConfig, GuestObject, GuestObjectDiff, Object, ObjectBorrow, ObjectDiff, ObjectDiffBorrow, }, display::{ ContextItem, ContextItemCopy, ContextItemNavigate, DiffText, DiffTextColor, DiffTextOpcode, DiffTextSegment, DiffTextSymbol, DisplayConfig, Guest as GuestDisplay, HoverItem, HoverItemColor, HoverItemText, InstructionDiffKind, InstructionDiffRow, SectionDisplay, SectionDisplaySymbol, SymbolDisplay, SymbolFilter, SymbolFlags, SymbolKind, SymbolNavigationKind, SymbolRef, }, }; struct Component; impl Guest for Component { fn init(level: logging::wasi_logging::Level) { logging::init(level); } fn version() -> String { env!("CARGO_PKG_VERSION").to_string() } } struct ResourceObject(Rc, u64); struct ResourceObjectDiff(Rc, diff::ObjectDiff); #[repr(transparent)] struct ResourceDiffConfig(RefCell); impl GuestDiff for Component { type DiffConfig = ResourceDiffConfig; type Object = ResourceObject; type ObjectDiff = ResourceObjectDiff; fn run_diff( left: Option, right: Option, diff_config: DiffConfigBorrow, ) -> Result { let diff_config = diff_config.get::().0.borrow(); log::debug!("Running diff with config: {:?}", diff_config); let result = diff::diff_objs( left.as_ref().map(|o| o.get::().0.as_ref()), right.as_ref().map(|o| o.get::().0.as_ref()), None, &diff_config, &diff::MappingConfig::default(), ) .map_err(|e| e.to_string())?; Ok(DiffResult { left: result.left.map(|d| { ObjectDiff::new(ResourceObjectDiff( left.unwrap().get::().0.clone(), d, )) }), right: result.right.map(|d| { ObjectDiff::new(ResourceObjectDiff( right.unwrap().get::().0.clone(), d, )) }), }) } } fn build_regex(s: &str) -> Option { if s.is_empty() { return None; } let e = match RegexBuilder::new(s).case_insensitive(true).build() { Ok(regex) => return Some(regex), Err(e) => e, }; // Use the string as a literal if the regex fails to compile let escaped = regex::escape(s); if let Ok(regex) = RegexBuilder::new(&escaped).case_insensitive(true).build() { return Some(regex); } // Use the original error if the escaped string also fails log::warn!("Failed to compile regex: {}", e); None } impl GuestDisplay for Component { fn display_sections( diff: ObjectDiffBorrow, filter: SymbolFilter, config: DisplayConfig, ) -> Vec { let regex = filter.regex.as_deref().and_then(build_regex); let filter = if let Some(mapping) = filter.mapping { diff::display::SymbolFilter::Mapping(mapping as usize, regex.as_ref()) } else if let Some(regex) = ®ex { diff::display::SymbolFilter::Search(regex) } else { diff::display::SymbolFilter::None }; let obj_diff = diff.get::(); diff::display::display_sections( obj_diff.0.as_ref(), &obj_diff.1, filter, config.show_hidden_symbols, config.show_mapped_symbols, config.reverse_fn_order, ) .into_iter() .map(|d| SectionDisplay { id: d.id, name: d.name, size: d.size, match_percent: d.match_percent, symbols: d .symbols .into_iter() .map(|s| SectionDisplaySymbol { symbol: s.symbol as SymbolRef, is_mapping_symbol: s.is_mapping_symbol, }) .collect(), }) .collect() } fn display_symbol( diff: ObjectDiffBorrow, symbol_display: SectionDisplaySymbol, ) -> SymbolDisplay { let obj_diff = diff.get::(); let obj = obj_diff.0.as_ref(); let obj_diff = &obj_diff.1; let symbol_idx = symbol_display.symbol as usize; let Some(symbol) = obj.symbols.get(symbol_idx) else { return SymbolDisplay { name: "".to_string(), ..Default::default() }; }; let symbol_diff = if symbol_display.is_mapping_symbol { obj_diff .mapping_symbols .iter() .find(|s| s.symbol_index == symbol_idx) .map(|s| &s.symbol_diff) } else { obj_diff.symbols.get(symbol_idx) }; SymbolDisplay { name: symbol.name.clone(), demangled_name: symbol.demangled_name.clone(), address: symbol.address, size: symbol.size, kind: SymbolKind::from(symbol.kind), section: symbol.section.map(|s| s as u32), flags: SymbolFlags::from(symbol.flags), align: symbol.align.map(|a| a.get()), virtual_address: symbol.virtual_address, target_symbol: symbol_diff.and_then(|sd| sd.target_symbol.map(|s| s as u32)), match_percent: symbol_diff.and_then(|sd| sd.match_percent), diff_score: symbol_diff.and_then(|sd| sd.diff_score), row_count: symbol_diff.map_or(0, |sd| sd.instruction_rows.len() as u32), } } fn display_instruction_row( diff: ObjectDiffBorrow, symbol_display: SectionDisplaySymbol, row_index: u32, diff_config: DiffConfigBorrow, ) -> InstructionDiffRow { let obj_diff = diff.get::(); let obj = obj_diff.0.as_ref(); let obj_diff = &obj_diff.1; let symbol_idx = symbol_display.symbol as usize; let symbol_diff = if symbol_display.is_mapping_symbol { obj_diff .mapping_symbols .iter() .find(|s| s.symbol_index == symbol_idx) .map(|s| &s.symbol_diff) } else { obj_diff.symbols.get(symbol_idx) }; let Some(row) = symbol_diff.and_then(|sd| sd.instruction_rows.get(row_index as usize)) else { return InstructionDiffRow::default(); }; let diff_config = diff_config.get::().0.borrow(); let mut segments = Vec::with_capacity(16); diff::display::display_row(obj, symbol_idx, row, &diff_config, |segment| { segments.push(DiffTextSegment::from(segment)); Ok(()) }) .unwrap(); InstructionDiffRow { segments, diff_kind: InstructionDiffKind::from(row.kind) } } fn symbol_context( diff: ObjectDiffBorrow, symbol_display: SectionDisplaySymbol, ) -> Vec { let obj_diff = diff.get::(); let obj = obj_diff.0.as_ref(); diff::display::symbol_context(obj, symbol_display.symbol as usize) .into_iter() .map(|item| ContextItem::from(item)) .collect() } fn symbol_hover( diff: ObjectDiffBorrow, symbol_display: SectionDisplaySymbol, ) -> Vec { let obj_diff = diff.get::(); let obj = obj_diff.0.as_ref(); let addend = 0; // TODO let override_color = None; // TODO: colorize replaced/deleted/inserted relocations diff::display::symbol_hover(obj, symbol_display.symbol as usize, addend, override_color) .into_iter() .map(|item| HoverItem::from(item)) .collect() } fn instruction_context( diff: ObjectDiffBorrow, symbol_display: SectionDisplaySymbol, row_index: u32, diff_config: DiffConfigBorrow, ) -> Vec { let obj_diff = diff.get::(); let obj = obj_diff.0.as_ref(); let obj_diff = &obj_diff.1; let symbol_idx = symbol_display.symbol as usize; let symbol_diff = if symbol_display.is_mapping_symbol { obj_diff .mapping_symbols .iter() .find(|s| s.symbol_index == symbol_idx) .map(|s| &s.symbol_diff) } else { obj_diff.symbols.get(symbol_idx) }; let Some(ins_ref) = symbol_diff .and_then(|sd| sd.instruction_rows.get(row_index as usize)) .and_then(|row| row.ins_ref) else { return Vec::new(); }; let diff_config = diff_config.get::().0.borrow(); let Some(resolved) = obj.resolve_instruction_ref(symbol_idx, ins_ref) else { return vec![ContextItem::Copy(ContextItemCopy { value: "Failed to resolve instruction".to_string(), label: Some("error".to_string()), })]; }; let ins = match obj.arch.process_instruction(resolved, &diff_config) { Ok(ins) => ins, Err(e) => { return vec![ContextItem::Copy(ContextItemCopy { value: e.to_string(), label: Some("error".to_string()), })]; } }; diff::display::instruction_context(obj, resolved, &ins) .into_iter() .map(|item| ContextItem::from(item)) .collect() } fn instruction_hover( diff: ObjectDiffBorrow, symbol_display: SectionDisplaySymbol, row_index: u32, diff_config: DiffConfigBorrow, ) -> Vec { let obj_diff = diff.get::(); let obj = obj_diff.0.as_ref(); let obj_diff = &obj_diff.1; let symbol_idx = symbol_display.symbol as usize; let symbol_diff = if symbol_display.is_mapping_symbol { obj_diff .mapping_symbols .iter() .find(|s| s.symbol_index == symbol_idx) .map(|s| &s.symbol_diff) } else { obj_diff.symbols.get(symbol_idx) }; let Some(ins_ref) = symbol_diff .and_then(|sd| sd.instruction_rows.get(row_index as usize)) .and_then(|row| row.ins_ref) else { return Vec::new(); }; let diff_config = diff_config.get::().0.borrow(); let Some(resolved) = obj.resolve_instruction_ref(symbol_idx, ins_ref) else { return vec![HoverItem::Text(HoverItemText { label: "Error".to_string(), value: "Failed to resolve instruction".to_string(), color: HoverItemColor::Delete, })]; }; let ins = match obj.arch.process_instruction(resolved, &diff_config) { Ok(ins) => ins, Err(e) => { return vec![HoverItem::Text(HoverItemText { label: "Error".to_string(), value: e.to_string(), color: HoverItemColor::Delete, })]; } }; diff::display::instruction_hover(obj, resolved, &ins) .into_iter() .map(|item| HoverItem::from(item)) .collect() } } impl From for SymbolKind { fn from(kind: obj::SymbolKind) -> Self { match kind { obj::SymbolKind::Unknown => SymbolKind::Unknown, obj::SymbolKind::Function => SymbolKind::Function, obj::SymbolKind::Object => SymbolKind::Object, obj::SymbolKind::Section => SymbolKind::Section, } } } impl From for SymbolFlags { fn from(flags: obj::SymbolFlagSet) -> SymbolFlags { let mut out = SymbolFlags::empty(); for flag in flags { out |= match flag { obj::SymbolFlag::Global => SymbolFlags::GLOBAL, obj::SymbolFlag::Local => SymbolFlags::LOCAL, obj::SymbolFlag::Weak => SymbolFlags::WEAK, obj::SymbolFlag::Common => SymbolFlags::COMMON, obj::SymbolFlag::Hidden => SymbolFlags::HIDDEN, obj::SymbolFlag::HasExtra => SymbolFlags::HAS_EXTRA, obj::SymbolFlag::SizeInferred => SymbolFlags::SIZE_INFERRED, obj::SymbolFlag::Ignored => SymbolFlags::IGNORED, }; } out } } impl From> for DiffText { fn from(text: diff::display::DiffText) -> Self { match text { diff::display::DiffText::Basic(v) => DiffText::Basic(v.to_string()), diff::display::DiffText::Line(v) => DiffText::Line(v), diff::display::DiffText::Address(v) => DiffText::Address(v), diff::display::DiffText::Opcode(n, op) => { DiffText::Opcode(DiffTextOpcode { mnemonic: n.to_string(), opcode: op }) } diff::display::DiffText::Argument(s) => match s { obj::InstructionArgValue::Signed(v) => DiffText::Signed(v), obj::InstructionArgValue::Unsigned(v) => DiffText::Unsigned(v), obj::InstructionArgValue::Opaque(v) => DiffText::Opaque(v.into_owned()), }, diff::display::DiffText::BranchDest(v) => DiffText::BranchDest(v), diff::display::DiffText::Symbol(s) => DiffText::Symbol(DiffTextSymbol { name: s.name.clone(), demangled_name: s.demangled_name.clone(), }), diff::display::DiffText::Addend(v) => DiffText::Addend(v), diff::display::DiffText::Spacing(v) => DiffText::Spacing(v), diff::display::DiffText::Eol => DiffText::Eol, } } } impl From for DiffTextColor { fn from(value: diff::display::DiffTextColor) -> Self { match value { diff::display::DiffTextColor::Normal => DiffTextColor::Normal, diff::display::DiffTextColor::Dim => DiffTextColor::Dim, diff::display::DiffTextColor::Bright => DiffTextColor::Bright, diff::display::DiffTextColor::Replace => DiffTextColor::Replace, diff::display::DiffTextColor::Delete => DiffTextColor::Delete, diff::display::DiffTextColor::Insert => DiffTextColor::Insert, diff::display::DiffTextColor::Rotating(v) => DiffTextColor::Rotating(v), } } } impl From> for DiffTextSegment { fn from(segment: diff::display::DiffTextSegment) -> Self { DiffTextSegment { text: DiffText::from(segment.text), color: DiffTextColor::from(segment.color), pad_to: segment.pad_to, } } } impl From for InstructionDiffKind { fn from(kind: diff::InstructionDiffKind) -> Self { match kind { diff::InstructionDiffKind::None => InstructionDiffKind::None, diff::InstructionDiffKind::OpMismatch => InstructionDiffKind::OpMismatch, diff::InstructionDiffKind::ArgMismatch => InstructionDiffKind::ArgMismatch, diff::InstructionDiffKind::Replace => InstructionDiffKind::Replace, diff::InstructionDiffKind::Insert => InstructionDiffKind::Insert, diff::InstructionDiffKind::Delete => InstructionDiffKind::Delete, } } } impl GuestDiffConfig for ResourceDiffConfig { fn new() -> Self { Self(RefCell::new(diff::DiffObjConfig::default())) } fn set_property(&self, key: String, value: String) -> Result<(), String> { let id = diff::ConfigPropertyId::from_str(&key) .map_err(|_| format!("Invalid property key {:?}", key))?; self.0 .borrow_mut() .set_property_value_str(id, &value) .map_err(|_| format!("Invalid property value {:?}", value)) } fn get_property(&self, key: String) -> Result { let id = diff::ConfigPropertyId::from_str(&key) .map_err(|_| format!("Invalid property key {:?}", key))?; Ok(self.0.borrow().get_property_value(id).to_string()) } } struct CachedObject(Weak, u64); struct ObjectCache(RefCell>); impl ObjectCache { #[inline] const fn new() -> Self { Self(RefCell::new(Vec::new())) } } impl core::ops::Deref for ObjectCache { type Target = RefCell>; fn deref(&self) -> &Self::Target { &self.0 } } // Assume single-threaded environment unsafe impl Sync for ObjectCache {} static OBJECT_CACHE: ObjectCache = ObjectCache::new(); impl GuestObject for ResourceObject { fn parse(data: Vec, diff_config: DiffConfigBorrow) -> Result { let hash = xxh3_64(&data); let mut cached = None; OBJECT_CACHE.borrow_mut().retain(|c| { if c.0.strong_count() == 0 { return false; } if c.1 == hash { cached = c.0.upgrade(); } true }); if let Some(obj) = cached { return Ok(Object::new(ResourceObject(obj, hash))); } let diff_config = diff_config.get::().0.borrow(); let obj = Rc::new(obj::read::parse(&data, &diff_config).map_err(|e| e.to_string())?); OBJECT_CACHE.borrow_mut().push(CachedObject(Rc::downgrade(&obj), hash)); Ok(Object::new(ResourceObject(obj, hash))) } fn hash(&self) -> u64 { self.1 } } impl GuestObjectDiff for ResourceObjectDiff { fn find_symbol(&self, name: String, section_name: Option) -> Option { let obj = self.0.as_ref(); obj.symbols .iter() .position(|s| { s.name == name && match section_name.as_deref() { Some(section_name) => { s.section.is_some_and(|n| obj.sections[n].name == section_name) } None => true, } }) .map(|i| i as SymbolRef) } } impl From for HoverItem { fn from(item: diff::display::HoverItem) -> Self { match item { diff::display::HoverItem::Text { label, value, color } => { HoverItem::Text(HoverItemText { label, value, color: HoverItemColor::from(color) }) } diff::display::HoverItem::Separator => HoverItem::Separator, } } } impl From for HoverItemColor { fn from(color: diff::display::HoverItemColor) -> Self { match color { diff::display::HoverItemColor::Normal => HoverItemColor::Normal, diff::display::HoverItemColor::Emphasized => HoverItemColor::Emphasized, diff::display::HoverItemColor::Special => HoverItemColor::Special, diff::display::HoverItemColor::Delete => HoverItemColor::Delete, diff::display::HoverItemColor::Insert => HoverItemColor::Insert, } } } impl From for ContextItem { fn from(item: diff::display::ContextItem) -> Self { match item { diff::display::ContextItem::Copy { value, label } => { ContextItem::Copy(ContextItemCopy { value, label }) } diff::display::ContextItem::Navigate { label, symbol_index, kind } => { ContextItem::Navigate(ContextItemNavigate { label, symbol: symbol_index as SymbolRef, kind: SymbolNavigationKind::from(kind), }) } diff::display::ContextItem::Separator => ContextItem::Separator, } } } impl From for SymbolNavigationKind { fn from(kind: diff::display::SymbolNavigationKind) -> Self { match kind { diff::display::SymbolNavigationKind::Normal => SymbolNavigationKind::Normal, diff::display::SymbolNavigationKind::Extab => SymbolNavigationKind::Extab, } } } impl Default for InstructionDiffKind { fn default() -> Self { Self::None } } impl Default for InstructionDiffRow { fn default() -> Self { Self { segments: Default::default(), diff_kind: Default::default() } } } impl Default for SymbolKind { fn default() -> Self { Self::Unknown } } impl Default for SymbolFlags { fn default() -> Self { Self::empty() } } impl Default for SymbolDisplay { fn default() -> Self { Self { name: Default::default(), demangled_name: Default::default(), address: Default::default(), size: Default::default(), kind: Default::default(), section: Default::default(), flags: Default::default(), align: Default::default(), virtual_address: Default::default(), target_symbol: Default::default(), match_percent: Default::default(), diff_score: Default::default(), row_count: Default::default(), } } } export!(Component);