Rust · 2747 bytes Raw Blame History
1 //! Differential harness: compare afs-ld output against Apple `ld` output.
2 //!
3 //! Sprint 0 lands the diffing surface. The `link_both` function that actually
4 //! shells out to both linkers arrives once afs-ld can produce a real binary
5 //! (Sprint 18). Until then, tests exercise `diff_macho` directly against
6 //! synthesized byte slices.
7
8 #![allow(dead_code)]
9
10 use std::path::PathBuf;
11
12 pub struct LinkCase {
13 pub name: &'static str,
14 pub inputs: Vec<PathBuf>,
15 pub args: Vec<String>,
16 }
17
18 pub struct LinkOutputs {
19 pub ours: Vec<u8>,
20 pub theirs: Vec<u8>,
21 }
22
23 #[derive(Debug, Clone, PartialEq, Eq)]
24 pub enum DiffCategory {
25 /// A diff we expect: UUID bytes, timestamps, hash-backed temp paths, etc.
26 Tolerated(&'static str),
27 /// Anything else. Fails the parity test.
28 Critical,
29 }
30
31 #[derive(Debug, Clone, PartialEq, Eq)]
32 pub struct DiffChunk {
33 pub offset: usize,
34 pub len: usize,
35 pub reason: String,
36 pub category: DiffCategory,
37 }
38
39 #[derive(Debug, Default)]
40 pub struct DiffReport {
41 pub tolerated: Vec<DiffChunk>,
42 pub critical: Vec<DiffChunk>,
43 }
44
45 impl DiffReport {
46 pub fn is_clean(&self) -> bool {
47 self.critical.is_empty()
48 }
49 }
50
51 /// Byte-level diff between two Mach-O images. Sprint 0 treats every byte diff
52 /// as Critical; later sprints layer in the tolerated-diff predicates (UUID,
53 /// timestamp, code-signature hashes, string-table suffix-dedup variance).
54 pub fn diff_macho(ours: &[u8], theirs: &[u8]) -> DiffReport {
55 let mut report = DiffReport::default();
56
57 if ours.len() != theirs.len() {
58 report.critical.push(DiffChunk {
59 offset: 0,
60 len: ours.len().max(theirs.len()),
61 reason: format!(
62 "total size differs: ours = {}, theirs = {}",
63 ours.len(),
64 theirs.len()
65 ),
66 category: DiffCategory::Critical,
67 });
68 return report;
69 }
70
71 let mut i = 0;
72 while i < ours.len() {
73 if ours[i] != theirs[i] {
74 let start = i;
75 while i < ours.len() && ours[i] != theirs[i] {
76 i += 1;
77 }
78 report.critical.push(DiffChunk {
79 offset: start,
80 len: i - start,
81 reason: format!("{} byte(s) differ starting at 0x{start:x}", i - start),
82 category: DiffCategory::Critical,
83 });
84 } else {
85 i += 1;
86 }
87 }
88
89 report
90 }
91
92 /// Placeholder for the full linker-spawning contract. Sprint 18 wires this to
93 /// real invocations of afs-ld and the system `ld` via `xcrun -f ld`.
94 pub fn link_both(_case: &LinkCase) -> LinkOutputs {
95 panic!("link_both is not implemented until Sprint 18 (hello-world milestone)");
96 }
97