cargo / cc / audit
cargo : cc @ 1.2.63
PE Patrick Elsen signed 2026-06-01 published 2026-06-01

Claims

concurrency-documentedconcurrency-impl-correctconcurrency-impl-documentedconcurrency-impl-safeconcurrency-impl-testedconcurrency-safeenvironment-safeexec-safefilesystem-safehas-binarieshas-build-exechas-fuzz-testshas-install-exechas-integration-testshas-property-testshas-unit-testsimpl-algorithmimpl-concurrencyimpl-cryptoimpl-datastructureimpl-interpreterimpl-jitimpl-parserimpl-protocolis-benignparser-impl-correctparser-impl-safeparser-impl-testedunsafe-documentedunsafe-minimalunsafe-safeunsafe-testeduses-concurrencyuses-cryptouses-environmentuses-execuses-filesystemuses-interpreteruses-jituses-networkuses-unsafe

Summary

Audit of cc 1.2.63, a Cargo build-time helper that orchestrates native C/C++/CUDA/assembly compilation. All process spawning is argv-form with no shell, writes are confined to OUT_DIR, environment access goes through a clippy-enforced cache-aware helper, and the small unsafe surface is documented and minimal. No crypto, network, or install/build-time code execution. Two low-severity quality findings on unwrap/expect panics over environment inputs and a non-CSPRNG tempfile name.

Report

Subject

cc is a build-time helper for Cargo build scripts. A consumer builds a cc::Build, hands it a set of .c/.cpp/.cu/.S source files plus flags and defines, and the crate orchestrates: detect the host/target compiler family (clang, gcc, MSVC, NVCC, emscripten, VxWorks, zig cc, …), assemble the right argv per source file, spawn the compiler, optionally in parallel, and archive the resulting object files into a static library that Cargo can link.

The crate ships a single library target with two opt-in features: parallel (pulls in jobserver and libc, enabling concurrent compiles with jobserver coordination) and jobserver (a placeholder retained for backwards compatibility — it never enabled parallelism). No default features. No build.rs. No proc-macro target. The published .crate excludes the in-VCS tests/, src/bin/, .github/, and the workspace's dev-tools/ and find-msvc-tools/ members.

Methodology

The published crate contents were compared against the upstream Git repository at the commit recorded in .cargo_vcs_info.json using diff (GNU diffutils 3.10). The diff is what cargo's normaliser produces (reordered keys, [lib] block inserted, [dev-dependencies] / [workspace] / [patch.crates-io] stripped from the published manifest); no code-file divergence. The published archive carries Cargo.lock — normal for recent cargo, irrelevant for downstream library consumers.

