Rust · 3160 bytes Raw Blame History
1 //! Sprint 5 real-world gate: build a tiny dylib with `clang`, parse it via
2 //! `DylibFile`, and confirm:
3 //!
4 //! - `install_name` is what `-install_name` requested;
5 //! - the exported symbol surfaces in the trie;
6 //! - at least one `LC_LOAD_DYLIB` dependency is present (every clang-linked
7 //! dylib picks up libSystem).
8 //!
9 //! Skipped if `xcrun clang` isn't available or fails for any reason.
10
11 use std::path::PathBuf;
12 use std::process::Command;
13
14 use afs_ld::macho::dylib::DylibFile;
15 use afs_ld::macho::exports::ExportKind;
16
17 fn build_test_dylib(src: &str, out: &PathBuf) -> Result<(), String> {
18 let mut child = Command::new("xcrun")
19 .args([
20 "--sdk", "macosx", "clang", "-x", "c", "-arch", "arm64", "-shared", "-o",
21 ])
22 .arg(out)
23 .arg("-install_name")
24 .arg("@rpath/libafsldtest.dylib")
25 .arg("-")
26 .stdin(std::process::Stdio::piped())
27 .stdout(std::process::Stdio::piped())
28 .stderr(std::process::Stdio::piped())
29 .spawn()
30 .map_err(|e| format!("spawn: {e}"))?;
31 use std::io::Write;
32 child
33 .stdin
34 .as_mut()
35 .unwrap()
36 .write_all(src.as_bytes())
37 .map_err(|e| format!("write: {e}"))?;
38 let out = child.wait_with_output().map_err(|e| format!("wait: {e}"))?;
39 if !out.status.success() {
40 return Err(format!(
41 "clang failed: {}",
42 String::from_utf8_lossy(&out.stderr)
43 ));
44 }
45 Ok(())
46 }
47
48 #[test]
49 fn small_clang_dylib_parses_and_exports_function() {
50 let which = Command::new("xcrun").arg("-f").arg("clang").output();
51 if !matches!(which, Ok(o) if o.status.success()) {
52 eprintln!("skipping: xcrun clang unavailable");
53 return;
54 }
55
56 use std::sync::atomic::{AtomicUsize, Ordering};
57 static SEQ: AtomicUsize = AtomicUsize::new(0);
58 let out_path = std::env::temp_dir().join(format!(
59 "afs-ld-dylib-{}-{}.dylib",
60 std::process::id(),
61 SEQ.fetch_add(1, Ordering::Relaxed)
62 ));
63
64 let src = r#"
65 int afsld_answer(int x) { return x + 42; }
66 "#;
67 if let Err(e) = build_test_dylib(src, &out_path) {
68 eprintln!("skipping: clang could not build test dylib: {e}");
69 return;
70 }
71
72 let bytes = std::fs::read(&out_path).expect("read test dylib");
73 let dy = DylibFile::parse(&out_path, &bytes).expect("parse test dylib");
74
75 assert_eq!(dy.install_name, "@rpath/libafsldtest.dylib");
76
77 // clang-linked dylibs pull libSystem in as a Normal dependency.
78 assert!(
79 dy.dependencies
80 .iter()
81 .any(|d| d.install_name.contains("libSystem")),
82 "expected a libSystem dependency; got {:?}",
83 dy.dependencies
84 );
85
86 let entries = dy.exports.entries().expect("decode exports");
87 let name = "_afsld_answer";
88 let found = entries.iter().find(|e| e.name == name).unwrap_or_else(|| {
89 let exported: Vec<&str> = entries.iter().map(|e| e.name.as_str()).collect();
90 panic!("expected {name} in exports; got {exported:?}")
91 });
92 assert!(matches!(found.kind, ExportKind::Regular { .. }));
93
94 let _ = std::fs::remove_file(&out_path);
95 }
96