Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions crates/codegraph-binary/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
11 changes: 10 additions & 1 deletion crates/codegraph-binary/src/cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
153 changes: 151 additions & 2 deletions crates/codegraph-binary/src/extract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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<u64, u64>,
Expand Down Expand Up @@ -427,3 +466,113 @@ fn resolve_call_target(target: Option<u64>, 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);
}
}
Loading