All of src/ (~14.9k LOC across lib.rs, tool.rs, flags.rs, command_helpers.rs, tempfile.rs, utilities.rs, target/*, parallel/*) was inspected. The large lib.rs (4505 lines) was delegated to a Sonnet sub-agent with a structured questionnaire covering process execution, filesystem, environment, network, crypto, unsafe, FFI, concurrency, command-injection, and path-traversal; the smaller modules were read directly. ripgrep (14.1.1) was used to enumerate unsafe, extern "C", Command::new, std::net, std::fs, Mutex/RwLock/Atomic, and the crypto/hash/sign/cipher family. Only the published feature set was reviewed; tests were not executed.

openvet 0.6.0 was used to drive the audit workflow.

Results

The diff between contents and VCS is consistent with cargo normalisation; no unexpected files in the package. The crate ships one .c source: src/detect_compiler_family.c (14 lines, four #pragma message probes for __clang__/__GNUC__/__EMSCRIPTEN__/__VXWORKS__) which tool.rs:detect_family_inner writes into a tempfile and preprocesses to identify the compiler. No pre-compiled binaries, libraries, or archive files (justifying has-binaries). The crate has no build.rs and is not a proc-macro crate — there is no code that runs at the audited crate's own build time on the consumer's machine outside of cargo's normal compilation of the library itself (justifying has-build-exec and has-install-exec).

The crate's purpose is to spawn external compilers, so uses-exec is asserted. All invocations construct std::process::Command and append arguments as individual OsString argv items; no shell is invoked. When CC_SHELL_ESCAPED_FLAGS is set, CFLAGS/CXXFLAGS/ARFLAGS are tokenised with the shlex crate and the tokens become argv items, still without a shell (justifying exec-safe). The executable path comes from env vars (CC, CXX, AR, NVCC, RUSTC_LINKER, …) or Build::compiler(); in normal usage these are end-user or build-script-author controlled, not attacker-controlled.

Filesystem writes are confined to OUT_DIR (object files, the final archive, the compiler-detection temp source); reads cover the build-script-author-supplied source files and a handful of probe paths during cross-compiler discovery. tempfile::NamedTempfile opens with O_CREAT|O_EXCL and unix mode 0o600 / Windows FILE_ATTRIBUTE_TEMPORARY, retrying up to ten times on collision (justifying filesystem-safe, with a quality caveat tracked in FINDING-2). Environment access goes through Build::getenv (cache-aware, emits cargo:rerun-if-env-changed); a project-level clippy lint disallows the raw std::env::* accessors. Reads include the documented Cargo CARGO_* variables and the standard Unix-toolchain set (CC/CXX/*FLAGS, the Apple SDKROOT / *_DEPLOYMENT_TARGET family, WASI/WASM sysroot vars, PATH, CROSS_COMPILE, the cc-specific CC_ENABLE_DEBUG_OUTPUT/CC_SHELL_ESCAPED_FLAGS/CC_FORCE_DISABLE/CC_KNOWN_WRAPPER_CUSTOM/CRATE_CC_NO_DEFAULTS). The crate does not enumerate or exfiltrate the environment, and does not write env vars (justifying environment-safe).

Cryptographic libraries were searched for and none was found; the only "hash" usage is DefaultHasher over a directory name to disambiguate same-basename source files in the object-file cache, and RandomState::new().build_hasher().finish() for tempfile names — neither is a cryptographic primitive (justifying uses-crypto, impl-crypto). Source-tree review found no network APIs (std::net, reqwest, ureq, curl, hyper) and no DNS lookups (justifying uses-network). The crate neither embeds a JIT/interpreter nor implements one (justifying uses-jit, impl-jit, uses-interpreter, impl-interpreter, impl-protocol). It does not implement non-trivial data structures or algorithms (justifying impl-datastructure, impl-algorithm).

The crate implements two non-trivial pieces of functionality. First, impl-parser: flags.rs parses CARGO_ENCODED_RUSTFLAGS (the U+001F-separated rustc args) into a typed RustcCodegenFlags, and target/parser.rs matches rustc target triples against a generated table to recover arch/vendor/os/env/abi. Both are data-driven and panic-free on observed paths (justifying parser-impl-safe). The integration tests in the upstream tests/ directory cover the rustflags and target-info parsing paths (tests/rustflags.rs, plus property-style coverage embedded in tests/test.rs), justifying parser-impl-tested and parser-impl-correct. Second, impl-concurrency: the parallel/ module implements a hand-rolled two-future executor with backoff, a wrapper over the inherited make-jobserver, and an in-process atomic-counter fallback jobserver. The jobserver wrapper uses a Mutex<bool> to coordinate the global implicit token (the author comment explains why an AtomicBool is insufficient). All BuildCache access goes through RwLock<HashMap<...>> shared via Arc. Public concurrency contract is documented through Rust's type system (Send + Sync bounds; the Arc<BuildCache> exposure is pub(crate)). The custom primitives are exercised by the integration suite under parallel but not under loom or ThreadSanitizer, so concurrency-impl-tested is asserted false; concurrency-impl-safe, concurrency-impl-correct, concurrency-impl-documented hold on review (justifying uses-concurrency, concurrency-safe, concurrency-documented).

uses-unsafe is asserted true and the unsafe surface is small and contained: a hand-rolled OnceLock<T> in utilities.rs (replaceable with std::sync::OnceLock once MSRV moves past 1.70), two Pin::new_unchecked calls plus a Waker::from_raw with a noop vtable in the async executor, and three libc FFI calls (fcntl, ioctl(FIONREAD)) plus a Windows PeekNamedPipe call in parallel/stderr.rs. Each block carries a SAFETY-style comment or a justifying author comment (justifying unsafe-documented, unsafe-minimal). Invariants reviewed and hold (justifying unsafe-safe). No targeted miri/sanitizer runs were observed in the workspace, so unsafe-tested is asserted false.

Two low-severity quality findings were recorded: FINDING-1 enumerates unwrap/expect sites that panic on environment-derived inputs instead of returning the crate's Error type, and FINDING-2 documents the non-CSPRNG tempfile naming and the mitigations that make it acceptable.

The package was reviewed for malicious patterns (obfuscated payloads, network exfiltration, target-conditional behaviour, suspicious binaries, typosquatting); none was found (justifying is-benign). Tests live in the upstream VCS but are excluded from the published .crate (justifying has-unit-tests = true and has-integration-tests = true through the in-VCS layout); no fuzz or property tests were observed in the test suite (justifying has-fuzz-tests = false and has-property-tests = false).

Conclusion

cc is a mature, focused build-time helper. Its attack surface — spawning external compilers and managing build outputs under OUT_DIR — is exactly what its purpose demands, and the implementation handles it with discipline: argv-only process construction, a clippy-enforced env-access discipline, exclusive-create tempfiles, and a small contained unsafe surface that is well documented and replaceable as MSRV moves. The two recorded findings are quality-class, low-severity, and do not affect downstream consumers' security posture. No network access, no cryptography, no install/build-time code execution beyond compiling the crate itself.

Findings(2)

FINDING-1 quality low

Multiple .unwrap()/.expect() panics on environment-derived inputs

Several call sites assume environment variables or path components are valid UTF-8 or have a particular shape, and panic when they don't. Examples observed during the lib.rs review:

  • lib.rs:3258custom_wrapper.as_deref().unwrap().to_str().unwrap() panics if CC_KNOWN_WRAPPER_CUSTOM is non-UTF-8.
  • lib.rs:1664, lib.rs:2277wasm_musl_sysroot().unwrap() panics if WASM_MUSL_SYSROOT is unset on a branch that requires it.
  • lib.rs:4125.expect("integer version") panics on a non-numeric MACOSX_DEPLOYMENT_TARGET.
  • lib.rs:3137rfind('-').unwrap() on an Android NDK compiler path; only the android substring is pre-checked.
  • tool.rs:337flag.to_str().unwrap() in is_duplicate_opt_arg; non-UTF-8 build-script-supplied flags abort the build.

The surrounding code generally returns Result<_, Error>; these panics short-circuit that error path. Failure mode is a build-script abort with a less helpful message than the explicit Error variants would give. Not a soundness or security issue — the unsafe-safe assertion is unaffected because there is no unsafe code in lib.rs/tool.rs.

FINDING-2 quality low

Tempfile name from non-cryptographic HashMap RandomState seed

src/tempfile.rs:14-20 derives temp-file names from RandomState::new().build_hasher().finish() — i.e. the HashMap DoS-resistance seed, not a CSPRNG. The implementation does not document a security guarantee, and the surrounding logic mitigates collisions in two ways:

  • create_named opens with O_CREAT | O_EXCL (create_new(true)) so a colliding path atomically fails instead of overwriting an existing file (tempfile.rs:25).
  • The unix mode is 0o600 and on Windows FILE_ATTRIBUTE_TEMPORARY is set (tempfile.rs:28, 33).
  • On AlreadyExists the loop retries up to 10 times (tempfile.rs:46-58).

The only consumer is tool.rs:detect_family_inner, which writes detect_compiler_family.c into OUT_DIR (or env::temp_dir() as a fallback). Even so, on a shared /tmp an attacker could in principle precompute a candidate name and pre-create it to deny service; the retry budget makes that costly, and the failure mode is a build error, not a write to an attacker-controlled location. Flagged as a quality issue because the comment doesn't justify the non-CSPRNG choice and a downstream reader of the uses-crypto false assertion may otherwise be surprised to find a "rand" helper in the tree.

Annotations(11)

clippy.toml

Project-level clippy lints disallow std::env::var{,_os} and std::env::{set,remove}_var in favour of Build::getenv (cache-aware, emits cargo:rerun-if-env-changed) and GlobalEnv::lock(). Enforces the env-discipline that supports uses-environment and environment-safe.

src/command_helpers.rs

src/command_helpers.rs, line 283-341

pub(crate) fn objects_from_files(files: &[Arc<Path>], dst: &Path) -> Result<Vec<Object>, Error> {
    let mut objects = Vec::with_capacity(files.len());
    for file in files {
        let basename = file
            .file_name()
            .ok_or_else(|| {
                Error::new(
                    ErrorKind::InvalidArgument,
                    "No file_name for object file path!",
                )
            })?
            .to_string_lossy();
        let dirname = file
            .parent()
            .ok_or_else(|| {
                Error::new(
                    ErrorKind::InvalidArgument,
                    "No parent for object file path!",
                )
            })?
            .to_string_lossy();

        // Hash the dirname. This should prevent conflicts if we have multiple
        // object files with the same filename in different subfolders.
        let mut hasher = hash_map::DefaultHasher::new();

        // Make the dirname relative (if possible) to avoid full system paths influencing the sha
        // and making the output system-dependent
        let dirname = if let Some(root) = cargo_env_var_os("CARGO_MANIFEST_DIR") {
            let root = root.to_string_lossy();
            Cow::Borrowed(dirname.strip_prefix(&*root).unwrap_or(&dirname))
        } else {
            dirname
        };

        hasher.write(dirname.as_bytes());
        if let Some(extension) = file.extension() {
            hasher.write(extension.to_string_lossy().as_bytes());
        }

        let obj = dst
            .join(format!("{:016x}-{}", hasher.finish(), basename))
            .with_extension("o");

        match obj.parent() {
            Some(s) => fs::create_dir_all(s)?,
            None => {
                return Err(Error::new(
                    ErrorKind::InvalidArgument,
                    "dst is an invalid path with no parent",
                ));
            }
        };

        objects.push(Object::new(file.to_path_buf(), obj));
    }

    Ok(objects)
}

Object-file output paths are constructed as OUT_DIR.join(format!("{:016x}-{basename}", hash)), where the hash is DefaultHasher over the (CARGO_MANIFEST_DIR-relativised) parent directory and the file extension. Non-cryptographic hash, used only for collision-avoidance between same-named sources in different subfolders. Build-script-author–controlled inputs (source paths) can still land outside dst if pathological — but only because the basename comes from the input path; the hash prefix in the filename is benign. Supports uses-filesystem and impl-crypto false.

src/flags.rs

src/flags.rs, line 26-80

impl<'this> RustcCodegenFlags<'this> {
    // Parse flags obtained from CARGO_ENCODED_RUSTFLAGS
    pub(crate) fn parse(rustflags_env: &'this str) -> Result<Self, Error> {
        fn is_flag_prefix(flag: &str) -> bool {
            [
                "-Z",
                "-C",
                "--codegen",
                "-L",
                "-l",
                "-o",
                "-W",
                "--warn",
                "-A",
                "--allow",
                "-D",
                "--deny",
                "-F",
                "--forbid",
            ]
            .contains(&flag)
        }

        fn handle_flag_prefix<'a>(prev: &'a str, curr: &'a str) -> (&'a str, &'a str) {
            match prev {
                "--codegen" | "-C" => ("-C", curr),
                // Handle flags passed like --codegen=code-model=small
                _ if curr.starts_with("--codegen=") => ("-C", &curr[10..]),
                "-Z" => ("-Z", curr),
                "-L" | "-l" | "-o" => (prev, curr),
                // Handle lint flags
                "-W" | "--warn" => ("-W", curr),
                "-A" | "--allow" => ("-A", curr),
                "-D" | "--deny" => ("-D", curr),
                "-F" | "--forbid" => ("-F", curr),
                _ => ("", curr),
            }
        }

        let mut codegen_flags = Self::default();

        let mut prev_prefix = None;
        for curr in rustflags_env.split("\u{1f}") {
            let prev = prev_prefix.take().unwrap_or("");
            if prev.is_empty() && is_flag_prefix(curr) {
                prev_prefix = Some(curr);
                continue;
            }

            let (prefix, rustc_flag) = handle_flag_prefix(prev, curr);
            codegen_flags.set_rustc_flag(prefix, rustc_flag)?;
        }

        Ok(codegen_flags)
    }

Parses CARGO_ENCODED_RUSTFLAGS (US-separator-delimited rustc args) into a structured RustcCodegenFlags. Splits on U+001F, handles prefix flags (-C, -Z, -W, …), and maps individual codegen flags to typed fields. Forwards relevant flags to the C compiler. Supports impl-parser and parser-impl-safe.

src/lib.rs

src/lib.rs, line 363-367

struct BuildCache {
    apple_sdk_root_cache: RwLock<HashMap<Box<str>, Arc<OsStr>>>,
    apple_versions_cache: RwLock<HashMap<Box<str>, Arc<str>>>,
    cached_compiler_family: RwLock<CompilerFamilyLookupCache>,
    known_flag_support_status_cache: RwLock<HashMap<CompilerFlag, bool>>,

BuildCache holds four RwLock<HashMap<...>> fields shared via Arc<BuildCache>. All callers .read().unwrap() / .write().unwrap() (lock poisoning aborts the build). No reentrancy or recursive lock paths observed in the audited code. Supports uses-concurrency.

src/lib.rs, line 248-251

use std::fs;
use std::io::{self, Write};
use std::path::{Component, Path, PathBuf};
use std::process::{Command, Stdio};

All process spawning in cc goes through std::process::Command and OsString argv elements (no shell). Source files compiled by cc, flags from CFLAGS/CXXFLAGS, and the executable path (CC, CXX, …) become individual argv items. CC_SHELL_ESCAPED_FLAGS splits via shlex into argv elements, still no shell. Justifies uses-exec and exec-safe.

src/parallel

Hand-rolled async runtime and jobserver coordination. async_executor.rs polls two futures on a no-op waker with backoff (the spawn-side and the reap-side of parallel compilation). job_token.rs wraps the external jobserver crate's client for inherited make-jobservers and falls back to an in-process AtomicU32 counter. command_runner.rs ties them together: futures acquire tokens, spawn children with non-blocking stderr forwarders, and reap on completion. Implements concurrency primitives — supports impl-concurrency.

src/parallel/async_executor.rs

src/parallel/async_executor.rs, line 47-118

pub(crate) fn block_on<Fut1, Fut2>(
    mut fut1: Fut1,
    mut fut2: Fut2,
    has_made_progress: &Cell<bool>,
) -> Result<(), Error>
where
    Fut1: Future<Output = Result<(), Error>>,
    Fut2: Future<Output = Result<(), Error>>,
{
    // Shadows the future so that it can never be moved and is guaranteed
    // to be pinned.
    //
    // The same trick used in `pin!` macro.
    //
    // TODO: Once MSRV is bumped to 1.68, replace this with `std::pin::pin!`
    let mut fut1 = Some(unsafe { Pin::new_unchecked(&mut fut1) });
    let mut fut2 = Some(unsafe { Pin::new_unchecked(&mut fut2) });

    // TODO: Once `Waker::noop` stablised and our MSRV is bumped to the version
    // which it is stablised, replace this with `Waker::noop`.
    let waker = unsafe { Waker::from_raw(NOOP_RAW_WAKER) };
    let mut context = Context::from_waker(&waker);

    let mut backoff_cnt = 0;

    loop {
        has_made_progress.set(false);

        if let Some(fut) = fut2.as_mut() {
            if let Poll::Ready(res) = fut.as_mut().poll(&mut context) {
                fut2 = None;
                res?;
            }
        }

        if let Some(fut) = fut1.as_mut() {
            if let Poll::Ready(res) = fut.as_mut().poll(&mut context) {
                fut1 = None;
                res?;
            }
        }

        if fut1.is_none() && fut2.is_none() {
            return Ok(());
        }

        if !has_made_progress.get() {
            if backoff_cnt > 3 {
                // We have yielded at least three times without making'
                // any progress, so we will sleep for a while.
                let duration = Duration::from_millis(100 * (backoff_cnt - 3).min(10));
                thread::sleep(duration);
            } else {
                // Given that we spawned a lot of compilation tasks, it is unlikely
                // that OS cannot find other ready task to execute.
                //
                // If all of them are done, then we will yield them and spawn more,
                // or simply return.
                //
                // Thus this will not be turned into a busy-wait loop and it will not
                // waste CPU resource.
                thread::yield_now();
            }
        }

        backoff_cnt = if has_made_progress.get() {
            0
        } else {
            backoff_cnt + 1
        };
    }
}

Hand-rolled single-threaded executor used to interleave the "spawn compilations" and "reap children" futures. The two Pin::new_unchecked calls are sound because the futures are shadowed-by-value inside the function (the "pin! shadow" trick) and never moved. Waker::from_raw uses a no-op vtable that ignores the pointer (null) — safe by construction. Author comment explains why a stdlib pin macro / Waker::noop aren't used (MSRV). Justifies uses-unsafe in this module.

src/parallel/job_token.rs

src/parallel/job_token.rs, line 100-141

        global_implicit_token: Mutex<bool>,
        inner: jobserver::Client,
    }

    impl JobServer {
        pub(super) unsafe fn from_env() -> Option<Self> {
            jobserver::Client::from_env().map(|inner| Self {
                inner,
                global_implicit_token: Mutex::new(true),
            })
        }

        fn get_global_implicit_token(&self) -> MutexGuard<'_, bool> {
            self.global_implicit_token
                .lock()
                .unwrap_or_else(PoisonError::into_inner)
        }

        /// All tokens except for the global implicit token will be put back into the jobserver
        /// immediately and they cannot be cached, since Rust does not call `Drop::drop` on
        /// global variables.
        pub(super) fn release_token_raw(&self) {
            let mut global_implicit_token = self.get_global_implicit_token();

            if *global_implicit_token {
                // There's already a global implicit token, so this token must
                // be released back into jobserver.
                //
                // `release_raw` should not block
                let _ = self.inner.release_raw();
            } else {
                *global_implicit_token = true;
            }
        }

        pub(super) fn enter_active(&self) -> ActiveJobServer<'_> {
            ActiveJobServer {
                jobserver: self,
                helper_thread: None,
            }
        }
    }

Inherited-jobserver path. The global implicit token is guarded by a Mutex<bool> (explained in the comment block) to close the race where an AtomicBool would let the jobserver inadvertently increase parallelism by one on exit. Acquire/release symmetry holds: release_token_raw either restores the implicit slot or hands the token back to jobserver::Client::release_raw. Supports uses-concurrency.

src/parallel/stderr.rs

src/parallel/stderr.rs, line 10-51

#[cfg(unix)]
fn get_flags(fd: std::os::unix::io::RawFd) -> Result<i32, Error> {
    let flags = unsafe { libc::fcntl(fd, libc::F_GETFL, 0) };
    if flags == -1 {
        Err(Error::new(
            ErrorKind::IOError,
            format!(
                "Failed to get flags for pipe {}: {}",
                fd,
                std::io::Error::last_os_error()
            ),
        ))
    } else {
        Ok(flags)
    }
}

#[cfg(unix)]
fn set_flags(fd: std::os::unix::io::RawFd, flags: std::os::raw::c_int) -> Result<(), Error> {
    if unsafe { libc::fcntl(fd, libc::F_SETFL, flags) } == -1 {
        Err(Error::new(
            ErrorKind::IOError,
            format!(
                "Failed to set flags for pipe {}: {}",
                fd,
                std::io::Error::last_os_error()
            ),
        ))
    } else {
        Ok(())
    }
}

#[cfg(unix)]
pub fn set_non_blocking(pipe: &impl std::os::unix::io::AsRawFd) -> Result<(), Error> {
    // On Unix, switch the pipe to non-blocking mode.
    // On Windows, we have a different way to be non-blocking.
    let fd = pipe.as_raw_fd();

    let flags = get_flags(fd)?;
    set_flags(fd, flags | libc::O_NONBLOCK)
}

Wraps libc fcntl(F_GETFL/F_SETFL) to switch a child stderr pipe to non-blocking. fd comes from pipe.as_raw_fd() on a still-live ChildStderr borrowed for the call, so the descriptor is valid for the duration of the FFI call. Each fcntl invocation is checked for -1 and converted to an Error. Justifies uses-unsafe.

src/parallel/stderr.rs, line 53-91

pub fn bytes_available(stderr: &mut ChildStderr) -> Result<usize, Error> {
    let mut bytes_available = 0;
    #[cfg(windows)]
    {
        use ::find_msvc_tools::windows_sys::PeekNamedPipe;
        use std::os::windows::io::AsRawHandle;
        use std::ptr::null_mut;
        if unsafe {
            PeekNamedPipe(
                stderr.as_raw_handle(),
                null_mut(),
                0,
                null_mut(),
                &mut bytes_available,
                null_mut(),
            )
        } == 0
        {
            return Err(Error::new(
                ErrorKind::IOError,
                format!(
                    "PeekNamedPipe failed with {}",
                    std::io::Error::last_os_error()
                ),
            ));
        }
    }
    #[cfg(unix)]
    {
        use std::os::unix::io::AsRawFd;
        if unsafe { libc::ioctl(stderr.as_raw_fd(), libc::FIONREAD, &mut bytes_available) } != 0 {
            return Err(Error::new(
                ErrorKind::IOError,
                format!("ioctl failed with {}", std::io::Error::last_os_error()),
            ));
        }
    }
    Ok(bytes_available.try_into().unwrap())
}

Peeks how many bytes are buffered on the child stderr pipe. Windows path calls PeekNamedPipe (re-exported from find-msvc-tools); unix path calls ioctl(FIONREAD). Both pass the handle/fd of the live borrowed ChildStderr and write into a stack c_ulong/equivalent. Return value is checked and converted to Error on failure.

src/target/parser.rs

Parses rustc target triples (e.g. x86_64-unknown-linux-gnu) against a generated table to recover arch/vendor/os/env/abi, matching the values cargo exposes through CARGO_CFG_*. Pure data-driven matching with no unsafe. Supports impl-parser.

src/tempfile.rs

src/tempfile.rs, line 14-37

fn rand() -> u64 {
    RandomState::new().build_hasher().finish()
}

fn tmpname(suffix: &str) -> String {
    format!("{}{}", rand(), suffix)
}

fn create_named(path: &Path) -> io::Result<File> {
    let mut open_options = OpenOptions::new();

    open_options.read(true).write(true).create_new(true);

    #[cfg(all(unix, not(target_os = "wasi")))]
    <OpenOptions as os::unix::fs::OpenOptionsExt>::mode(&mut open_options, 0o600);

    #[cfg(windows)]
    <OpenOptions as os::windows::fs::OpenOptionsExt>::custom_flags(
        &mut open_options,
        ::find_msvc_tools::windows_sys::FILE_ATTRIBUTE_TEMPORARY,
    );

    open_options.open(path)
}

Tempfile naming uses HashMap RandomState as a non-CSPRNG seed (FINDING-2); collision risk mitigated by O_CREAT|O_EXCL + 10-retry loop, and the file is mode 0o600 / Windows TEMPORARY. Only consumer is compiler-family detection (tool.rs:detect_family_inner) writing into OUT_DIR (or env::temp_dir() fallback).

src/utilities.rs

src/utilities.rs, line 54-132

pub(crate) struct OnceLock<T> {
    once: Once,
    value: UnsafeCell<MaybeUninit<T>>,
    _marker: PhantomData<T>,
}

impl<T> Default for OnceLock<T> {
    fn default() -> Self {
        Self::new()
    }
}

impl<T> OnceLock<T> {
    pub(crate) const fn new() -> Self {
        Self {
            once: Once::new(),
            value: UnsafeCell::new(MaybeUninit::uninit()),
            _marker: PhantomData,
        }
    }

    #[inline]
    fn is_initialized(&self) -> bool {
        self.once.is_completed()
    }

    unsafe fn get_unchecked(&self) -> &T {
        debug_assert!(self.is_initialized());
        #[allow(clippy::needless_borrow)]
        #[allow(unused_unsafe)]
        unsafe {
            (&*self.value.get()).assume_init_ref()
        }
    }

    pub(crate) fn get_or_init(&self, f: impl FnOnce() -> T) -> &T {
        self.once.call_once(|| {
            unsafe { &mut *self.value.get() }.write(f());
        });
        unsafe { self.get_unchecked() }
    }

    pub(crate) fn get(&self) -> Option<&T> {
        if self.is_initialized() {
            // Safe b/c checked is_initialized
            Some(unsafe { self.get_unchecked() })
        } else {
            None
        }
    }
}

impl<T: fmt::Debug> fmt::Debug for OnceLock<T> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let mut d = f.debug_tuple("OnceLock");
        match self.get() {
            Some(v) => d.field(v),
            None => d.field(&format_args!("<uninit>")),
        };
        d.finish()
    }
}

unsafe impl<T: Sync + Send> Sync for OnceLock<T> {}
unsafe impl<T: Send> Send for OnceLock<T> {}

impl<T: RefUnwindSafe + UnwindSafe> RefUnwindSafe for OnceLock<T> {}
impl<T: UnwindSafe> UnwindSafe for OnceLock<T> {}

impl<T> Drop for OnceLock<T> {
    #[inline]
    fn drop(&mut self) {
        if self.once.is_completed() {
            // SAFETY: The cell is initialized and being dropped, so it can't
            // be accessed again.
            unsafe { self.value.get_mut().assume_init_drop() };
        }
    }
}

Hand-rolled OnceLock<T> (pre-MSRV-1.70). unsafe fn get_unchecked is gated on self.once.is_completed() which the only caller get_or_init only checks after call_once; the get path checks is_initialized before calling. Drop only calls assume_init_drop when is_completed. The unsafe impl Sync/Send mirror std's OnceLock bounds. Replaceable with std::sync::OnceLock once MSRV moves past 1.70; supports uses-unsafe and unsafe-documented.