use std::sync::OnceLock;
use crate::{
container::{Arch, Container, Format},
detect::{DetectionMatches, DetectionReport},
error::Result,
inits::{self, InitFunction},
metadata::{self, GcMode},
modules::{self, ModuleMap},
paths::{self, NimblePath},
raises::{self, ExceptionRef},
rtti::symbols::{self as rtti_symbols, RttiSymbol},
shims::{self, EntryShim},
sites::{self, RaiseSite},
stacktrace::{self, StackTraceHarvest},
strings::{v1 as strings_v1, v2 as strings_v2},
};
#[derive(Default)]
struct Cache {
entry_shims: OnceLock<Vec<EntryShim>>,
init_functions: OnceLock<Vec<InitFunction>>,
rtti_symbols: OnceLock<Vec<RttiSymbol>>,
string_literals_v2: OnceLock<Vec<strings_v2::StringLiteral>>,
string_literals_v1: OnceLock<Vec<strings_v1::StringLiteralV1>>,
stack_trace: OnceLock<StackTraceHarvest>,
nimble_paths: OnceLock<Vec<NimblePath>>,
exception_types: OnceLock<Vec<ExceptionRef>>,
raise_sites: OnceLock<Vec<RaiseSite>>,
module_map: OnceLock<ModuleMap>,
}
pub struct NimBinary<'a> {
container: Container<'a>,
detection: DetectionReport,
cache: Cache,
}
impl<'a> NimBinary<'a> {
pub fn from_bytes(bytes: &'a [u8]) -> Result<Self> {
let container = Container::parse(bytes)?;
let detection = DetectionReport::run(&container);
Ok(Self {
container,
detection,
cache: Cache::default(),
})
}
pub fn as_bytes(&self) -> &'a [u8] {
self.container.bytes()
}
pub fn format(&self) -> Format {
self.container.format()
}
pub fn arch(&self) -> Arch {
self.container.arch()
}
pub fn container(&self) -> &Container<'a> {
&self.container
}
pub fn is_nim(&self) -> bool {
self.detection.is_nim
}
pub fn detection(&self) -> &DetectionReport {
&self.detection
}
pub fn detection_matches(&self) -> DetectionMatches {
self.detection.matches
}
pub fn entry_shims(&self) -> &[EntryShim] {
self.cache
.entry_shims
.get_or_init(|| shims::scan(&self.container))
}
pub fn init_functions(&self) -> &[InitFunction] {
self.cache
.init_functions
.get_or_init(|| inits::scan(&self.container))
}
pub fn gc_mode(&self) -> GcMode {
metadata::gc_mode(self.detection.matches)
}
pub fn nim_main_prefix(&self) -> Option<&str> {
metadata::nim_main_prefix(&self.container)
}
pub fn rtti_symbols(&self) -> &[RttiSymbol] {
self.cache
.rtti_symbols
.get_or_init(|| rtti_symbols::scan(&self.container))
}
pub fn string_literals_v2(&self) -> &[strings_v2::StringLiteral] {
self.cache
.string_literals_v2
.get_or_init(|| strings_v2::scan(&self.container))
}
pub fn string_literals_v1(&self) -> &[strings_v1::StringLiteralV1] {
self.cache
.string_literals_v1
.get_or_init(|| strings_v1::scan(&self.container))
}
pub fn stack_trace(&self) -> &StackTraceHarvest {
self.cache
.stack_trace
.get_or_init(|| stacktrace::harvest(&self.container))
}
pub fn nimble_paths(&self) -> &[NimblePath] {
self.cache
.nimble_paths
.get_or_init(|| paths::scan(&self.container))
}
pub fn exception_types(&self) -> &[ExceptionRef] {
self.cache
.exception_types
.get_or_init(|| raises::scan(&self.container))
}
pub fn raise_sites(&self) -> &[RaiseSite] {
self.cache
.raise_sites
.get_or_init(|| sites::scan(&self.container))
}
pub fn module_map(&self) -> &ModuleMap {
self.cache
.module_map
.get_or_init(|| modules::build(&self.container))
}
pub fn image_base(&self) -> u64 {
self.container.image_base()
}
pub fn shim_rva(&self, shim: &EntryShim) -> Option<u64> {
self.container.va_to_rva(shim.address)
}
pub fn init_rva(&self, init: &InitFunction) -> Option<u64> {
self.container.va_to_rva(init.address)
}
pub fn raise_rva(&self, site: &RaiseSite) -> Option<u64> {
self.container.va_to_rva(site.call_addr)
}
pub fn rtti_rva(&self, sym: &RttiSymbol) -> Option<u64> {
self.container.va_to_rva(sym.address)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn from_bytes_rejects_garbage() {
let result = NimBinary::from_bytes(b"not an elf or pe or macho");
assert!(result.is_err());
}
}