Skip to main content

supercov_engine/
rust_libtest_companion.rs

1//! Deterministic source preparation for Supercov's exact-toolchain libtest
2//! companion.
3//!
4//! Published Supercov platform packages will contain the already-built rlib.
5//! This module is the release/development builder: it consumes the selected
6//! toolchain's exact `library/test` source, rejects unrecognized layouts, and
7//! atomically publishes a patched tree whose identity contains no scratch
8//! paths.
9
10use std::{
11    ffi::OsString,
12    fs::{self, OpenOptions},
13    io::{Cursor, Write},
14    path::{Component, Path, PathBuf},
15    process::Command,
16    thread,
17    time::{Duration, Instant, SystemTime, UNIX_EPOCH},
18};
19
20use ar_archive_writer::{
21    ArchiveKind as WritableArchiveKind, DEFAULT_OBJECT_READER, NewArchiveMember,
22    write_archive_to_stream,
23};
24use object::read::archive::{ArchiveFile, ArchiveKind as ReadableArchiveKind};
25use serde::{Deserialize, Serialize};
26use sha2::{Digest, Sha256};
27use supercov_contracts::{
28    RUST_LIBTEST_COMPANION_BUNDLE_SCHEMA_VERSION, RUST_LIBTEST_EVENT_PROTOCOL_VERSION,
29    RustCompilerIdentity,
30};
31
32use crate::{
33    rust_compiler_selection::{
34        SelectedRustCompilerCompanion, probe_rustc_identity, select_rust_compiler_companion,
35    },
36    rust_libtest_events::rust_libtest_event_runtime_source,
37};
38
39const SOURCE_IDENTITY_FILE: &str = "supercov-libtest-source.json";
40const BUILD_LOCK_TIMEOUT: Duration = Duration::from_secs(300);
41
42const LIB_ANCHOR: &str = "mod console;";
43const LIB_REPLACEMENT: &str = "mod console;\nmod supercov_events;";
44const CONSOLE_ANCHOR: &str = "fn on_test_event(\n    event: &TestEvent,\n    st: &mut ConsoleTestState,\n    out: &mut dyn OutputFormatter,\n) -> io::Result<()> {\n    match (*event).clone() {";
45const CONSOLE_REPLACEMENT: &str = "fn on_test_event(\n    event: &TestEvent,\n    st: &mut ConsoleTestState,\n    out: &mut dyn OutputFormatter,\n) -> io::Result<()> {\n    crate::supercov_events::emit(event)?;\n    match (*event).clone() {";
46const LISTING_ANCHOR: &str =
47    "    out.write_discovery_start()?;\n    for test in filter_tests(opts, tests).into_iter() {";
48const LISTING_REPLACEMENT: &str = "    out.write_discovery_start()?;\n    let tests_len = tests.len();\n    let filtered_tests = filter_tests(opts, tests);\n    crate::supercov_events::emit_listing(tests_len - filtered_tests.len(), filtered_tests.len())?;\n    for test in filtered_tests {";
49const IN_PROCESS_ANCHOR: &str = "    // Buffer for capturing standard I/O\n    let data =";
50const IN_PROCESS_REPLACEMENT: &str = "    let _supercov_context = crate::supercov_events::enter_test(desc.name.as_slice())\n        .expect(\"Supercov could not enter the exact libtest context\");\n\n    // Buffer for capturing standard I/O\n    let data =";
51const SPAWNED_PROCESS_ANCHOR: &str = "fn run_test_in_spawned_subprocess(desc: TestDesc, runnable_test: RunnableTest) -> ! {\n    let builtin_panic_hook";
52const SPAWNED_PROCESS_REPLACEMENT: &str = "fn run_test_in_spawned_subprocess(desc: TestDesc, runnable_test: RunnableTest) -> ! {\n    let _supercov_context = crate::supercov_events::enter_test(desc.name.as_slice())\n        .expect(\"Supercov could not enter the exact spawned libtest context\");\n    let builtin_panic_hook";
53const BENCH_ANCHOR: &str = "        Runnable::Bench(runnable_bench) => {\n            // Benchmarks aren't expected to panic, so we run them all in-process.\n            runnable_bench.run";
54const BENCH_REPLACEMENT: &str = "        Runnable::Bench(runnable_bench) => {\n            // Benchmarks aren't expected to panic, so we run them all in-process.\n            let _supercov_context = crate::supercov_events::enter_test(desc.name.as_slice())\n                .expect(\"Supercov could not enter the exact benchmark context\");\n            runnable_bench.run";
55
56#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
57#[serde(rename_all = "camelCase", deny_unknown_fields)]
58pub struct RustLibtestCompanionSourceIdentity {
59    pub event_protocol_version: u32,
60    pub rustc_commit_hash: String,
61    pub rustc_release: String,
62    pub host_triple: String,
63    pub original_source_sha256: String,
64    pub event_runtime_sha256: String,
65    pub patched_source_sha256: String,
66}
67
68#[derive(Debug, Clone, PartialEq, Eq)]
69pub struct RustLibtestCompanionBuildPlan {
70    pub source: PathBuf,
71    pub output: PathBuf,
72    pub arguments: Vec<OsString>,
73    pub rustc_bootstrap: OsString,
74}
75
76#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
77#[serde(rename_all = "camelCase", deny_unknown_fields)]
78pub struct RustLibtestCompanionBundle {
79    pub schema_version: u32,
80    pub event_protocol_version: u32,
81    pub compiler_companion_build_id: String,
82    pub rustc_commit_hash: String,
83    pub host_triple: String,
84    pub original_source_sha256: String,
85    pub event_runtime_sha256: String,
86    pub patched_source_sha256: String,
87    pub artifact_file: String,
88    pub artifact_sha256: String,
89}
90
91#[derive(Debug, Clone, PartialEq, Eq)]
92pub struct SelectedRustLibtestCompanion {
93    pub bundle_path: PathBuf,
94    pub artifact_path: PathBuf,
95    pub bundle: RustLibtestCompanionBundle,
96}
97
98#[derive(Debug, Clone, PartialEq, Eq)]
99pub enum RustLibtestCompanionError {
100    Io {
101        path: PathBuf,
102        reason: String,
103    },
104    UnsafeSource(PathBuf),
105    NonUtf8Path(PathBuf),
106    UnrecognizedSource {
107        path: PathBuf,
108        anchor: &'static str,
109    },
110    DependencyMetadata {
111        directory: PathBuf,
112        crate_name: &'static str,
113        count: usize,
114    },
115    InvalidBundle {
116        path: PathBuf,
117        reason: String,
118    },
119    InvalidArchive(String),
120    BundleMismatch(String),
121    BuildFailed {
122        program: PathBuf,
123        status: Option<i32>,
124        stdout: String,
125        stderr: String,
126    },
127    LockTimeout(PathBuf),
128}
129
130impl std::fmt::Display for RustLibtestCompanionError {
131    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
132        match self {
133            Self::Io { path, reason } => write!(formatter, "{}: {reason}", path.display()),
134            Self::UnsafeSource(path) => write!(
135                formatter,
136                "exact libtest source contains a symlink or special file: {}",
137                path.display()
138            ),
139            Self::NonUtf8Path(path) => write!(
140                formatter,
141                "exact libtest source contains a non-UTF-8 path: {}",
142                path.display()
143            ),
144            Self::UnrecognizedSource { path, anchor } => write!(
145                formatter,
146                "selected toolchain libtest source {} does not contain exactly one {anchor} patch anchor",
147                path.display()
148            ),
149            Self::DependencyMetadata {
150                directory,
151                crate_name,
152                count,
153            } => write!(
154                formatter,
155                "expected exactly one full {crate_name} metadata file in {}, found {count}",
156                directory.display()
157            ),
158            Self::InvalidBundle { path, reason } => write!(
159                formatter,
160                "invalid libtest companion bundle {}: {reason}",
161                path.display()
162            ),
163            Self::InvalidArchive(reason) => {
164                write!(formatter, "invalid libtest companion rlib: {reason}")
165            }
166            Self::BundleMismatch(reason) => {
167                write!(formatter, "libtest companion bundle mismatch: {reason}")
168            }
169            Self::BuildFailed {
170                program,
171                status,
172                stdout,
173                stderr,
174            } => write!(
175                formatter,
176                "{} failed with status {status:?}: {stderr}{stdout}",
177                program.display()
178            ),
179            Self::LockTimeout(path) => write!(
180                formatter,
181                "timed out waiting for the exact libtest builder lock {}",
182                path.display()
183            ),
184        }
185    }
186}
187
188impl std::error::Error for RustLibtestCompanionError {}
189
190fn io_error(path: &Path, error: impl std::fmt::Display) -> RustLibtestCompanionError {
191    RustLibtestCompanionError::Io {
192        path: path.to_path_buf(),
193        reason: error.to_string(),
194    }
195}
196
197#[cfg(unix)]
198fn sync_directory(path: &Path) -> Result<(), RustLibtestCompanionError> {
199    let directory = OpenOptions::new()
200        .read(true)
201        .open(path)
202        .map_err(|error| io_error(path, error))?;
203    directory.sync_all().map_err(|error| io_error(path, error))
204}
205
206#[cfg(not(unix))]
207fn sync_directory(_path: &Path) -> Result<(), RustLibtestCompanionError> {
208    Ok(())
209}
210
211fn regular_directory(path: &Path) -> Result<PathBuf, RustLibtestCompanionError> {
212    let canonical = fs::canonicalize(path).map_err(|error| io_error(path, error))?;
213    let metadata = fs::symlink_metadata(&canonical).map_err(|error| io_error(&canonical, error))?;
214    if !metadata.file_type().is_dir() {
215        return Err(RustLibtestCompanionError::UnsafeSource(canonical));
216    }
217    Ok(canonical)
218}
219
220fn safe_component(path: &Path) -> Result<String, RustLibtestCompanionError> {
221    if path
222        .components()
223        .any(|component| !matches!(component, Component::Normal(_)))
224    {
225        return Err(RustLibtestCompanionError::UnsafeSource(path.to_path_buf()));
226    }
227    path.to_str()
228        .map(|value| value.replace('\\', "/"))
229        .ok_or_else(|| RustLibtestCompanionError::NonUtf8Path(path.to_path_buf()))
230}
231
232fn collect_tree(
233    root: &Path,
234    relative: &Path,
235    entries: &mut Vec<(String, PathBuf, bool)>,
236) -> Result<(), RustLibtestCompanionError> {
237    let directory = root.join(relative);
238    let mut children = fs::read_dir(&directory)
239        .map_err(|error| io_error(&directory, error))?
240        .collect::<Result<Vec<_>, _>>()
241        .map_err(|error| io_error(&directory, error))?;
242    children.sort_by_key(std::fs::DirEntry::file_name);
243    for child in children {
244        let name = child.file_name();
245        let child_relative = relative.join(name);
246        let display = safe_component(&child_relative)?;
247        let file_type = child
248            .file_type()
249            .map_err(|error| io_error(&child.path(), error))?;
250        if file_type.is_dir() {
251            entries.push((display, child.path(), true));
252            collect_tree(root, &child_relative, entries)?;
253        } else if file_type.is_file() {
254            entries.push((display, child.path(), false));
255        } else {
256            return Err(RustLibtestCompanionError::UnsafeSource(child.path()));
257        }
258    }
259    Ok(())
260}
261
262fn source_tree_digest(root: &Path) -> Result<String, RustLibtestCompanionError> {
263    source_tree_digest_excluding(root, None)
264}
265
266fn source_tree_digest_excluding(
267    root: &Path,
268    excluded_root_file: Option<&str>,
269) -> Result<String, RustLibtestCompanionError> {
270    let mut entries = Vec::new();
271    collect_tree(root, Path::new(""), &mut entries)?;
272    let mut digest = Sha256::new();
273    for (relative, path, directory) in entries {
274        if excluded_root_file.is_some_and(|excluded| relative == excluded) {
275            continue;
276        }
277        digest.update([u8::from(directory)]);
278        digest.update((relative.len() as u64).to_le_bytes());
279        digest.update(relative.as_bytes());
280        if !directory {
281            let bytes = fs::read(&path).map_err(|error| io_error(&path, error))?;
282            digest.update((bytes.len() as u64).to_le_bytes());
283            digest.update(bytes);
284        }
285    }
286    Ok(format!("{:x}", digest.finalize()))
287}
288
289fn copy_regular_tree(source: &Path, destination: &Path) -> Result<(), RustLibtestCompanionError> {
290    let mut entries = Vec::new();
291    collect_tree(source, Path::new(""), &mut entries)?;
292    for (relative, path, directory) in entries {
293        let destination_path = destination.join(&relative);
294        if directory {
295            fs::create_dir(&destination_path)
296                .map_err(|error| io_error(&destination_path, error))?;
297        } else {
298            let bytes = fs::read(&path).map_err(|error| io_error(&path, error))?;
299            let mut options = OpenOptions::new();
300            options.write(true).create_new(true);
301            #[cfg(unix)]
302            {
303                use std::os::unix::fs::OpenOptionsExt as _;
304                options.mode(0o600);
305            }
306            let mut file = options
307                .open(&destination_path)
308                .map_err(|error| io_error(&destination_path, error))?;
309            file.write_all(&bytes)
310                .and_then(|()| file.sync_all())
311                .map_err(|error| io_error(&destination_path, error))?;
312        }
313    }
314    Ok(())
315}
316
317fn replace_once(
318    path: &Path,
319    anchor: &'static str,
320    replacement: &str,
321) -> Result<(), RustLibtestCompanionError> {
322    let source = fs::read_to_string(path).map_err(|error| io_error(path, error))?;
323    if source.matches(anchor).count() != 1 {
324        return Err(RustLibtestCompanionError::UnrecognizedSource {
325            path: path.into(),
326            anchor,
327        });
328    }
329    let bytes = source.replacen(anchor, replacement, 1).into_bytes();
330    let mut file = OpenOptions::new()
331        .write(true)
332        .truncate(true)
333        .open(path)
334        .map_err(|error| io_error(path, error))?;
335    file.write_all(&bytes)
336        .and_then(|()| file.sync_all())
337        .map_err(|error| io_error(path, error))
338}
339
340struct RemoveDirectoryOnDrop(Option<PathBuf>);
341
342impl Drop for RemoveDirectoryOnDrop {
343    fn drop(&mut self) {
344        if let Some(path) = self.0.take() {
345            let _ = fs::remove_dir_all(path);
346        }
347    }
348}
349
350struct RemoveFileOnDrop(Option<PathBuf>);
351
352impl Drop for RemoveFileOnDrop {
353    fn drop(&mut self) {
354        if let Some(path) = self.0.take() {
355            let _ = fs::remove_file(path);
356        }
357    }
358}
359
360fn acquire_kernel_lock(path: &Path) -> Result<fs::File, RustLibtestCompanionError> {
361    if let Ok(metadata) = fs::symlink_metadata(path)
362        && !metadata.file_type().is_file()
363    {
364        return Err(RustLibtestCompanionError::UnsafeSource(path.to_path_buf()));
365    }
366    let started = Instant::now();
367    loop {
368        let mut options = OpenOptions::new();
369        options.read(true).write(true).create(true);
370        #[cfg(unix)]
371        {
372            use std::os::unix::fs::OpenOptionsExt as _;
373            #[cfg(target_os = "linux")]
374            const O_NOFOLLOW: i32 = 0x2_0000;
375            #[cfg(target_os = "macos")]
376            const O_NOFOLLOW: i32 = 0x100;
377            #[cfg(any(target_os = "linux", target_os = "macos"))]
378            options.custom_flags(O_NOFOLLOW);
379            options.mode(0o600);
380        }
381        let mut file = options.open(path).map_err(|error| io_error(path, error))?;
382        if !file
383            .metadata()
384            .map_err(|error| io_error(path, error))?
385            .file_type()
386            .is_file()
387        {
388            return Err(RustLibtestCompanionError::UnsafeSource(path.to_path_buf()));
389        }
390        match file.try_lock() {
391            Ok(()) => {
392                file.set_len(0)
393                    .and_then(|()| writeln!(file, "{}", std::process::id()))
394                    .and_then(|()| file.sync_all())
395                    .map_err(|error| io_error(path, error))?;
396                return Ok(file);
397            }
398            Err(fs::TryLockError::WouldBlock) => {
399                if started.elapsed() >= BUILD_LOCK_TIMEOUT {
400                    return Err(RustLibtestCompanionError::LockTimeout(path.to_path_buf()));
401                }
402                thread::sleep(Duration::from_millis(25));
403            }
404            Err(fs::TryLockError::Error(error)) => return Err(io_error(path, error)),
405        }
406    }
407}
408
409#[cfg(unix)]
410fn inherit_lock_through_exec(command: &mut Command, lock: &fs::File) {
411    use std::os::{fd::AsRawFd as _, unix::process::CommandExt as _};
412
413    let lock_fd = lock.as_raw_fd();
414    // OpenOptions uses close-on-exec. Duplicate the already locked open-file
415    // description in the post-fork child so an abruptly killed builder cannot
416    // release publication ownership while its rustc child is still writing.
417    // `dup` is async-signal-safe and the child-only duplicate intentionally
418    // survives exec until rustc exits.
419    unsafe {
420        command.pre_exec(move || {
421            if libc::dup(lock_fd) < 0 {
422                Err(std::io::Error::last_os_error())
423            } else {
424                Ok(())
425            }
426        });
427    }
428}
429
430#[cfg(not(unix))]
431fn inherit_lock_through_exec(_command: &mut Command, _lock: &fs::File) {
432    // Windows process/handle inheritance remains a private-platform promotion
433    // gate; public Rust support stays fail-closed there until it is proven.
434}
435
436fn remove_owned_path(path: &Path, expect_directory: bool) -> Result<(), RustLibtestCompanionError> {
437    let metadata = match fs::symlink_metadata(path) {
438        Ok(metadata) => metadata,
439        Err(error) if error.kind() == std::io::ErrorKind::NotFound => return Ok(()),
440        Err(error) => return Err(io_error(path, error)),
441    };
442    if expect_directory && metadata.file_type().is_dir() {
443        fs::remove_dir_all(path).map_err(|error| io_error(path, error))
444    } else if !expect_directory && metadata.file_type().is_file() {
445        fs::remove_file(path).map_err(|error| io_error(path, error))
446    } else {
447        Err(RustLibtestCompanionError::UnsafeSource(path.to_path_buf()))
448    }
449}
450
451fn remove_owned_partials(
452    directory: &Path,
453    prefix: &str,
454    expect_directory: bool,
455) -> Result<(), RustLibtestCompanionError> {
456    for entry in fs::read_dir(directory).map_err(|error| io_error(directory, error))? {
457        let entry = entry.map_err(|error| io_error(directory, error))?;
458        let name = entry.file_name();
459        let Some(name) = name.to_str() else {
460            continue;
461        };
462        if name.starts_with(prefix) && name.ends_with(".partial") {
463            remove_owned_path(&entry.path(), expect_directory)?;
464        }
465    }
466    Ok(())
467}
468
469fn validate_prepared_libtest_source(
470    source_root: &Path,
471    destination: &Path,
472    compiler: &RustCompilerIdentity,
473) -> Result<RustLibtestCompanionSourceIdentity, RustLibtestCompanionError> {
474    let destination = regular_directory(destination)?;
475    let identity_path = destination.join(SOURCE_IDENTITY_FILE);
476    let identity: RustLibtestCompanionSourceIdentity =
477        serde_json::from_slice(&read_regular_file(&identity_path)?).map_err(|error| {
478            RustLibtestCompanionError::InvalidBundle {
479                path: identity_path.clone(),
480                reason: error.to_string(),
481            }
482        })?;
483    let runtime_sha256 = format!(
484        "{:x}",
485        Sha256::digest(rust_libtest_event_runtime_source().as_bytes())
486    );
487    if identity.event_protocol_version != RUST_LIBTEST_EVENT_PROTOCOL_VERSION
488        || identity.rustc_commit_hash != compiler.rustc_commit_hash
489        || identity.rustc_release != compiler.rustc_release
490        || identity.host_triple != compiler.host_triple
491        || identity.event_runtime_sha256 != runtime_sha256
492        || [
493            identity.original_source_sha256.as_str(),
494            identity.event_runtime_sha256.as_str(),
495            identity.patched_source_sha256.as_str(),
496        ]
497        .iter()
498        .any(|value| !canonical_lower_sha256(value))
499    {
500        return Err(RustLibtestCompanionError::BundleMismatch(
501            "prepared libtest source identity differs from the exact compiler/runtime".into(),
502        ));
503    }
504    if source_tree_digest(source_root)? != identity.original_source_sha256 {
505        return Err(RustLibtestCompanionError::BundleMismatch(
506            "exact toolchain libtest source changed after preparation".into(),
507        ));
508    }
509    if source_tree_digest_excluding(&destination, Some(SOURCE_IDENTITY_FILE))?
510        != identity.patched_source_sha256
511    {
512        return Err(RustLibtestCompanionError::BundleMismatch(
513            "prepared libtest source tree digest differs".into(),
514        ));
515    }
516    Ok(identity)
517}
518
519/// Copy and patch an exact `library/test` source tree without modifying the
520/// toolchain sysroot. `destination` becomes visible only after the complete
521/// patched tree and its identity have been validated.
522pub fn prepare_exact_libtest_source(
523    source_root: &Path,
524    destination: &Path,
525    compiler: &RustCompilerIdentity,
526) -> Result<RustLibtestCompanionSourceIdentity, RustLibtestCompanionError> {
527    let source_root = regular_directory(source_root)?;
528    let parent = destination
529        .parent()
530        .ok_or_else(|| RustLibtestCompanionError::UnsafeSource(destination.to_path_buf()))?;
531    let parent = regular_directory(parent)?;
532    let name = destination
533        .file_name()
534        .and_then(|name| name.to_str())
535        .filter(|name| !name.is_empty() && *name != "." && *name != "..")
536        .ok_or_else(|| RustLibtestCompanionError::UnsafeSource(destination.to_path_buf()))?;
537    let destination = parent.join(name);
538    let lock_path = parent.join(format!(".{name}.lock"));
539    let _lock = acquire_kernel_lock(&lock_path)?;
540    remove_owned_partials(&parent, &format!(".{name}."), true)?;
541    match fs::symlink_metadata(&destination) {
542        Ok(metadata) if metadata.file_type().is_dir() => {
543            return validate_prepared_libtest_source(&source_root, &destination, compiler);
544        }
545        Ok(_) => return Err(RustLibtestCompanionError::UnsafeSource(destination)),
546        Err(error) if error.kind() == std::io::ErrorKind::NotFound => {}
547        Err(error) => return Err(io_error(&destination, error)),
548    }
549    let nonce = SystemTime::now()
550        .duration_since(UNIX_EPOCH)
551        .map_err(|error| io_error(&destination, error))?
552        .as_nanos();
553    let partial = parent.join(format!(".{name}.{}-{nonce}.partial", std::process::id()));
554    fs::create_dir(&partial).map_err(|error| io_error(&partial, error))?;
555    let mut cleanup = RemoveDirectoryOnDrop(Some(partial.clone()));
556
557    let original_source_sha256 = source_tree_digest(&source_root)?;
558    copy_regular_tree(&source_root, &partial)?;
559    if source_tree_digest(&source_root)? != original_source_sha256 {
560        return Err(RustLibtestCompanionError::BundleMismatch(
561            "exact toolchain libtest source changed while it was copied".into(),
562        ));
563    }
564    let lib = partial.join("src/lib.rs");
565    let console = partial.join("src/console.rs");
566    replace_once(&lib, LIB_ANCHOR, LIB_REPLACEMENT)?;
567    replace_once(&lib, IN_PROCESS_ANCHOR, IN_PROCESS_REPLACEMENT)?;
568    replace_once(&lib, SPAWNED_PROCESS_ANCHOR, SPAWNED_PROCESS_REPLACEMENT)?;
569    replace_once(&lib, BENCH_ANCHOR, BENCH_REPLACEMENT)?;
570    replace_once(&console, CONSOLE_ANCHOR, CONSOLE_REPLACEMENT)?;
571    replace_once(&console, LISTING_ANCHOR, LISTING_REPLACEMENT)?;
572    let event_runtime = rust_libtest_event_runtime_source();
573    let event_path = partial.join("src/supercov_events.rs");
574    let mut options = OpenOptions::new();
575    options.write(true).create_new(true);
576    #[cfg(unix)]
577    {
578        use std::os::unix::fs::OpenOptionsExt as _;
579        options.mode(0o600);
580    }
581    let mut event_file = options
582        .open(&event_path)
583        .map_err(|error| io_error(&event_path, error))?;
584    event_file
585        .write_all(event_runtime.as_bytes())
586        .and_then(|()| event_file.sync_all())
587        .map_err(|error| io_error(&event_path, error))?;
588
589    let identity = RustLibtestCompanionSourceIdentity {
590        event_protocol_version: RUST_LIBTEST_EVENT_PROTOCOL_VERSION,
591        rustc_commit_hash: compiler.rustc_commit_hash.clone(),
592        rustc_release: compiler.rustc_release.clone(),
593        host_triple: compiler.host_triple.clone(),
594        original_source_sha256,
595        event_runtime_sha256: format!("{:x}", Sha256::digest(event_runtime.as_bytes())),
596        patched_source_sha256: source_tree_digest(&partial)?,
597    };
598    let identity_path = partial.join(SOURCE_IDENTITY_FILE);
599    let mut identity_bytes =
600        serde_json::to_vec_pretty(&identity).map_err(|error| io_error(&identity_path, error))?;
601    identity_bytes.push(b'\n');
602    let mut identity_file = OpenOptions::new()
603        .write(true)
604        .create_new(true)
605        .open(&identity_path)
606        .map_err(|error| io_error(&identity_path, error))?;
607    identity_file
608        .write_all(&identity_bytes)
609        .and_then(|()| identity_file.sync_all())
610        .map_err(|error| io_error(&identity_path, error))?;
611    fs::rename(&partial, &destination).map_err(|error| io_error(&destination, error))?;
612    sync_directory(&parent)?;
613    cleanup.0 = None;
614    Ok(identity)
615}
616
617fn one_metadata(
618    directory: &Path,
619    crate_name: &'static str,
620) -> Result<PathBuf, RustLibtestCompanionError> {
621    let prefix = format!("lib{crate_name}-");
622    let mut matches = fs::read_dir(directory)
623        .map_err(|error| io_error(directory, error))?
624        .collect::<Result<Vec<_>, _>>()
625        .map_err(|error| io_error(directory, error))?
626        .into_iter()
627        .filter(|entry| {
628            let metadata = entry
629                .file_name()
630                .to_str()
631                .is_some_and(|name| name.starts_with(&prefix) && name.ends_with(".rmeta"));
632            let archive = entry.path().with_extension("rlib");
633            metadata
634                && entry.file_type().is_ok_and(|file_type| file_type.is_file())
635                && fs::symlink_metadata(archive)
636                    .is_ok_and(|metadata| metadata.file_type().is_file())
637        })
638        .map(|entry| entry.path())
639        .collect::<Vec<_>>();
640    matches.sort();
641    matches.dedup();
642    match matches.as_slice() {
643        [path] => Ok(path.clone()),
644        _ => Err(RustLibtestCompanionError::DependencyMetadata {
645            directory: directory.to_path_buf(),
646            crate_name,
647            count: matches.len(),
648        }),
649    }
650}
651
652pub fn rust_libtest_companion_build_plan(
653    patched_source: &Path,
654    target_libdir: &Path,
655    output: &Path,
656) -> Result<RustLibtestCompanionBuildPlan, RustLibtestCompanionError> {
657    let patched_source = regular_directory(patched_source)?;
658    let target_libdir = regular_directory(target_libdir)?;
659    let source = patched_source.join("src/lib.rs");
660    if !fs::symlink_metadata(&source).is_ok_and(|metadata| metadata.file_type().is_file()) {
661        return Err(RustLibtestCompanionError::UnsafeSource(source));
662    }
663    let getopts = one_metadata(&target_libdir, "getopts")?;
664    let libc = one_metadata(&target_libdir, "libc")?;
665    let identity_path = patched_source.join("supercov-libtest-source.json");
666    let identity: RustLibtestCompanionSourceIdentity =
667        serde_json::from_slice(&read_regular_file(&identity_path)?).map_err(|error| {
668            RustLibtestCompanionError::InvalidBundle {
669                path: identity_path,
670                reason: error.to_string(),
671            }
672        })?;
673    if !canonical_lower_sha256(&identity.patched_source_sha256) {
674        return Err(RustLibtestCompanionError::BundleMismatch(
675            "patched libtest source identity is noncanonical".into(),
676        ));
677    }
678    Ok(RustLibtestCompanionBuildPlan {
679        source: source.clone(),
680        output: output.to_path_buf(),
681        arguments: vec![
682            source.into_os_string(),
683            "--crate-name".into(),
684            "test".into(),
685            "--crate-type".into(),
686            "rlib".into(),
687            "--edition".into(),
688            "2024".into(),
689            "-Zcrate-attr=feature(rustc_private)".into(),
690            format!(
691                "--remap-path-prefix={}=/supercov/libtest-source",
692                patched_source.display()
693            )
694            .into(),
695            format!(
696                "-Cmetadata=supercov_{}",
697                &identity.patched_source_sha256[..16]
698            )
699            .into(),
700            "-L".into(),
701            format!("dependency={}", target_libdir.display()).into(),
702            "--extern".into(),
703            format!("getopts={}", getopts.display()).into(),
704            "--extern".into(),
705            format!("libc={}", libc.display()).into(),
706            "-o".into(),
707            output.as_os_str().to_owned(),
708        ],
709        rustc_bootstrap: "1".into(),
710    })
711}
712
713fn canonical_archive_kind(
714    kind: ReadableArchiveKind,
715    host_triple: &str,
716) -> Result<WritableArchiveKind, RustLibtestCompanionError> {
717    match kind {
718        ReadableArchiveKind::Gnu => Ok(WritableArchiveKind::Gnu),
719        ReadableArchiveKind::Gnu64 => Ok(WritableArchiveKind::Gnu64),
720        ReadableArchiveKind::Bsd if host_triple.contains("-apple-") => {
721            Ok(WritableArchiveKind::Darwin)
722        }
723        ReadableArchiveKind::Bsd => Ok(WritableArchiveKind::Bsd),
724        ReadableArchiveKind::Bsd64 => Ok(WritableArchiveKind::Darwin64),
725        ReadableArchiveKind::Coff => Ok(WritableArchiveKind::Coff),
726        ReadableArchiveKind::AixBig => Ok(WritableArchiveKind::AixBig),
727        _ => Err(RustLibtestCompanionError::InvalidArchive(format!(
728            "unsupported archive kind {kind:?} for {host_triple}"
729        ))),
730    }
731}
732
733/// Rebuild rustc's rlib container with content-derived member names.
734///
735/// rustc's object and metadata payloads are reproducible for an exact libtest
736/// source/compiler identity, but its temporary codegen archive-member suffix
737/// is deliberately per-session. The suffix has no linking semantics, yet it
738/// changes the rlib digest and its symbol table. Supercov owns release artifact
739/// identity, so it replaces only those container-local names and lets LLVM's
740/// archive writer reconstruct the target-format symbol table from the exact,
741/// unmodified payloads.
742pub fn canonicalize_rust_libtest_rlib(
743    bytes: &[u8],
744    host_triple: &str,
745) -> Result<Vec<u8>, RustLibtestCompanionError> {
746    let archive = ArchiveFile::parse(bytes).map_err(|error| {
747        RustLibtestCompanionError::InvalidArchive(format!("cannot parse archive: {error}"))
748    })?;
749    if archive.is_thin() {
750        return Err(RustLibtestCompanionError::InvalidArchive(
751            "thin archives are not self-contained".into(),
752        ));
753    }
754    let archive_kind = canonical_archive_kind(archive.kind(), host_triple)?;
755
756    let mut metadata = None;
757    let mut objects = Vec::new();
758    for member in archive.members() {
759        let member = member.map_err(|error| {
760            RustLibtestCompanionError::InvalidArchive(format!(
761                "cannot parse archive member: {error}"
762            ))
763        })?;
764        let name = std::str::from_utf8(member.name()).map_err(|_| {
765            RustLibtestCompanionError::InvalidArchive(
766                "archive contains a non-UTF-8 member name".into(),
767            )
768        })?;
769        let data = member.data(bytes).map_err(|error| {
770            RustLibtestCompanionError::InvalidArchive(format!(
771                "cannot read archive member {name}: {error}"
772            ))
773        })?;
774        if name == "lib.rmeta" {
775            if metadata.replace(data.to_vec()).is_some() {
776                return Err(RustLibtestCompanionError::InvalidArchive(
777                    "archive contains more than one lib.rmeta member".into(),
778                ));
779            }
780        } else if name.ends_with(".rcgu.o") {
781            objects.push((format!("{:x}", Sha256::digest(data)), data.to_vec()));
782        } else {
783            return Err(RustLibtestCompanionError::InvalidArchive(format!(
784                "unexpected archive member {name}"
785            )));
786        }
787    }
788    let metadata = metadata.ok_or_else(|| {
789        RustLibtestCompanionError::InvalidArchive("archive has no lib.rmeta member".into())
790    })?;
791    if objects.is_empty() {
792        return Err(RustLibtestCompanionError::InvalidArchive(
793            "archive has no codegen object members".into(),
794        ));
795    }
796    objects.sort_by(|left, right| left.0.cmp(&right.0).then_with(|| left.1.cmp(&right.1)));
797
798    let mut owned_members = Vec::with_capacity(objects.len() + 1);
799    owned_members.push(("lib.rmeta".to_owned(), metadata));
800    for (ordinal, (digest, data)) in objects.into_iter().enumerate() {
801        owned_members.push((
802            format!("supercov-libtest-{ordinal:06}-{digest}.rcgu.o"),
803            data,
804        ));
805    }
806    let members = owned_members
807        .iter()
808        .map(|(name, data)| NewArchiveMember::new(data, &DEFAULT_OBJECT_READER, name.clone()))
809        .collect::<Vec<_>>();
810    let mut output = Cursor::new(Vec::new());
811    write_archive_to_stream(
812        &mut output,
813        &members,
814        archive_kind,
815        false,
816        Some(host_triple.contains("arm64ec")),
817    )
818    .map_err(|error| {
819        RustLibtestCompanionError::InvalidArchive(format!(
820            "cannot write canonical archive: {error}"
821        ))
822    })?;
823    let output = output.into_inner();
824    let reparsed = ArchiveFile::parse(output.as_slice()).map_err(|error| {
825        RustLibtestCompanionError::InvalidArchive(format!(
826            "canonical archive did not parse: {error}"
827        ))
828    })?;
829    if reparsed.is_thin() || reparsed.members().count() != owned_members.len() {
830        return Err(RustLibtestCompanionError::InvalidArchive(
831            "canonical archive failed structural verification".into(),
832        ));
833    }
834    Ok(output)
835}
836
837pub fn rust_libtest_companion_bundle_path(compiler_companion: &Path) -> PathBuf {
838    let mut value = compiler_companion.as_os_str().to_owned();
839    value.push(".libtest.json");
840    PathBuf::from(value)
841}
842
843fn canonical_lower_sha256(value: &str) -> bool {
844    value.len() == 64
845        && value
846            .bytes()
847            .all(|byte| byte.is_ascii_digit() || (b'a'..=b'f').contains(&byte))
848}
849
850fn sha256_file(path: &Path) -> Result<String, RustLibtestCompanionError> {
851    Ok(format!("{:x}", Sha256::digest(read_regular_file(path)?)))
852}
853
854fn read_regular_file(path: &Path) -> Result<Vec<u8>, RustLibtestCompanionError> {
855    let metadata = fs::symlink_metadata(path).map_err(|error| io_error(path, error))?;
856    if !metadata.file_type().is_file() {
857        return Err(RustLibtestCompanionError::UnsafeSource(path.to_path_buf()));
858    }
859    let mut options = OpenOptions::new();
860    options.read(true);
861    #[cfg(any(target_os = "linux", target_os = "macos"))]
862    {
863        use std::os::unix::fs::OpenOptionsExt as _;
864        #[cfg(target_os = "linux")]
865        const O_NOFOLLOW: i32 = 0x2_0000;
866        #[cfg(target_os = "macos")]
867        const O_NOFOLLOW: i32 = 0x100;
868        options.custom_flags(O_NOFOLLOW);
869    }
870    let mut file = options.open(path).map_err(|error| io_error(path, error))?;
871    if !file
872        .metadata()
873        .map_err(|error| io_error(path, error))?
874        .file_type()
875        .is_file()
876    {
877        return Err(RustLibtestCompanionError::UnsafeSource(path.to_path_buf()));
878    }
879    let mut bytes = Vec::new();
880    std::io::Read::read_to_end(&mut file, &mut bytes).map_err(|error| io_error(path, error))?;
881    Ok(bytes)
882}
883
884fn safe_artifact_basename(value: &str) -> bool {
885    let path = Path::new(value);
886    !value.is_empty()
887        && value != "."
888        && value != ".."
889        && path.components().count() == 1
890        && path
891            .components()
892            .all(|component| matches!(component, Component::Normal(_)))
893}
894
895/// Authenticate the release-built libtest rlib against the already verified
896/// compiler companion. The sidecar binds both content hashes and exact rustc
897/// identity; npm/crate package integrity then authenticates the sidecar as part
898/// of the same platform artifact.
899pub fn select_rust_libtest_companion(
900    selection: &SelectedRustCompilerCompanion,
901) -> Result<SelectedRustLibtestCompanion, RustLibtestCompanionError> {
902    let bundle_path = rust_libtest_companion_bundle_path(&selection.companion_path);
903    let bundle_metadata =
904        fs::symlink_metadata(&bundle_path).map_err(|error| io_error(&bundle_path, error))?;
905    if !bundle_metadata.file_type().is_file() {
906        return Err(RustLibtestCompanionError::UnsafeSource(bundle_path));
907    }
908    let bundle: RustLibtestCompanionBundle =
909        serde_json::from_slice(&read_regular_file(&bundle_path)?).map_err(|error| {
910            RustLibtestCompanionError::InvalidBundle {
911                path: bundle_path.clone(),
912                reason: error.to_string(),
913            }
914        })?;
915    if bundle.schema_version != RUST_LIBTEST_COMPANION_BUNDLE_SCHEMA_VERSION
916        || bundle.event_protocol_version != RUST_LIBTEST_EVENT_PROTOCOL_VERSION
917        || !safe_artifact_basename(&bundle.artifact_file)
918        || [
919            bundle.compiler_companion_build_id.as_str(),
920            bundle.original_source_sha256.as_str(),
921            bundle.event_runtime_sha256.as_str(),
922            bundle.patched_source_sha256.as_str(),
923            bundle.artifact_sha256.as_str(),
924        ]
925        .iter()
926        .any(|value| !canonical_lower_sha256(value))
927    {
928        return Err(RustLibtestCompanionError::InvalidBundle {
929            path: bundle_path,
930            reason: "unsupported schema/protocol, unsafe artifact name or noncanonical digest"
931                .into(),
932        });
933    }
934    if bundle.compiler_companion_build_id != selection.handshake.companion_build_id {
935        return Err(RustLibtestCompanionError::BundleMismatch(
936            "compiler companion build ID differs".into(),
937        ));
938    }
939    if bundle.rustc_commit_hash != selection.compiler.rustc_commit_hash
940        || bundle.host_triple != selection.compiler.host_triple
941    {
942        return Err(RustLibtestCompanionError::BundleMismatch(
943            "selected rustc identity differs".into(),
944        ));
945    }
946    let directory = selection.companion_path.parent().ok_or_else(|| {
947        RustLibtestCompanionError::BundleMismatch(
948            "compiler companion has no artifact directory".into(),
949        )
950    })?;
951    let artifact_path = directory.join(&bundle.artifact_file);
952    if sha256_file(&artifact_path)? != bundle.artifact_sha256 {
953        return Err(RustLibtestCompanionError::BundleMismatch(
954            "libtest artifact digest differs".into(),
955        ));
956    }
957    Ok(SelectedRustLibtestCompanion {
958        bundle_path,
959        artifact_path,
960        bundle,
961    })
962}
963
964pub fn write_rust_libtest_companion_bundle(
965    compiler_companion: &Path,
966    source_identity: &RustLibtestCompanionSourceIdentity,
967    artifact: &Path,
968) -> Result<PathBuf, RustLibtestCompanionError> {
969    let compiler_companion = fs::canonicalize(compiler_companion)
970        .map_err(|error| io_error(compiler_companion, error))?;
971    let artifact = fs::canonicalize(artifact).map_err(|error| io_error(artifact, error))?;
972    let directory = compiler_companion.parent().ok_or_else(|| {
973        RustLibtestCompanionError::BundleMismatch(
974            "compiler companion has no artifact directory".into(),
975        )
976    })?;
977    if artifact.parent() != Some(directory) {
978        return Err(RustLibtestCompanionError::BundleMismatch(
979            "libtest artifact is not adjacent to the compiler companion".into(),
980        ));
981    }
982    let artifact_file = artifact
983        .file_name()
984        .and_then(|value| value.to_str())
985        .filter(|value| safe_artifact_basename(value))
986        .ok_or_else(|| {
987            RustLibtestCompanionError::BundleMismatch(
988                "libtest artifact has an unsafe filename".into(),
989            )
990        })?
991        .to_owned();
992    let bundle = RustLibtestCompanionBundle {
993        schema_version: RUST_LIBTEST_COMPANION_BUNDLE_SCHEMA_VERSION,
994        event_protocol_version: source_identity.event_protocol_version,
995        compiler_companion_build_id: sha256_file(&compiler_companion)?,
996        rustc_commit_hash: source_identity.rustc_commit_hash.clone(),
997        host_triple: source_identity.host_triple.clone(),
998        original_source_sha256: source_identity.original_source_sha256.clone(),
999        event_runtime_sha256: source_identity.event_runtime_sha256.clone(),
1000        patched_source_sha256: source_identity.patched_source_sha256.clone(),
1001        artifact_file,
1002        artifact_sha256: sha256_file(&artifact)?,
1003    };
1004    let path = rust_libtest_companion_bundle_path(&compiler_companion);
1005    let mut bytes = serde_json::to_vec_pretty(&bundle).map_err(|error| io_error(&path, error))?;
1006    bytes.push(b'\n');
1007    let partial = path.with_file_name(format!(
1008        ".{}.{}-{}.partial",
1009        path.file_name()
1010            .and_then(|value| value.to_str())
1011            .unwrap_or("libtest"),
1012        std::process::id(),
1013        SystemTime::now()
1014            .duration_since(UNIX_EPOCH)
1015            .map_err(|error| io_error(&path, error))?
1016            .as_nanos()
1017    ));
1018    let mut cleanup = RemoveFileOnDrop(Some(partial.clone()));
1019    let mut options = OpenOptions::new();
1020    options.write(true).create_new(true);
1021    #[cfg(unix)]
1022    {
1023        use std::os::unix::fs::OpenOptionsExt as _;
1024        options.mode(0o600);
1025    }
1026    let mut file = options
1027        .open(&partial)
1028        .map_err(|error| io_error(&partial, error))?;
1029    file.write_all(&bytes)
1030        .and_then(|()| file.sync_all())
1031        .map_err(|error| io_error(&partial, error))?;
1032    drop(file);
1033    if let Ok(metadata) = fs::symlink_metadata(&path)
1034        && !metadata.file_type().is_file()
1035    {
1036        return Err(RustLibtestCompanionError::UnsafeSource(path));
1037    }
1038    fs::rename(&partial, &path).map_err(|error| io_error(&path, error))?;
1039    sync_directory(directory)?;
1040    cleanup.0 = None;
1041    Ok(path)
1042}
1043
1044fn libtest_builder_lock_path(compiler_companion: &Path) -> PathBuf {
1045    let mut value = compiler_companion.as_os_str().to_owned();
1046    value.push(".libtest.lock");
1047    PathBuf::from(value)
1048}
1049
1050fn bundle_matches_source_identity(
1051    selected: &SelectedRustLibtestCompanion,
1052    identity: &RustLibtestCompanionSourceIdentity,
1053) -> Result<(), RustLibtestCompanionError> {
1054    if selected.bundle.event_protocol_version != identity.event_protocol_version
1055        || selected.bundle.rustc_commit_hash != identity.rustc_commit_hash
1056        || selected.bundle.host_triple != identity.host_triple
1057        || selected.bundle.original_source_sha256 != identity.original_source_sha256
1058        || selected.bundle.event_runtime_sha256 != identity.event_runtime_sha256
1059        || selected.bundle.patched_source_sha256 != identity.patched_source_sha256
1060    {
1061        return Err(RustLibtestCompanionError::BundleMismatch(
1062            "published bundle differs from the authenticated prepared source".into(),
1063        ));
1064    }
1065    Ok(())
1066}
1067
1068/// Build and atomically publish the exact-toolchain libtest companion.
1069///
1070/// All mutable builder state is protected by kernel locks. A killed process
1071/// releases its lock immediately; the next builder removes only narrowly
1072/// named, regular builder-owned partials, authenticates any completed state,
1073/// and resumes. Final source trees, artifacts and bundles become visible only
1074/// after their bytes have been synced and verified.
1075pub fn build_exact_rust_libtest_companion(
1076    source_root: &Path,
1077    work_root: &Path,
1078    rustc: &Path,
1079    compiler_companion: &Path,
1080) -> Result<SelectedRustLibtestCompanion, RustLibtestCompanionError> {
1081    let rustc = fs::canonicalize(rustc).map_err(|error| io_error(rustc, error))?;
1082    let compiler = probe_rustc_identity(&rustc).map_err(|error| {
1083        RustLibtestCompanionError::BundleMismatch(format!(
1084            "could not authenticate exact rustc: {error}"
1085        ))
1086    })?;
1087    let work_root = regular_directory(work_root)?;
1088    let compiler_companion = fs::canonicalize(compiler_companion)
1089        .map_err(|error| io_error(compiler_companion, error))?;
1090    if !fs::symlink_metadata(&compiler_companion)
1091        .is_ok_and(|metadata| metadata.file_type().is_file())
1092    {
1093        return Err(RustLibtestCompanionError::UnsafeSource(compiler_companion));
1094    }
1095    let artifact_directory = compiler_companion.parent().ok_or_else(|| {
1096        RustLibtestCompanionError::BundleMismatch(
1097            "compiler companion has no artifact directory".into(),
1098        )
1099    })?;
1100    let lock_path = libtest_builder_lock_path(&compiler_companion);
1101    let builder_lock = acquire_kernel_lock(&lock_path)?;
1102    let selection =
1103        select_rust_compiler_companion(&rustc, std::slice::from_ref(&compiler_companion), false)
1104            .map_err(|error| {
1105                RustLibtestCompanionError::BundleMismatch(format!(
1106                    "could not authenticate compiler companion: {error}"
1107                ))
1108            })?;
1109
1110    let bundle_path = rust_libtest_companion_bundle_path(&compiler_companion);
1111    let bundle_name = bundle_path
1112        .file_name()
1113        .and_then(|name| name.to_str())
1114        .ok_or_else(|| RustLibtestCompanionError::UnsafeSource(bundle_path.clone()))?;
1115    remove_owned_partials(artifact_directory, &format!(".{bundle_name}."), false)?;
1116
1117    let patched_source = work_root.join("patched-libtest");
1118    let source_identity = prepare_exact_libtest_source(source_root, &patched_source, &compiler)?;
1119    match fs::symlink_metadata(&bundle_path) {
1120        Ok(metadata) if metadata.file_type().is_file() => {
1121            let selected = select_rust_libtest_companion(&selection)?;
1122            bundle_matches_source_identity(&selected, &source_identity)?;
1123            return Ok(selected);
1124        }
1125        Ok(_) => return Err(RustLibtestCompanionError::UnsafeSource(bundle_path)),
1126        Err(error) if error.kind() == std::io::ErrorKind::NotFound => {}
1127        Err(error) => return Err(io_error(&bundle_path, error)),
1128    }
1129
1130    let target_libdir_output = Command::new(&rustc)
1131        .args(["--print", "target-libdir"])
1132        .env_remove("RUSTC_WRAPPER")
1133        .env_remove("RUSTC_WORKSPACE_WRAPPER")
1134        .output()
1135        .map_err(|error| io_error(&rustc, error))?;
1136    if !target_libdir_output.status.success() || !target_libdir_output.stderr.is_empty() {
1137        return Err(RustLibtestCompanionError::BuildFailed {
1138            program: rustc,
1139            status: target_libdir_output.status.code(),
1140            stdout: String::from_utf8_lossy(&target_libdir_output.stdout).into_owned(),
1141            stderr: String::from_utf8_lossy(&target_libdir_output.stderr).into_owned(),
1142        });
1143    }
1144    let target_libdir = PathBuf::from(
1145        std::str::from_utf8(&target_libdir_output.stdout)
1146            .map_err(|_| {
1147                RustLibtestCompanionError::BundleMismatch("rustc target libdir is not UTF-8".into())
1148            })?
1149            .trim(),
1150    );
1151    let artifact_name = format!(
1152        "libtest-supercov-v{}-{}-{}.rlib",
1153        RUST_LIBTEST_COMPANION_BUNDLE_SCHEMA_VERSION,
1154        &source_identity.rustc_commit_hash[..12],
1155        &source_identity.patched_source_sha256[..12]
1156    );
1157    let artifact = artifact_directory.join(&artifact_name);
1158    remove_owned_partials(artifact_directory, &format!(".{artifact_name}."), false)?;
1159
1160    // The stable, content-derived output basename is part of rustc's codegen
1161    // identity. It is built in the dedicated work root and copied into a
1162    // unique adjacent publication partial only after canonicalization.
1163    let build_output = work_root.join(&artifact_name);
1164    remove_owned_path(&build_output, false)?;
1165    let mut build_cleanup = RemoveFileOnDrop(Some(build_output.clone()));
1166    let plan = rust_libtest_companion_build_plan(&patched_source, &target_libdir, &build_output)?;
1167    let mut command = Command::new(&rustc);
1168    command
1169        .args(&plan.arguments)
1170        .env("RUSTC_BOOTSTRAP", &plan.rustc_bootstrap)
1171        .env_remove("RUSTC_WRAPPER")
1172        .env_remove("RUSTC_WORKSPACE_WRAPPER")
1173        .env_remove(crate::rust_compiler_orchestration::RUST_COMPILER_WRAPPER_CONFIG_ENV)
1174        .env_remove(crate::rust_compiler_orchestration::RUST_COMPILER_INNER_MODE_ENV);
1175    inherit_lock_through_exec(&mut command, &builder_lock);
1176    let output = command.output().map_err(|error| io_error(&rustc, error))?;
1177    if !output.status.success() {
1178        return Err(RustLibtestCompanionError::BuildFailed {
1179            program: rustc,
1180            status: output.status.code(),
1181            stdout: String::from_utf8_lossy(&output.stdout).into_owned(),
1182            stderr: String::from_utf8_lossy(&output.stderr).into_owned(),
1183        });
1184    }
1185    let compiled = read_regular_file(&build_output)?;
1186    let canonical = canonicalize_rust_libtest_rlib(&compiled, &compiler.host_triple)?;
1187    let partial = artifact_directory.join(format!(
1188        ".{artifact_name}.{}-{}.partial",
1189        std::process::id(),
1190        SystemTime::now()
1191            .duration_since(UNIX_EPOCH)
1192            .map_err(|error| io_error(&artifact, error))?
1193            .as_nanos()
1194    ));
1195    let mut partial_cleanup = RemoveFileOnDrop(Some(partial.clone()));
1196    let mut options = OpenOptions::new();
1197    options.write(true).create_new(true);
1198    #[cfg(unix)]
1199    {
1200        use std::os::unix::fs::OpenOptionsExt as _;
1201        options.mode(0o600);
1202    }
1203    let mut partial_file = options
1204        .open(&partial)
1205        .map_err(|error| io_error(&partial, error))?;
1206    partial_file
1207        .write_all(&canonical)
1208        .and_then(|()| partial_file.sync_all())
1209        .map_err(|error| io_error(&partial, error))?;
1210    drop(partial_file);
1211
1212    match fs::symlink_metadata(&artifact) {
1213        Ok(metadata) if metadata.file_type().is_file() => {
1214            if read_regular_file(&artifact)? != canonical {
1215                return Err(RustLibtestCompanionError::BundleMismatch(
1216                    "the same exact libtest identity produced different artifact bytes".into(),
1217                ));
1218            }
1219            fs::remove_file(&partial).map_err(|error| io_error(&partial, error))?;
1220            partial_cleanup.0 = None;
1221        }
1222        Ok(_) => return Err(RustLibtestCompanionError::UnsafeSource(artifact)),
1223        Err(error) if error.kind() == std::io::ErrorKind::NotFound => {
1224            fs::rename(&partial, &artifact).map_err(|error| io_error(&artifact, error))?;
1225            sync_directory(artifact_directory)?;
1226            partial_cleanup.0 = None;
1227        }
1228        Err(error) => return Err(io_error(&artifact, error)),
1229    }
1230    fs::remove_file(&build_output).map_err(|error| io_error(&build_output, error))?;
1231    build_cleanup.0 = None;
1232
1233    let published_bundle =
1234        write_rust_libtest_companion_bundle(&compiler_companion, &source_identity, &artifact)?;
1235    let selected = select_rust_libtest_companion(&selection)?;
1236    bundle_matches_source_identity(&selected, &source_identity)?;
1237    if selected.bundle_path != published_bundle || selected.artifact_path != artifact {
1238        return Err(RustLibtestCompanionError::BundleMismatch(
1239            "published libtest companion did not reselect exactly".into(),
1240        ));
1241    }
1242    Ok(selected)
1243}
1244
1245#[cfg(test)]
1246mod tests {
1247    use std::{
1248        sync::atomic::{AtomicU64, Ordering},
1249        time::{SystemTime, UNIX_EPOCH},
1250    };
1251
1252    use super::*;
1253    use supercov_contracts::{
1254        EVIDENCE_ARCHIVE_SCHEMA_VERSION, RUST_COMPILER_COMPANION_PROTOCOL_VERSION,
1255        RustCompilerCompanionCapabilities, RustCompilerCompanionHandshake,
1256    };
1257
1258    fn compiler() -> RustCompilerIdentity {
1259        RustCompilerIdentity {
1260            rustc_commit_hash: "a".repeat(40),
1261            rustc_release: "1.95.0".into(),
1262            host_triple: "aarch64-apple-darwin".into(),
1263            rustc_driver_sha256: "b".repeat(64),
1264        }
1265    }
1266
1267    fn selection(
1268        compiler_companion: PathBuf,
1269        compiler: RustCompilerIdentity,
1270    ) -> SelectedRustCompilerCompanion {
1271        let build_id = sha256_file(&compiler_companion).unwrap();
1272        SelectedRustCompilerCompanion {
1273            rustc_path: compiler_companion.with_file_name("rustc"),
1274            compiler_library_directory: compiler_companion.parent().unwrap().to_path_buf(),
1275            companion_path: compiler_companion,
1276            compiler: compiler.clone(),
1277            handshake: RustCompilerCompanionHandshake {
1278                protocol_version: RUST_COMPILER_COMPANION_PROTOCOL_VERSION,
1279                frontend_id: "rust".into(),
1280                coverage_model_variant: "rust-source-v1".into(),
1281                evidence_schema_version: EVIDENCE_ARCHIVE_SCHEMA_VERSION,
1282                companion_build_id: build_id,
1283                compiler,
1284                capabilities: RustCompilerCompanionCapabilities {
1285                    expanded_hir_provenance: true,
1286                    runtime_mir_probe_insertion: true,
1287                    generated_source_provenance: true,
1288                    ctfe_path_tracing: true,
1289                    rustdoc_doctest_tracing: true,
1290                    exact_test_harness_attribution: true,
1291                },
1292            },
1293        }
1294    }
1295
1296    fn fixture() -> PathBuf {
1297        static NEXT_FIXTURE: AtomicU64 = AtomicU64::new(0);
1298        let nonce = SystemTime::now()
1299            .duration_since(UNIX_EPOCH)
1300            .unwrap()
1301            .as_nanos();
1302        let root = std::env::temp_dir().join(format!(
1303            "supercov-libtest-source-{}-{nonce}-{}",
1304            std::process::id(),
1305            NEXT_FIXTURE.fetch_add(1, Ordering::Relaxed)
1306        ));
1307        fs::create_dir_all(root.join("source/src")).unwrap();
1308        fs::write(root.join("source/Cargo.toml"), b"[package]\nname='test'\n").unwrap();
1309        fs::write(root.join("source/src/lib.rs"), fixture_lib_source()).unwrap();
1310        fs::write(
1311            root.join("source/src/console.rs"),
1312            format!("use std::io;\n{CONSOLE_ANCHOR}\n    }}\n}}\n{LISTING_ANCHOR}\n}}\n"),
1313        )
1314        .unwrap();
1315        root
1316    }
1317
1318    fn fixture_lib_source() -> String {
1319        format!(
1320            "#![feature(test)]\n{LIB_ANCHOR}\n{IN_PROCESS_ANCHOR} synthetic;\n{SPAWNED_PROCESS_ANCHOR};\n{BENCH_ANCHOR}(synthetic);\n"
1321        )
1322    }
1323
1324    #[test]
1325    fn patches_atomically_with_relocation_stable_identity() {
1326        let first = fixture();
1327        let second = fixture();
1328        let first_identity = prepare_exact_libtest_source(
1329            &first.join("source"),
1330            &first.join("patched"),
1331            &compiler(),
1332        )
1333        .unwrap();
1334        let second_identity = prepare_exact_libtest_source(
1335            &second.join("source"),
1336            &second.join("patched"),
1337            &compiler(),
1338        )
1339        .unwrap();
1340        assert_eq!(first_identity, second_identity);
1341        assert!(
1342            fs::read_to_string(first.join("patched/src/lib.rs"))
1343                .unwrap()
1344                .contains("mod supercov_events;")
1345        );
1346        assert_eq!(
1347            fs::read_to_string(first.join("patched/src/lib.rs"))
1348                .unwrap()
1349                .matches("supercov_events::enter_test")
1350                .count(),
1351            3
1352        );
1353        assert!(
1354            fs::read_to_string(first.join("patched/src/console.rs"))
1355                .unwrap()
1356                .contains("crate::supercov_events::emit(event)?;")
1357        );
1358        assert!(
1359            fs::read_to_string(first.join("patched/src/console.rs"))
1360                .unwrap()
1361                .contains("crate::supercov_events::emit_listing(")
1362        );
1363        assert_eq!(
1364            fs::read_to_string(first.join("source/src/lib.rs")).unwrap(),
1365            fixture_lib_source()
1366        );
1367        assert!(first.join("patched/supercov-libtest-source.json").is_file());
1368        assert_eq!(
1369            prepare_exact_libtest_source(
1370                &first.join("source"),
1371                &first.join("patched"),
1372                &compiler()
1373            )
1374            .unwrap(),
1375            first_identity
1376        );
1377        fs::remove_dir_all(first).unwrap();
1378        fs::remove_dir_all(second).unwrap();
1379    }
1380
1381    #[test]
1382    fn prepared_source_reuse_fails_closed_on_tree_or_identity_tampering() {
1383        let root = fixture();
1384        let source = root.join("source");
1385        let patched = root.join("patched");
1386        prepare_exact_libtest_source(&source, &patched, &compiler()).unwrap();
1387        fs::write(patched.join("src/lib.rs"), b"tampered\n").unwrap();
1388        assert!(matches!(
1389            prepare_exact_libtest_source(&source, &patched, &compiler()),
1390            Err(RustLibtestCompanionError::BundleMismatch(reason))
1391                if reason.contains("tree digest")
1392        ));
1393
1394        fs::remove_dir_all(&patched).unwrap();
1395        prepare_exact_libtest_source(&source, &patched, &compiler()).unwrap();
1396        let identity_path = patched.join(SOURCE_IDENTITY_FILE);
1397        let mut identity: serde_json::Value =
1398            serde_json::from_slice(&fs::read(&identity_path).unwrap()).unwrap();
1399        identity["unknown"] = serde_json::json!(true);
1400        fs::write(&identity_path, serde_json::to_vec(&identity).unwrap()).unwrap();
1401        assert!(matches!(
1402            prepare_exact_libtest_source(&source, &patched, &compiler()),
1403            Err(RustLibtestCompanionError::InvalidBundle { .. })
1404        ));
1405        fs::remove_dir_all(root).unwrap();
1406    }
1407
1408    #[test]
1409    fn concurrent_source_preparation_converges_without_partial_debris() {
1410        let root = fixture();
1411        let source = root.join("source");
1412        let patched = root.join("patched");
1413        let workers = (0..8)
1414            .map(|_| {
1415                let source = source.clone();
1416                let patched = patched.clone();
1417                std::thread::spawn(move || {
1418                    prepare_exact_libtest_source(&source, &patched, &compiler()).unwrap()
1419                })
1420            })
1421            .collect::<Vec<_>>();
1422        let identities = workers
1423            .into_iter()
1424            .map(|worker| worker.join().unwrap())
1425            .collect::<Vec<_>>();
1426        assert!(identities.windows(2).all(|pair| pair[0] == pair[1]));
1427        assert!(fs::read_dir(&root).unwrap().all(|entry| {
1428            !entry
1429                .unwrap()
1430                .file_name()
1431                .to_string_lossy()
1432                .ends_with(".partial")
1433        }));
1434        fs::remove_dir_all(root).unwrap();
1435    }
1436
1437    #[cfg(unix)]
1438    #[test]
1439    fn libtest_builder_lock_holder_helper() {
1440        let Some(lock) = std::env::var_os("SUPERCOV_TEST_LIBTEST_LOCK") else {
1441            return;
1442        };
1443        let partial =
1444            PathBuf::from(std::env::var_os("SUPERCOV_TEST_LIBTEST_PARTIAL").expect("partial path"));
1445        let ready =
1446            PathBuf::from(std::env::var_os("SUPERCOV_TEST_LIBTEST_READY").expect("ready path"));
1447        let _lock = acquire_kernel_lock(Path::new(&lock)).unwrap();
1448        fs::write(&partial, b"incomplete\n").unwrap();
1449        fs::write(&ready, b"locked\n").unwrap();
1450        loop {
1451            std::thread::sleep(Duration::from_secs(1));
1452        }
1453    }
1454
1455    #[cfg(unix)]
1456    #[test]
1457    fn killed_builder_releases_lock_and_owned_partial_is_recoverable() {
1458        use std::process::Stdio;
1459
1460        let root = fixture();
1461        let lock = root.join("companion.libtest.lock");
1462        let partial = root.join(".artifact.123.partial");
1463        let ready = root.join("builder-ready");
1464        let mut child = Command::new(std::env::current_exe().unwrap())
1465            .args([
1466                "--exact",
1467                "rust_libtest_companion::tests::libtest_builder_lock_holder_helper",
1468                "--nocapture",
1469            ])
1470            .env("SUPERCOV_TEST_LIBTEST_LOCK", &lock)
1471            .env("SUPERCOV_TEST_LIBTEST_PARTIAL", &partial)
1472            .env("SUPERCOV_TEST_LIBTEST_READY", &ready)
1473            .stdin(Stdio::null())
1474            .stdout(Stdio::null())
1475            .stderr(Stdio::null())
1476            .spawn()
1477            .unwrap();
1478        let started = Instant::now();
1479        while !ready.is_file() {
1480            assert!(
1481                started.elapsed() < Duration::from_secs(10),
1482                "libtest builder helper did not acquire its kernel lock"
1483            );
1484            std::thread::sleep(Duration::from_millis(10));
1485        }
1486        assert_eq!(
1487            unsafe { libc::kill(child.id().try_into().unwrap(), libc::SIGKILL) },
1488            0
1489        );
1490        assert_eq!(child.wait().unwrap().code(), None);
1491        let recovery_started = Instant::now();
1492        let _lock = acquire_kernel_lock(&lock).unwrap();
1493        assert!(recovery_started.elapsed() < Duration::from_secs(5));
1494        remove_owned_partials(&root, ".artifact.", false).unwrap();
1495        assert!(!partial.exists());
1496        fs::remove_dir_all(root).unwrap();
1497    }
1498
1499    #[cfg(unix)]
1500    #[test]
1501    fn libtest_builder_child_lock_holder_helper() {
1502        let Some(lock) = std::env::var_os("SUPERCOV_TEST_LIBTEST_CHILD_LOCK") else {
1503            return;
1504        };
1505        let ready = PathBuf::from(
1506            std::env::var_os("SUPERCOV_TEST_LIBTEST_CHILD_READY").expect("ready path"),
1507        );
1508        let lock = acquire_kernel_lock(Path::new(&lock)).unwrap();
1509        let mut command = Command::new("/bin/sh");
1510        command.args(["-c", "sleep 2"]);
1511        inherit_lock_through_exec(&mut command, &lock);
1512        let child = command.spawn().unwrap();
1513        fs::write(&ready, format!("{}\n", child.id())).unwrap();
1514        // This helper is deliberately SIGKILLed by its parent test; dropping
1515        // the handle leaves the compiler-shaped child alive so it alone proves
1516        // that the inherited open-file description retains the kernel lock.
1517        drop(child);
1518        loop {
1519            std::thread::sleep(Duration::from_secs(1));
1520        }
1521    }
1522
1523    #[cfg(unix)]
1524    #[test]
1525    fn killed_builder_keeps_lock_until_its_compiler_child_exits() {
1526        use std::process::Stdio;
1527
1528        let root = fixture();
1529        let lock = root.join("companion-child.libtest.lock");
1530        let ready = root.join("compiler-ready");
1531        let mut builder = Command::new(std::env::current_exe().unwrap())
1532            .args([
1533                "--exact",
1534                "rust_libtest_companion::tests::libtest_builder_child_lock_holder_helper",
1535                "--nocapture",
1536            ])
1537            .env("SUPERCOV_TEST_LIBTEST_CHILD_LOCK", &lock)
1538            .env("SUPERCOV_TEST_LIBTEST_CHILD_READY", &ready)
1539            .stdin(Stdio::null())
1540            .stdout(Stdio::null())
1541            .stderr(Stdio::null())
1542            .spawn()
1543            .unwrap();
1544        let started = Instant::now();
1545        while !ready.is_file() {
1546            assert!(started.elapsed() < Duration::from_secs(10));
1547            std::thread::sleep(Duration::from_millis(10));
1548        }
1549        assert_eq!(
1550            unsafe { libc::kill(builder.id().try_into().unwrap(), libc::SIGKILL) },
1551            0
1552        );
1553        assert_eq!(builder.wait().unwrap().code(), None);
1554        let recovery_started = Instant::now();
1555        let _lock = acquire_kernel_lock(&lock).unwrap();
1556        assert!(
1557            recovery_started.elapsed() >= Duration::from_millis(500),
1558            "the compiler child did not retain the publication lock"
1559        );
1560        assert!(recovery_started.elapsed() < Duration::from_secs(5));
1561        fs::remove_dir_all(root).unwrap();
1562    }
1563
1564    #[test]
1565    fn rejects_unrecognized_or_unsafe_exact_source() {
1566        let root = fixture();
1567        fs::write(root.join("source/src/console.rs"), "not libtest\n").unwrap();
1568        assert!(matches!(
1569            prepare_exact_libtest_source(&root.join("source"), &root.join("patched"), &compiler()),
1570            Err(RustLibtestCompanionError::UnrecognizedSource { .. })
1571        ));
1572        assert!(!root.join("patched").exists());
1573        assert_eq!(
1574            fs::read_to_string(root.join("source/src/console.rs")).unwrap(),
1575            "not libtest\n"
1576        );
1577        fs::remove_dir_all(root).unwrap();
1578    }
1579
1580    #[cfg(unix)]
1581    #[test]
1582    fn rejects_symlinks_without_leaving_a_destination() {
1583        use std::os::unix::fs::symlink;
1584
1585        let root = fixture();
1586        symlink("lib.rs", root.join("source/src/alias.rs")).unwrap();
1587        assert!(matches!(
1588            prepare_exact_libtest_source(&root.join("source"), &root.join("patched"), &compiler()),
1589            Err(RustLibtestCompanionError::UnsafeSource(_))
1590        ));
1591        assert!(!root.join("patched").exists());
1592        fs::remove_dir_all(root).unwrap();
1593    }
1594
1595    #[test]
1596    fn build_plan_requires_exact_full_metadata() {
1597        let root = fixture();
1598        prepare_exact_libtest_source(&root.join("source"), &root.join("patched"), &compiler())
1599            .unwrap();
1600        fs::create_dir(root.join("libdir")).unwrap();
1601        fs::write(root.join("libdir/libgetopts-a.rmeta"), b"getopts").unwrap();
1602        fs::write(root.join("libdir/libgetopts-a.rlib"), b"getopts archive").unwrap();
1603        fs::write(root.join("libdir/liblibc-b.rmeta"), b"libc").unwrap();
1604        fs::write(root.join("libdir/liblibc-b.rlib"), b"libc archive").unwrap();
1605        let plan = rust_libtest_companion_build_plan(
1606            &root.join("patched"),
1607            &root.join("libdir"),
1608            &root.join("libtest-supercov.rlib"),
1609        )
1610        .unwrap();
1611        assert_eq!(
1612            plan.source,
1613            fs::canonicalize(root.join("patched/src/lib.rs")).unwrap()
1614        );
1615        assert!(
1616            plan.arguments
1617                .iter()
1618                .any(|value| value == "-Zcrate-attr=feature(rustc_private)")
1619        );
1620        assert!(
1621            !plan
1622                .arguments
1623                .iter()
1624                .any(|value| value.to_string_lossy().starts_with("-Cincremental"))
1625        );
1626        fs::write(root.join("libdir/liblibc-c.rmeta"), b"duplicate").unwrap();
1627        fs::write(root.join("libdir/liblibc-c.rlib"), b"duplicate archive").unwrap();
1628        assert!(
1629            rust_libtest_companion_build_plan(
1630                &root.join("patched"),
1631                &root.join("libdir"),
1632                &root.join("duplicate.rlib")
1633            )
1634            .is_err()
1635        );
1636        fs::remove_dir_all(root).unwrap();
1637    }
1638
1639    fn synthetic_rlib(object_suffix: &str, reverse: bool) -> Vec<u8> {
1640        let metadata = b"metadata".as_slice();
1641        let first = b"first object".as_slice();
1642        let second = b"second object".as_slice();
1643        let first_name = format!("test.alpha.{object_suffix}.rcgu.o");
1644        let second_name = format!("test.beta.{object_suffix}.rcgu.o");
1645        let mut owned = [
1646            ("lib.rmeta".to_owned(), metadata),
1647            (first_name, first),
1648            (second_name, second),
1649        ];
1650        if reverse {
1651            owned[1..].reverse();
1652        }
1653        let members = owned
1654            .iter()
1655            .map(|(name, data)| NewArchiveMember::new(data, &DEFAULT_OBJECT_READER, name.clone()))
1656            .collect::<Vec<_>>();
1657        let mut output = Cursor::new(Vec::new());
1658        write_archive_to_stream(
1659            &mut output,
1660            &members,
1661            WritableArchiveKind::Darwin,
1662            false,
1663            Some(false),
1664        )
1665        .unwrap();
1666        output.into_inner()
1667    }
1668
1669    #[test]
1670    fn canonical_rlib_ignores_session_names_and_member_order() {
1671        let first = canonicalize_rust_libtest_rlib(
1672            &synthetic_rlib("random-one", false),
1673            "aarch64-apple-darwin",
1674        )
1675        .unwrap();
1676        let second = canonicalize_rust_libtest_rlib(
1677            &synthetic_rlib("random-two", true),
1678            "aarch64-apple-darwin",
1679        )
1680        .unwrap();
1681        assert_eq!(first, second);
1682
1683        let archive = ArchiveFile::parse(first.as_slice()).unwrap();
1684        let names = archive
1685            .members()
1686            .map(|member| {
1687                std::str::from_utf8(member.unwrap().name())
1688                    .unwrap()
1689                    .to_owned()
1690            })
1691            .collect::<Vec<_>>();
1692        assert_eq!(names.first().map(String::as_str), Some("lib.rmeta"));
1693        assert!(
1694            names[1..]
1695                .iter()
1696                .all(|name| name.starts_with("supercov-libtest-") && name.ends_with(".rcgu.o"))
1697        );
1698    }
1699
1700    #[test]
1701    fn bundle_binds_exact_compiler_source_runtime_and_artifact_bytes() {
1702        let root = fixture();
1703        let compiler = compiler();
1704        let source_identity =
1705            prepare_exact_libtest_source(&root.join("source"), &root.join("patched"), &compiler)
1706                .unwrap();
1707        let compiler_companion = root.join("supercov-rustc-companion");
1708        let artifact = root.join("libtest-supercov.rlib");
1709        fs::write(&compiler_companion, b"compiler companion").unwrap();
1710        fs::write(&artifact, b"exact libtest rlib").unwrap();
1711        let selected = selection(
1712            fs::canonicalize(&compiler_companion).unwrap(),
1713            compiler.clone(),
1714        );
1715        let bundle_path =
1716            write_rust_libtest_companion_bundle(&compiler_companion, &source_identity, &artifact)
1717                .unwrap();
1718        let bound = select_rust_libtest_companion(&selected).unwrap();
1719        assert_eq!(bound.bundle_path, bundle_path);
1720        assert_eq!(bound.artifact_path, fs::canonicalize(&artifact).unwrap());
1721        assert_eq!(
1722            bound.bundle.compiler_companion_build_id,
1723            selected.handshake.companion_build_id
1724        );
1725        assert_eq!(
1726            bound.bundle.original_source_sha256,
1727            source_identity.original_source_sha256
1728        );
1729
1730        fs::write(&artifact, b"tampered").unwrap();
1731        assert!(matches!(
1732            select_rust_libtest_companion(&selected),
1733            Err(RustLibtestCompanionError::BundleMismatch(_))
1734        ));
1735        fs::remove_dir_all(root).unwrap();
1736    }
1737
1738    #[test]
1739    fn bundle_rejects_unknown_fields_unsafe_paths_and_companion_mismatch() {
1740        let root = fixture();
1741        let compiler = compiler();
1742        let source_identity =
1743            prepare_exact_libtest_source(&root.join("source"), &root.join("patched"), &compiler)
1744                .unwrap();
1745        let compiler_companion = root.join("supercov-rustc-companion");
1746        let artifact = root.join("libtest-supercov.rlib");
1747        fs::write(&compiler_companion, b"compiler companion").unwrap();
1748        fs::write(&artifact, b"exact libtest rlib").unwrap();
1749        let mut selected = selection(
1750            fs::canonicalize(&compiler_companion).unwrap(),
1751            compiler.clone(),
1752        );
1753        let bundle_path =
1754            write_rust_libtest_companion_bundle(&compiler_companion, &source_identity, &artifact)
1755                .unwrap();
1756        let mut value: serde_json::Value =
1757            serde_json::from_slice(&fs::read(&bundle_path).unwrap()).unwrap();
1758        value["unknown"] = serde_json::json!(true);
1759        fs::write(&bundle_path, serde_json::to_vec(&value).unwrap()).unwrap();
1760        assert!(matches!(
1761            select_rust_libtest_companion(&selected),
1762            Err(RustLibtestCompanionError::InvalidBundle { .. })
1763        ));
1764
1765        value.as_object_mut().unwrap().remove("unknown");
1766        value["artifactFile"] = serde_json::json!("../escaped.rlib");
1767        fs::write(&bundle_path, serde_json::to_vec(&value).unwrap()).unwrap();
1768        assert!(matches!(
1769            select_rust_libtest_companion(&selected),
1770            Err(RustLibtestCompanionError::InvalidBundle { .. })
1771        ));
1772
1773        value["artifactFile"] = serde_json::json!("libtest-supercov.rlib");
1774        fs::write(&bundle_path, serde_json::to_vec(&value).unwrap()).unwrap();
1775        selected.handshake.companion_build_id = "f".repeat(64);
1776        assert!(matches!(
1777            select_rust_libtest_companion(&selected),
1778            Err(RustLibtestCompanionError::BundleMismatch(_))
1779        ));
1780        fs::remove_dir_all(root).unwrap();
1781    }
1782}