1use std::{
2 fs,
3 io::{Cursor, Read as _},
4};
5
6use anyhow::{Context as _, Result};
7use cargo_metadata::camino::Utf8Path;
8use object::{
9 Object as _, ObjectSymbol as _, SymbolKind as ObjectSymbolKind,
10 SymbolScope as ObjectSymbolScope,
11};
12
13pub use capability::{Capability, CapabilitySet};
14
15use crate::symbol::{Symbol, SymbolKind, SymbolScope};
16
17mod build_graph_analysis;
18mod cap_rule;
19mod capability;
20mod checker;
21mod commands;
22mod config;
23mod crate_name;
24mod demangle;
25mod print;
26mod reservoir_sample;
27mod rust_path;
28mod rust_type;
29mod symbol;
30mod tree;
31
32pub use commands::Commands;
33use crate_name::CrateName;
34
35fn extract_symbols(binary_path: &Utf8Path) -> Result<Vec<Symbol>> {
37 let file_bytes =
38 fs::read(binary_path).with_context(|| format!("Failed to read {binary_path}"))?;
39
40 let is_ar_archive = file_bytes.starts_with(b"!<arch>\n");
42
43 let mut symbols = Vec::new();
44
45 if is_ar_archive {
46 let mut archive = ar::Archive::new(Cursor::new(file_bytes));
48
49 while let Some(entry_result) = archive.next_entry() {
51 let mut entry = entry_result.context("Failed to read archive entry")?;
52
53 let header = entry.header();
55 let filename = String::from_utf8_lossy(header.identifier());
56
57 if !filename.ends_with(".o") {
58 continue;
59 }
60
61 let mut obj_data = Vec::new();
63 entry
64 .read_to_end(&mut obj_data)
65 .context("Failed to read object file from archive")?;
66
67 if let Ok(file) = object::File::parse(&*obj_data) {
69 collect_file_symbols(&mut symbols, &file);
70 } else {
71 }
73 }
74 } else {
75 let file =
77 object::File::parse(&*file_bytes).with_context(|| "Failed to parse binary file")?;
78 collect_file_symbols(&mut symbols, &file);
79 }
80
81 Ok(symbols)
82}
83
84pub fn filter_symbols(
86 symbols: Vec<Symbol>,
87 include_local: bool,
88 include_all_kinds: bool,
89) -> Vec<Symbol> {
90 symbols
91 .into_iter()
92 .filter(|symbol| {
93 let scope_allowed = include_local || !matches!(symbol.scope, SymbolScope::Compilation);
95
96 let kind_allowed = include_all_kinds
98 || matches!(
99 symbol.kind,
100 SymbolKind::Text | SymbolKind::Label | SymbolKind::Unknown
101 );
102
103 scope_allowed && kind_allowed
104 })
105 .collect()
106}
107
108fn collect_file_symbols(all_symbols: &mut Vec<Symbol>, file: &object::File<'_>) {
109 for symbol in file.symbols() {
110 if let Ok(name) = symbol.name()
111 && !name.is_empty()
112 {
113 let scope = match symbol.scope() {
114 ObjectSymbolScope::Unknown => SymbolScope::Unknown,
115 ObjectSymbolScope::Compilation => SymbolScope::Compilation,
116 ObjectSymbolScope::Linkage => SymbolScope::Linkage,
117 ObjectSymbolScope::Dynamic => SymbolScope::Dynamic,
118 };
119
120 #[expect(clippy::match_same_arms)]
121 let kind = match symbol.kind() {
122 ObjectSymbolKind::Unknown => SymbolKind::Unknown,
123 ObjectSymbolKind::Text => SymbolKind::Text,
124 ObjectSymbolKind::Data => SymbolKind::Data,
125 ObjectSymbolKind::Section => SymbolKind::Section,
126 ObjectSymbolKind::File => SymbolKind::File,
127 ObjectSymbolKind::Label => SymbolKind::Label,
128 ObjectSymbolKind::Tls => SymbolKind::Tls,
129 _ => SymbolKind::Unknown,
130 };
131
132 all_symbols.push(Symbol::with_metadata(name.to_owned(), scope, kind));
133 }
134 }
135}
136
137#[cfg(test)]
138mod tests {
139 use super::*;
140
141 #[test]
142 fn test_filter_symbols() {
143 let symbols = vec![
144 Symbol::with_metadata("func1".to_owned(), SymbolScope::Linkage, SymbolKind::Text),
145 Symbol::with_metadata(
146 "local_func".to_owned(),
147 SymbolScope::Compilation,
148 SymbolKind::Text,
149 ),
150 Symbol::with_metadata(
151 "data_var".to_owned(),
152 SymbolScope::Linkage,
153 SymbolKind::Data,
154 ),
155 Symbol::with_metadata("label1".to_owned(), SymbolScope::Dynamic, SymbolKind::Label),
156 Symbol::with_metadata(
157 "unknown_sym".to_owned(),
158 SymbolScope::Linkage,
159 SymbolKind::Unknown,
160 ),
161 ];
162
163 let filtered = filter_symbols(symbols.clone(), false, false);
165 assert_eq!(filtered.len(), 3); let filtered = filter_symbols(symbols.clone(), true, false);
169 assert_eq!(filtered.len(), 4); let filtered = filter_symbols(symbols.clone(), false, true);
173 assert_eq!(filtered.len(), 4); let filtered = filter_symbols(symbols, true, true);
177 assert_eq!(filtered.len(), 5); }
179}