symcheck: Add a wrapper around an archive

Rather than re-opening the archive file for each check, add a wrapper
that keeps the data in memory. Additionally, collect the `--target`
argument so it can be used within this crate.
This commit is contained in:
Trevor Gross 2025-06-14 23:39:24 +00:00
parent bcc96da3c3
commit 9f4670fd2b

View file

@ -8,7 +8,9 @@ use std::path::{Path, PathBuf};
use std::process::{Command, Stdio};
use object::read::archive::{ArchiveFile, ArchiveMember};
use object::{Object, ObjectSymbol, Symbol, SymbolKind, SymbolScope, SymbolSection};
use object::{
File as ObjFile, Object, ObjectSymbol, Symbol, SymbolKind, SymbolScope, SymbolSection,
};
use serde_json::Value;
const CHECK_LIBRARIES: &[&str] = &["compiler_builtins", "builtins_test_intrinsics"];
@ -28,13 +30,11 @@ fn main() {
let args_ref = args.iter().map(String::as_str).collect::<Vec<_>>();
match &args_ref[1..] {
["build-and-check", rest @ ..] if !rest.is_empty() => {
let paths = exec_cargo_with_args(rest);
for path in paths {
println!("Checking {}", path.display());
verify_no_duplicates(&path);
verify_core_symbols(&path);
}
["build-and-check", "--target", target, args @ ..] if !args.is_empty() => {
run_build_and_check(Some(target), args);
}
["build-and-check", args @ ..] if !args.is_empty() => {
run_build_and_check(None, args);
}
_ => {
println!("{USAGE}");
@ -43,12 +43,42 @@ fn main() {
}
}
fn run_build_and_check(target: Option<&str>, args: &[&str]) {
let paths = exec_cargo_with_args(target, args);
for path in paths {
println!("Checking {}", path.display());
let archive = Archive::from_path(&path);
verify_no_duplicates(&archive);
verify_core_symbols(&archive);
}
}
fn host_target() -> String {
let out = Command::new("rustc")
.arg("--version")
.arg("--verbose")
.output()
.unwrap();
assert!(out.status.success());
let out = String::from_utf8(out.stdout).unwrap();
out.lines()
.find_map(|s| s.strip_prefix("host: "))
.unwrap()
.to_owned()
}
/// Run `cargo build` with the provided additional arguments, collecting the list of created
/// libraries.
fn exec_cargo_with_args(args: &[&str]) -> Vec<PathBuf> {
fn exec_cargo_with_args(target: Option<&str>, args: &[&str]) -> Vec<PathBuf> {
let mut host = String::new();
let target = target.unwrap_or_else(|| {
host = host_target();
host.as_str()
});
let mut cmd = Command::new("cargo");
cmd.arg("build")
.arg("--message-format=json")
cmd.args(["build", "--target", target, "--message-format=json"])
.args(args)
.stdout(Stdio::piped());
@ -133,12 +163,12 @@ impl SymInfo {
/// Note that this will also locate cases where a symbol is weakly defined in more than one place.
/// Technically there are no linker errors that will come from this, but it keeps our binary more
/// straightforward and saves some distribution size.
fn verify_no_duplicates(path: &Path) {
fn verify_no_duplicates(archive: &Archive) {
let mut syms = BTreeMap::<String, SymInfo>::new();
let mut dups = Vec::new();
let mut found_any = false;
for_each_symbol(path, |symbol, member| {
archive.for_each_symbol(|symbol, member| {
// Only check defined globals
if !symbol.is_global() || symbol.is_undefined() {
return;
@ -185,12 +215,12 @@ fn verify_no_duplicates(path: &Path) {
}
/// Ensure that there are no references to symbols from `core` that aren't also (somehow) defined.
fn verify_core_symbols(path: &Path) {
fn verify_core_symbols(archive: &Archive) {
let mut defined = BTreeSet::new();
let mut undefined = Vec::new();
let mut has_symbols = false;
for_each_symbol(path, |symbol, member| {
archive.for_each_symbol(|symbol, member| {
has_symbols = true;
// Find only symbols from `core`
@ -219,14 +249,40 @@ fn verify_core_symbols(path: &Path) {
println!(" success: no undefined references to core found");
}
/// For a given archive path, do something with each symbol.
fn for_each_symbol(path: &Path, mut f: impl FnMut(Symbol, &ArchiveMember)) {
let data = fs::read(path).expect("reading file failed");
let archive = ArchiveFile::parse(data.as_slice()).expect("archive parse failed");
for member in archive.members() {
let member = member.expect("failed to access member");
let obj_data = member.data(&*data).expect("failed to access object");
let obj = object::File::parse(obj_data).expect("failed to parse object");
obj.symbols().for_each(|sym| f(sym, &member));
/// Thin wrapper for owning data used by `object`.
struct Archive {
data: Vec<u8>,
}
impl Archive {
fn from_path(path: &Path) -> Self {
Self {
data: fs::read(path).expect("reading file failed"),
}
}
fn file(&self) -> ArchiveFile<'_> {
ArchiveFile::parse(self.data.as_slice()).expect("archive parse failed")
}
/// For a given archive, do something with each object file.
fn for_each_object(&self, mut f: impl FnMut(ObjFile, &ArchiveMember)) {
let archive = self.file();
for member in archive.members() {
let member = member.expect("failed to access member");
let obj_data = member
.data(self.data.as_slice())
.expect("failed to access object");
let obj = ObjFile::parse(obj_data).expect("failed to parse object");
f(obj, &member);
}
}
/// For a given archive, do something with each symbol.
fn for_each_symbol(&self, mut f: impl FnMut(Symbol, &ArchiveMember)) {
self.for_each_object(|obj, member| {
obj.symbols().for_each(|sym| f(sym, member));
});
}
}