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 src_analysis;
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 match object::File::parse(&*obj_data) {
69 Ok(file) => {
70 collect_file_symbols(&mut symbols, &file);
71 }
72 Err(err) => {
73 anyhow::bail!("Failed to pearse entry in {binary_path:?}: {err}")
74 }
75 }
76 }
77 } else {
78 let file =
80 object::File::parse(&*file_bytes).with_context(|| "Failed to parse binary file")?;
81 collect_file_symbols(&mut symbols, &file);
82 }
83
84 Ok(symbols)
85}
86
87pub fn filter_symbols(
89 symbols: Vec<Symbol>,
90 include_local: bool, include_all_kinds: bool, ) -> Vec<Symbol> {
93 symbols
94 .into_iter()
95 .filter(|symbol| {
96 let scope_allowed = include_local || !matches!(symbol.scope, SymbolScope::Compilation);
98
99 let kind_allowed = include_all_kinds
101 || matches!(
102 symbol.kind,
103 SymbolKind::Text | SymbolKind::Label | SymbolKind::Unknown
104 );
105
106 scope_allowed && kind_allowed
107 })
108 .collect()
109}
110
111fn collect_file_symbols(all_symbols: &mut Vec<Symbol>, file: &object::File<'_>) {
112 for symbol in file.symbols() {
113 if let Ok(name) = symbol.name()
114 && !name.is_empty()
115 {
116 let scope = match symbol.scope() {
117 ObjectSymbolScope::Unknown => SymbolScope::Unknown,
118 ObjectSymbolScope::Compilation => SymbolScope::Compilation,
119 ObjectSymbolScope::Linkage => SymbolScope::Linkage,
120 ObjectSymbolScope::Dynamic => SymbolScope::Dynamic,
121 };
122
123 #[expect(clippy::match_same_arms)]
124 let kind = match symbol.kind() {
125 ObjectSymbolKind::Unknown => SymbolKind::Unknown,
126 ObjectSymbolKind::Text => SymbolKind::Text,
127 ObjectSymbolKind::Data => SymbolKind::Data,
128 ObjectSymbolKind::Section => SymbolKind::Section,
129 ObjectSymbolKind::File => SymbolKind::File,
130 ObjectSymbolKind::Label => SymbolKind::Label,
131 ObjectSymbolKind::Tls => SymbolKind::Tls,
132 _ => SymbolKind::Unknown,
133 };
134
135 all_symbols.push(Symbol::with_metadata(name.to_owned(), scope, kind));
136 }
137 }
138}
139
140#[cfg(test)]
141mod tests {
142 use super::*;
143
144 #[test]
145 fn test_filter_symbols() {
146 let symbols = vec![
147 Symbol::with_metadata("func1".to_owned(), SymbolScope::Linkage, SymbolKind::Text),
148 Symbol::with_metadata(
149 "local_func".to_owned(),
150 SymbolScope::Compilation,
151 SymbolKind::Text,
152 ),
153 Symbol::with_metadata(
154 "data_var".to_owned(),
155 SymbolScope::Linkage,
156 SymbolKind::Data,
157 ),
158 Symbol::with_metadata("label1".to_owned(), SymbolScope::Dynamic, SymbolKind::Label),
159 Symbol::with_metadata(
160 "unknown_sym".to_owned(),
161 SymbolScope::Linkage,
162 SymbolKind::Unknown,
163 ),
164 ];
165
166 let filtered = filter_symbols(symbols.clone(), false, false);
168 assert_eq!(filtered.len(), 3); let filtered = filter_symbols(symbols.clone(), true, false);
172 assert_eq!(filtered.len(), 4); let filtered = filter_symbols(symbols.clone(), false, true);
176 assert_eq!(filtered.len(), 4); let filtered = filter_symbols(symbols, true, true);
180 assert_eq!(filtered.len(), 5); }
182}