Rework to support a basic query language.
Currently broken and needs tests/debugging.
This commit is contained in:
parent
6ec952ba16
commit
68fa7d2810
46
Cargo.lock
generated
46
Cargo.lock
generated
@ -2,6 +2,15 @@
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "1.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstream"
|
||||
version = "0.6.18"
|
||||
@ -154,6 +163,12 @@ version = "0.2.171"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.21.3"
|
||||
@ -178,6 +193,35 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-automata",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.4.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.8.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
|
||||
|
||||
[[package]]
|
||||
name = "shatterc"
|
||||
version = "0.1.0"
|
||||
@ -186,6 +230,8 @@ dependencies = [
|
||||
"clang",
|
||||
"clap",
|
||||
"ghost-cell",
|
||||
"glob",
|
||||
"regex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -8,3 +8,5 @@ bumpalo = { version = "3.17", features = ["collections"] }
|
||||
clang = { version = "2.0", features = ["clang_10_0"] }
|
||||
clap = { version = "4.5", features = ["derive"] }
|
||||
ghost-cell = "0.2.6"
|
||||
glob = "0.3.2"
|
||||
regex = "1.11"
|
||||
|
@ -262,7 +262,7 @@ pub struct File<'arena, 'id> {
|
||||
}
|
||||
|
||||
impl<'arena, 'id> File<'arena, 'id> {
|
||||
pub fn path(&self) -> &Path {
|
||||
pub fn path(&self) -> &'arena Path {
|
||||
&self.path
|
||||
}
|
||||
|
||||
@ -393,10 +393,7 @@ impl<'arena, 'id> Graph<'arena, 'id> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn find_fn_definition(
|
||||
&mut self,
|
||||
fn_name: &str,
|
||||
) -> Option<FunctionDefinitionRef<'arena, 'id>> {
|
||||
pub fn find_fn_definition(&self, fn_name: &str) -> Option<FunctionDefinitionRef<'arena, 'id>> {
|
||||
self.fns.get(fn_name).copied()
|
||||
}
|
||||
|
||||
|
109
src/main.rs
109
src/main.rs
@ -1,49 +1,82 @@
|
||||
use clang::{Clang, EntityKind, Index};
|
||||
use clap::Parser;
|
||||
|
||||
use std::path::PathBuf;
|
||||
use std::{collections::HashSet, path::PathBuf};
|
||||
|
||||
use crate::query::Target;
|
||||
|
||||
mod call_graph;
|
||||
mod discover;
|
||||
mod link;
|
||||
mod output;
|
||||
mod query;
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
struct Args {
|
||||
/// Files defining symbols to exclude. All symbols discovered in or
|
||||
/// referenced by these files will be ignored.
|
||||
/// Files or symbols to ignore, specified with a prefix (file:, fn:, enum:,
|
||||
/// struct:, or type:).
|
||||
#[arg(long)]
|
||||
ignore: Vec<PathBuf>,
|
||||
ignore: Vec<String>,
|
||||
|
||||
/// Files to search for symbols. The dependency graph of symbols will be loaded from these files.
|
||||
/// Files to ignore. All symbols reachable from these files will also be
|
||||
/// ignored. File name globbing ("/usr/include/*.h") and path globbing
|
||||
/// ("/usr/include/linux/**/*.h") are supported.
|
||||
#[arg(long)]
|
||||
ignore_paths: Vec<String>,
|
||||
|
||||
/// Files, functions, and types to search for. All items reachable from them
|
||||
/// will be output. If not specified, all files and symbols will be output.
|
||||
#[arg(short, long)]
|
||||
search: Vec<String>,
|
||||
|
||||
/// Files to search. Directories will be traversed, recursively. The
|
||||
/// dependency graph of symbols will be loaded from these files. File name
|
||||
/// globbing and path globbing are supported.
|
||||
#[arg()]
|
||||
files: Vec<PathBuf>,
|
||||
search_paths: Vec<String>,
|
||||
}
|
||||
|
||||
fn find_files(pattern: &str) -> Result<HashSet<PathBuf>, String> {
|
||||
let mut paths = HashSet::new();
|
||||
for entry in glob::glob(pattern).map_err(|e| format!("{}", e))? {
|
||||
match entry {
|
||||
Ok(path) => paths.insert(path),
|
||||
Err(e) => return Err(format!("{}", e)),
|
||||
};
|
||||
}
|
||||
Ok(paths)
|
||||
}
|
||||
|
||||
fn main() -> Result<(), String> {
|
||||
let args = Args::parse();
|
||||
|
||||
let mut search_paths = HashSet::new();
|
||||
for pattern in args.search_paths.iter() {
|
||||
search_paths.extend(find_files(pattern).map_err(|e| format!("{}", e))?);
|
||||
}
|
||||
let search_paths = search_paths;
|
||||
|
||||
let mut ignore_paths = HashSet::new();
|
||||
for pattern in &args.ignore_paths {
|
||||
ignore_paths.extend(find_files(pattern).map_err(|e| format!("{}", e))?);
|
||||
}
|
||||
let ignore_paths = ignore_paths;
|
||||
|
||||
let clang = Clang::new()?;
|
||||
let index = Index::new(&clang, /*exclude=*/ false, /*diagnostics=*/ false);
|
||||
|
||||
let arena = bumpalo::Bump::new();
|
||||
call_graph::new(&arena, |mut tok, mut graph| {
|
||||
// Parse first, ask questions later.
|
||||
let mut parsed = Vec::with_capacity(args.files.len());
|
||||
for file in args.files.iter().chain(args.ignore.iter()) {
|
||||
match index.parser(file).parse() {
|
||||
Ok(p) => {
|
||||
let file = match graph.find_or_add_file_with_path(&file) {
|
||||
Ok(f) => f,
|
||||
Err(_) => {
|
||||
eprintln!("could not canonicalize file path: `{}`", file.display());
|
||||
continue;
|
||||
}
|
||||
};
|
||||
parsed.push((file, p));
|
||||
}
|
||||
Err(_) => eprintln!("could not parse file: `{}`", file.display()),
|
||||
}
|
||||
let all_paths: HashSet<_> = search_paths.union(&ignore_paths).collect();
|
||||
let mut parsed = Vec::with_capacity(all_paths.len());
|
||||
for file in &all_paths {
|
||||
parsed.push((
|
||||
graph
|
||||
.find_or_add_file_with_path(&file)
|
||||
.map_err(|e| format!("{}", e))?,
|
||||
index.parser(file).parse()?,
|
||||
));
|
||||
}
|
||||
let parsed = parsed;
|
||||
|
||||
@ -98,7 +131,39 @@ fn main() -> Result<(), String> {
|
||||
}
|
||||
|
||||
let mut stdout = std::io::stdout();
|
||||
output::to_dot(&graph, &tok, &args.ignore, &mut stdout).map_err(|e| format!("{:?}", e))?;
|
||||
let mut q = query::Query::new();
|
||||
q.ignore_defaults(&graph);
|
||||
|
||||
if args.search.is_empty() {
|
||||
for target in graph.files().map(Target::from) {
|
||||
q.find_reachable_from(target);
|
||||
}
|
||||
} else {
|
||||
for target in args
|
||||
.search
|
||||
.iter()
|
||||
.filter_map(|n| query::search_by_name(&graph, &n))
|
||||
{
|
||||
q.find_reachable_from(target);
|
||||
}
|
||||
}
|
||||
for ignore in args
|
||||
.ignore
|
||||
.iter()
|
||||
.filter_map(|n| query::search_by_name(&graph, n))
|
||||
{
|
||||
q.ignore(ignore);
|
||||
}
|
||||
for ignore_target in ignore_paths
|
||||
.iter()
|
||||
.filter_map(|p| graph.find_file_with_path(p))
|
||||
.map(Target::from)
|
||||
{
|
||||
q.ignore_reachable_from(ignore_target);
|
||||
}
|
||||
let results = q.search(&tok);
|
||||
eprintln!("found {} results", results.len());
|
||||
output::to_dot(&tok, &results, &mut stdout).map_err(|e| format!("{:?}", e))?;
|
||||
|
||||
Ok(())
|
||||
})
|
||||
|
284
src/output.rs
284
src/output.rs
@ -1,5 +1,4 @@
|
||||
use std::{
|
||||
collections::HashSet,
|
||||
fmt,
|
||||
io::{self, Write},
|
||||
path::Path,
|
||||
@ -7,107 +6,10 @@ use std::{
|
||||
|
||||
use ghost_cell::GhostToken;
|
||||
|
||||
use crate::call_graph::{Graph, Symbol, TypeDefinitionRef};
|
||||
|
||||
const IGNORE_FUNCTIONS: &'static [&'static str] = &[
|
||||
"__bswap_16",
|
||||
"__bswap_32",
|
||||
"__bswap_64",
|
||||
"__uint16_identity",
|
||||
"__uint32_identity",
|
||||
"__uint64_identity",
|
||||
];
|
||||
|
||||
const IGNORE_TYPEDEFS: &[&'static str] = &[
|
||||
"_Float32x",
|
||||
"__mode_t",
|
||||
"__suseconds64_t",
|
||||
"__syscall_ulong_t",
|
||||
"__blksize_t",
|
||||
"__ino64_t",
|
||||
"__socklen_t",
|
||||
"_IO_lock_t",
|
||||
"cookie_io_functions_t",
|
||||
"FILE",
|
||||
"__fsblkcnt64_t",
|
||||
"__loff_t",
|
||||
"_Float32",
|
||||
"__gid_t",
|
||||
"__intmax_t",
|
||||
"__uint_least64_t",
|
||||
"__timer_t",
|
||||
"__dev_t",
|
||||
"__cfloat128",
|
||||
"__fsid_t",
|
||||
"__uint_least32_t",
|
||||
"__u_short",
|
||||
"__time_t",
|
||||
"__uint16_t",
|
||||
"__suseconds_t",
|
||||
"__uint8_t",
|
||||
"__int_least16_t",
|
||||
"__clockid_t",
|
||||
"__blkcnt64_t",
|
||||
"__fpos_t",
|
||||
"__fsblkcnt_t",
|
||||
"__fsfilcnt_t",
|
||||
"__int_least64_t",
|
||||
"__int16_t",
|
||||
"__rlim64_t",
|
||||
"__int64_t",
|
||||
"__int_least32_t",
|
||||
"__fpos64_t",
|
||||
"__ino_t",
|
||||
"cookie_seek_function_t",
|
||||
"__u_quad_t",
|
||||
"__sig_atomic_t",
|
||||
"__FILE",
|
||||
"__quad_t",
|
||||
"fpos_t",
|
||||
"__u_char",
|
||||
"__uintmax_t",
|
||||
"__uint_least8_t",
|
||||
"__useconds_t",
|
||||
"__fsfilcnt64_t",
|
||||
"__fsword_t",
|
||||
"__id_t",
|
||||
"__caddr_t",
|
||||
"off_t",
|
||||
"__uid_t",
|
||||
"va_list",
|
||||
"__key_t",
|
||||
"__intptr_t",
|
||||
"_Float128",
|
||||
"__rlim_t",
|
||||
"__nlink_t",
|
||||
"__int8_t",
|
||||
"__int_least8_t",
|
||||
"ssize_t",
|
||||
"__clock_t",
|
||||
"__syscall_slong_t",
|
||||
"__uint64_t",
|
||||
"__u_long",
|
||||
"cookie_close_function_t",
|
||||
"__blkcnt_t",
|
||||
"__ssize_t",
|
||||
"__off_t",
|
||||
"__gnuc_va_list",
|
||||
"size_t",
|
||||
"cookie_write_function_t",
|
||||
"__mbstate_t",
|
||||
"_Float64",
|
||||
"__uint32_t",
|
||||
"cookie_read_function_t",
|
||||
"__daddr_t",
|
||||
"__int32_t",
|
||||
"_Float64x",
|
||||
"__off64_t",
|
||||
"__uint_least16_t",
|
||||
"__u_int",
|
||||
"__pid_t",
|
||||
];
|
||||
|
||||
const IGNORE_STRUCTS: &[&'static str] = &["_IO_FILE"];
|
||||
use crate::{
|
||||
call_graph::Symbol,
|
||||
query::{Target, targets_from},
|
||||
};
|
||||
|
||||
#[derive(Clone, Copy, Eq, Hash, PartialEq)]
|
||||
enum DisplayVertex<'a> {
|
||||
@ -117,6 +19,7 @@ enum DisplayVertex<'a> {
|
||||
Struct(&'a str),
|
||||
Typedef(&'a str),
|
||||
}
|
||||
|
||||
impl<'a> fmt::Display for DisplayVertex<'a> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self {
|
||||
@ -129,178 +32,31 @@ impl<'a> fmt::Display for DisplayVertex<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
fn find_reachable_from<'arena, 'id>(
|
||||
graph: &Graph<'arena, 'id>,
|
||||
tok: &GhostToken<'id>,
|
||||
files: &[impl AsRef<Path>],
|
||||
) -> HashSet<Symbol<'arena, 'id>> {
|
||||
let mut reachable = HashSet::new();
|
||||
for file in files {
|
||||
let file = match graph.find_file_with_path(file.as_ref()) {
|
||||
Some(f) => f,
|
||||
None => return reachable,
|
||||
};
|
||||
let mut frontier = Vec::new();
|
||||
let symbols = file
|
||||
.borrow(tok)
|
||||
.structs()
|
||||
.map(Symbol::from)
|
||||
.chain(file.borrow(tok).enums().map(Symbol::from))
|
||||
.chain(file.borrow(tok).typedefs().map(Symbol::from))
|
||||
.chain(file.borrow(tok).fns().map(Symbol::from));
|
||||
for s in symbols {
|
||||
if reachable.insert(s) {
|
||||
frontier.push(s);
|
||||
}
|
||||
}
|
||||
|
||||
while let Some(s) = frontier.pop() {
|
||||
match s {
|
||||
Symbol::Enum(_) => (),
|
||||
Symbol::Struct(s) => {
|
||||
for s in s.borrow(tok).types().map(Symbol::from) {
|
||||
if reachable.insert(s) {
|
||||
frontier.push(s);
|
||||
}
|
||||
}
|
||||
}
|
||||
Symbol::Typedef(s) => {
|
||||
for typ in s.borrow(tok).target() {
|
||||
let s = Symbol::from(*typ);
|
||||
if reachable.insert(s) {
|
||||
frontier.push(s);
|
||||
}
|
||||
}
|
||||
}
|
||||
Symbol::Function(s) => {
|
||||
let symbols = s
|
||||
.borrow(tok)
|
||||
.types()
|
||||
.map(Symbol::from)
|
||||
.chain(s.borrow(tok).fn_calls().map(Symbol::from));
|
||||
for s in symbols {
|
||||
if reachable.insert(s) {
|
||||
frontier.push(s);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<'arena, 'id> From<(&GhostToken<'id>, Target<'arena, 'id>)> for DisplayVertex<'arena> {
|
||||
fn from((tok, target): (&GhostToken<'id>, Target<'arena, 'id>)) -> Self {
|
||||
match target {
|
||||
Target::File(f) => DisplayVertex::File(f.borrow(tok).path()),
|
||||
Target::Symbol(Symbol::Enum(e)) => DisplayVertex::Enum(e.borrow(tok).name()),
|
||||
Target::Symbol(Symbol::Function(fun)) => DisplayVertex::Fun(fun.borrow(tok).name()),
|
||||
Target::Symbol(Symbol::Struct(s)) => DisplayVertex::Struct(s.borrow(tok).name()),
|
||||
Target::Symbol(Symbol::Typedef(t)) => DisplayVertex::Typedef(t.borrow(tok).name()),
|
||||
}
|
||||
}
|
||||
reachable
|
||||
}
|
||||
|
||||
pub fn to_dot<'arena, 'id>(
|
||||
graph: &Graph<'arena, 'id>,
|
||||
tok: &GhostToken<'id>,
|
||||
ignore_paths: &[impl AsRef<Path>],
|
||||
items: &[Target<'arena, 'id>],
|
||||
out: &mut impl Write,
|
||||
) -> io::Result<()> {
|
||||
let ignore = find_reachable_from(graph, tok, ignore_paths);
|
||||
eprintln!(
|
||||
"will ignore {} symbol(s) from {} ignore path(s)",
|
||||
ignore.len(),
|
||||
ignore_paths.len()
|
||||
);
|
||||
let mut edges = HashSet::new();
|
||||
|
||||
writeln!(out, "digraph {{")?;
|
||||
for file in graph.files() {
|
||||
let file_name = file.borrow(tok).path();
|
||||
for fun in file.borrow(tok).fns() {
|
||||
let fn_name = fun.borrow(tok).name();
|
||||
if IGNORE_FUNCTIONS.contains(&fn_name) || ignore.contains(&Symbol::Function(fun)) {
|
||||
continue;
|
||||
}
|
||||
edges.insert((DisplayVertex::File(file_name), DisplayVertex::Fun(fn_name)));
|
||||
for typ in fun.borrow(tok).types() {
|
||||
match typ {
|
||||
TypeDefinitionRef::Enum(typ) => {
|
||||
let type_name = typ.borrow(tok).name();
|
||||
if !ignore.contains(&Symbol::Enum(typ)) {
|
||||
edges.insert((
|
||||
DisplayVertex::Fun(fn_name),
|
||||
DisplayVertex::Enum(type_name),
|
||||
));
|
||||
}
|
||||
}
|
||||
TypeDefinitionRef::Struct(typ) => {
|
||||
let type_name = typ.borrow(tok).name();
|
||||
if !ignore.contains(&Symbol::Struct(typ)) {
|
||||
edges.insert((
|
||||
DisplayVertex::Fun(fn_name),
|
||||
DisplayVertex::Struct(type_name),
|
||||
));
|
||||
}
|
||||
}
|
||||
TypeDefinitionRef::Typedef(typ) => {
|
||||
let type_name = typ.borrow(tok).name();
|
||||
if !(IGNORE_TYPEDEFS.contains(&type_name)
|
||||
|| ignore.contains(&Symbol::Typedef(typ)))
|
||||
{
|
||||
edges.insert((
|
||||
DisplayVertex::Fun(fn_name),
|
||||
DisplayVertex::Typedef(type_name),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for callee in fun.borrow(tok).fn_calls() {
|
||||
let callee_name = callee.borrow(tok).name();
|
||||
if IGNORE_FUNCTIONS.contains(&callee_name) {
|
||||
continue;
|
||||
}
|
||||
edges.insert((DisplayVertex::Fun(fn_name), DisplayVertex::Fun(callee_name)));
|
||||
}
|
||||
}
|
||||
for typ in file.borrow(tok).typedefs() {
|
||||
let type_name = typ.borrow(tok).name();
|
||||
if !(IGNORE_TYPEDEFS.contains(&type_name) || ignore.contains(&Symbol::Typedef(typ))) {
|
||||
edges.insert((
|
||||
DisplayVertex::File(file_name),
|
||||
DisplayVertex::Typedef(type_name),
|
||||
));
|
||||
}
|
||||
}
|
||||
for struct_def in graph.structs() {
|
||||
let struct_name = struct_def.borrow(tok).name();
|
||||
if IGNORE_STRUCTS.contains(&struct_name) || ignore.contains(&Symbol::Struct(struct_def))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
for typ in struct_def.borrow(tok).types() {
|
||||
match typ {
|
||||
TypeDefinitionRef::Enum(typ) => {
|
||||
let type_name = typ.borrow(tok).name();
|
||||
edges.insert((
|
||||
DisplayVertex::Struct(struct_name),
|
||||
DisplayVertex::Enum(type_name),
|
||||
));
|
||||
}
|
||||
TypeDefinitionRef::Struct(typ) => {
|
||||
let type_name = typ.borrow(tok).name();
|
||||
edges.insert((
|
||||
DisplayVertex::Struct(struct_name),
|
||||
DisplayVertex::Struct(type_name),
|
||||
));
|
||||
}
|
||||
TypeDefinitionRef::Typedef(typ) => {
|
||||
let type_name = typ.borrow(tok).name();
|
||||
if !IGNORE_TYPEDEFS.contains(&type_name) {
|
||||
edges.insert((
|
||||
DisplayVertex::Struct(struct_name),
|
||||
DisplayVertex::Typedef(type_name),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (src, dest) in &edges {
|
||||
writeln!(out, "\"{}\" -> \"{}\"", src, dest)?;
|
||||
for src in items {
|
||||
let src_vertex = DisplayVertex::from((tok, *src));
|
||||
for dest in targets_from(tok, *src) {
|
||||
let dest = DisplayVertex::from((tok, dest));
|
||||
writeln!(out, "\"{}\" -> \"{}\"", src_vertex, dest)?;
|
||||
}
|
||||
}
|
||||
|
||||
writeln!(out, "}}")
|
||||
|
404
src/query.rs
Normal file
404
src/query.rs
Normal file
@ -0,0 +1,404 @@
|
||||
use crate::call_graph::{
|
||||
EnumDefinitionRef, FileRef, FunctionDefinitionRef, Graph, StructDefinitionRef, Symbol,
|
||||
TypeDefinitionRef, TypedefRef,
|
||||
};
|
||||
|
||||
use ghost_cell::GhostToken;
|
||||
use regex::Regex;
|
||||
|
||||
use std::{
|
||||
collections::HashSet,
|
||||
hash::{Hash, Hasher},
|
||||
iter,
|
||||
path::Path,
|
||||
ptr,
|
||||
sync::OnceLock,
|
||||
};
|
||||
|
||||
static SYMBOL_NAME: OnceLock<Regex> = OnceLock::new();
|
||||
|
||||
const IGNORE_FUNCTIONS: &'static [&'static str] = &[
|
||||
"__bswap_16",
|
||||
"__bswap_32",
|
||||
"__bswap_64",
|
||||
"__uint16_identity",
|
||||
"__uint32_identity",
|
||||
"__uint64_identity",
|
||||
];
|
||||
|
||||
const IGNORE_TYPEDEFS: &[&'static str] = &[
|
||||
"_Float32x",
|
||||
"__mode_t",
|
||||
"__suseconds64_t",
|
||||
"__syscall_ulong_t",
|
||||
"__blksize_t",
|
||||
"__ino64_t",
|
||||
"__socklen_t",
|
||||
"_IO_lock_t",
|
||||
"cookie_io_functions_t",
|
||||
"FILE",
|
||||
"__fsblkcnt64_t",
|
||||
"__loff_t",
|
||||
"_Float32",
|
||||
"__gid_t",
|
||||
"__intmax_t",
|
||||
"__uint_least64_t",
|
||||
"__timer_t",
|
||||
"__dev_t",
|
||||
"__cfloat128",
|
||||
"__fsid_t",
|
||||
"__uint_least32_t",
|
||||
"__u_short",
|
||||
"__time_t",
|
||||
"__uint16_t",
|
||||
"__suseconds_t",
|
||||
"__uint8_t",
|
||||
"__int_least16_t",
|
||||
"__clockid_t",
|
||||
"__blkcnt64_t",
|
||||
"__fpos_t",
|
||||
"__fsblkcnt_t",
|
||||
"__fsfilcnt_t",
|
||||
"__int_least64_t",
|
||||
"__int16_t",
|
||||
"__rlim64_t",
|
||||
"__int64_t",
|
||||
"__int_least32_t",
|
||||
"__fpos64_t",
|
||||
"__ino_t",
|
||||
"cookie_seek_function_t",
|
||||
"__u_quad_t",
|
||||
"__sig_atomic_t",
|
||||
"__FILE",
|
||||
"__quad_t",
|
||||
"fpos_t",
|
||||
"__u_char",
|
||||
"__uintmax_t",
|
||||
"__uint_least8_t",
|
||||
"__useconds_t",
|
||||
"__fsfilcnt64_t",
|
||||
"__fsword_t",
|
||||
"__id_t",
|
||||
"__caddr_t",
|
||||
"off_t",
|
||||
"__uid_t",
|
||||
"va_list",
|
||||
"__key_t",
|
||||
"__intptr_t",
|
||||
"_Float128",
|
||||
"__rlim_t",
|
||||
"__nlink_t",
|
||||
"__int8_t",
|
||||
"__int_least8_t",
|
||||
"ssize_t",
|
||||
"__clock_t",
|
||||
"__syscall_slong_t",
|
||||
"__uint64_t",
|
||||
"__u_long",
|
||||
"cookie_close_function_t",
|
||||
"__blkcnt_t",
|
||||
"__ssize_t",
|
||||
"__off_t",
|
||||
"__gnuc_va_list",
|
||||
"size_t",
|
||||
"cookie_write_function_t",
|
||||
"__mbstate_t",
|
||||
"_Float64",
|
||||
"__uint32_t",
|
||||
"cookie_read_function_t",
|
||||
"__daddr_t",
|
||||
"__int32_t",
|
||||
"_Float64x",
|
||||
"__off64_t",
|
||||
"__uint_least16_t",
|
||||
"__u_int",
|
||||
"__pid_t",
|
||||
];
|
||||
|
||||
const IGNORE_STRUCTS: &[&'static str] = &["_IO_FILE"];
|
||||
|
||||
pub fn default_ignores<'arena, 'id>(graph: &Graph<'arena, 'id>) -> Vec<Target<'arena, 'id>> {
|
||||
IGNORE_FUNCTIONS
|
||||
.iter()
|
||||
.filter_map(|x| graph.find_fn_definition(x))
|
||||
.map(Target::from)
|
||||
.chain(
|
||||
IGNORE_TYPEDEFS
|
||||
.iter()
|
||||
.filter_map(|x| graph.find_typedef(x))
|
||||
.map(Target::from),
|
||||
)
|
||||
.chain(
|
||||
IGNORE_STRUCTS
|
||||
.iter()
|
||||
.filter_map(|x| graph.find_struct_definition(x).map(Target::from)),
|
||||
)
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum Target<'arena, 'id> {
|
||||
File(FileRef<'arena, 'id>),
|
||||
Symbol(Symbol<'arena, 'id>),
|
||||
}
|
||||
|
||||
impl<'arena, 'id> From<FileRef<'arena, 'id>> for Target<'arena, 'id> {
|
||||
fn from(f: FileRef<'arena, 'id>) -> Self {
|
||||
Target::File(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'arena, 'id> From<StructDefinitionRef<'arena, 'id>> for Target<'arena, 'id> {
|
||||
fn from(s: StructDefinitionRef<'arena, 'id>) -> Self {
|
||||
Target::Symbol(Symbol::Struct(s))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'arena, 'id> From<EnumDefinitionRef<'arena, 'id>> for Target<'arena, 'id> {
|
||||
fn from(e: EnumDefinitionRef<'arena, 'id>) -> Self {
|
||||
Target::Symbol(Symbol::Enum(e))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'arena, 'id> From<FunctionDefinitionRef<'arena, 'id>> for Target<'arena, 'id> {
|
||||
fn from(f: FunctionDefinitionRef<'arena, 'id>) -> Self {
|
||||
Target::Symbol(Symbol::Function(f))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'arena, 'id> From<TypedefRef<'arena, 'id>> for Target<'arena, 'id> {
|
||||
fn from(t: TypedefRef<'arena, 'id>) -> Self {
|
||||
Target::Symbol(Symbol::Typedef(t))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'arena, 'id> From<TypeDefinitionRef<'arena, 'id>> for Target<'arena, 'id> {
|
||||
fn from(t: TypeDefinitionRef<'arena, 'id>) -> Self {
|
||||
match t {
|
||||
TypeDefinitionRef::Enum(e) => Target::from(e),
|
||||
TypeDefinitionRef::Struct(s) => Target::from(s),
|
||||
TypeDefinitionRef::Typedef(t) => Target::from(t),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'arena, 'id> PartialEq for Target<'arena, 'id> {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
match (self, other) {
|
||||
(Target::File(a), Target::File(b)) => ptr::addr_eq(*a, *b),
|
||||
(Target::Symbol(a), Target::Symbol(b)) => a == b,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'arena, 'id> Eq for Target<'arena, 'id> {}
|
||||
|
||||
impl<'arena, 'id> Hash for Target<'arena, 'id> {
|
||||
fn hash<H: Hasher>(&self, h: &mut H) {
|
||||
match self {
|
||||
Target::File(f) => ptr::hash(*f, h),
|
||||
Target::Symbol(s) => Hash::hash(s, h),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn search_by_name<'arena, 'id>(
|
||||
graph: &Graph<'arena, 'id>,
|
||||
name: &str,
|
||||
) -> Option<Target<'arena, 'id>> {
|
||||
let regex =
|
||||
SYMBOL_NAME.get_or_init(|| Regex::new(r"^(file|fn|enum|struct|type):(.+?)").unwrap());
|
||||
for (_, [typ, name]) in regex.captures_iter(name).map(|c| c.extract()) {
|
||||
if typ == "file" {
|
||||
return graph
|
||||
.find_file_with_path(Path::new(name))
|
||||
.map(|f| Target::File(f));
|
||||
} else if typ == "fn" {
|
||||
return graph
|
||||
.find_fn_definition(name)
|
||||
.map(|f| Target::Symbol(Symbol::Function(f)));
|
||||
} else if typ == "enum" {
|
||||
return graph
|
||||
.find_enum_definition(name)
|
||||
.map(|e| Target::Symbol(Symbol::Enum(e)));
|
||||
} else if typ == "struct" {
|
||||
return graph
|
||||
.find_struct_definition(name)
|
||||
.map(|s| Target::Symbol(Symbol::Struct(s)));
|
||||
} else if typ == "type" {
|
||||
return graph
|
||||
.find_typedef(name)
|
||||
.map(|t| Target::Symbol(Symbol::Typedef(t)));
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub fn targets_from<'a, 'arena: 'a, 'id>(
|
||||
tok: &'a GhostToken<'id>,
|
||||
src: Target<'arena, 'id>,
|
||||
) -> Box<dyn Iterator<Item = Target<'arena, 'id>> + 'a> {
|
||||
match src {
|
||||
Target::File(f) => Box::new(
|
||||
f.borrow(tok)
|
||||
.structs()
|
||||
.map(Target::from)
|
||||
.chain(f.borrow(tok).enums().map(Target::from))
|
||||
.chain(f.borrow(tok).structs().map(Target::from))
|
||||
.chain(f.borrow(tok).typedefs().map(Target::from)),
|
||||
),
|
||||
Target::Symbol(Symbol::Enum(_)) => Box::new(iter::empty()),
|
||||
Target::Symbol(Symbol::Function(f)) => Box::new(
|
||||
f.borrow(tok)
|
||||
.fn_calls()
|
||||
.map(Target::from)
|
||||
.chain(f.borrow(tok).types().map(Target::from)),
|
||||
),
|
||||
Target::Symbol(Symbol::Struct(s)) => Box::new(s.borrow(tok).types().map(Target::from)),
|
||||
Target::Symbol(Symbol::Typedef(t)) => {
|
||||
Box::new(t.borrow(tok).target().iter().copied().map(Target::from))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn find_reachable_from<'arena, 'id>(
|
||||
tok: &GhostToken<'id>,
|
||||
targets: impl Iterator<Item = Target<'arena, 'id>>,
|
||||
) -> HashSet<Target<'arena, 'id>> {
|
||||
let mut reachable = HashSet::new();
|
||||
let mut frontier: Vec<_> = targets.collect();
|
||||
while let Some(t) = frontier.pop() {
|
||||
for target in targets_from(tok, t) {
|
||||
if reachable.insert(target) {
|
||||
frontier.push(target);
|
||||
}
|
||||
}
|
||||
}
|
||||
reachable
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Query<'arena, 'id> {
|
||||
roots: HashSet<Target<'arena, 'id>>,
|
||||
transitive_ignores: HashSet<Target<'arena, 'id>>,
|
||||
ignores: HashSet<Target<'arena, 'id>>,
|
||||
}
|
||||
|
||||
impl<'arena, 'id> Query<'arena, 'id> {
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
pub fn ignore_defaults(&mut self, graph: &Graph<'arena, 'id>) -> &mut Self {
|
||||
self.ignores.extend(default_ignores(graph).into_iter());
|
||||
self
|
||||
}
|
||||
|
||||
pub fn find_reachable_from(&mut self, root: Target<'arena, 'id>) -> &mut Self {
|
||||
self.roots.insert(root);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn ignore_reachable_from(&mut self, ignore: Target<'arena, 'id>) -> &mut Self {
|
||||
self.transitive_ignores.insert(ignore);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn ignore(&mut self, ignore: Target<'arena, 'id>) -> &mut Self {
|
||||
self.ignores.insert(ignore);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn search(&self, tok: &GhostToken<'id>) -> Vec<Target<'arena, 'id>> {
|
||||
let ignore: HashSet<_> = find_reachable_from(tok, self.ignores.iter().copied())
|
||||
.union(&self.ignores)
|
||||
.copied()
|
||||
.collect();
|
||||
eprintln!("will ignore up to {} items", ignore.len());
|
||||
find_reachable_from(tok, self.roots.iter().copied())
|
||||
.difference(&ignore)
|
||||
.copied()
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
// use super::*;
|
||||
// use std::collections::HashSet;
|
||||
// use std::hash::{self, Hash};
|
||||
|
||||
#[test]
|
||||
fn distinct_target_hash() {
|
||||
todo!()
|
||||
// let arena = bumpalo::Bump::new();
|
||||
// new(&arena, |mut tok, mut graph| {
|
||||
// let file = graph.find_or_add_file_with_path("/").unwrap();
|
||||
|
||||
// let f1 =
|
||||
// Symbol::Function(graph.find_or_add_fn_definition(&mut tok, file, "my_function"));
|
||||
// let f2 =
|
||||
// Symbol::Function(graph.find_or_add_fn_definition(&mut tok, file, "my_function"));
|
||||
|
||||
// let mut h1 = hash::DefaultHasher::new();
|
||||
// let mut h2 = hash::DefaultHasher::new();
|
||||
// f1.hash(&mut h1);
|
||||
// f1.hash(&mut h2);
|
||||
// let h1 = h1.finish();
|
||||
// let h2 = h2.finish();
|
||||
// assert_eq!(h1, h2);
|
||||
|
||||
// let mut h2 = hash::DefaultHasher::new();
|
||||
// f2.hash(&mut h2);
|
||||
// let h2 = h2.finish();
|
||||
// assert_eq!(h1, h2);
|
||||
// });
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn target_equality() {
|
||||
todo!()
|
||||
// let arena = bumpalo::Bump::new();
|
||||
// new(&arena, |mut tok, mut graph| {
|
||||
// let file = graph.find_or_add_file_with_path("/").unwrap();
|
||||
|
||||
// let f1 = graph.find_or_add_fn_definition(&mut tok, file, "my_function");
|
||||
// let f2 = graph.find_fn_definition("my_function").unwrap();
|
||||
// assert!(ptr::addr_eq(f1, f2));
|
||||
|
||||
// let f2 = graph.find_or_add_fn_definition(&mut tok, file, "my_function");
|
||||
// assert!(ptr::addr_eq(f1, f2));
|
||||
|
||||
// let f1 = Symbol::Function(f1);
|
||||
// let f2 = Symbol::Function(f2);
|
||||
// assert_eq!(f1, f2);
|
||||
|
||||
// let f3 =
|
||||
// Symbol::Function(graph.find_or_add_fn_definition(&mut tok, file, "other_function"));
|
||||
// assert_ne!(f1, f3);
|
||||
// });
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn target_hashset_population() {
|
||||
todo!()
|
||||
// let arena = bumpalo::Bump::new();
|
||||
// new(&arena, |mut tok, mut graph| {
|
||||
// let mut set = HashSet::new();
|
||||
// let file = graph.find_or_add_file_with_path("/").unwrap();
|
||||
|
||||
// let fun = graph.find_or_add_fn_definition(&mut tok, file, "my_function");
|
||||
// set.insert(Symbol::Function(fun));
|
||||
// assert_eq!(set.len(), 1);
|
||||
|
||||
// let typ = graph.find_or_add_typedef(&mut tok, file, "my_type");
|
||||
// set.insert(Symbol::Typedef(typ));
|
||||
// assert_eq!(set.len(), 2);
|
||||
|
||||
// let fun = graph.find_fn_definition("my_function").unwrap();
|
||||
// set.insert(Symbol::Function(fun));
|
||||
// assert_eq!(set.len(), 2);
|
||||
// });
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user