use std::fmt;
use std::hash::{BuildHasher, RandomState};
use std::io::Write;
use hashbrown::HashTable;
use crate::model::{AddressRange, FileEntry, FileIndex, Function};
use crate::validation::validate_for_builder;
use crate::writer::WriterOptions;
use crate::{Error, GsymVersion, Result};
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
#[non_exhaustive]
pub enum FunctionSetPolicy {
#[default]
Deduplicate,
MergeEqualRanges,
Preserve,
}
#[derive(Clone, Eq, PartialEq)]
pub struct BuilderOptions {
pub writer: WriterOptions,
pub executable_ranges: Box<[AddressRange]>,
pub repair_zero_sized_functions: bool,
pub merge_equal_address_functions: bool,
}
impl fmt::Debug for BuilderOptions {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_struct("BuilderOptions")
.field("writer", &self.writer)
.field("executable_range_count", &self.executable_ranges.len())
.field(
"repair_zero_sized_functions",
&self.repair_zero_sized_functions,
)
.field(
"merge_equal_address_functions",
&self.merge_equal_address_functions,
)
.finish()
}
}
impl Default for BuilderOptions {
fn default() -> Self {
Self {
writer: WriterOptions::default(),
executable_ranges: Box::default(),
repair_zero_sized_functions: true,
merge_equal_address_functions: false,
}
}
}
pub struct GsymBuilder {
options: BuilderOptions,
function_set: FunctionSetPolicy,
files: Vec<FileEntry>,
file_index: HashTable<FileSlot>,
hasher: RandomState,
functions: Vec<Function>,
}
#[derive(Clone, Copy, Debug)]
struct FileSlot {
index: u32,
hash: u64,
}
fn interned_file(files: &[FileEntry], index: u32) -> Option<&FileEntry> {
files.get(usize::try_from(index).ok()?)
}
impl fmt::Debug for GsymBuilder {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_struct("GsymBuilder")
.field("options", &self.options)
.field("file_count", &self.files.len())
.field("function_count", &self.functions.len())
.finish_non_exhaustive()
}
}
impl Default for GsymBuilder {
fn default() -> Self {
Self::new()
}
}
impl GsymBuilder {
#[must_use]
pub fn new() -> Self {
let hasher = RandomState::new();
let empty = FileEntry::default();
let slot = FileSlot {
index: 0,
hash: hasher.hash_one(&empty),
};
let mut file_index = HashTable::new();
file_index.insert_unique(slot.hash, slot, |slot| slot.hash);
Self {
options: BuilderOptions::default(),
function_set: FunctionSetPolicy::Deduplicate,
files: vec![empty],
file_index,
hasher,
functions: Vec::new(),
}
}
#[must_use]
pub fn with_options(options: BuilderOptions) -> Self {
let mut builder = Self::new();
builder.function_set = if options.merge_equal_address_functions {
FunctionSetPolicy::MergeEqualRanges
} else {
FunctionSetPolicy::Deduplicate
};
builder.options = options;
builder
}
#[must_use]
pub const fn options(&self) -> &BuilderOptions {
&self.options
}
#[must_use]
pub const fn version(mut self, version: GsymVersion) -> Self {
self.options.writer.version = version;
self
}
#[must_use]
pub const fn endian(mut self, endian: crate::Endian) -> Self {
self.options.writer.endian = endian;
self
}
#[must_use]
pub const fn base_address(mut self, address: u64) -> Self {
self.options.writer.base_address = Some(address);
self
}
#[must_use]
pub fn build_id(mut self, build_id: impl Into<Vec<u8>>) -> Self {
self.options.writer.build_id = build_id.into();
self
}
#[must_use]
pub const fn repair_zero_sized_functions(mut self, enabled: bool) -> Self {
self.options.repair_zero_sized_functions = enabled;
self
}
#[must_use]
pub const fn function_set(mut self, policy: FunctionSetPolicy) -> Self {
self.options.merge_equal_address_functions =
matches!(policy, FunctionSetPolicy::MergeEqualRanges);
self.function_set = policy;
self
}
#[must_use]
pub const fn merge_equal_address_functions(mut self, enabled: bool) -> Self {
self.options.merge_equal_address_functions = enabled;
self.function_set = if enabled {
FunctionSetPolicy::MergeEqualRanges
} else {
FunctionSetPolicy::Deduplicate
};
self
}
#[must_use]
pub const fn function_set_policy(&self) -> FunctionSetPolicy {
self.function_set
}
#[must_use]
pub fn executable_ranges(mut self, ranges: impl IntoIterator<Item = AddressRange>) -> Self {
self.options.executable_ranges = ranges.into_iter().collect::<Vec<_>>().into_boxed_slice();
self
}
pub fn add_file(&mut self, file: FileEntry) -> Result<FileIndex> {
let hash = self.hasher.hash_one(&file);
if let Some(slot) = self.file_index.find(hash, |slot| {
interned_file(&self.files, slot.index) == Some(&file)
}) {
return Ok(FileIndex::new(slot.index));
}
let index = u32::try_from(self.files.len()).map_err(|_| Error::Limit {
context: "file table",
value: self.files.len() as u64,
limit: u64::from(u32::MAX),
})?;
self.files.push(file);
self.file_index
.insert_unique(hash, FileSlot { index, hash }, |slot| slot.hash);
Ok(FileIndex::new(index))
}
pub fn add_function(&mut self, function: Function) -> Result<()> {
validate_for_builder(&function)?;
self.functions.push(function);
Ok(())
}
#[must_use]
pub fn files(&self) -> &[FileEntry] {
&self.files
}
#[must_use]
pub fn functions(&self) -> &[Function] {
&self.functions
}
pub fn write_to(self, output: impl Write) -> Result<()> {
crate::writer::write_builder(self, output)
}
pub fn to_bytes(self) -> Result<Vec<u8>> {
crate::writer::encode_builder_to_bytes(self)
}
pub(crate) fn into_parts(
self,
) -> (
BuilderOptions,
FunctionSetPolicy,
Vec<FileEntry>,
Vec<Function>,
) {
(self.options, self.function_set, self.files, self.functions)
}
}