Preliminary call graph definition.

This commit is contained in:
2025-03-30 11:23:00 -04:00
commit 4243b66588
5 changed files with 329 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
*~
/target

9
Cargo.toml Normal file
View File

@@ -0,0 +1,9 @@
[package]
name = "shatterc"
version = "0.1.0"
edition = "2024"
[dependencies]
bumpalo = "3.17"
clang = { version = "2.0", features = ["clang_10_0"] }
ghost-cell = "0.2.6"

193
src/call_graph.rs Normal file
View File

@@ -0,0 +1,193 @@
use std::collections::{HashMap, HashSet, hash_map};
use bumpalo::Bump;
use ghost_cell::{GhostCell, GhostToken};
pub struct Typedef<'arena, 'id> {
file: FileRef<'arena, 'id>,
name: String,
target: TypeDefinitionRef<'arena, 'id>,
}
pub type TypedefRef<'arena, 'id> = &'arena GhostCell<'id, Typedef<'arena, 'id>>;
pub struct StructDefinition<'arena, 'id> {
file: FileRef<'arena, 'id>,
name: String,
types: HashSet<TypeDefinitionRef<'arena, 'id>>,
}
pub type StructDefinitionRef<'arena, 'id> = &'arena StructDefinition<'arena, 'id>;
pub struct EnumDefinition<'arena, 'id> {
file: FileRef<'arena, 'id>,
name: String,
}
pub type EnumDefinitionRef<'arena, 'id> = &'arena EnumDefinition<'arena, 'id>;
pub enum TypeDefinitionRef<'arena, 'id> {
Typedef(TypedefRef<'arena, 'id>),
Struct(StructDefinitionRef<'arena, 'id>),
Enum(EnumDefinitionRef<'arena, 'id>),
}
pub struct File<'arena, 'id> {
path: String,
typedefs: HashMap<String, TypedefRef<'arena, 'id>>,
structs: HashMap<String, StructDefinitionRef<'arena, 'id>>,
enums: HashMap<String, EnumDefinitionRef<'arena, 'id>>,
fns: HashMap<String, FunctionDefinitionRef<'arena, 'id>>,
}
impl<'arena, 'id> File<'arena, 'id> {
pub fn path(&self) -> &str {
&self.path
}
pub fn typedefs(&self) -> impl Iterator<Item = TypedefRef<'arena, 'id>> + '_ {
self.typedefs.values().copied()
}
pub fn structs(&self) -> impl Iterator<Item = StructDefinitionRef<'arena, 'id>> + '_ {
self.structs.values().copied()
}
pub fn enums(&self) -> impl Iterator<Item = EnumDefinitionRef<'arena, 'id>> + '_ {
self.enums.values().copied()
}
pub fn fns(&self) -> impl Iterator<Item = FunctionDefinitionRef<'arena, 'id>> + '_ {
self.fns.values().copied()
}
}
pub type FileRef<'arena, 'id> = &'arena GhostCell<'id, File<'arena, 'id>>;
pub struct FunctionDefinition<'arena, 'id> {
public: bool,
name: String,
file: FileRef<'arena, 'id>,
types: HashSet<TypeDefinitionRef<'arena, 'id>>,
fn_calls: HashSet<FunctionDefinitionRef<'arena, 'id>>,
}
impl<'arena, 'id> FunctionDefinition<'arena, 'id> {
pub fn is_public(&self) -> bool {
self.public
}
pub fn name(&self) -> &str {
&self.name
}
pub fn file(&self) -> &FileRef<'arena, 'id> {
&self.file
}
pub fn types(&self) -> impl Iterator<Item = TypeDefinitionRef<'arena, 'id>> + '_ {
self.types.iter().copied()
}
pub fn fn_calls(&self) -> impl Iterator<Item = FunctionDefinitionRef<'arena, 'id>> + '_ {
self.fn_calls.iter().copied()
}
}
pub type FunctionDefinitionRef<'arena, 'id> =
&'arena GhostCell<'id, FunctionDefinition<'arena, 'id>>;
pub struct Graph<'arena, 'id> {
arena: &'arena Bump,
files: HashMap<String, FileRef<'arena, 'id>>,
}
impl<'arena, 'id> Graph<'arena, 'id> {
pub fn find_or_add_file_with_path(&mut self, path: &str) -> FileRef<'arena, 'id> {
match self.files.entry(path.into()) {
hash_map::Entry::Occupied(o) => *o.get(),
hash_map::Entry::Vacant(v) => {
let f = GhostCell::from_mut(self.arena.alloc(File {
path: String::from(path),
typedefs: HashMap::new(),
structs: HashMap::new(),
enums: HashMap::new(),
fns: HashMap::new(),
}));
v.insert(f);
f
}
}
}
pub fn find_or_add_fn_definition(
&mut self,
tok: &mut GhostToken<'id>,
file: FileRef<'arena, 'id>,
public: bool,
fn_name: &str,
) -> FunctionDefinitionRef<'arena, 'id> {
if let Some(existing) = file
.borrow(tok)
.fns()
.find(|f| f.borrow(tok).name() == fn_name)
{
return *existing;
}
let def = GhostCell::from_mut(self.arena.alloc(FunctionDefinition {
public,
name: fn_name.into(),
file,
types: HashSet::new(),
fn_calls: HashSet::new(),
}));
file.borrow_mut(tok).fns.push(def);
def
}
pub fn find_or_add_typedef(
&mut self,
tok: &mut GhostToken<'id>,
file: FileRef<'arena, 'id>,
type_name: &str,
) -> TypedefRef<'arena, 'id> {
unimplemented!()
}
pub fn find_or_add_enum_definition(
&mut self,
tok: &mut GhostToken<'id>,
file: FileRef<'arena, 'id>,
type_name: &str,
) -> EnumDefinitionRef<'arena, 'id> {
unimplemented!()
}
pub fn find_or_add_struct_definition(
&mut self,
tok: &mut GhostToken<'id>,
file: FileRef<'arena, 'id>,
type_name: &str,
) -> StructDefinitionRef<'arena, 'id> {
unimplemented!()
}
pub fn files(&self) -> impl Iterator<Item = FileRef<'arena, 'id>> + '_ {
self.files.values().copied()
}
}
pub fn new<R>(
arena: &Bump,
f: impl for<'arena, 'id> FnOnce(GhostToken<'id>, Graph<'arena, 'id>) -> R,
) -> R {
GhostToken::new(|t| {
f(
t,
Graph {
arena,
files: HashMap::new(),
},
)
})
}

8
src/discover.rs Normal file
View File

@@ -0,0 +1,8 @@
use crate::call_graph::{FileRef, FunctionDefinitionRef, Graph};
pub fn function_body<'arena, 'id>(
graph: &mut Graph<'arena, 'id>,
file: FileRef<'arena, 'id>,
) -> FunctionDefinitionRef<'arena, 'id> {
unimplemented!()
}

117
src/main.rs Normal file
View File

@@ -0,0 +1,117 @@
use clang::{Clang, EntityKind, Index, StorageClass};
use std::{
collections::{VecDeque, hash_map::Entry},
env,
};
mod call_graph;
mod discover;
fn main() -> Result<(), String> {
let files: Vec<_> = env::args().skip(1).collect();
let clang = Clang::new()?;
let index = Index::new(&clang, /*exclude=*/ false, /*diagnostics=*/ false);
let ignore_functions = vec![
format!("__bswap_16"),
format!("__bswap_32"),
format!("__bswap_64"),
format!("__uint16_identity"),
format!("__uint32_identity"),
format!("__uint64_identity"),
];
let arena = bumpalo::Bump::new();
call_graph::new(&arena, |tok, graph| {
// Parse first, ask questions later.
let mut parsed = Vec::with_capacity(files.len());
for file in &files {
match index.parse(file).parse() {
Ok(parsed) => parsed.push((graph.find_or_add_file_with_path(file), parsed)),
Err(_) => eprintln!("could not parse file: `{}`", file),
}
}
let parsed = parsed;
// First pass: discover function, struct, enum, and typedef definitions.
let typedefs = HashMap::new();
let structs = HashMap::new();
let enums = HashMap::new();
let fns = HashMap::new();
for (file, parsed) in &parsed {
for child in parsed.get_entity().get_children().into_iter() {
if let Some(def) = child.get_definition() {
match child.get_kind() {
EntityKind::FunctionDecl => unimplemented!(),
EntityKind::StructDecl => unimplemented!(),
EntityKind::EnumDecl => unimplemented!(),
EntityKind::TypedefDecl => unimplemented!(),
_ => (),
}
}
}
}
// Second pass: descend into definitions.
for (typedef_vertex, def) in typedefs.into_iter() {
unimplemented!()
}
for (struct_vertex, def) in structs.into_iter() {
unimplemented!()
}
for (enum_vertex, def) in enums.into_iter() {
unimplemented!()
}
for (fn_vertex, def) in fns.into_iter() {
unimplemented!()
}
// if let Some(def) = fn_decl.get_definition() {
// if let Some(decl_name) = fn_decl.get_name() {
// if ignore_functions.contains(&decl_name) {
// continue;
// }
// let fn_decl = graph.find_or_add_fn_definition(
// &mut tok,
// infile,
// /*public=*/
// fn_decl.get_storage_class() == Some(StorageClass::Static),
// /*fn_name=*/ &decl_name,
// );
// let mut frontier = VecDeque::from(def.get_children());
// while let Some(e) = frontier.pop_front() {
// match e.get_kind() {
// EntityKind::CallExpr => {
// if let Some(called_name) = e.get_name() {
// }
// }
// }
// if e.get_kind() == EntityKind::CallExpr {
// if let Some(called_name) = e.get_name() {
// match caller2called.entry(decl.name().into()) {
// Entry::Occupied(mut o) => o.get_mut().push(called_name),
// Entry::Vacant(v) => {
// v.insert(vec![called_name]);
// }
// }
// }
// for c in e.get_children().into_iter() {
// frontier.push_back(c);
// }
// }
// }
// } else {
// return Err(format!(
// "function definition in {} has no name",
// infile.borrow(&tok).path()
// ));
// }
// }
// }
// }
Ok(())
})
}