From c6501552fd6437e03d3839d6aed81ec152a14e66 Mon Sep 17 00:00:00 2001 From: Truman Kilen Date: Mon, 22 Dec 2025 15:37:39 -0600 Subject: [PATCH 1/2] Fix QualifiedName::default() incorrectly creating QualifiedName with empty separator --- rust/src/qualified_name.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/rust/src/qualified_name.rs b/rust/src/qualified_name.rs index fc362cf64c..62da86b859 100644 --- a/rust/src/qualified_name.rs +++ b/rust/src/qualified_name.rs @@ -35,7 +35,7 @@ use std::ops::{Index, IndexMut}; /// let qn = QualifiedName::new_with_separator(["a", "b", "c"], "."); /// assert_eq!(qn.to_string(), "a.b.c"); /// ``` -#[derive(Default, Debug, Clone, Hash, PartialEq, Eq, Ord, PartialOrd)] +#[derive(Debug, Clone, Hash, PartialEq, Eq, Ord, PartialOrd)] pub struct QualifiedName { // TODO: Make this Option where default is "::". pub separator: String, @@ -177,6 +177,12 @@ impl QualifiedName { } } +impl Default for QualifiedName { + fn default() -> Self { + Self::new(Vec::::new()) + } +} + impl From for QualifiedName { fn from(value: String) -> Self { Self { From 07d3adc86453d2bbb8c1e4a2dac13951137e8492 Mon Sep 17 00:00:00 2001 From: Truman Kilen Date: Mon, 22 Dec 2025 17:07:06 -0600 Subject: [PATCH 2/2] Fix vtable type name parsing --- plugins/pdb-ng/src/symbol_parser.rs | 128 +++++++++++++++++----------- 1 file changed, 80 insertions(+), 48 deletions(-) diff --git a/plugins/pdb-ng/src/symbol_parser.rs b/plugins/pdb-ng/src/symbol_parser.rs index 79049d0399..7e3e5f6ec4 100644 --- a/plugins/pdb-ng/src/symbol_parser.rs +++ b/plugins/pdb-ng/src/symbol_parser.rs @@ -1885,54 +1885,17 @@ impl<'a, S: Source<'a> + 'a> PDBParserInstance<'a, S> { } } - // VTables have types on their data symbols, - if let Some((last, class_name)) = name.split_last() { - if last.contains("`vftable'") { - let mut vt_name = class_name.with_item("VTable"); - if last.contains("{for") { - // DerivedClass::`vftable'{for `BaseClass'} - let mut base_name = last.to_owned(); - base_name.drain(0..("`vftable'{for `".len())); - base_name.drain((base_name.len() - "'}".len())..(base_name.len())); - // Multiply inherited classes have multiple vtable types - // TODO: Do that - vt_name = QualifiedName::new(vec![base_name, "VTable".to_string()]); - } - - vt_name = vt_name - .replace("class ", "") - .replace("struct ", "") - .replace("enum ", ""); - - if let Some(ty) = self.named_types.get(&vt_name) { - t = Some(Conf::new( - Type::named_type_from_type(vt_name, ty.as_ref()), - DEMANGLE_CONFIDENCE, - )); - } else { - // Sometimes the demangler has trouble with `class Foo` in templates - vt_name = vt_name - .replace("class ", "") - .replace("struct ", "") - .replace("enum ", ""); - - if let Some(ty) = self.named_types.get(&vt_name) { - t = Some(Conf::new( - Type::named_type_from_type(vt_name, ty.as_ref()), - DEMANGLE_CONFIDENCE, - )); - } else { - t = Some(Conf::new( - Type::named_type_from_type( - vt_name, - Type::structure(StructureBuilder::new().finalize().as_ref()) - .as_ref(), - ), - DEMANGLE_CONFIDENCE, - )); - } - } - } + // VTables have types on their data symbols + // Format: "ClassName::`vftable'" or "ClassName::`vftable'{for `BaseClass'}" + if let Some(vt_name) = parse_vtable_type_name(&name) { + let ty = + self.named_types.get(&vt_name).cloned().unwrap_or_else(|| { + Type::structure(StructureBuilder::new().finalize().as_ref()) + }); + t = Some(Conf::new( + Type::named_type_from_type(vt_name, ty.as_ref()), + DEMANGLE_CONFIDENCE, + )); } if let Some(last_name) = name.last_mut() { @@ -2041,3 +2004,72 @@ impl<'a, S: Source<'a> + 'a> PDBParserInstance<'a, S> { } } } + +/// Parse a vtable symbol name and return the parts for the vtable type. +/// Takes the last element of (e.g., `"ClassName::`vftable'{for `BaseClass'}"`) +/// and converts it to a vtable type name (e.g., `["ClassName", "BaseClass", "VTable"]`). +fn parse_vtable_type_name(name: &QualifiedName) -> Option { + let (last, qualified_name_base) = name.items.split_last()?; + let (class_name, rest) = last.split_once("::`vftable'")?; + + let clean = |s: &str| { + s.replace("class ", "") + .replace("struct ", "") + .replace("enum ", "") + }; + + let mut parts = qualified_name_base.to_vec(); + parts.extend(class_name.split("::").map(|s| clean(s))); + + if let Some(base_class) = rest + .split_once("{for `") + .and_then(|(_, base_start)| base_start.split_once("'}")) + .map(|(base, _)| base) + { + parts.push(clean(base_class)); + } + + parts.push("VTable".to_string()); + Some(QualifiedName::new(parts)) +} + +#[cfg(test)] +mod tests { + use super::*; + + /// (input_demangled_name, expected_output) + #[rustfmt::skip] + const VTABLE_TEST_CASES: &[(&str, &[&str])] = &[ + // Simple vtables + ("Base::`vftable'", &["Base", "VTable"]), + // Multiple inheritance with {for} + ("MultiDerived::`vftable'{for `InterfaceA'}", &["MultiDerived", "InterfaceA", "VTable"]), + // Simple templates + ("Container::`vftable'", &["Container", "VTable"]), + ("Pair::`vftable'", &["Pair", "VTable"]), + ("FixedArray::`vftable'", &["FixedArray", "VTable"]), + // Namespaced classes + ("outer::inner::NamespacedTemplate::`vftable'", &["outer", "inner", "NamespacedTemplate", "VTable"]), + // Nested class inside template + ("complex::HasNested::Nested::`vftable'", &["complex", "HasNested", "Nested", "VTable"]), + // Template with {for} clause + ("TemplateMultiDerived::`vftable'{for `TemplateInterfaceA'}", &["TemplateMultiDerived", "TemplateInterfaceA", "VTable"]), + ("TemplateDiamond::`vftable'{for `TemplateLeftVirtual'}", &["TemplateDiamond", "TemplateLeftVirtual", "VTable"]), + // Nested templates - note: "class " prefix gets stripped + ("Wrapper >::`vftable'", &["Wrapper >", "VTable"]), + // Class/struct/enum keyword removal + ("Wrapper::`vftable'", &["Wrapper", "VTable"]), + ("Container::`vftable'", &["Container", "VTable"]), + ("Holder::`vftable'", &["Holder", "VTable"]), + ]; + + #[test] + fn test_vtable_type_name_parsing() { + for (input, expected) in VTABLE_TEST_CASES { + let name = QualifiedName::new(vec![input.to_string()]); + let result = parse_vtable_type_name(&name); + let expected_qn = QualifiedName::new(expected.into_iter().cloned()); + assert_eq!(result, Some(expected_qn), "Failed for input: {}", input); + } + } +}