Skip to content

Commit

Permalink
WIP: Implement module listing for resymc
Browse files Browse the repository at this point in the history
  • Loading branch information
ergrelet committed Mar 5, 2024
1 parent 9566ac9 commit d573c5e
Show file tree
Hide file tree
Showing 5 changed files with 185 additions and 25 deletions.
11 changes: 8 additions & 3 deletions resym/src/resym_app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -379,9 +379,14 @@ impl ResymApp {
log::error!("Failed to update type filter value: {}", err);
}
// Request a module list update
if let Err(err) = self.backend.send_command(
BackendCommand::ListModules(ResymPDBSlots::Main as usize),
) {
if let Err(err) =
self.backend.send_command(BackendCommand::ListModules(
ResymPDBSlots::Main as usize,
String::default(),
false,
false,
))
{
log::error!("Failed to update module list: {}", err);
}
} else if pdb_slot == ResymPDBSlots::Diff as usize {
Expand Down
110 changes: 94 additions & 16 deletions resym_core/src/backend.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,17 +21,17 @@ use std::{path::PathBuf, time::Instant};
#[cfg(all(not(feature = "rayon"), target_arch = "wasm32"))]
use wasm_thread::{self as thread, JoinHandle};

use crate::{diffing::diff_module_by_path, pdb_file::PDBDataSource};
use crate::{
cond_par_iter, cond_sort_by,
diffing::diff_type_by_name,
error::{Result, ResymCoreError},
frontend::FrontendCommand,
frontend::{FrontendController, ModuleList},
par_iter_if_available, par_sort_by_if_available,
pdb_file::PdbFile,
pdb_types::{include_headers_for_flavor, PrimitiveReconstructionFlavor},
PKG_VERSION,
};
use crate::{diffing::diff_module_by_path, pdb_file::PDBDataSource};

pub type PDBSlot = usize;

Expand Down Expand Up @@ -74,7 +74,7 @@ pub enum BackendCommand {
/// and merge the result.
UpdateTypeFilterMerged(Vec<PDBSlot>, String, bool, bool),
/// Retrieve the list of all modules in a given PDB.
ListModules(PDBSlot),
ListModules(PDBSlot, String, bool, bool),
/// Reconstruct a module given its index for a given PDB.
ReconstructModuleByIndex(PDBSlot, usize, PrimitiveReconstructionFlavor, bool),
/// Reconstruct the diff of a type given its name.
Expand Down Expand Up @@ -416,9 +416,19 @@ fn worker_thread_routine(
}
}

BackendCommand::ListModules(pdb_slot) => {
BackendCommand::ListModules(
pdb_slot,
search_filter,
case_insensitive_search,
use_regex,
) => {
if let Some(pdb_file) = pdb_files.get(&pdb_slot) {
let module_list = list_modules_command(&pdb_file.borrow());
let module_list = list_modules_command(
&pdb_file.borrow(),
&search_filter,
case_insensitive_search,
use_regex,
);
frontend_controller
.send_command(FrontendCommand::UpdateModuleList(module_list))?;
}
Expand Down Expand Up @@ -545,13 +555,6 @@ where
}
}

fn list_modules_command<'p, T>(pdb_file: &PdbFile<'p, T>) -> Result<ModuleList>
where
T: io::Seek + io::Read + std::fmt::Debug + 'p,
{
pdb_file.module_list()
}

fn reconstruct_module_by_index_command<'p, T>(
pdb_file: &mut PdbFile<'p, T>,
module_index: usize,
Expand Down Expand Up @@ -630,7 +633,7 @@ where
if sort_by_index {
// Order types by type index, so the order is deterministic
// (i.e., independent from DashMap's hash function)
cond_sort_by!(filtered_type_list, |lhs, rhs| lhs.1.cmp(&rhs.1));
par_sort_by_if_available!(filtered_type_list, |lhs, rhs| lhs.1.cmp(&rhs.1));
}

