From f89f6bdba231820ec6b4791b381cc777f6a887c7 Mon Sep 17 00:00:00 2001 From: Hung Pham Date: Tue, 8 Sep 2026 05:54:18 +0700 Subject: [PATCH 1/3] Improve by showing more meaningful object naming from r2 --- Cargo.lock | 10 ++++++ crates/codegraph-binary/Cargo.toml | 1 + crates/codegraph-binary/src/cache.rs | 11 ++++++- crates/codegraph-binary/src/extract.rs | 43 ++++++++++++++++++++++++-- 4 files changed, 62 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 11c860c3f..2a6c79bd9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -781,6 +781,7 @@ dependencies = [ "camino", "codegraph-core", "codegraph-graph", + "cpp_demangle", "ignore", "r2pipe", "serde", @@ -1165,6 +1166,15 @@ version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" +[[package]] +name = "cpp_demangle" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0667304c32ea56cb4cd6d2d7c0cfe9a2f8041229db8c033af7f8d69492429def" +dependencies = [ + "cfg-if", +] + [[package]] name = "cpufeatures" version = "0.2.17" diff --git a/crates/codegraph-binary/Cargo.toml b/crates/codegraph-binary/Cargo.toml index 8ffd98f2d..4188f3b31 100644 --- a/crates/codegraph-binary/Cargo.toml +++ b/crates/codegraph-binary/Cargo.toml @@ -18,6 +18,7 @@ tracing = { workspace = true } camino = { workspace = true } ignore = { workspace = true } sha2 = "0.11" +cpp_demangle = "0.5.1" [dev-dependencies] tempfile = "3" diff --git a/crates/codegraph-binary/src/cache.rs b/crates/codegraph-binary/src/cache.rs index 81c26d425..daa19fd18 100644 --- a/crates/codegraph-binary/src/cache.rs +++ b/crates/codegraph-binary/src/cache.rs @@ -6,8 +6,17 @@ use sha2::{Digest, Sha256}; use std::fs; use std::path::Path; +/// Bump khi format output extract thay đổi (vd: sửa mapping field r2) để +/// cache cũ từ bản binary trước tự vô hiệu thay vì được nạp lại nguyên si. +pub const EXTRACT_VERSION: &str = "2"; + pub fn cache_path(root: &Utf8Path, path: &Path) -> camino::Utf8PathBuf { - let key = format!("{}|{}|{}", path.display(), mtime(path), size(path)); + let key = format!( + "{EXTRACT_VERSION}|{}|{}|{}", + path.display(), + mtime(path), + size(path) + ); let hash = Sha256::digest(key.as_bytes()); let hex: String = hash.iter().map(|b| format!("{b:02x}")).collect(); root.join(".codegraph") diff --git a/crates/codegraph-binary/src/extract.rs b/crates/codegraph-binary/src/extract.rs index 88e7a670e..4e1ad5d41 100644 --- a/crates/codegraph-binary/src/extract.rs +++ b/crates/codegraph-binary/src/extract.rs @@ -5,6 +5,7 @@ use crate::model::*; use crate::r2::R2Session; use codegraph_core::{Annotation, CallRecord, EffectType, Error, Symbol, SymbolKind, SYMBOL_BASE}; use codegraph_graph::ParseResult; +use cpp_demangle::Symbol as CppSymbol; use serde_json::Value; use std::collections::{HashMap, HashSet}; use std::path::Path; @@ -86,17 +87,19 @@ fn do_extract( if raw_name.starts_with("sym.imp.") { continue; } - let name = strip_r2_prefix(&raw_name); + let name = demangle(&strip_r2_prefix(&raw_name)); let size = entry.size.unwrap_or(0); let sig = build_signature(addr, size, entry); let id = next_id; next_id += 1; fn_by_addr.insert(addr, id); fn_id_to_name.insert(id, name.clone()); + // r2 6.x tự sinh symbol C++: class.X, method.Class.foo, namespace.X, enum.X + let (kind, name) = classify_symbol(&raw_name, &name); symbols.push(Symbol { id, name, - kind: SymbolKind::Function, + kind, scope: codegraph_core::ScopeLevel::Global, scope_id: 0, type_ref: 0, @@ -250,6 +253,42 @@ fn strip_r2_prefix(name: &str) -> String { name.strip_prefix("sym.").unwrap_or(name).to_string() } +/// Demangle tên C++ Itanium (_ZN...) để readable hơn khi search/index. +/// Giữ nguyên tên không phải C++ (bao gồm cả `sub_`, `fcn.`). +fn demangle(name: &str) -> String { + if name.starts_with("_ZN") || name.starts_with("_TS") || name.starts_with("_Z") { + match CppSymbol::new(name) { + Ok(s) => match s.demangle() { + Ok(d) => d, + Err(_) => name.to_string(), + }, + Err(_) => name.to_string(), + } + } else { + name.to_string() + } +} + +/// Phân loại symbol từ tên thô do r2 trả về. +/// r2 6.x tự sinh symbol C++: `class.X`, `method.Class.foo`, +/// `namespace.X`, `enum.X`. Trả về `(kind, name)` — name đã được làm sạch. +fn classify_symbol(raw_name: &str, name: &str) -> (SymbolKind, String) { + // Dùng raw_name vì nó giữ nguyên tên gốc từ r2 (chưa strip sym. prefix). + if raw_name.starts_with("class.") || name.starts_with("class.") { + return (SymbolKind::Class, name.to_string()); + } + if raw_name.starts_with("method.") || name.starts_with("method.") { + return (SymbolKind::Method, name.to_string()); + } + if raw_name.starts_with("namespace.") || name.starts_with("namespace.") { + return (SymbolKind::Module, name.to_string()); + } + if raw_name.starts_with("enum.") || name.starts_with("enum.") { + return (SymbolKind::Enum, name.to_string()); + } + (SymbolKind::Function, name.to_string()) +} + /// Bản đồ tra cứu từ address/name sang symbol id — gom parameter cho chain builder. struct FnMaps<'a> { fn_by_addr: &'a HashMap, From e4789889a238d57507d01a07f450d3f3033751bc Mon Sep 17 00:00:00 2001 From: Hung Pham Date: Tue, 8 Sep 2026 06:20:14 +0700 Subject: [PATCH 2/3] Increase code-coverage --- crates/codegraph-binary/src/extract.rs | 110 +++++++++++++++++++++++++ 1 file changed, 110 insertions(+) diff --git a/crates/codegraph-binary/src/extract.rs b/crates/codegraph-binary/src/extract.rs index 4e1ad5d41..e6993aea2 100644 --- a/crates/codegraph-binary/src/extract.rs +++ b/crates/codegraph-binary/src/extract.rs @@ -466,3 +466,113 @@ fn resolve_call_target(target: Option, maps: &FnMaps) -> (u64, String) { } (0, format!("sub_{addr:x}")) } + +#[cfg(test)] +mod tests { + use super::*; + use codegraph_core::SymbolKind; + + #[test] + fn test_classify_symbol_class() { + let raw = "class.MyClass"; + let name = "MyClass"; + let (kind, cleaned_name) = classify_symbol(raw, name); + assert_eq!(kind, SymbolKind::Class); + assert_eq!(cleaned_name, name); + } + + #[test] + fn test_classify_symbol_method() { + let raw = "method.MyClass.my_method"; + let name = "MyClass.my_method"; + let (kind, cleaned_name) = classify_symbol(raw, name); + assert_eq!(kind, SymbolKind::Method); + assert_eq!(cleaned_name, name); + } + + #[test] + fn test_classify_symbol_namespace() { + let raw = "namespace.std"; + let name = "std"; + let (kind, cleaned_name) = classify_symbol(raw, name); + assert_eq!(kind, SymbolKind::Module); + assert_eq!(cleaned_name, name); + } + + #[test] + fn test_classify_symbol_enum() { + let raw = "enum.Color"; + let name = "Color"; + let (kind, cleaned_name) = classify_symbol(raw, name); + assert_eq!(kind, SymbolKind::Enum); + assert_eq!(cleaned_name, name); + } + + #[test] + fn test_classify_symbol_function_default() { + let raw = "fcn.00401000"; + let name = "fcn.00401000"; + let (kind, cleaned_name) = classify_symbol(raw, name); + assert_eq!(kind, SymbolKind::Function); + assert_eq!(cleaned_name, name); + } + +#[test] +fn test_classify_symbol_stripped_name_fallback() { + // Test when raw_name doesn't match but stripped name does + let raw = "sym.class.MyClass"; // r2 adds sym. prefix + let name = "class.MyClass"; // after strip_r2_prefix + let (kind, cleaned_name) = classify_symbol(raw, name); + assert_eq!(kind, SymbolKind::Class); + assert_eq!(cleaned_name, "class.MyClass"); +} + +#[test] +fn test_classify_symbol_name_starts_with_class() { + // Test when name (not raw_name) starts with prefix + let raw = "something.class.MyClass"; // raw_name doesn't start with class. + let name = "class.MyClass"; // but name does + let (kind, cleaned_name) = classify_symbol(raw, name); + assert_eq!(kind, SymbolKind::Class); + assert_eq!(cleaned_name, name); +} + +#[test] +fn test_classify_symbol_name_starts_with_method() { + // Test when name (not raw_name) starts with prefix + let raw = "something.method.MyClass.my_method"; // raw_name doesn't start with method. + let name = "method.MyClass.my_method"; // but name does + let (kind, cleaned_name) = classify_symbol(raw, name); + assert_eq!(kind, SymbolKind::Method); + assert_eq!(cleaned_name, name); +} + +#[test] +fn test_classify_symbol_name_starts_with_namespace() { + // Test when name (not raw_name) starts with prefix + let raw = "something.namespace.std"; // raw_name doesn't start with namespace. + let name = "namespace.std"; // but name does + let (kind, cleaned_name) = classify_symbol(raw, name); + assert_eq!(kind, SymbolKind::Module); + assert_eq!(cleaned_name, name); +} + +#[test] +fn test_classify_symbol_name_starts_with_enum() { + // Test when name (not raw_name) starts with prefix + let raw = "something.enum.Color"; // raw_name doesn't start with enum. + let name = "enum.Color"; // but name does + let (kind, cleaned_name) = classify_symbol(raw, name); + assert_eq!(kind, SymbolKind::Enum); + assert_eq!(cleaned_name, name); +} + +#[test] +fn test_classify_symbol_no_match() { + let raw = "some.other.symbol"; + let name = "some.other.symbol"; + let (kind, cleaned_name) = classify_symbol(raw, name); + assert_eq!(kind, SymbolKind::Function); + assert_eq!(cleaned_name, name); +} +} From 4b5282d1fa4327b6a947ecb744ad84146746688e Mon Sep 17 00:00:00 2001 From: hungpham10 <136320753+hungpham10@users.noreply.github.com> Date: Mon, 7 Sep 2026 23:20:42 +0000 Subject: [PATCH 3/3] style: apply rustfmt --- crates/codegraph-binary/src/extract.rs | 106 ++++++++++++------------- 1 file changed, 53 insertions(+), 53 deletions(-) diff --git a/crates/codegraph-binary/src/extract.rs b/crates/codegraph-binary/src/extract.rs index e6993aea2..04a34ee1c 100644 --- a/crates/codegraph-binary/src/extract.rs +++ b/crates/codegraph-binary/src/extract.rs @@ -517,62 +517,62 @@ mod tests { assert_eq!(cleaned_name, name); } -#[test] -fn test_classify_symbol_stripped_name_fallback() { - // Test when raw_name doesn't match but stripped name does - let raw = "sym.class.MyClass"; // r2 adds sym. prefix - let name = "class.MyClass"; // after strip_r2_prefix - let (kind, cleaned_name) = classify_symbol(raw, name); - assert_eq!(kind, SymbolKind::Class); - assert_eq!(cleaned_name, "class.MyClass"); -} + #[test] + fn test_classify_symbol_stripped_name_fallback() { + // Test when raw_name doesn't match but stripped name does + let raw = "sym.class.MyClass"; // r2 adds sym. prefix + let name = "class.MyClass"; // after strip_r2_prefix + let (kind, cleaned_name) = classify_symbol(raw, name); + assert_eq!(kind, SymbolKind::Class); + assert_eq!(cleaned_name, "class.MyClass"); + } -#[test] -fn test_classify_symbol_name_starts_with_class() { - // Test when name (not raw_name) starts with prefix - let raw = "something.class.MyClass"; // raw_name doesn't start with class. - let name = "class.MyClass"; // but name does - let (kind, cleaned_name) = classify_symbol(raw, name); - assert_eq!(kind, SymbolKind::Class); - assert_eq!(cleaned_name, name); -} + #[test] + fn test_classify_symbol_name_starts_with_class() { + // Test when name (not raw_name) starts with prefix + let raw = "something.class.MyClass"; // raw_name doesn't start with class. + let name = "class.MyClass"; // but name does + let (kind, cleaned_name) = classify_symbol(raw, name); + assert_eq!(kind, SymbolKind::Class); + assert_eq!(cleaned_name, name); + } -#[test] -fn test_classify_symbol_name_starts_with_method() { - // Test when name (not raw_name) starts with prefix - let raw = "something.method.MyClass.my_method"; // raw_name doesn't start with method. - let name = "method.MyClass.my_method"; // but name does - let (kind, cleaned_name) = classify_symbol(raw, name); - assert_eq!(kind, SymbolKind::Method); - assert_eq!(cleaned_name, name); -} + #[test] + fn test_classify_symbol_name_starts_with_method() { + // Test when name (not raw_name) starts with prefix + let raw = "something.method.MyClass.my_method"; // raw_name doesn't start with method. + let name = "method.MyClass.my_method"; // but name does + let (kind, cleaned_name) = classify_symbol(raw, name); + assert_eq!(kind, SymbolKind::Method); + assert_eq!(cleaned_name, name); + } -#[test] -fn test_classify_symbol_name_starts_with_namespace() { - // Test when name (not raw_name) starts with prefix - let raw = "something.namespace.std"; // raw_name doesn't start with namespace. - let name = "namespace.std"; // but name does - let (kind, cleaned_name) = classify_symbol(raw, name); - assert_eq!(kind, SymbolKind::Module); - assert_eq!(cleaned_name, name); -} + #[test] + fn test_classify_symbol_name_starts_with_namespace() { + // Test when name (not raw_name) starts with prefix + let raw = "something.namespace.std"; // raw_name doesn't start with namespace. + let name = "namespace.std"; // but name does + let (kind, cleaned_name) = classify_symbol(raw, name); + assert_eq!(kind, SymbolKind::Module); + assert_eq!(cleaned_name, name); + } -#[test] -fn test_classify_symbol_name_starts_with_enum() { - // Test when name (not raw_name) starts with prefix - let raw = "something.enum.Color"; // raw_name doesn't start with enum. - let name = "enum.Color"; // but name does - let (kind, cleaned_name) = classify_symbol(raw, name); - assert_eq!(kind, SymbolKind::Enum); - assert_eq!(cleaned_name, name); -} + #[test] + fn test_classify_symbol_name_starts_with_enum() { + // Test when name (not raw_name) starts with prefix + let raw = "something.enum.Color"; // raw_name doesn't start with enum. + let name = "enum.Color"; // but name does + let (kind, cleaned_name) = classify_symbol(raw, name); + assert_eq!(kind, SymbolKind::Enum); + assert_eq!(cleaned_name, name); + } -#[test] -fn test_classify_symbol_no_match() { - let raw = "some.other.symbol"; - let name = "some.other.symbol"; - let (kind, cleaned_name) = classify_symbol(raw, name); - assert_eq!(kind, SymbolKind::Function); - assert_eq!(cleaned_name, name); -} + #[test] + fn test_classify_symbol_no_match() { + let raw = "some.other.symbol"; + let name = "some.other.symbol"; + let (kind, cleaned_name) = classify_symbol(raw, name); + assert_eq!(kind, SymbolKind::Function); + assert_eq!(cleaned_name, name); + } }