Merge pull request #71 from ergrelet/symbol_kind_filtering

Allow filtering types and symbols by kind in resym
This commit is contained in:
Erwan Grelet
2025-02-08 18:21:26 -05:00
committed by GitHub
11 changed files with 679 additions and 208 deletions
+1
View File
@@ -8,6 +8,7 @@
- Add 4 commands to `resymc`: `list-symbols`, `dump-symbol`, `dump-all-symbols` and `diff-symbol`
- Add a `MSVC` primitive type representations (@HaydnTrigg)
- Add a setting to switch display of integer values between decimal and hexadecimal (@HaydnTrigg)
- Add new `classes`, `unions` and `enums` filters for type searches
### Changed
Generated
+189 -2
View File
@@ -127,6 +127,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011"
dependencies = [
"cfg-if",
"const-random",
"getrandom 0.2.15",
"once_cell",
"serde",
@@ -143,6 +144,12 @@ dependencies = [
"memchr",
]
[[package]]
name = "allocator-api2"
version = "0.2.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
[[package]]
name = "android-activity"
version = "0.6.0"
@@ -230,6 +237,17 @@ dependencies = [
"libloading",
]
[[package]]
name = "assert_type_match"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f548ad2c4031f2902e3edc1f29c29e835829437de49562d8eb5dc5584d3a1043"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.98",
]
[[package]]
name = "async-broadcast"
version = "0.7.2"
@@ -489,6 +507,81 @@ version = "0.22.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
[[package]]
name = "bevy_macro_utils"
version = "0.15.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "090371a2cd85574989febff6063a21d1fbbc2939e80f00fe075f62aa8e616136"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.98",
"toml_edit",
]
[[package]]
name = "bevy_ptr"
version = "0.15.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4da2111eefa2000ea8c9dc1beee2eb7283b29b5ef90a29fe43c748df549f84ad"
[[package]]
name = "bevy_reflect"
version = "0.15.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "82af24a68fd8feff476d9672ff34d220d3f45e95ef2f2324e7cb674614d18138"
dependencies = [
"assert_type_match",
"bevy_ptr",
"bevy_reflect_derive",
"bevy_utils",
"derive_more",
"disqualified",
"downcast-rs",
"erased-serde",
"serde",
"smallvec",
]
[[package]]
name = "bevy_reflect_derive"
version = "0.15.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8369e6e779ab3540f9dcd93d062139f62551b3d2fe1ab451c6ddf74757e22ccd"
dependencies = [
"bevy_macro_utils",
"proc-macro2",
"quote",
"syn 2.0.98",
"uuid",
]
[[package]]
name = "bevy_utils"
version = "0.15.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2993cac374b3f88cfaf59506c71f8e3e7ad8b4961f4e9864bc76e1c9e1e4400c"
dependencies = [
"ahash",
"bevy_utils_proc_macros",
"getrandom 0.2.15",
"hashbrown 0.14.5",
"thread_local",
"tracing",
"web-time",
]
[[package]]
name = "bevy_utils_proc_macros"
version = "0.15.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2606f79dfe359a88e2a59bb6cd632cd42e9d4bcd250ac8bc3a4e7657e82f4f39"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.98",
]
[[package]]
name = "bincode"
version = "1.3.3"
@@ -791,6 +884,26 @@ dependencies = [
"wasm-bindgen",
]
[[package]]
name = "const-random"
version = "0.1.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87e00182fe74b066627d63b85fd550ac2998d4b0bd86bfed477a0ae4c7c71359"
dependencies = [
"const-random-macro",
]
[[package]]
name = "const-random-macro"
version = "0.1.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e"
dependencies = [
"getrandom 0.2.15",
"once_cell",
"tiny-keccak",
]
[[package]]
name = "core-foundation"
version = "0.9.4"
@@ -938,6 +1051,27 @@ dependencies = [
"powerfmt",
]
[[package]]
name = "derive_more"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4a9b99b9cbbe49445b21764dc0625032a89b145a2642e67603e1c936f5458d05"
dependencies = [
"derive_more-impl",
]
[[package]]
name = "derive_more-impl"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.98",
"unicode-xid",
]
[[package]]
name = "digest"
version = "0.10.7"
@@ -965,6 +1099,12 @@ dependencies = [
"syn 2.0.98",
]
[[package]]
name = "disqualified"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c9c272297e804878a2a4b707cfcfc6d2328b5bb936944613b4fdf2b9269afdfd"
[[package]]
name = "dlib"
version = "0.5.2"
@@ -1240,6 +1380,16 @@ version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
[[package]]
name = "erased-serde"
version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24e2389d65ab4fab27dc2a5de7b191e1f6617d1f1c8855c0dc569c94a4cbb18d"
dependencies = [
"serde",
"typeid",
]
[[package]]
name = "errno"
version = "0.3.10"
@@ -1576,8 +1726,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7"
dependencies = [
"cfg-if",
"js-sys",
"libc",
"wasi 0.11.0+wasi-snapshot-preview1",
"wasm-bindgen",
]
[[package]]
@@ -1797,6 +1949,11 @@ name = "hashbrown"
version = "0.14.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
dependencies = [
"ahash",
"allocator-api2",
"serde",
]
[[package]]
name = "hashbrown"
@@ -3199,6 +3356,7 @@ version = "0.4.0"
dependencies = [
"ahash",
"anyhow",
"bevy_reflect",
"console_error_panic_hook",
"crossbeam-channel",
"eframe",
@@ -3218,6 +3376,7 @@ dependencies = [
name = "resym_core"
version = "0.4.0"
dependencies = [
"bevy_reflect",
"crossbeam-channel",
"dashmap",
"ehttp",
@@ -3829,6 +3988,16 @@ dependencies = [
"syn 2.0.98",
]
[[package]]
name = "thread_local"
version = "1.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c"
dependencies = [
"cfg-if",
"once_cell",
]
[[package]]
name = "tiff"
version = "0.9.1"
@@ -3871,6 +4040,15 @@ dependencies = [
"time-core",
]
[[package]]
name = "tiny-keccak"
version = "2.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237"
dependencies = [
"crunchy",
]
[[package]]
name = "tiny-skia"
version = "0.11.4"
@@ -4005,6 +4183,12 @@ dependencies = [
"rustc-hash",
]
[[package]]
name = "typeid"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e13db2e0ccd5e14a544e8a246ba2312cd25223f616442d7f2cb0e3db614236e"
[[package]]
name = "typenum"
version = "1.17.0"
@@ -4093,9 +4277,12 @@ checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
[[package]]
name = "uuid"
version = "1.13.1"
version = "1.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ced87ca4be083373936a67f8de945faa23b6b42384bd5b64434850802c6dccd0"
checksum = "b3758f5e68192bb96cc8f9b7e2c2cfdabb435499a28499a42f8f984092adad4b"
dependencies = [
"getrandom 0.2.15",
]
[[package]]
name = "version-compare"
+1
View File
@@ -39,6 +39,7 @@ ahash = { version = "0.8", default-features = false, features = [
"no-rng", # we don't need DOS-protection, so we let users opt-in to it instead
"std",
] }
bevy_reflect = "0.15"
# Non-Web
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
+97 -58
View File
@@ -2,7 +2,7 @@ use anyhow::Result;
use eframe::egui;
use memory_logger::blocking::MemoryLogger;
use resym_core::{
backend::{Backend, BackendCommand, PDBSlot},
backend::{Backend, BackendCommand, PDBSlot, SymbolFilters, TypeFilters},
frontend::FrontendCommand,
pdb_file::{SymbolIndex, TypeIndex},
};
@@ -20,7 +20,7 @@ use crate::{
settings::ResymAppSettings,
ui_components::{
CodeViewComponent, ConsoleComponent, IndexListComponent, IndexListOrdering,
ModuleTreeComponent, SettingsComponent, TextSearchComponent,
ModuleTreeComponent, SearchFiltersComponent, SettingsComponent, TextSearchComponent,
},
};
@@ -64,9 +64,11 @@ pub struct ResymApp {
// Components used in the left-side panel
left_panel_selected_tab: LeftPanelTab,
type_search: TextSearchComponent,
type_filters: SearchFiltersComponent<TypeFilters>,
type_list: IndexListComponent<TypeIndex>,
selected_type_index: Option<TypeIndex>,
symbol_search: TextSearchComponent,
symbol_filters: SearchFiltersComponent<SymbolFilters>,
symbol_list: IndexListComponent<SymbolIndex>,
selected_symbol_index: Option<SymbolIndex>,
module_search: TextSearchComponent,
@@ -164,9 +166,11 @@ impl ResymApp {
current_mode: ResymAppMode::Idle,
left_panel_selected_tab: LeftPanelTab::TypeSearch,
type_search: TextSearchComponent::new(),
type_filters: SearchFiltersComponent::new("Search filters"),
type_list: IndexListComponent::new(IndexListOrdering::Alphabetical),
selected_type_index: None,
symbol_search: TextSearchComponent::new(),
symbol_filters: SearchFiltersComponent::new("Search filters"),
symbol_list: IndexListComponent::new(IndexListOrdering::Alphabetical),
selected_symbol_index: None,
module_search: TextSearchComponent::new(),
@@ -232,37 +236,53 @@ impl ResymApp {
match self.left_panel_selected_tab {
LeftPanelTab::TypeSearch => {
let update_type_list =
|search_query: &str, search_filters: &TypeFilters| {
// Update filtered list if filter has changed
let result = if let ResymAppMode::Comparing(..) = self.current_mode
{
self.backend.send_command(BackendCommand::ListTypesMerged(
vec![
ResymPDBSlots::Main as usize,
ResymPDBSlots::Diff as usize,
],
search_query.to_string(),
self.settings.app_settings.search_case_insensitive,
self.settings.app_settings.search_use_regex,
self.settings.app_settings.ignore_std_types,
search_filters.clone(),
))
} else {
self.backend.send_command(BackendCommand::ListTypes(
ResymPDBSlots::Main as usize,
search_query.to_string(),
self.settings.app_settings.search_case_insensitive,
self.settings.app_settings.search_use_regex,
self.settings.app_settings.ignore_std_types,
search_filters.clone(),
))
};
if let Err(err) = result {
log::error!("Failed to update type filter value: {}", err);
}
};
// Callback run when the search query changes
let on_query_update = |search_query: &str| {
// Update filtered list if filter has changed
let result = if let ResymAppMode::Comparing(..) = self.current_mode {
self.backend.send_command(BackendCommand::ListTypesMerged(
vec![
ResymPDBSlots::Main as usize,
ResymPDBSlots::Diff as usize,
],
search_query.to_string(),
self.settings.app_settings.search_case_insensitive,
self.settings.app_settings.search_use_regex,
self.settings.app_settings.ignore_std_types,
))
} else {
self.backend.send_command(BackendCommand::ListTypes(
ResymPDBSlots::Main as usize,
search_query.to_string(),
self.settings.app_settings.search_case_insensitive,
self.settings.app_settings.search_use_regex,
self.settings.app_settings.ignore_std_types,
))
};
if let Err(err) = result {
log::error!("Failed to update type filter value: {}", err);
}
let search_filters = self.type_filters.filters();
update_type_list(search_query, search_filters);
};
// Update the type search bar
ui.label("Search");
self.type_search.update(ui, &on_query_update);
// Callback run when the search filter is updated
let on_filter_update = |search_filters: &TypeFilters| {
let search_query = self.type_search.search_filter();
update_type_list(search_query, search_filters);
};
self.type_filters.update(ui, &on_filter_update);
ui.separator();
ui.add_space(4.0);
@@ -313,37 +333,53 @@ impl ResymApp {
}
LeftPanelTab::SymbolSearch => {
// Callback run when the search query changes
let on_query_update = |search_query: &str| {
// Update filtered list if filter has changed
let result = if let ResymAppMode::Comparing(..) = self.current_mode {
self.backend.send_command(BackendCommand::ListSymbolsMerged(
vec![
let update_symbol_list =
|search_query: &str, search_filters: &SymbolFilters| {
// Update filtered list if filter has changed
let result = if let ResymAppMode::Comparing(..) = self.current_mode
{
self.backend.send_command(BackendCommand::ListSymbolsMerged(
vec![
ResymPDBSlots::Main as usize,
ResymPDBSlots::Diff as usize,
],
search_query.to_string(),
self.settings.app_settings.search_case_insensitive,
self.settings.app_settings.search_use_regex,
self.settings.app_settings.ignore_std_types,
search_filters.clone(),
))
} else {
self.backend.send_command(BackendCommand::ListSymbols(
ResymPDBSlots::Main as usize,
ResymPDBSlots::Diff as usize,
],
search_query.to_string(),
self.settings.app_settings.search_case_insensitive,
self.settings.app_settings.search_use_regex,
self.settings.app_settings.ignore_std_types,
))
} else {
self.backend.send_command(BackendCommand::ListSymbols(
ResymPDBSlots::Main as usize,
search_query.to_string(),
self.settings.app_settings.search_case_insensitive,
self.settings.app_settings.search_use_regex,
self.settings.app_settings.ignore_std_types,
))
search_query.to_string(),
self.settings.app_settings.search_case_insensitive,
self.settings.app_settings.search_use_regex,
self.settings.app_settings.ignore_std_types,
search_filters.clone(),
))
};
if let Err(err) = result {
log::error!("Failed to update type filter value: {}", err);
}
};
if let Err(err) = result {
log::error!("Failed to update type filter value: {}", err);
}
// Callback run when the search query is updated
let on_query_update = |search_query: &str| {
let search_filters = self.symbol_filters.filters();
update_symbol_list(search_query, search_filters);
};
// Update the symbol search bar
ui.label("Search");
self.symbol_search.update(ui, &on_query_update);
// Callback run when the search filter is updated
let on_filter_update = |search_filters: &SymbolFilters| {
let search_query = self.symbol_search.search_filter();
update_symbol_list(search_query, search_filters);
};
self.symbol_filters.update(ui, &on_filter_update);
ui.separator();
ui.add_space(4.0);
@@ -659,10 +695,11 @@ impl ResymApp {
// Request a type list update
if let Err(err) = self.backend.send_command(BackendCommand::ListTypes(
ResymPDBSlots::Main as usize,
String::default(),
Default::default(),
false,
false,
self.settings.app_settings.ignore_std_types,
Default::default(),
)) {
log::error!("Failed to update type filter value: {}", err);
}
@@ -670,10 +707,11 @@ impl ResymApp {
if let Err(err) =
self.backend.send_command(BackendCommand::ListSymbols(
ResymPDBSlots::Main as usize,
String::default(),
Default::default(),
false,
false,
self.settings.app_settings.ignore_std_types,
Default::default(),
))
{
log::error!("Failed to update type filter value: {}", err);
@@ -682,7 +720,7 @@ impl ResymApp {
if let Err(err) =
self.backend.send_command(BackendCommand::ListModules(
ResymPDBSlots::Main as usize,
String::default(),
Default::default(),
false,
false,
))
@@ -692,11 +730,11 @@ impl ResymApp {
} else if pdb_slot == ResymPDBSlots::Diff as usize {
// Reset current mode
self.current_mode = ResymAppMode::Comparing(
String::default(),
String::default(),
Default::default(),
Default::default(),
0,
vec![],
String::default(),
Default::default(),
Default::default(),
);
// Reset selected type
self.selected_type_index = None;
@@ -711,10 +749,11 @@ impl ResymApp {
ResymPDBSlots::Main as usize,
ResymPDBSlots::Diff as usize,
],
String::default(),
Default::default(),
false,
false,
self.settings.app_settings.ignore_std_types,
Default::default(),
))
{
log::error!("Failed to update type filter value: {}", err);
+2
View File
@@ -4,6 +4,7 @@ mod index_list;
mod module_tree;
#[cfg(feature = "http")]
mod open_url;
mod search_filters;
mod settings;
mod text_search;
@@ -13,5 +14,6 @@ pub use index_list::*;
pub use module_tree::*;
#[cfg(feature = "http")]
pub use open_url::*;
pub use search_filters::*;
pub use settings::*;
pub use text_search::*;
+49
View File
@@ -0,0 +1,49 @@
use bevy_reflect::{Reflect, Struct};
use eframe::egui;
pub struct SearchFiltersComponent<FilterType> {
header: String,
filters: FilterType,
}
impl<FilterType: Default + Clone + Reflect + Struct> SearchFiltersComponent<FilterType> {
pub fn new(header: &str) -> Self {
Self {
header: header.into(),
filters: FilterType::default(),
}
}
pub fn filters(&self) -> &FilterType {
&self.filters
}
/// Update/render the UI component
pub fn update<CB: Fn(&FilterType)>(&mut self, ui: &mut egui::Ui, on_filter_update: &CB) {
ui.collapsing(&self.header, |ui| {
ui.horizontal(|ui| {
let field_count = self.filters.field_len();
// Iterate over the struct's fields
for i in 0..field_count {
// Get current field name as a string
let field_name = self
.filters
.name_at(i)
.expect("name_at should succeed")
.to_string();
// Get mutable ref (as bool) to the current field
let field_value = self
.filters
.field_at_mut(i)
.expect("field_at_mut should succeed")
.try_downcast_mut::<bool>()
.expect("filter field should be bool");
if ui.checkbox(field_value, field_name).changed() {
on_filter_update(&self.filters);
}
}
});
});
}
}
+5 -1
View File
@@ -11,10 +11,14 @@ impl TextSearchComponent {
}
}
pub fn search_filter(&self) -> &str {
self.search_filter.as_str()
}
/// Update/render the UI component
pub fn update<CB: Fn(&str)>(&mut self, ui: &mut egui::Ui, on_query_update: &CB) {
if ui.text_edit_singleline(&mut self.search_filter).changed() {
on_query_update(self.search_filter.as_str());
on_query_update(self.search_filter());
}
}
}
+1
View File
@@ -27,6 +27,7 @@ similar = "2.4"
ehttp = { version = "0.5", optional = true }
url = { version = "2.5", optional = true }
msvc-demangler = "0.10"
bevy_reflect = "0.15"
# Web:
[target.'cfg(target_arch = "wasm32")'.dependencies]
+166 -69
View File
@@ -1,3 +1,4 @@
use bevy_reflect::Reflect;
use crossbeam_channel::{Receiver, Sender};
#[cfg(target_arch = "wasm32")]
use instant::Instant;
@@ -7,6 +8,8 @@ use rayon::{
prelude::ParallelSliceMut,
ThreadPool,
};
#[cfg(all(not(feature = "rayon"), target_arch = "wasm32"))]
use wasm_thread::{self as thread, JoinHandle};
use core::fmt;
#[cfg(all(not(feature = "rayon"), not(target_arch = "wasm32")))]
@@ -18,15 +21,16 @@ use std::{
};
#[cfg(not(target_arch = "wasm32"))]
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, diff_symbol_by_name, diff_type_by_name},
error::{Result, ResymCoreError},
frontend::{FrontendCommand, FrontendController, ReconstructedType},
par_iter_if_available, par_sort_by_if_available,
pdb_file::{self, ModuleList, PDBDataSource, PdbFile, SymbolList, SymbolListView, TypeList},
pdb_file::{
self, ModuleInfo, ModuleList, PDBDataSource, PdbFile, SymbolInfoEx, SymbolKind, SymbolList,
SymbolListExView, TypeInfoEx, TypeKind, TypeList, TypeListExView,
},
pdb_types::{include_headers_for_flavor, PrimitiveReconstructionFlavor},
PKG_VERSION,
};
@@ -78,16 +82,16 @@ pub enum BackendCommand {
bool,
),
/// Retrieve a list of types that match the given filter for a given PDB.
ListTypes(PDBSlot, String, bool, bool, bool),
ListTypes(PDBSlot, String, bool, bool, bool, TypeFilters),
/// Retrieve a list of types that match the given filter for multiple PDBs
/// and merge the result.
ListTypesMerged(Vec<PDBSlot>, String, bool, bool, bool),
ListTypesMerged(Vec<PDBSlot>, String, bool, bool, bool, TypeFilters),
/// Retrieve a list of symbols that match the given filter for multiple PDBs
/// and merge the result.
ListSymbols(PDBSlot, String, bool, bool, bool),
ListSymbols(PDBSlot, String, bool, bool, bool, SymbolFilters),
/// Retrieve a list of symbols that match the given filter for multiple PDBs
/// and merge the result.
ListSymbolsMerged(Vec<PDBSlot>, String, bool, bool, bool),
ListSymbolsMerged(Vec<PDBSlot>, String, bool, bool, bool, SymbolFilters),
/// Reconstruct a symbol given its index for a given PDB.
ReconstructSymbolByIndex(
PDBSlot,
@@ -139,6 +143,42 @@ pub enum BackendCommand {
ListTypeCrossReferences(PDBSlot, pdb_file::TypeIndex),
}
/// Search filters for types
#[derive(Clone, Reflect)]
pub struct TypeFilters {
classes: bool,
unions: bool,
enums: bool,
}
impl Default for TypeFilters {
fn default() -> Self {
Self {
classes: true,
unions: true,
enums: true,
}
}
}
/// Search filters for symbols
#[derive(Clone, Reflect)]
pub struct SymbolFilters {
functions: bool,
variables: bool,
types: bool,
}
impl Default for SymbolFilters {
fn default() -> Self {
Self {
functions: true,
variables: true,
types: true,
}
}
}
/// Struct that represents the backend. The backend is responsible
/// for the actual PDB processing (e.g., type listing and reconstruction).
pub struct Backend {
@@ -394,19 +434,21 @@ fn worker_thread_routine(
BackendCommand::ListTypes(
pdb_slot,
search_filter,
search_query,
case_insensitive_search,
use_regex,
ignore_std_types,
search_filters,
) => {
if let Some(pdb_file) = pdb_files.get(&pdb_slot) {
let filtered_type_list = update_type_filter_command(
pdb_file,
&search_filter,
&search_query,
case_insensitive_search,
use_regex,
ignore_std_types,
true,
search_filters,
);
frontend_controller
.send_command(FrontendCommand::ListTypesResult(filtered_type_list))?;
@@ -415,21 +457,23 @@ fn worker_thread_routine(
BackendCommand::ListTypesMerged(
pdb_slots,
search_filter,
search_query,
case_insensitive_search,
use_regex,
ignore_std_types,
search_filters,
) => {
let mut filtered_type_set = BTreeSet::default();
for pdb_slot in pdb_slots {
if let Some(pdb_file) = pdb_files.get(&pdb_slot) {
let filtered_type_list = update_type_filter_command(
pdb_file,
&search_filter,
&search_query,
case_insensitive_search,
use_regex,
ignore_std_types,
false,
search_filters.clone(),
);
filtered_type_set.extend(filtered_type_list.into_iter().map(|(s, _)| {
// Collapse all type indices to `default`. When merging
@@ -446,18 +490,20 @@ fn worker_thread_routine(
BackendCommand::ListSymbols(
pdb_slot,
search_filter,
search_query,
case_insensitive_search,
use_regex,
ignore_std_types,
search_filters,
) => {
if let Some(pdb_file) = pdb_files.get_mut(&pdb_slot) {
let filtered_symbol_list = update_symbol_filter_command(
pdb_file,
&search_filter,
&search_query,
case_insensitive_search,
use_regex,
ignore_std_types,
search_filters,
);
frontend_controller
.send_command(FrontendCommand::ListSymbolsResult(filtered_symbol_list))?;
@@ -466,20 +512,22 @@ fn worker_thread_routine(
BackendCommand::ListSymbolsMerged(
pdb_slots,
search_filter,
search_query,
case_insensitive_search,
use_regex,
ignore_std_types,
search_filters,
) => {
let mut filtered_symbol_set = BTreeSet::default();
for pdb_slot in pdb_slots {
if let Some(pdb_file) = pdb_files.get_mut(&pdb_slot) {
let filtered_symbol_list = update_symbol_filter_command(
pdb_file,
&search_filter,
&search_query,
case_insensitive_search,
use_regex,
ignore_std_types,
search_filters.clone(),
);
filtered_symbol_set.extend(filtered_symbol_list.into_iter().map(
|(s, _)| {
@@ -891,33 +939,41 @@ where
fn update_type_filter_command<T>(
pdb_file: &PdbFile<T>,
search_filter: &str,
search_query: &str,
case_insensitive_search: bool,
use_regex: bool,
ignore_std_types: bool,
sort_by_index: bool,
search_filters: TypeFilters,
) -> TypeList
where
T: io::Seek + io::Read,
T: io::Seek + io::Read + std::fmt::Debug,
{
let filter_start = Instant::now();
// Filter out std types if needed
let filtered_type_list = if ignore_std_types {
filter_std_types(&pdb_file.complete_type_list)
} else {
pdb_file.complete_type_list.clone()
};
// Retrieve view to the type list
let type_list: TypeListExView = pdb_file.type_list();
// Filter types following the search filter
let mut filtered_type_list = if search_filter.is_empty() {
let filtered_type_list = if search_query.is_empty() {
// No need to filter
filtered_type_list
type_list
} else if use_regex {
filter_types_regex(&filtered_type_list, search_filter, case_insensitive_search)
filter_types_regex(&type_list, search_query, case_insensitive_search)
} else {
filter_types_regular(&filtered_type_list, search_filter, case_insensitive_search)
filter_types_regular(&type_list, search_query, case_insensitive_search)
};
// Apply filters by type kind
let filtered_type_list = filter_types_kind(&filtered_type_list, &search_filters);
// Filter out std types if needed
let mut filtered_type_list = if ignore_std_types {
filter_std_types(&filtered_type_list)
} else {
filtered_type_list
};
if sort_by_index {
// Order types by type index, so the order is deterministic
// (i.e., independent from DashMap's hash function)
@@ -929,15 +985,18 @@ where
filter_start.elapsed().as_millis()
);
filtered_type_list
// Convert refs to symbol info into clones and return it
par_iter_if_available!(filtered_type_list)
.map(|type_info| (type_info.0.clone(), type_info.1))
.collect()
}
/// Filter type list with a regular expression
fn filter_types_regex(
type_list: &[(String, u32)],
fn filter_types_regex<'s>(
type_list: &'s [&TypeInfoEx],
search_filter: &str,
case_insensitive_search: bool,
) -> TypeList {
) -> TypeListExView<'s> {
match regex::RegexBuilder::new(search_filter)
.case_insensitive(case_insensitive_search)
.build()
@@ -952,11 +1011,11 @@ fn filter_types_regex(
}
/// Filter type list with a plain (sub-)string
fn filter_types_regular(
type_list: &[(String, u32)],
fn filter_types_regular<'s>(
type_list: &'s [&TypeInfoEx],
search_filter: &str,
case_insensitive_search: bool,
) -> TypeList {
) -> TypeListExView<'s> {
if case_insensitive_search {
let search_filter = search_filter.to_lowercase();
par_iter_if_available!(type_list)
@@ -972,19 +1031,42 @@ fn filter_types_regular(
}
/// Filter type list to remove types in the `std` namespace
fn filter_std_types(type_list: &[(String, pdb_file::TypeIndex)]) -> TypeList {
fn filter_std_types<'s>(type_list: &'s [&TypeInfoEx]) -> TypeListExView<'s> {
par_iter_if_available!(type_list)
.filter(|r| !r.0.starts_with("std::"))
.cloned()
.collect()
}
/// Filter type list with a regular expression
#[allow(clippy::if_same_then_else, clippy::needless_bool)]
fn filter_types_kind<'s>(
type_list: &'s [&TypeInfoEx],
type_filters: &TypeFilters,
) -> TypeListExView<'s> {
par_iter_if_available!(type_list)
.filter(|(_, _, type_kind)| {
if !type_filters.classes && *type_kind == TypeKind::Class {
false
} else if !type_filters.unions && *type_kind == TypeKind::Union {
false
} else if !type_filters.enums && *type_kind == TypeKind::Enum {
false
} else {
true
}
})
.cloned()
.collect()
}
fn update_symbol_filter_command<T>(
pdb_file: &mut PdbFile<T>,
search_filter: &str,
search_query: &str,
case_insensitive_search: bool,
use_regex: bool,
ignore_std_symbols: bool,
search_filters: SymbolFilters,
) -> SymbolList
where
T: io::Seek + io::Read + fmt::Debug,
@@ -992,30 +1074,25 @@ where
let filter_start = Instant::now();
match pdb_file.symbol_list() {
Err(_) => SymbolList::default(),
Err(_) => Default::default(),
Ok(symbol_list) => {
// Filter out std types if needed
let filtered_symbol_list = if ignore_std_symbols {
filter_std_symbols(&symbol_list)
} else {
let filtered_symbol_list = if search_query.is_empty() {
// No need to filter
symbol_list
} else if use_regex {
filter_symbols_regex(&symbol_list, search_query, case_insensitive_search)
} else {
filter_symbols_regular(&symbol_list, search_query, case_insensitive_search)
};
let filtered_symbol_list = if search_filter.is_empty() {
// No need to filter
filtered_symbol_list
} else if use_regex {
filter_symbols_regex(
&filtered_symbol_list,
search_filter,
case_insensitive_search,
)
// Apply filters by symbol kind
let filtered_symbol_list = filter_symbols_type(&filtered_symbol_list, &search_filters);
// Filter out std types if needed
let filtered_symbol_list = if ignore_std_symbols {
filter_std_symbols(&filtered_symbol_list)
} else {
filter_symbols_regular(
&filtered_symbol_list,
search_filter,
case_insensitive_search,
)
filtered_symbol_list
};
log::debug!(
@@ -1025,28 +1102,26 @@ where
// Convert refs to symbol info into clones and return it
par_iter_if_available!(filtered_symbol_list)
.map(|e| (e.0.clone(), e.1))
.map(|symbol_info| (symbol_info.0.clone(), symbol_info.1))
.collect()
}
}
}
/// Filter symbol list to remove types in the `std` namespace
fn filter_std_symbols<'s>(
symbol_list: &'s [&(String, pdb_file::SymbolIndex)],
) -> SymbolListView<'s> {
fn filter_std_symbols<'s>(symbol_list: &'s [&SymbolInfoEx]) -> SymbolListExView<'s> {
par_iter_if_available!(symbol_list)
.filter(|r| !r.0.starts_with("std::"))
.cloned()
.collect()
}
/// Filter type list with a regular expression
/// Filter symbol list with a regular expression
fn filter_symbols_regex<'s>(
symbol_list: &'s [&(String, pdb_file::SymbolIndex)],
symbol_list: &'s [&SymbolInfoEx],
search_filter: &str,
case_insensitive_search: bool,
) -> SymbolListView<'s> {
) -> SymbolListExView<'s> {
match regex::RegexBuilder::new(search_filter)
.case_insensitive(case_insensitive_search)
.build()
@@ -1060,12 +1135,12 @@ fn filter_symbols_regex<'s>(
}
}
/// Filter type list with a plain (sub-)string
/// Filter symbol list with a plain (sub-)string
fn filter_symbols_regular<'s>(
symbol_list: &'s [&(String, pdb_file::SymbolIndex)],
symbol_list: &'s [&SymbolInfoEx],
search_filter: &str,
case_insensitive_search: bool,
) -> SymbolListView<'s> {
) -> SymbolListExView<'s> {
if case_insensitive_search {
let search_filter = search_filter.to_lowercase();
par_iter_if_available!(symbol_list)
@@ -1080,6 +1155,28 @@ fn filter_symbols_regular<'s>(
}
}
/// Filter symbol list by type
#[allow(clippy::if_same_then_else, clippy::needless_bool)]
fn filter_symbols_type<'s>(
symbol_list: &'s [&SymbolInfoEx],
symbol_filters: &SymbolFilters,
) -> SymbolListExView<'s> {
par_iter_if_available!(symbol_list)
.filter(|(_, _, symbol_kind)| {
if !symbol_filters.functions && *symbol_kind == SymbolKind::Function {
false
} else if !symbol_filters.variables && *symbol_kind == SymbolKind::Variable {
false
} else if !symbol_filters.types && *symbol_kind == SymbolKind::Type {
false
} else {
true
}
})
.cloned()
.collect()
}
fn list_modules_command<'p, T>(
pdb_file: &PdbFile<'p, T>,
search_filter: &str,
@@ -1118,10 +1215,10 @@ where
/// Filter module list with a regular expression
fn filter_modules_regex(
module_list: &[(String, usize)],
module_list: &[ModuleInfo],
search_filter: &str,
case_insensitive_search: bool,
) -> Vec<(String, usize)> {
) -> ModuleList {
match regex::RegexBuilder::new(search_filter)
.case_insensitive(case_insensitive_search)
.build()
@@ -1137,10 +1234,10 @@ fn filter_modules_regex(
/// Filter module list with a plain (sub-)string
fn filter_modules_regular(
module_list: &[(String, usize)],
module_list: &[ModuleInfo],
search_filter: &str,
case_insensitive_search: bool,
) -> Vec<(String, usize)> {
) -> ModuleList {
if case_insensitive_search {
let search_filter = search_filter.to_lowercase();
par_iter_if_available!(module_list)
+164 -78
View File
@@ -24,14 +24,45 @@ use crate::{
},
};
// Type
pub type TypeIndex = u32;
pub type TypeList = Vec<(String, TypeIndex)>;
/// Minimal symbol info (used by frontends)
pub type TypeInfo = (String, TypeIndex);
pub type TypeList = Vec<TypeInfo>;
/// Extended symbol info (used by the backend)
pub type TypeInfoEx = (String, TypeIndex, TypeKind);
pub type TypeListEx = Vec<TypeInfoEx>;
pub type TypeListExView<'t> = Vec<&'t TypeInfoEx>;
#[derive(Eq, PartialEq)]
pub enum TypeKind {
Class,
Union,
Enum,
Unknown,
}
// Symbol
/// `SymbolIndex` have two parts: a module index and a symbol index
pub type SymbolIndex = (ModuleIndex, u32);
pub type SymbolList = Vec<(String, SymbolIndex)>;
pub type SymbolListView<'t> = Vec<&'t (String, SymbolIndex)>;
/// Minimal symbol info (used by frontends)
pub type SymbolInfo = (String, SymbolIndex);
pub type SymbolList = Vec<SymbolInfo>;
/// Extended symbol info (used by the backend)
pub type SymbolInfoEx = (String, SymbolIndex, SymbolKind);
pub type SymbolListEx = Vec<SymbolInfoEx>;
pub type SymbolListExView<'t> = Vec<&'t SymbolInfoEx>;
#[derive(Eq, PartialEq)]
pub enum SymbolKind {
Function,
Variable,
Type,
Unknown,
}
// Module
pub type ModuleIndex = usize;
pub type ModuleList = Vec<(String, ModuleIndex)>;
pub type ModuleInfo = (String, ModuleIndex);
pub type ModuleList = Vec<ModuleInfo>;
const GLOBAL_MODULE_INDEX: usize = usize::MAX;
@@ -67,8 +98,9 @@ impl Read for PDBDataSource {
#[derive(PartialEq, Eq)]
struct PrioritizedSymbol {
priority: u16,
index: SymbolIndex,
name: String,
index: SymbolIndex,
kind: SymbolKind,
}
impl PartialOrd for PrioritizedSymbol {
@@ -87,9 +119,9 @@ pub struct PdbFile<'p, T>
where
T: io::Seek + io::Read + 'p,
{
pub complete_type_list: Vec<(String, TypeIndex)>,
pub complete_type_list: TypeListEx,
pub forwarder_to_complete_type: Arc<DashMap<pdb::TypeIndex, pdb::TypeIndex>>,
pub symbol_list: SymbolList,
pub symbol_list: SymbolListEx,
pub machine_type: pdb::MachineType,
pub type_information: pdb::TypeInformation<'p>,
pub debug_information: pdb::DebugInformation<'p>,
@@ -228,7 +260,8 @@ where
if is_unnamed_type(&class_name) {
class_name = format!("_unnamed_{type_index}");
}
self.complete_type_list.push((class_name, type_index.0));
self.complete_type_list
.push((class_name, type_index.0, TypeKind::Class));
}
pdb::TypeData::Union(data) => {
let mut class_name = data.name.to_string().into_owned();
@@ -244,7 +277,8 @@ where
if is_unnamed_type(&class_name) {
class_name = format!("_unnamed_{type_index}");
}
self.complete_type_list.push((class_name, type_index.0));
self.complete_type_list
.push((class_name, type_index.0, TypeKind::Union));
}
pdb::TypeData::Enumeration(data) => {
let mut class_name = data.name.to_string().into_owned();
@@ -260,7 +294,8 @@ where
if is_unnamed_type(&class_name) {
class_name = format!("_unnamed_{type_index}");
}
self.complete_type_list.push((class_name, type_index.0));
self.complete_type_list
.push((class_name, type_index.0, TypeKind::Enum));
}
_ => {}
}
@@ -417,7 +452,11 @@ where
)
}
pub fn symbol_list(&mut self) -> Result<SymbolListView> {
pub fn type_list(&self) -> TypeListExView {
self.complete_type_list.iter().collect()
}
pub fn symbol_list(&mut self) -> Result<SymbolListExView> {
// If cache is populated, return the cached list
if !self.symbol_list.is_empty() {
return Ok(self.symbol_list.iter().collect());
@@ -439,12 +478,15 @@ where
let mut module_symbols = module_info.symbols()?;
while let Some(symbol) = module_symbols.next()? {
if let Some(symbol_name) = get_symbol_name(&symbol) {
symbol_heap.push(PrioritizedSymbol {
priority: symbol_priority(&symbol),
index: (module_index, symbol.index().0),
name: symbol_name.clone(),
});
if let Ok(symbol_data) = symbol.parse() {
if let Some(symbol_name) = get_symbol_name(&symbol_data) {
symbol_heap.push(PrioritizedSymbol {
priority: symbol_priority(&symbol_data),
name: symbol_name.clone(),
index: (module_index, symbol.index().0),
kind: get_symbol_type(&symbol_data),
});
}
}
}
}
@@ -453,12 +495,15 @@ where
// Global symbols
let mut symbol_table = self.global_symbols.iter();
while let Some(symbol) = symbol_table.next()? {
if let Some(symbol_name) = get_symbol_name(&symbol) {
symbol_heap.push(PrioritizedSymbol {
priority: symbol_priority(&symbol),
index: (GLOBAL_MODULE_INDEX, symbol.index().0),
name: symbol_name.clone(),
});
if let Ok(symbol_data) = symbol.parse() {
if let Some(symbol_name) = get_symbol_name(&symbol_data) {
symbol_heap.push(PrioritizedSymbol {
priority: symbol_priority(&symbol_data),
name: symbol_name.clone(),
index: (GLOBAL_MODULE_INDEX, symbol.index().0),
kind: get_symbol_type(&symbol_data),
});
}
}
}
@@ -470,7 +515,8 @@ where
.filter_map(|s| {
if !symbol_names.contains(&s.name) {
symbol_names.insert(s.name.clone());
Some((s.name, s.index))
Some((s.name, s.index, s.kind))
} else {
None
}
@@ -565,16 +611,18 @@ where
// Global symbols
let mut symbol_table = self.global_symbols.iter();
while let Some(symbol) = symbol_table.next()? {
if let Some(current_symbol_name) = get_symbol_name(&symbol) {
if current_symbol_name == symbol_name {
return Ok(self
.reconstruct_symbol(
&type_finder,
&symbol,
primitives_flavor,
print_access_specifiers,
)
.unwrap_or_default());
if let Ok(symbol_data) = symbol.parse() {
if let Some(current_symbol_name) = get_symbol_name(&symbol_data) {
if current_symbol_name == symbol_name {
return Ok(self
.reconstruct_symbol(
&type_finder,
&symbol,
primitives_flavor,
print_access_specifiers,
)
.unwrap_or_default());
}
}
}
}
@@ -587,16 +635,18 @@ where
if let Some(module_info) = pdb.module_info(&module)? {
let mut module_symbols = module_info.symbols()?;
while let Some(symbol) = module_symbols.next()? {
if let Some(current_symbol_name) = get_symbol_name(&symbol) {
if current_symbol_name == symbol_name {
return Ok(self
.reconstruct_symbol(
&type_finder,
&symbol,
primitives_flavor,
print_access_specifiers,
)
.unwrap_or_default());
if let Ok(symbol_data) = symbol.parse() {
if let Some(current_symbol_name) = get_symbol_name(&symbol_data) {
if current_symbol_name == symbol_name {
return Ok(self
.reconstruct_symbol(
&type_finder,
&symbol,
primitives_flavor,
print_access_specifiers,
)
.unwrap_or_default());
}
}
}
}
@@ -629,14 +679,16 @@ where
// Global symbols
let mut symbol_table = self.global_symbols.iter();
while let Some(symbol) = symbol_table.next()? {
if get_symbol_name(&symbol).is_some() {
if let Some(reconstructed_symbol) = self.reconstruct_symbol(
&type_finder,
&symbol,
primitives_flavor,
print_access_specifiers,
) {
writeln!(&mut reconstruction_output, "{}", reconstructed_symbol)?;
if let Ok(symbol_data) = symbol.parse() {
if get_symbol_name(&symbol_data).is_some() {
if let Some(reconstructed_symbol) = self.reconstruct_symbol(
&type_finder,
&symbol,
primitives_flavor,
print_access_specifiers,
) {
writeln!(&mut reconstruction_output, "{}", reconstructed_symbol)?;
}
}
}
}
@@ -649,14 +701,20 @@ where
if let Some(module_info) = pdb.module_info(&module)? {
let mut module_symbols = module_info.symbols()?;
while let Some(symbol) = module_symbols.next()? {
if get_symbol_name(&symbol).is_some() {
if let Some(reconstructed_symbol) = self.reconstruct_symbol(
&type_finder,
&symbol,
primitives_flavor,
print_access_specifiers,
) {
writeln!(&mut reconstruction_output, "{}", reconstructed_symbol)?;
if let Ok(symbol_data) = symbol.parse() {
if get_symbol_name(&symbol_data).is_some() {
if let Some(reconstructed_symbol) = self.reconstruct_symbol(
&type_finder,
&symbol,
primitives_flavor,
print_access_specifiers,
) {
writeln!(
&mut reconstruction_output,
"{}",
reconstructed_symbol
)?;
}
}
}
}
@@ -1020,7 +1078,7 @@ where
fn type_list_from_type_indices(&self, type_indices: &[TypeIndex]) -> TypeList {
par_iter_if_available!(self.complete_type_list)
.filter_map(|(type_name, type_index)| {
.filter_map(|(type_name, type_index, _)| {
if type_indices.contains(type_index) {
Some((type_name.clone(), *type_index))
} else {
@@ -1236,11 +1294,11 @@ fn compute_type_depth_map(
inverted_type_depth_map
}
fn get_symbol_name(symbol: &pdb::Symbol) -> Option<String> {
fn get_symbol_name(symbol_data: &pdb::SymbolData) -> Option<String> {
const UNNAMED_CONSTANT_PREFIXES: [&str; 5] = ["`", "??_", "__@@_PchSym_", "__real@", "__xmm@"];
const UNNAMED_CONSTANT_SUFFIXES: [&str; 1] = ["@@9@9"];
match symbol.parse().ok()? {
match symbol_data {
pdb::SymbolData::UserDefinedType(udt) => Some(udt.name.to_string().to_string()),
// Functions and methods
@@ -1277,6 +1335,38 @@ fn get_symbol_name(symbol: &pdb::Symbol) -> Option<String> {
})
}
fn get_symbol_type(symbol_data: &pdb::SymbolData) -> SymbolKind {
match symbol_data {
pdb::SymbolData::UserDefinedType(_) => SymbolKind::Type,
// Functions and methods
pdb::SymbolData::Procedure(_) => SymbolKind::Function,
// Global variables
pdb::SymbolData::Data(_) => SymbolKind::Variable,
// Public symbols
pdb::SymbolData::Public(data) => {
if data.function {
SymbolKind::Function
} else {
SymbolKind::Variable
}
}
// Exported symbols
pdb::SymbolData::Export(data) => {
if data.flags.data {
SymbolKind::Variable
} else {
SymbolKind::Function
}
}
_ => SymbolKind::Unknown,
}
}
fn symbol_rva(
symbol_offset: &pdb::PdbInternalSectionOffset,
sections: &[pdb::ImageSectionHeader],
@@ -1314,20 +1404,16 @@ fn demangle_symbol_name(
.ok()
}
fn symbol_priority(symbol: &pdb::Symbol) -> u16 {
if let Ok(symbol) = symbol.parse() {
match symbol {
// Functions and methods, user types, global variables
pdb::SymbolData::Procedure(_)
| pdb::SymbolData::UserDefinedType(_)
| pdb::SymbolData::Data(_) => 0,
// Public symbols
pdb::SymbolData::Public(_) => 1,
// Exported symbols
pdb::SymbolData::Export(_) => 2,
_ => 10,
}
} else {
0
fn symbol_priority(symbol_data: &pdb::SymbolData) -> u16 {
match symbol_data {
// Functions and methods, user types, global variables
pdb::SymbolData::Procedure(_)
| pdb::SymbolData::UserDefinedType(_)
| pdb::SymbolData::Data(_) => 0,
// Public symbols
pdb::SymbolData::Public(_) => 1,
// Exported symbols
pdb::SymbolData::Export(_) => 2,
_ => 10,
}
}
+4
View File
@@ -64,6 +64,8 @@ impl ResymcApp {
case_insensitive,
use_regex,
ignore_std_types,
// TODO(ergrelet): allow users to control these filters
Default::default(),
))?;
// Wait for the backend to finish filtering types
if let FrontendCommand::ListTypesResult(type_list) =
@@ -491,6 +493,8 @@ impl ResymcApp {
case_insensitive,
use_regex,
ignore_std_types,
// TODO(ergrelet): allow users to control these filters
Default::default(),
))?;
// Wait for the backend to finish listing modules
if let FrontendCommand::ListSymbolsResult(symbol_list) =