log::debug!(
Expand All @@ -653,7 +656,7 @@ fn filter_types_regex(
{
// In case of error, return an empty result
Err(_) => vec![],
Ok(regex) => cond_par_iter!(type_list)
Ok(regex) => par_iter_if_available!(type_list)
.filter(|r| regex.find(&r.0).is_some())
.cloned()
.collect(),
Expand All @@ -668,12 +671,87 @@ fn filter_types_regular(
) -> Vec<(String, pdb::TypeIndex)> {
if case_insensitive_search {
let search_filter = search_filter.to_lowercase();
cond_par_iter!(type_list)
par_iter_if_available!(type_list)
.filter(|r| r.0.to_lowercase().contains(&search_filter))
.cloned()
.collect()
} else {
par_iter_if_available!(type_list)
.filter(|r| r.0.contains(search_filter))
.cloned()
.collect()
}
}

fn list_modules_command<'p, T>(
pdb_file: &PdbFile<'p, T>,
search_filter: &str,
case_insensitive_search: bool,
use_regex: bool,
) -> Result<ModuleList>
where
T: io::Seek + io::Read + std::fmt::Debug + 'p,
{
let filter_start = Instant::now();

let filtered_module_list = if search_filter.is_empty() {
// No need to filter
pdb_file.module_list()?
} else if use_regex {
filter_modules_regex(
&pdb_file.module_list()?,
search_filter,
case_insensitive_search,
)
} else {
filter_modules_regular(
&pdb_file.module_list()?,
search_filter,
case_insensitive_search,
)
};

log::debug!(
"Module filtering took {} ms",
filter_start.elapsed().as_millis()
);

Ok(filtered_module_list)
}

/// Filter module list with a regular expression
fn filter_modules_regex(
module_list: &[(String, usize)],
search_filter: &str,
case_insensitive_search: bool,
) -> Vec<(String, usize)> {
match regex::RegexBuilder::new(search_filter)
.case_insensitive(case_insensitive_search)
.build()
{
// In case of error, return an empty result
Err(_) => vec![],
Ok(regex) => par_iter_if_available!(module_list)
.filter(|r| regex.find(&r.0).is_some())
.cloned()
.collect(),
}
}

/// Filter module list with a plain (sub-)string
fn filter_modules_regular(
module_list: &[(String, usize)],
search_filter: &str,
case_insensitive_search: bool,
) -> Vec<(String, usize)> {
if case_insensitive_search {
let search_filter = search_filter.to_lowercase();
par_iter_if_available!(module_list)
.filter(|r| r.0.to_lowercase().contains(&search_filter))
.cloned()
.collect()
} else {
cond_par_iter!(type_list)
par_iter_if_available!(module_list)
.filter(|r| r.0.contains(search_filter))
.cloned()
.collect()
Expand Down
8 changes: 4 additions & 4 deletions resym_core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,14 @@ const PKG_VERSION: &str = env!("CARGO_PKG_VERSION");
/// Macro used to switch between iterators depending on rayon's availability
#[macro_export]
#[cfg(not(feature = "rayon"))]
macro_rules! cond_par_iter {
macro_rules! par_iter_if_available {
($expression:expr) => {
$expression.iter()
};
}
#[macro_export]
#[cfg(feature = "rayon")]
macro_rules! cond_par_iter {
macro_rules! par_iter_if_available {
($expression:expr) => {
$expression.par_iter()
};
Expand All @@ -29,14 +29,14 @@ macro_rules! cond_par_iter {
/// Macro used to switch between functions depending on rayon's availability
#[macro_export]
#[cfg(not(feature = "rayon"))]
macro_rules! cond_sort_by {
macro_rules! par_sort_by_if_available {
($expression:expr, $($x:tt)*) => {
$expression.sort_by($($x)*)
};
}
#[macro_export]
#[cfg(feature = "rayon")]
macro_rules! cond_sort_by {
macro_rules! par_sort_by_if_available {
($expression:expr, $($x:tt)*) => {
$expression.par_sort_by($($x)*)
};
Expand Down
4 changes: 2 additions & 2 deletions resym_core/src/pdb_file.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@ use std::{
use std::{fs::File, path::Path, time::Instant};

use crate::{
cond_par_iter,
error::{Result, ResymCoreError},
frontend::ModuleList,
par_iter_if_available,
pdb_types::{
self, is_unnamed_type, type_name, DataFormatConfiguration, PrimitiveReconstructionFlavor,
},
Expand Down Expand Up @@ -216,7 +216,7 @@ where

// Resolve forwarder references to their corresponding complete type, in parallel
let fwd_start = Instant::now();
cond_par_iter!(forwarders).for_each(|(fwd_name, fwd_type_id)| {
par_iter_if_available!(forwarders).for_each(|(fwd_name, fwd_type_id)| {
if let Some(complete_type_index) = complete_symbol_map.get(fwd_name) {
self.forwarder_to_complete_type
.insert(*fwd_type_id, *complete_type_index);
Expand Down
77 changes: 77 additions & 0 deletions resymc/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,19 @@ fn main() -> Result<()> {
highlight_syntax,
output_file_path,
),
ResymOptions::ListModules {
pdb_path,
module_path_filter,
output_file_path,
case_insensitive,
use_regex,
} => app.list_modules_command(
pdb_path,
module_path_filter,
case_insensitive,
use_regex,
output_file_path,
),
}
}

Expand Down Expand Up @@ -191,6 +204,21 @@ enum ResymOptions {
#[structopt(short = "H", long)]
highlight_syntax: bool,
},
/// List modules from a given PDB file
ListModules {
/// Path to the PDB file
pdb_path: PathBuf,
/// Search filter
module_path_filter: String,
/// Path of the output file
output_file_path: Option<PathBuf>,
/// Do not match case
#[structopt(short = "i", long)]
case_insensitive: bool,
/// Use regular expressions
#[structopt(short = "r", long)]
use_regex: bool,
},
}

/// Struct that represents our CLI application.
Expand Down Expand Up @@ -432,6 +460,55 @@ impl ResymcApp {
Err(anyhow!("Invalid response received from the backend?"))
}
}

fn list_modules_command(
&self,
pdb_path: PathBuf,
module_path_filter: String,
case_insensitive: bool,
use_regex: bool,
output_file_path: Option<PathBuf>,
) -> Result<()> {
// Request the backend to load the PDB
self.backend
.send_command(BackendCommand::LoadPDBFromPath(PDB_MAIN_SLOT, pdb_path))?;
// Wait for the backend to finish loading the PDB
if let FrontendCommand::LoadPDBResult(result) = self.frontend_controller.rx_ui.recv()? {
if let Err(err) = result {
return Err(anyhow!("Failed to load PDB: {}", err));
}
} else {
return Err(anyhow!("Invalid response received from the backend?"));
}

// Queue a request for the backend to return the list of all modules
self.backend.send_command(BackendCommand::ListModules(
PDB_MAIN_SLOT,
module_path_filter,
case_insensitive,
use_regex,
))?;
// Wait for the backend to finish listing modules
if let FrontendCommand::UpdateModuleList(module_list) =
self.frontend_controller.rx_ui.recv()?
{
// Dump output
let module_list = module_list?;
if let Some(output_file_path) = output_file_path {
let mut output_file = File::create(output_file_path)?;
for (module_path, _) in module_list {
writeln!(output_file, "{}", &module_path)?;
}
} else {
for (module_path, _) in module_list {
println!("{module_path}");
}
}
Ok(())
} else {
Err(anyhow!("Invalid response received from the backend?"))
}
}
}

#[cfg(test)]
Expand Down

0 comments on commit d573c5e

Please sign in to comment.