Skip to main content

supercov_engine/
rust_runtime.rs

1//! The region runtime for owned Rust probes, and its strict reader.
2//!
3//! Each instrumented crate carries a copy of the runtime
4//! (`runtime-assets/rust-region-runtime.rs`); every copy in a process maps
5//! one region, a slot per probe holding its first sighting's sequence and
6//! thread. The runtime never computes coverage: the reader rebuilds the
7//! records in execution order and the evidence model takes them from there.
8
9use serde::{Deserialize, Serialize};
10use std::{fs, path::Path};
11
12#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
13#[serde(rename_all = "camelCase", tag = "kind")]
14pub enum RustProbeObservation {
15    Hit {
16        id: String,
17    },
18    Decision {
19        id: String,
20        values: Vec<Option<bool>>,
21        outcome: bool,
22    },
23    /// Control passed a statement that asserts. A failing assertion panics,
24    /// so reaching the marker means it held.
25    Assertion {
26        id: String,
27    },
28}
29
30/// One record a process wrote, with the thread that wrote it. Records come
31/// back in execution order, but a test that spawns threads interleaves them:
32/// an assertion witnesses its own thread's records and no other's.
33#[derive(Debug, Clone, PartialEq, Eq)]
34pub struct RustProbeEntry {
35    pub thread: u64,
36    pub observation: RustProbeObservation,
37}
38
39#[derive(Debug, Clone, PartialEq, Eq)]
40pub enum RustProbeReadError {
41    Io(String),
42    UnsafeEntry(String),
43    InvalidHeader,
44    InvalidRecord(usize),
45}
46
47impl std::fmt::Display for RustProbeReadError {
48    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
49        match self {
50            Self::Io(error) => write!(formatter, "Rust probe I/O failed: {error}"),
51            Self::UnsafeEntry(path) => write!(formatter, "unsafe Rust probe entry: {path}"),
52            Self::InvalidHeader => write!(formatter, "invalid Rust probe header"),
53            Self::InvalidRecord(line) => {
54                write!(formatter, "invalid Rust probe record at line {line}")
55            }
56        }
57    }
58}
59
60impl std::error::Error for RustProbeReadError {}
61
62const REGION_TEMPLATE: &str = include_str!("../runtime-assets/rust-region-runtime.rs");
63const REGION_MAGIC: &[u8; 8] = b"SCVRGN01";
64const REGION_VERSION: u32 = 1;
65
66/// The region runtime for one crate root. Every crate of an instrumentation
67/// shares `token` and `layout`, so their copies map one region per process.
68/// `crate_key` names the crate's exports where the region is static memory
69/// (wasm): each crate of a module keeps its own there.
70pub fn render_rust_region_runtime(
71    module_name: &str,
72    token: &str,
73    crate_key: &str,
74    layout: &crate::rust_instrumenter::RustProbeLayout,
75    // The ordinals of the `cfgs` in this crate's own files.
76    crate_cfgs: &[usize],
77) -> Result<String, String> {
78    let valid_identifier = !module_name.is_empty()
79        && module_name.bytes().enumerate().all(|(index, byte)| {
80            byte == b'_' || byte.is_ascii_alphabetic() || (index > 0 && byte.is_ascii_digit())
81        });
82    if !valid_identifier {
83        return Err("invalid Rust runtime module name".into());
84    }
85    if token.is_empty() || token.len() > 16 || !token.bytes().all(|byte| byte.is_ascii_hexdigit()) {
86        return Err("invalid Rust region token".into());
87    }
88    if crate_key.is_empty() || !crate_key.bytes().all(|byte| byte.is_ascii_hexdigit()) {
89        return Err("invalid Rust runtime crate key".into());
90    }
91    let mut table = String::new();
92    for ordinal in crate_cfgs {
93        let gate = layout
94            .cfgs
95            .get(*ordinal)
96            .ok_or("a crate names a cfg gate the layout lacks")?;
97        // Re-parsed, so only a predicate `cfg!` accepts reaches the source.
98        let predicate = gate
99            .predicate
100            .parse::<cargo_platform::CfgExpr>()
101            .map_err(|error| format!("invalid cfg predicate {:?}: {error}", gate.predicate))?;
102        table.push_str(&format!("({ordinal}, cfg!({predicate})), "));
103    }
104    Ok(REGION_TEMPLATE
105        .replace("__SUPERCOV_MODULE__", module_name)
106        .replace("__SUPERCOV_HITS__", &layout.hits.len().to_string())
107        .replace(
108            "__SUPERCOV_ASSERTIONS__",
109            &layout.assertions.len().to_string(),
110        )
111        .replace(
112            "__SUPERCOV_DECISIONS__",
113            &layout.decisions.len().to_string(),
114        )
115        .replace("__SUPERCOV_CFGS__", &layout.cfgs.len().to_string())
116        .replace("__SUPERCOV_CFG_TABLE__", &table)
117        .replace("__SUPERCOV_TOKEN__", token)
118        .replace(
119            "__SUPERCOV_REGION_EXPORT__",
120            &format!("__supercov_region_{crate_key}"),
121        )
122        .replace(
123            "__SUPERCOV_CLEAR_EXPORT__",
124            &format!("__supercov_clear_{crate_key}"),
125        ))
126}
127
128struct RegionHeader {
129    entries_per_decision: usize,
130    body_at: usize,
131}
132
133/// A region's header, checked against this instrumentation: its token and
134/// the size of every table.
135fn region_header(
136    bytes: &[u8],
137    token: &str,
138    layout: &crate::rust_instrumenter::RustProbeLayout,
139) -> Result<RegionHeader, RustProbeReadError> {
140    const HEADER: usize = 128;
141    let u32_at = |offset: usize| -> Result<u32, RustProbeReadError> {
142        bytes
143            .get(offset..offset + 4)
144            .map(|word| u32::from_le_bytes(word.try_into().unwrap_or([0; 4])))
145            .ok_or(RustProbeReadError::InvalidHeader)
146    };
147    if bytes.get(..8) != Some(REGION_MAGIC.as_slice())
148        || u32_at(8)? != REGION_VERSION
149        || u32_at(12)? as usize != HEADER
150        || u32_at(16)? as usize != layout.hits.len()
151        || u32_at(20)? as usize != layout.assertions.len()
152        || u32_at(24)? as usize != layout.decisions.len()
153        || u32_at(72)? as usize != layout.cfgs.len()
154    {
155        return Err(RustProbeReadError::InvalidHeader);
156    }
157    let stored_token = bytes
158        .get(56..56 + token.len())
159        .ok_or(RustProbeReadError::InvalidHeader)?;
160    if stored_token != token.as_bytes() {
161        return Err(RustProbeReadError::InvalidHeader);
162    }
163    Ok(RegionHeader {
164        entries_per_decision: u32_at(28)? as usize,
165        body_at: u32_at(32)? as usize,
166    })
167}
168
169/// One region, read back as the records its process wrote, in the order it
170/// wrote them: each slot holds its first sighting's sequence and thread, and
171/// the overflow file beside it the decision combinations the region had no
172/// room for. Pages the bitmap says no probe touched are not read.
173pub fn parse_rust_region(
174    bytes: &[u8],
175    overflow: Option<&[u8]>,
176    token: &str,
177    layout: &crate::rust_instrumenter::RustProbeLayout,
178) -> Result<Vec<RustProbeEntry>, RustProbeReadError> {
179    const HEADER: usize = 128;
180    const PAGE: usize = 4096;
181    const ENTRY: usize = 32;
182    let u64_at = |offset: usize| -> Option<u64> {
183        bytes
184            .get(offset..offset + 8)
185            .map(|word| u64::from_le_bytes(word.try_into().unwrap_or([0; 8])))
186    };
187    let RegionHeader {
188        entries_per_decision,
189        body_at,
190    } = region_header(bytes, token, layout)?;
191    let touched = |offset: usize| -> bool {
192        let page = (offset - body_at) / PAGE;
193        bytes
194            .get(HEADER + page / 8)
195            .is_some_and(|byte| byte & (1 << (page % 8)) != 0)
196    };
197    let slot = |offset: usize| -> Option<(u64, u64)> {
198        if !touched(offset) {
199            return None;
200        }
201        let value = u64_at(offset)?;
202        (value != 0).then_some((value >> 32, value & 0xffff_ffff))
203    };
204    let mut records = Vec::<(u64, RustProbeEntry)>::new();
205    let hits_at = body_at;
206    for (ordinal, id) in layout.hits.iter().enumerate() {
207        if let Some((sequence, thread)) = slot(hits_at + ordinal * 8) {
208            records.push((
209                sequence,
210                RustProbeEntry {
211                    thread,
212                    observation: RustProbeObservation::Hit { id: id.clone() },
213                },
214            ));
215        }
216    }
217    let assertions_at = hits_at + layout.hits.len() * 8;
218    for (ordinal, id) in layout.assertions.iter().enumerate() {
219        if let Some((sequence, thread)) = slot(assertions_at + ordinal * 8) {
220            records.push((
221                sequence,
222                RustProbeEntry {
223                    thread,
224                    observation: RustProbeObservation::Assertion { id: id.clone() },
225                },
226            ));
227        }
228    }
229    let decisions_at = assertions_at + layout.assertions.len() * 8;
230    let vector = |values: u128, width: usize| -> Vec<Option<bool>> {
231        (0..width)
232            .map(|index| match (values >> (2 * index)) & 3 {
233                1 => Some(false),
234                2 => Some(true),
235                _ => None,
236            })
237            .collect()
238    };
239    for (ordinal, id) in layout.decisions.iter().enumerate() {
240        for entry in 0..entries_per_decision {
241            let at = decisions_at + (ordinal * entries_per_decision + entry) * ENTRY;
242            if !touched(at) || bytes.get(at) != Some(&2) {
243                continue;
244            }
245            let outcome = bytes.get(at + 1).copied().unwrap_or(0) == 2;
246            let width = bytes.get(at + 2).copied().unwrap_or(0) as usize;
247            let values = bytes
248                .get(at + 16..at + 32)
249                .map(|word| u128::from_le_bytes(word.try_into().unwrap_or([0; 16])))
250                .unwrap_or(0);
251            let Some(first) = u64_at(at + 8).filter(|value| *value != 0) else {
252                continue;
253            };
254            records.push((
255                first >> 32,
256                RustProbeEntry {
257                    thread: first & 0xffff_ffff,
258                    observation: RustProbeObservation::Decision {
259                        id: id.clone(),
260                        values: vector(values, width),
261                        outcome,
262                    },
263                },
264            ));
265        }
266    }
267    // Overflow: `D <sequence> <thread> <ordinal> <values> <outcome>`.
268    for (index, line) in overflow
269        .unwrap_or_default()
270        .split(|byte| *byte == b'\n')
271        .filter(|line| !line.is_empty())
272        .enumerate()
273    {
274        let line =
275            std::str::from_utf8(line).map_err(|_| RustProbeReadError::InvalidRecord(index + 1))?;
276        let fields = line.split('\t').collect::<Vec<_>>();
277        let invalid = || RustProbeReadError::InvalidRecord(index + 1);
278        let [kind, sequence, thread, ordinal, values, outcome] = fields.as_slice() else {
279            // A record cut off by the process dying mid-write.
280            continue;
281        };
282        if *kind != "D" {
283            return Err(invalid());
284        }
285        let id = layout
286            .decisions
287            .get(ordinal.parse::<usize>().map_err(|_| invalid())?)
288            .ok_or_else(invalid)?;
289        let values = values
290            .bytes()
291            .map(|digit| match digit {
292                b'1' => Ok(Some(false)),
293                b'2' => Ok(Some(true)),
294                b'0' => Ok(None),
295                _ => Err(invalid()),
296            })
297            .collect::<Result<Vec<_>, _>>()?;
298        records.push((
299            sequence.parse().map_err(|_| invalid())?,
300            RustProbeEntry {
301                thread: thread.parse().map_err(|_| invalid())?,
302                observation: RustProbeObservation::Decision {
303                    id: id.clone(),
304                    values,
305                    outcome: *outcome == "1",
306                },
307            },
308        ));
309    }
310    records.sort_by_key(|(sequence, _)| *sequence);
311    Ok(records.into_iter().map(|(_, entry)| entry).collect())
312}
313
314/// What the builds of a run said of each `cfg` gate: bit 0 some crate was
315/// built without the gated code, bit 1 some crate with it. Zero: no process
316/// of a crate holding it ever mapped a region, and the run cannot tell.
317pub fn parse_rust_region_cfgs(
318    bytes: &[u8],
319    token: &str,
320    layout: &crate::rust_instrumenter::RustProbeLayout,
321) -> Result<Vec<u8>, RustProbeReadError> {
322    let RegionHeader {
323        entries_per_decision,
324        body_at,
325    } = region_header(bytes, token, layout)?;
326    let cfgs_at = body_at
327        + layout.hits.len() * 8
328        + layout.assertions.len() * 8
329        + layout.decisions.len() * entries_per_decision * 32;
330    Ok((0..layout.cfgs.len())
331        .map(|ordinal| bytes.get(cfgs_at + ordinal).copied().unwrap_or(0) & 3)
332        .collect())
333}
334
335/// Every region of `token`'s instrumentation under `root`, at any depth --
336/// a run keeps a directory per test -- with what each said of the `cfg`
337/// gates, accumulated.
338pub fn read_rust_cfg_settlement(
339    root: &Path,
340    token: &str,
341    layout: &crate::rust_instrumenter::RustProbeLayout,
342) -> Result<Vec<u8>, RustProbeReadError> {
343    let mut settled = vec![0_u8; layout.cfgs.len()];
344    if layout.cfgs.is_empty() {
345        return Ok(settled);
346    }
347    let mut directories = vec![root.to_path_buf()];
348    while let Some(directory) = directories.pop() {
349        let entries = match fs::read_dir(&directory) {
350            Ok(entries) => entries,
351            Err(error) if error.kind() == std::io::ErrorKind::NotFound => continue,
352            Err(error) => return Err(RustProbeReadError::Io(error.to_string())),
353        };
354        for entry in entries {
355            let entry = entry.map_err(|error| RustProbeReadError::Io(error.to_string()))?;
356            let kind = entry
357                .file_type()
358                .map_err(|error| RustProbeReadError::Io(error.to_string()))?;
359            let name = entry.file_name().to_string_lossy().into_owned();
360            if kind.is_dir() {
361                directories.push(entry.path());
362            } else if kind.is_file()
363                && name.starts_with(&format!("{token}-"))
364                && name.ends_with(".region")
365            {
366                let bytes = fs::read(entry.path())
367                    .map_err(|error| RustProbeReadError::Io(error.to_string()))?;
368                for (slot, bits) in settled
369                    .iter_mut()
370                    .zip(parse_rust_region_cfgs(&bytes, token, layout)?)
371                {
372                    *slot |= bits;
373                }
374            }
375        }
376    }
377    Ok(settled)
378}
379
380/// Every region of `token`'s instrumentation in a test's evidence
381/// directory -- one per process the test ran -- read back as records, by
382/// file name. A region another instrumentation wrote (a fixture instrumented
383/// on its own inside the workspace) is not this run's and is left out.
384pub fn read_rust_region_directory(
385    directory: &Path,
386    token: &str,
387    layout: &crate::rust_instrumenter::RustProbeLayout,
388) -> Result<Vec<(String, Vec<RustProbeEntry>)>, RustProbeReadError> {
389    let mut files = Vec::new();
390    let entries = match fs::read_dir(directory) {
391        Ok(entries) => entries,
392        Err(error) if error.kind() == std::io::ErrorKind::NotFound => return Ok(Vec::new()),
393        Err(error) => return Err(RustProbeReadError::Io(error.to_string())),
394    };
395    for entry in entries {
396        let entry = entry.map_err(|error| RustProbeReadError::Io(error.to_string()))?;
397        let name = entry.file_name().to_string_lossy().into_owned();
398        if !name.starts_with(&format!("{token}-")) || !name.ends_with(".region") {
399            continue;
400        }
401        if !entry
402            .file_type()
403            .map_err(|error| RustProbeReadError::Io(error.to_string()))?
404            .is_file()
405        {
406            return Err(RustProbeReadError::UnsafeEntry(name));
407        }
408        files.push(name);
409    }
410    files.sort();
411    let mut read = Vec::new();
412    for name in files {
413        let bytes = fs::read(directory.join(&name))
414            .map_err(|error| RustProbeReadError::Io(error.to_string()))?;
415        let overflow_name = format!("{}.overflow", name.trim_end_matches(".region"));
416        let overflow = match fs::read(directory.join(&overflow_name)) {
417            Ok(bytes) => Some(bytes),
418            Err(error) if error.kind() == std::io::ErrorKind::NotFound => None,
419            Err(error) => return Err(RustProbeReadError::Io(error.to_string())),
420        };
421        read.push((
422            name,
423            parse_rust_region(&bytes, overflow.as_deref(), token, layout)?,
424        ));
425    }
426    Ok(read)
427}
428
429#[cfg(test)]
430mod tests {
431    use std::{
432        fs,
433        process::Command,
434        time::{SystemTime, UNIX_EPOCH},
435    };
436
437    use super::*;
438    use crate::rust_instrumenter::{RustProbeLayout, instrument_rust_source};
439    use std::collections::BTreeMap;
440
441    const TOKEN: &str = "0123456789ab";
442
443    /// The region runtime as a crate root gets it, for a source whose probes
444    /// were numbered alone.
445    fn region_runtime(probes: &RustProbeLayout) -> String {
446        // One crate: every gate is in its files.
447        let crate_cfgs = (0..probes.cfgs.len()).collect::<Vec<_>>();
448        render_rust_region_runtime(
449            "__supercov_runtime_v1",
450            TOKEN,
451            "abcdef012345",
452            probes,
453            &crate_cfgs,
454        )
455        .unwrap()
456    }
457
458    /// Every region an evidence directory holds, read back as records.
459    fn read_regions(
460        evidence: &std::path::Path,
461        probes: &RustProbeLayout,
462    ) -> std::collections::BTreeMap<String, Vec<RustProbeEntry>> {
463        read_rust_region_directory(evidence, TOKEN, probes)
464            .unwrap()
465            .into_iter()
466            .collect()
467    }
468
469    fn temporary_directory(name: &str) -> std::path::PathBuf {
470        let nonce = SystemTime::now()
471            .duration_since(UNIX_EPOCH)
472            .unwrap()
473            .as_nanos();
474        let path = std::env::temp_dir().join(format!(
475            "supercov-rust-runtime-{}-{nonce}-{name}",
476            std::process::id()
477        ));
478        fs::create_dir(&path).unwrap();
479        path
480    }
481
482    /// A program in a container writes into a region on a bind mount the
483    /// host reads while it runs. Docker Desktop's VM shows the host a shared
484    /// mapping only after `msync`, so with `SUPERCOV_CONTAINER` set a flusher
485    /// syncs every few milliseconds and stamps an epoch. Needs Docker and
486    /// pulls an image: `scripts/rust-region-container.mjs` runs it.
487    #[test]
488    #[ignore = "needs a Docker daemon; run by scripts/rust-region-container.mjs"]
489    fn a_region_in_a_container_is_read_live_from_the_host() {
490        // The program maps its region first (main's own probe), waits to be
491        // told to go, takes the decision, and says so: the host then has a
492        // second to see it while the program sleeps on. Without the flusher
493        // the VM's writeback shows it only seconds later, or at exit.
494        let source = r#"fn work(n: u32) -> u32 {
495    if n > 2 { n * 2 } else { n }
496}
497
498fn main() {
499    std::fs::write("/w/ready", "ready").unwrap();
500    while !std::path::Path::new("/w/go").exists() {
501        std::thread::sleep(std::time::Duration::from_millis(10));
502    }
503    println!("{}", work(5));
504    std::fs::write("/w/done", "done").unwrap();
505    std::thread::sleep(std::time::Duration::from_secs(120));
506}
507"#;
508        let transformed =
509            instrument_rust_source("src/main.rs", source, "crate::__supercov_runtime_v1").unwrap();
510        let runtime = region_runtime(&transformed.probes);
511        let directory = temporary_directory("container");
512        fs::write(
513            directory.join("main.rs"),
514            format!("{}\n{runtime}", transformed.code),
515        )
516        .unwrap();
517        let image =
518            std::env::var("SUPERCOV_RUST_IMAGE").unwrap_or_else(|_| "rust:1-bookworm".into());
519        let name = format!(
520            "supercov-region-{}-{}",
521            std::process::id(),
522            SystemTime::now()
523                .duration_since(UNIX_EPOCH)
524                .unwrap()
525                .as_nanos()
526        );
527        let started = Command::new("docker")
528            .args(["run", "-d", "--name", &name])
529            .arg("-v")
530            .arg(format!("{}:/w", directory.display()))
531            .args([
532                "-e",
533                "SUPERCOV_CONTAINER=1",
534                "-e",
535                "SUPERCOV_RUST_EVIDENCE_DIR=/w/evidence",
536                &image,
537                "sh",
538                "-c",
539                "cd /tmp && rustc --edition=2024 -O /w/main.rs -o program && ./program",
540            ])
541            .output()
542            .unwrap();
543        assert!(
544            started.status.success(),
545            "{}",
546            String::from_utf8_lossy(&started.stderr)
547        );
548        struct Removed(String);
549        impl Drop for Removed {
550            fn drop(&mut self) {
551                let _ = Command::new("docker").args(["rm", "-f", &self.0]).output();
552            }
553        }
554        let _container = Removed(name.clone());
555        let deadline = std::time::Instant::now() + std::time::Duration::from_secs(600);
556        while !directory.join("ready").exists() {
557            assert!(
558                std::time::Instant::now() < deadline,
559                "the program never got ready: {}",
560                String::from_utf8_lossy(
561                    &Command::new("docker")
562                        .args(["logs", &name])
563                        .output()
564                        .unwrap()
565                        .stderr
566                )
567            );
568            std::thread::sleep(std::time::Duration::from_millis(100));
569        }
570        // Let the guest's periodic writeback carry everything written so far.
571        std::thread::sleep(std::time::Duration::from_secs(6));
572        fs::write(directory.join("go"), "go").unwrap();
573        let deadline = std::time::Instant::now() + std::time::Duration::from_secs(30);
574        while !directory.join("done").exists() {
575            assert!(
576                std::time::Instant::now() < deadline,
577                "the program never finished"
578            );
579            std::thread::sleep(std::time::Duration::from_millis(5));
580        }
581        // The program sleeps for two minutes yet: whatever the host reads
582        // now came through the live mapping.
583        let evidence = directory.join("evidence");
584        let work = transformed
585            .manifest
586            .decisions
587            .iter()
588            .find(|decision| decision.line == 2)
589            .unwrap()
590            .id
591            .clone();
592        let deadline = std::time::Instant::now() + std::time::Duration::from_secs(1);
593        let (decisions, epoch) = loop {
594            let regions = read_regions(&evidence, &transformed.probes);
595            let decisions = regions
596                .values()
597                .flatten()
598                .filter(|entry| {
599                    matches!(&entry.observation, RustProbeObservation::Decision { id, .. } if *id == work)
600                })
601                .count();
602            let epoch = fs::read_dir(&evidence)
603                .ok()
604                .and_then(|mut entries| entries.next())
605                .and_then(|entry| fs::read(entry.ok()?.path()).ok())
606                .and_then(|bytes| {
607                    bytes
608                        .get(48..52)
609                        .map(|word| u32::from_le_bytes(word.try_into().unwrap()))
610                })
611                .unwrap_or(0);
612            if decisions > 0 && epoch > 0 || std::time::Instant::now() > deadline {
613                break (decisions, epoch);
614            }
615            std::thread::sleep(std::time::Duration::from_millis(50));
616        };
617        let running = Command::new("docker")
618            .args(["inspect", "-f", "{{.State.Running}}", &name])
619            .output()
620            .unwrap();
621        assert_eq!(String::from_utf8_lossy(&running.stdout).trim(), "true");
622        assert_eq!(
623            decisions, 1,
624            "the decision the program took is visible while it runs"
625        );
626        assert!(epoch > 0, "the flusher stamps its epoch");
627        drop(_container);
628        let _ = Command::new("docker")
629            .args(["run", "--rm", "-v"])
630            .arg(format!("{}:/w", directory.display()))
631            .args([
632                &image,
633                "rm",
634                "-rf",
635                "/w/evidence",
636                "/w/ready",
637                "/w/go",
638                "/w/done",
639            ])
640            .output();
641        fs::remove_dir_all(directory).ok();
642    }
643
644    #[test]
645    fn a_statement_after_one_the_target_strips_keeps_its_probe() {
646        // semver's identifier.rs: the probe of the line after a stripped
647        // statement goes right after that statement's `;`, and was dropped
648        // with it, so a line every test ran read as never executed.
649        let source = r#"fn width(value: u64) -> u32 {
650    #[cfg(target_endian = "little")]
651    let zero = value.leading_zeros();
652    #[cfg(target_endian = "big")]
653    let zero = value.trailing_zeros();
654
655    let bytes = 8 - zero / 8;
656    bytes
657}
658
659fn main() {
660    println!("{}", width(1));
661}
662"#;
663        let endian = if cfg!(target_endian = "little") {
664            "little"
665        } else {
666            "big"
667        };
668        let cfg =
669            crate::rust_instrumenter::TargetCfg::new(&[format!("target_endian=\"{endian}\"")
670                .parse::<cargo_platform::Cfg>()
671                .unwrap()]);
672        let transformed = crate::rust_instrumenter::instrument_rust_file(
673            "src/main.rs",
674            source,
675            "crate::__supercov_runtime_v1",
676            false,
677            Some(&cfg),
678        )
679        .unwrap();
680        let after = transformed
681            .manifest
682            .points
683            .iter()
684            .find(|point| point.line == 7)
685            .unwrap()
686            .id
687            .clone();
688        let runtime = region_runtime(&transformed.probes);
689        let directory = temporary_directory("stripped-neighbour");
690        let input = directory.join("main.rs");
691        let binary = directory.join("program");
692        let evidence = directory.join("evidence");
693        fs::write(&input, format!("{}\n{runtime}", transformed.code)).unwrap();
694        let compile = Command::new("rustc")
695            .arg("--edition=2024")
696            .arg(&input)
697            .arg("-o")
698            .arg(&binary)
699            .output()
700            .unwrap();
701        assert!(
702            compile.status.success(),
703            "{}",
704            String::from_utf8_lossy(&compile.stderr)
705        );
706        let output = Command::new(&binary)
707            .env("SUPERCOV_RUST_EVIDENCE_DIR", &evidence)
708            .output()
709            .unwrap();
710        assert!(output.status.success());
711        let hits = read_regions(&evidence, &transformed.probes)
712            .into_values()
713            .flatten()
714            .filter_map(|entry| match entry.observation {
715                RustProbeObservation::Hit { id } => Some(id),
716                _ => None,
717            })
718            .collect::<Vec<_>>();
719        assert!(hits.contains(&after), "{after} not among {hits:?}");
720        fs::remove_dir_all(directory).unwrap();
721    }
722
723    /// A program that hands its process to another with `exec` -- a cache
724    /// front end handing over to the runner's Node -- runs no exit code; the
725    /// region is the file, so what it recorded is there anyway.
726    #[cfg(unix)]
727    #[test]
728    fn what_a_program_ran_before_exec_is_kept() {
729        let source = r#"use std::os::unix::process::CommandExt;
730
731fn restore(key: &str) -> &'static str {
732    if key.starts_with("v1-") { "hit" } else { "miss" }
733}
734
735fn main() {
736    let outcome = restore("v1-abc");
737    let error = std::process::Command::new("/bin/sh")
738        .args(["-c", &format!("echo {outcome}")])
739        .exec();
740    panic!("exec failed: {error}");
741}
742"#;
743        let transformed =
744            instrument_rust_source("src/main.rs", source, "crate::__supercov_runtime_v1").unwrap();
745        let runtime = region_runtime(&transformed.probes);
746        let directory = temporary_directory("exec");
747        let input = directory.join("main.rs");
748        let binary = directory.join("program");
749        let evidence = directory.join("evidence");
750        fs::write(&input, format!("{}\n{runtime}", transformed.code)).unwrap();
751        let compile = Command::new("rustc")
752            .arg("--edition=2024")
753            .arg(&input)
754            .arg("-o")
755            .arg(&binary)
756            .output()
757            .unwrap();
758        assert!(
759            compile.status.success(),
760            "{}",
761            String::from_utf8_lossy(&compile.stderr)
762        );
763        let output = Command::new(&binary)
764            .env("SUPERCOV_RUST_EVIDENCE_DIR", &evidence)
765            .output()
766            .unwrap();
767        assert!(output.status.success());
768        assert_eq!(output.stdout, b"hit\n");
769        let decisions = read_regions(&evidence, &transformed.probes)
770            .into_values()
771            .flatten()
772            .filter_map(|entry| match entry.observation {
773                RustProbeObservation::Decision {
774                    values, outcome, ..
775                } => Some((values, outcome)),
776                _ => None,
777            })
778            .collect::<Vec<_>>();
779        assert_eq!(decisions, [(vec![Some(true)], true)]);
780        fs::remove_dir_all(directory).unwrap();
781    }
782
783    #[test]
784    fn the_runtime_records_which_gated_code_the_compiler_built() {
785        let source = r#"#[cfg(feature = "on")]
786fn built() -> u8 {
787    if std::env::args().count() > 99 { 1 } else { 2 }
788}
789
790#[cfg(feature = "off")]
791fn left_out() -> u8 {
792    3
793}
794
795#[cfg(not(feature = "on"))]
796fn not_on() -> u8 {
797    4
798}
799
800fn main() {
801    println!("{}", built());
802}
803"#;
804        let transformed =
805            instrument_rust_source("src/main.rs", source, "crate::__supercov_runtime_v1").unwrap();
806        let predicates = transformed
807            .probes
808            .cfgs
809            .iter()
810            .map(|gate| gate.predicate.as_str())
811            .collect::<Vec<_>>();
812        assert_eq!(
813            predicates,
814            [
815                "feature = \"on\"",
816                "feature = \"off\"",
817                "not(feature = \"on\")"
818            ]
819        );
820        let runtime = region_runtime(&transformed.probes);
821        let directory = temporary_directory("cfg-table");
822        let input = directory.join("main.rs");
823        let binary = directory.join("program");
824        let evidence = directory.join("evidence").join("test-1");
825        fs::write(&input, format!("{}\n{runtime}", transformed.code)).unwrap();
826        let compile = Command::new("rustc")
827            .args(["--edition=2024", "--cfg", "feature=\"on\""])
828            .arg(&input)
829            .arg("-o")
830            .arg(&binary)
831            .output()
832            .unwrap();
833        assert!(
834            compile.status.success(),
835            "{}",
836            String::from_utf8_lossy(&compile.stderr)
837        );
838        let output = Command::new(&binary)
839            .env("SUPERCOV_RUST_EVIDENCE_DIR", &evidence)
840            .output()
841            .unwrap();
842        assert!(output.status.success());
843        assert_eq!(output.stdout, b"2\n");
844        // Read from the run's root, a directory per test below it.
845        let settled =
846            read_rust_cfg_settlement(&directory.join("evidence"), TOKEN, &transformed.probes)
847                .unwrap();
848        assert_eq!(settled, [2, 1, 1]);
849        // A process that never maps a region answers for nothing.
850        let silent = directory.join("silent");
851        fs::create_dir(&silent).unwrap();
852        assert_eq!(
853            read_rust_cfg_settlement(&silent, TOKEN, &transformed.probes).unwrap(),
854            [0, 0, 0]
855        );
856        fs::remove_dir_all(directory).unwrap();
857    }
858
859    #[test]
860    fn generated_runtime_records_owned_points_and_exact_short_circuit_vectors() {
861        let source = r#"fn choose(first: bool, second: bool) -> i32 {
862    if first && second { 7 } else { 3 }
863}
864
865fn main() {
866    println!("{} {}", choose(false, true), choose(true, true));
867}
868"#;
869        let transformed =
870            instrument_rust_source("src/main.rs", source, "crate::__supercov_runtime_v1").unwrap();
871        let runtime = region_runtime(&transformed.probes);
872        let directory = temporary_directory("record");
873        let input = directory.join("main.rs");
874        let binary = directory.join("program");
875        let evidence = directory.join("evidence");
876        fs::write(&input, format!("{}\n{runtime}", transformed.code)).unwrap();
877        let compile = Command::new("rustc")
878            .arg("--edition=2024")
879            .arg(&input)
880            .arg("-o")
881            .arg(&binary)
882            .output()
883            .unwrap();
884        assert!(
885            compile.status.success(),
886            "{}",
887            String::from_utf8_lossy(&compile.stderr)
888        );
889        let output = Command::new(&binary)
890            .env("SUPERCOV_RUST_EVIDENCE_DIR", &evidence)
891            .output()
892            .unwrap();
893        assert!(output.status.success());
894        assert_eq!(output.stdout, b"3 7\n");
895        let files = read_regions(&evidence, &transformed.probes);
896        assert_eq!(files.len(), 1);
897        let observations = files.values().next().unwrap();
898        let decisions = observations
899            .iter()
900            .filter_map(|entry| match &entry.observation {
901                RustProbeObservation::Decision {
902                    values, outcome, ..
903                } => Some((values.clone(), *outcome)),
904                RustProbeObservation::Hit { .. } | RustProbeObservation::Assertion { .. } => None,
905            })
906            .collect::<Vec<_>>();
907        assert_eq!(
908            decisions,
909            [
910                (vec![Some(false), None], false),
911                (vec![Some(true), Some(true)], true)
912            ]
913        );
914        fs::remove_dir_all(directory).unwrap();
915    }
916
917    #[test]
918    fn generated_runtime_compiles_into_a_no_std_host_crate() {
919        // `#![no_std]` swaps the std prelude for core's, so `format!`, `vec!`
920        // and `Vec` are simply not in scope. The injected module named them
921        // unqualified and every no_std crate failed to build -- found on
922        // bytes-1.12.1, which is `#![no_std]` (src/lib.rs:6). `extern crate std`
923        // here mirrors what the injected module does: it links std without
924        // restoring the prelude, which is precisely the condition under test.
925        let source = r#"#![no_std]
926
927extern crate std;
928
929fn choose(first: bool, second: bool) -> i32 {
930    if first && second { 7 } else { 3 }
931}
932
933fn main() {
934    std::println!("{} {}", choose(false, true), choose(true, true));
935}
936"#;
937        let transformed =
938            instrument_rust_source("src/main.rs", source, "crate::__supercov_runtime_v1").unwrap();
939        let runtime = region_runtime(&transformed.probes);
940        let directory = temporary_directory("no-std");
941        let input = directory.join("main.rs");
942        let binary = directory.join("program");
943        let evidence = directory.join("evidence");
944        fs::write(&input, format!("{}\n{runtime}", transformed.code)).unwrap();
945        let compile = Command::new("rustc")
946            .arg("--edition=2024")
947            .arg(&input)
948            .arg("-o")
949            .arg(&binary)
950            .output()
951            .unwrap();
952        assert!(
953            compile.status.success(),
954            "{}",
955            String::from_utf8_lossy(&compile.stderr)
956        );
957        let output = Command::new(&binary)
958            .env("SUPERCOV_RUST_EVIDENCE_DIR", &evidence)
959            .output()
960            .unwrap();
961        assert!(output.status.success());
962        assert_eq!(output.stdout, b"3 7\n");
963        // Probes must still record, not merely compile.
964        let files = read_regions(&evidence, &transformed.probes);
965        assert_eq!(files.len(), 1);
966        assert!(!files.values().next().unwrap().is_empty());
967        fs::remove_dir_all(directory).unwrap();
968    }
969
970    #[test]
971    fn probes_reached_through_a_global_allocator_do_not_recurse() {
972        // The shape from bytes-1.12.1 tests/test_bytes_vec_alloc.rs: the
973        // allocator's `alloc` calls an inherent method, which calls a FREE
974        // FUNCTION. Skipping `impl GlobalAlloc` blocks syntactically does not
975        // cover `note`, and nothing syntactic can -- the chain may leave the
976        // file or the crate. Only the runtime knows a probe is already running,
977        // so the guard has to live there. Without it this binary dies with
978        // SIGSEGV instead of printing anything.
979        let source = r#"use std::alloc::{GlobalAlloc, Layout, System};
980use std::sync::atomic::{AtomicUsize, Ordering};
981
982static SEEN: AtomicUsize = AtomicUsize::new(0);
983
984fn note(size: usize) {
985    if size > 0 {
986        SEEN.fetch_add(1, Ordering::SeqCst);
987    }
988}
989
990struct Ledger;
991
992impl Ledger {
993    fn record(&self, size: usize) {
994        note(size);
995    }
996}
997
998unsafe impl GlobalAlloc for Ledger {
999    unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
1000        self.record(layout.size());
1001        System.alloc(layout)
1002    }
1003
1004    unsafe fn dealloc(&self, pointer: *mut u8, layout: Layout) {
1005        // dealloc must be instrumented too, or the test never exercises the
1006        // ladder that actually crashed: freeing the probe's OWN buffer re-enters
1007        // here, and a guard released before that free recurses without bound.
1008        self.record(layout.size());
1009        System.dealloc(pointer, layout);
1010    }
1011}
1012
1013#[global_allocator]
1014static LEDGER: Ledger = Ledger;
1015
1016fn classify(flag: bool) -> usize {
1017    if flag { 1 } else { 2 }
1018}
1019
1020fn main() {
1021    let held = std::vec![7u8; 32];
1022    println!("{} {}", classify(!held.is_empty()), held.len());
1023}
1024"#;
1025        let transformed =
1026            instrument_rust_source("src/main.rs", source, "crate::__supercov_runtime_v1").unwrap();
1027        // `note` is a free function, so it IS instrumented -- proving the guard,
1028        // not a syntactic skip, is what prevents the recursion.
1029        assert!(
1030            transformed
1031                .code
1032                .contains("fn note(size: usize) {crate::__supercov_runtime_v1::hit("),
1033            "the free function reached from the allocator should still be probed"
1034        );
1035        let runtime = region_runtime(&transformed.probes);
1036        let directory = temporary_directory("allocator-reentry");
1037        let input = directory.join("main.rs");
1038        let binary = directory.join("program");
1039        let evidence = directory.join("evidence");
1040        fs::write(&input, format!("{}\n{runtime}", transformed.code)).unwrap();
1041        let compile = Command::new("rustc")
1042            .arg("--edition=2024")
1043            .arg(&input)
1044            .arg("-o")
1045            .arg(&binary)
1046            .output()
1047            .unwrap();
1048        assert!(
1049            compile.status.success(),
1050            "{}",
1051            String::from_utf8_lossy(&compile.stderr)
1052        );
1053        let output = Command::new(&binary)
1054            .env("SUPERCOV_RUST_EVIDENCE_DIR", &evidence)
1055            .output()
1056            .unwrap();
1057        assert!(
1058            output.status.success(),
1059            "instrumented allocator did not survive: {:?}",
1060            output.status
1061        );
1062        assert_eq!(output.stdout, b"1 32\n");
1063        let files = read_regions(&evidence, &transformed.probes);
1064        let observations = files.values().next().unwrap();
1065        let decisions = observations
1066            .iter()
1067            .filter_map(|entry| match &entry.observation {
1068                RustProbeObservation::Decision {
1069                    id,
1070                    values,
1071                    outcome,
1072                } => Some((id.clone(), values.clone(), *outcome)),
1073                RustProbeObservation::Hit { .. } | RustProbeObservation::Assertion { .. } => None,
1074            })
1075            .collect::<Vec<_>>();
1076        // Suppression costs only duplicates: `classify` runs outside any probe,
1077        // so its decision is still recorded exactly.
1078        let classify = transformed
1079            .manifest
1080            .decisions
1081            .iter()
1082            .find(|decision| decision.source == "flag")
1083            .expect("classify's decision reached the manifest");
1084        assert!(
1085            decisions
1086                .iter()
1087                .any(|(id, values, outcome)| id == &classify.id
1088                    && values == &[Some(true)]
1089                    && *outcome),
1090            "classify's decision was lost: {decisions:?}"
1091        );
1092        // `note` records too -- every ordinary allocation reaches it outside a
1093        // probe -- which is why suppressing the nested ones loses nothing.
1094        let note = transformed
1095            .manifest
1096            .decisions
1097            .iter()
1098            .find(|decision| decision.source == "size > 0")
1099            .expect("note's decision reached the manifest");
1100        assert!(decisions.iter().any(|(id, ..)| id == &note.id));
1101        // No frame built while nested may reach the log: a zero-width vector
1102        // for a one-condition decision is a malformed record, not a lost one.
1103        assert!(
1104            decisions.iter().all(|(_, values, _)| values.len() == 1),
1105            "a suppressed frame emitted a malformed vector: {decisions:?}"
1106        );
1107        fs::remove_dir_all(directory).unwrap();
1108    }
1109
1110    #[test]
1111    fn a_hit_in_a_loop_is_written_once_but_decisions_keep_every_vector() {
1112        // bytes' advance_bytes_mut_remaining_capacity is a triple-nested loop of
1113        // ~2.8M iterations. One write syscall per probe per iteration left it
1114        // still running after four minutes. A hit only answers "did this ever
1115        // run", so the repeats carry nothing -- but a decision's condition
1116        // vector differs per iteration and every distinct one must survive.
1117        let source = r#"fn step(value: usize) -> bool {
1118    let doubled = value * 2;
1119    doubled > 4
1120}
1121
1122fn main() {
1123    let mut seen = 0;
1124    for value in 0..64 {
1125        if step(value) { seen += 1; }
1126    }
1127    println!("{seen}");
1128}
1129"#;
1130        let transformed =
1131            instrument_rust_source("src/main.rs", source, "crate::__supercov_runtime_v1").unwrap();
1132        let runtime = region_runtime(&transformed.probes);
1133        let directory = temporary_directory("dedup");
1134        let input = directory.join("main.rs");
1135        let binary = directory.join("program");
1136        let evidence = directory.join("evidence");
1137        fs::write(&input, format!("{}\n{runtime}", transformed.code)).unwrap();
1138        let compile = Command::new("rustc")
1139            .arg("--edition=2024")
1140            .arg(&input)
1141            .arg("-o")
1142            .arg(&binary)
1143            .output()
1144            .unwrap();
1145        assert!(
1146            compile.status.success(),
1147            "{}",
1148            String::from_utf8_lossy(&compile.stderr)
1149        );
1150        let output = Command::new(&binary)
1151            .env("SUPERCOV_RUST_EVIDENCE_DIR", &evidence)
1152            .output()
1153            .unwrap();
1154        assert_eq!(output.stdout, b"61\n");
1155        let files = read_regions(&evidence, &transformed.probes);
1156        let observations = files.values().next().unwrap();
1157
1158        // `doubled > 4` runs 64 times; its hit is recorded once.
1159        let mut hits = BTreeMap::<&str, usize>::new();
1160        for entry in observations {
1161            if let RustProbeObservation::Hit { id } = &entry.observation {
1162                *hits.entry(id.as_str()).or_default() += 1;
1163            }
1164        }
1165        assert!(!hits.is_empty(), "no hits recorded at all");
1166        assert!(
1167            hits.values().all(|count| *count == 1),
1168            "a repeated hit was written more than once: {hits:?}"
1169        );
1170
1171        // MC/DC needs the SET of condition vectors, not how often each recurred,
1172        // so a repeat of an already-seen vector carries nothing -- but every
1173        // DISTINCT vector must still arrive. `doubled > 4` is evaluated 64 times
1174        // and takes exactly two distinct shapes, so exactly two records survive.
1175        let decisions = observations
1176            .iter()
1177            .filter_map(|entry| match &entry.observation {
1178                RustProbeObservation::Decision {
1179                    values, outcome, ..
1180                } => Some((values.clone(), *outcome)),
1181                RustProbeObservation::Hit { .. } | RustProbeObservation::Assertion { .. } => None,
1182            })
1183            .collect::<Vec<_>>();
1184        assert_eq!(
1185            decisions,
1186            // In first-occurrence order: `step(0)` is false before any value
1187            // exceeds the threshold.
1188            [(vec![Some(false)], false), (vec![Some(true)], true)],
1189            "both distinct vectors must survive, and neither may repeat"
1190        );
1191        fs::remove_dir_all(directory).unwrap();
1192    }
1193
1194    #[test]
1195    fn generated_runtime_survives_a_host_without_the_prelude() {
1196        // tracing's macro tests carry `#![no_implicit_prelude]` on purpose,
1197        // to prove their macros do not depend on it. The runtime imports
1198        // every name it uses for `no_std` hosts, but three `TryProbe` impls
1199        // reached for `matches!`, which is a prelude macro.
1200        let source = r#"#![no_implicit_prelude]
1201
1202fn parse(text: &str) -> ::std::option::Option<i32> {
1203    let value: i32 = text.parse().ok()?;
1204    ::std::option::Option::Some(value * 2)
1205}
1206
1207fn main() {
1208    ::std::println!("{:?} {:?}", parse("4"), parse("x"));
1209}
1210"#;
1211        let transformed =
1212            instrument_rust_source("src/main.rs", source, "crate::__supercov_runtime_v1").unwrap();
1213        // The `?` takes a probe, which is what pulls in the TryProbe impls.
1214        assert!(transformed.code.contains("TryProbe"));
1215        let runtime = region_runtime(&transformed.probes);
1216        let directory = temporary_directory("no-implicit-prelude");
1217        let input = directory.join("main.rs");
1218        let binary = directory.join("program");
1219        fs::write(&input, format!("{}\n{runtime}", transformed.code)).unwrap();
1220        let compile = Command::new("rustc")
1221            .arg("--edition=2024")
1222            .arg("--cap-lints=allow")
1223            .arg(&input)
1224            .arg("-o")
1225            .arg(&binary)
1226            .output()
1227            .unwrap();
1228        assert!(
1229            compile.status.success(),
1230            "{}",
1231            String::from_utf8_lossy(&compile.stderr)
1232        );
1233        let output = Command::new(&binary).output().unwrap();
1234        assert_eq!(output.stdout, b"Some(8) None\n");
1235        fs::remove_dir_all(directory).unwrap();
1236    }
1237
1238    #[test]
1239    fn generated_runtime_survives_a_deny_warnings_host() {
1240        // serde builds with `#![deny(warnings)]`; the injected module's
1241        // fully-qualified imports (required for no_std hosts) read as unused
1242        // imports and became hard errors. Injected code must be immune to the
1243        // host's lint policy.
1244        let source = r#"#![deny(warnings)]
1245
1246fn choose(first: bool, second: bool) -> i32 {
1247    if first && second { 7 } else { 3 }
1248}
1249
1250fn main() {
1251    println!("{} {}", choose(false, true), choose(true, true));
1252}
1253"#;
1254        let transformed =
1255            instrument_rust_source("src/main.rs", source, "crate::__supercov_runtime_v1").unwrap();
1256        let runtime = region_runtime(&transformed.probes);
1257        let directory = temporary_directory("deny-warnings");
1258        let input = directory.join("main.rs");
1259        let binary = directory.join("program");
1260        fs::write(&input, format!("{}\n{runtime}", transformed.code)).unwrap();
1261        // --cap-lints=allow mirrors what the runner passes for the instrumented
1262        // workspace: the host policy must not reject generated code, including
1263        // the `if ({{ frame ... }})` decision wrapping that trips unused_parens.
1264        let compile = Command::new("rustc")
1265            .arg("--edition=2024")
1266            .arg("--cap-lints=allow")
1267            .arg(&input)
1268            .arg("-o")
1269            .arg(&binary)
1270            .output()
1271            .unwrap();
1272        assert!(
1273            compile.status.success(),
1274            "{}",
1275            String::from_utf8_lossy(&compile.stderr)
1276        );
1277        let output = Command::new(&binary).output().unwrap();
1278        assert_eq!(output.stdout, b"3 7\n");
1279        fs::remove_dir_all(directory).unwrap();
1280    }
1281
1282    fn layout(hits: &[&str], decisions: &[&str]) -> RustProbeLayout {
1283        RustProbeLayout {
1284            token: TOKEN.into(),
1285            hits: hits.iter().map(|id| (*id).to_owned()).collect(),
1286            assertions: Vec::new(),
1287            decisions: decisions.iter().map(|id| (*id).to_owned()).collect(),
1288            cfgs: Vec::new(),
1289            crates: Default::default(),
1290        }
1291    }
1292
1293    #[test]
1294    fn a_region_of_another_layout_or_instrumentation_is_refused() {
1295        let probes = layout(&["rs:point:a"], &[]);
1296        assert_eq!(
1297            parse_rust_region(b"not a region", None, TOKEN, &probes),
1298            Err(RustProbeReadError::InvalidHeader)
1299        );
1300        // A valid header, written for two probes, read with a layout of one.
1301        let mut bytes = vec![0u8; 4096];
1302        bytes[..8].copy_from_slice(b"SCVRGN01");
1303        bytes[8..12].copy_from_slice(&1u32.to_le_bytes());
1304        bytes[12..16].copy_from_slice(&128u32.to_le_bytes());
1305        bytes[16..20].copy_from_slice(&2u32.to_le_bytes());
1306        bytes[56..56 + TOKEN.len()].copy_from_slice(TOKEN.as_bytes());
1307        assert_eq!(
1308            parse_rust_region(&bytes, None, TOKEN, &probes),
1309            Err(RustProbeReadError::InvalidHeader)
1310        );
1311        // The right layout, but another instrumentation's token.
1312        bytes[16..20].copy_from_slice(&1u32.to_le_bytes());
1313        assert_eq!(
1314            parse_rust_region(&bytes, None, "ba9876543210", &probes),
1315            Err(RustProbeReadError::InvalidHeader)
1316        );
1317        assert_eq!(
1318            parse_rust_region(&bytes, None, TOKEN, &probes),
1319            Ok(Vec::new())
1320        );
1321    }
1322
1323    #[test]
1324    fn the_directory_reader_takes_only_this_instrumentations_regular_files() {
1325        let directory = temporary_directory("regions");
1326        let probes = layout(&["rs:point:a"], &[]);
1327        fs::write(
1328            directory.join("ba9876543210-7.region"),
1329            b"another instrumentation",
1330        )
1331        .unwrap();
1332        fs::write(directory.join("notes.txt"), b"unrelated").unwrap();
1333        assert_eq!(
1334            read_rust_region_directory(&directory, TOKEN, &probes),
1335            Ok(Vec::new())
1336        );
1337        fs::create_dir(directory.join(format!("{TOKEN}-9.region"))).unwrap();
1338        assert!(matches!(
1339            read_rust_region_directory(&directory, TOKEN, &probes),
1340            Err(RustProbeReadError::UnsafeEntry(_))
1341        ));
1342        assert!(
1343            read_rust_region_directory(&directory.join("absent"), TOKEN, &probes)
1344                .unwrap()
1345                .is_empty()
1346        );
1347        fs::remove_dir_all(directory).unwrap();
1348    }
1349
1350    #[test]
1351    fn combinations_beyond_a_decisions_entries_overflow_and_are_all_read_back() {
1352        // `(a || b) && (c || d) && e` reaches eleven distinct (vector,
1353        // outcome) combinations, against eight entries in the region.
1354        let source = r#"fn all(a: bool, b: bool, c: bool, d: bool, e: bool) -> bool {
1355    if (a || b) && (c || d) && e { true } else { false }
1356}
1357
1358fn main() {
1359    let mut taken = 0;
1360    for bits in 0..32u8 {
1361        for _ in 0..3 {
1362            if all(bits & 1 != 0, bits & 2 != 0, bits & 4 != 0, bits & 8 != 0, bits & 16 != 0) {
1363                taken += 1;
1364            }
1365        }
1366    }
1367    println!("{taken}");
1368}
1369"#;
1370        let transformed =
1371            instrument_rust_source("src/main.rs", source, "crate::__supercov_runtime_v1").unwrap();
1372        let directory = temporary_directory("overflow");
1373        let input = directory.join("main.rs");
1374        let binary = directory.join("program");
1375        let evidence = directory.join("evidence");
1376        fs::write(
1377            &input,
1378            format!(
1379                "{}\n{}",
1380                transformed.code,
1381                region_runtime(&transformed.probes)
1382            ),
1383        )
1384        .unwrap();
1385        let compile = Command::new("rustc")
1386            .arg("--edition=2024")
1387            .arg(&input)
1388            .arg("-o")
1389            .arg(&binary)
1390            .output()
1391            .unwrap();
1392        assert!(
1393            compile.status.success(),
1394            "{}",
1395            String::from_utf8_lossy(&compile.stderr)
1396        );
1397        let output = Command::new(&binary)
1398            .env("SUPERCOV_RUST_EVIDENCE_DIR", &evidence)
1399            .output()
1400            .unwrap();
1401        assert!(output.status.success());
1402        assert_eq!(output.stdout, b"27\n");
1403        let mut vectors = read_regions(&evidence, &transformed.probes)
1404            .into_values()
1405            .flatten()
1406            .filter_map(|entry| match entry.observation {
1407                // The five-condition decision; `main`'s `if` is one of its own.
1408                RustProbeObservation::Decision {
1409                    values, outcome, ..
1410                } if values.len() == 5 => Some((values, outcome)),
1411                _ => None,
1412            })
1413            .collect::<Vec<_>>();
1414        let total = vectors.len();
1415        vectors.sort();
1416        vectors.dedup();
1417        // Every combination, the three the region had no room for included,
1418        // read back once.
1419        assert_eq!(total, vectors.len(), "a combination was recorded twice");
1420        assert_eq!(vectors.len(), 11, "{vectors:?}");
1421        // A record cut off by the process dying mid-write is not an error.
1422        let overflow = fs::read_dir(&evidence)
1423            .unwrap()
1424            .flatten()
1425            .find(|entry| entry.file_name().to_string_lossy().ends_with(".overflow"));
1426        let overflow = overflow.expect("the combinations past eight overflowed");
1427        {
1428            let mut bytes = fs::read(overflow.path()).unwrap();
1429            bytes.extend_from_slice(b"D\t99\t1");
1430            fs::write(overflow.path(), bytes).unwrap();
1431            assert!(read_rust_region_directory(&evidence, TOKEN, &transformed.probes).is_ok());
1432        }
1433        fs::remove_dir_all(directory).unwrap();
1434    }
1435}