Rust · 3256 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",
21 "macosx",
22 "clang",
23 "-x",
24 "c",
25 "-arch",
26 "arm64",
27 "-shared",
28 "-o",
29 ])
30 .arg(out)
31 .arg("-install_name")
32 .arg("@rpath/libafsldtest.dylib")
33 .arg("-")
34 .stdin(std::process::Stdio::piped())
35 .stdout(std::process::Stdio::piped())
36 .stderr(std::process::Stdio::piped())
37 .spawn()
38 .map_err(|e| format!("spawn: {e}"))?;
39 use std::io::Write;
40 child
41 .stdin
42 .as_mut()
43 .unwrap()
44 .write_all(src.as_bytes())
45 .map_err(|e| format!("write: {e}"))?;
46 let out = child.wait_with_output().map_err(|e| format!("wait: {e}"))?;
47 if !out.status.success() {
48 return Err(format!(
49 "clang failed: {}",
50 String::from_utf8_lossy(&out.stderr)
51 ));
52 }
53 Ok(())
54 }
55
56 #[test]
57 fn small_clang_dylib_parses_and_exports_function() {
58 let which = Command::new("xcrun").arg("-f").arg("clang").output();
59 if !matches!(which, Ok(o) if o.status.success()) {
60 eprintln!("skipping: xcrun clang unavailable");
61 return;
62 }
63
64 use std::sync::atomic::{AtomicUsize, Ordering};
65 static SEQ: AtomicUsize = AtomicUsize::new(0);
66 let out_path = std::env::temp_dir().join(format!(
67 "afs-ld-dylib-{}-{}.dylib",
68 std::process::id(),
69 SEQ.fetch_add(1, Ordering::Relaxed)
70 ));
71
72 let src = r#"
73 int afsld_answer(int x) { return x + 42; }
74 "#;
75 if let Err(e) = build_test_dylib(src, &out_path) {
76 eprintln!("skipping: clang could not build test dylib: {e}");
77 return;
78 }
79
80 let bytes = std::fs::read(&out_path).expect("read test dylib");
81 let dy = DylibFile::parse(&out_path, &bytes).expect("parse test dylib");
82
83 assert_eq!(dy.install_name, "@rpath/libafsldtest.dylib");
84
85 // clang-linked dylibs pull libSystem in as a Normal dependency.
86 assert!(
87 dy.dependencies
88 .iter()
89 .any(|d| d.install_name.contains("libSystem")),
90 "expected a libSystem dependency; got {:?}",
91 dy.dependencies
92 );
93
94 let entries = dy.exports.entries().expect("decode exports");
95 let name = "_afsld_answer";
96 let found = entries.iter().find(|e| e.name == name).unwrap_or_else(|| {
97 let exported: Vec<&str> = entries.iter().map(|e| e.name.as_str()).collect();
98 panic!("expected {name} in exports; got {exported:?}")
99 });
100 assert!(matches!(found.kind, ExportKind::Regular { .. }));
101
102 let _ = std::fs::remove_file(&out_path);
103 }
104