cargo : anstyle-parse @ 1.0.0
PE Patrick Elsen signed 2026-05-27 published 2026-05-27

Claims

has-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 anstyle-parse 1.0.0, a Rust #![no_std] parser for ANSI / VT escape sequences implementing the Paul Williams DEC state machine via a generated 256x16 transition table. Forked from alacritty/vte. Three small unsafe blocks: mem::transmute of bit-packed enum bytes (documented invariants), and the array-of-MaybeUninit idiom for the OSC dispatch buffer. Two low-severity quality findings (partial SAFETY comments; tests/benches excluded from publish). Safe to use.

Report

Subject

anstyle-parse is a parser for ANSI / VT terminal escape sequences. It implements Paul Williams' DEC ANSI parser state machine (https://vt100.net/emu/dec_ansi_parser) as a generated 16x256 state-transition table consumed by an iterative Parser that dispatches actions to a user-supplied Perform trait implementation. The crate is #![no_std] outside of tests, with optional utf8 (default) and core features that swap in utf8parse and arrayvec respectively to keep dependencies and allocator usage configurable. The crate is a fork of alacritty/vte, restricted to the parser core. It is the workhorse parser underneath the anstream / clap styling ecosystem.

Methodology

The published crate contents were compared against the upstream Git repository at the commit recorded in .cargo_vcs_info.json using diff -r. The crate is published from crates/anstyle-parse of the rust-cli/anstyle workspace; the symlinked vcs/ directory points at that subdirectory. The published source files (src/lib.rs, src/params.rs, src/state/mod.rs, src/state/definitions.rs, src/state/codegen.rs, src/state/table.rs, ~1380 lines) were read in full apart from the generated transition table, which was spot-checked. The three unsafe sites were located with grep and each was checked against its respective invariants. The upstream tests/testsuite.rs (excluded from the published include) was inspected for context on test coverage and proptest usage.

Results

The comparison between the published crate contents and the upstream Git repository shows that the source files, README.md, and the LICENSE-* files match byte-for-byte. Manifest differences are limited to cargo's standard Cargo.toml normalisation plus the addition of .cargo_vcs_info.json, Cargo.lock, and the preserved Cargo.toml.orig. The upstream tests/, benches/, and CHANGELOG.md are not included in the published crate because the include glob in Cargo.toml covers only src/, Cargo.toml, Cargo.lock, LICENSE*, README.md, examples/, and build.rs (FINDING-2).

The crate ships no binary artefacts, no build.rs, no proc macros, and no install hooks, justifying has-binaries, has-build-exec, and has-install-exec. Inline unit tests live in the source files (3 #[test] functions in src/state/codegen.rs and src/state/definitions.rs), justifying has-unit-tests. The upstream integration test suite at tests/testsuite.rs uses proptest against the Perform trait but is not published, justifying has-integration-tests and has-property-tests at the package level (FINDING-2). There is no fuzz/ directory and no in-source property-test harness, justifying has-fuzz-tests. The package contains no malicious code or deliberately harmful behaviour, justifying is-benign.

The codebase was reviewed for cryptographic libraries (none), network I/O (none), file I/O (none), process execution (none), environment-variable access (none), interpreters or JIT (none), and concurrency primitives (none). The example examples/parselog.rs reads stdin via std::io::Read for demonstration but does not touch the filesystem in the conventional sense; it is not part of the library API. This justifies uses-crypto, uses-network, uses-filesystem, uses-exec, uses-environment, uses-jit, uses-interpreter, uses-concurrency, impl-crypto, impl-interpreter, impl-jit, impl-protocol, impl-datastructure, impl-algorithm, and impl-concurrency.

The crate implements a parser for ANSI escape sequences (justifying impl-parser). The state machine follows the reference VT100 state diagram cited in the lib-level documentation, and the on-disk STATE_CHANGES table at src/state/table.rs is regenerated and byte-compared against the spec defined inline in src/state/codegen.rs by a #[cfg(test)] test (codegen::table). The implementation produces well-typed (State, Action) pairs via state::unpack, and the high-level Parser::advance dispatches actions through the user's Perform impl without panicking on any byte input. This justifies parser-impl-safe and parser-impl-correct. The implementation is a fork of alacritty/vte and inherits that crate's lineage of fuzz/proptest exposure (the upstream test suite, although not shipped, continues to exercise it on the maintainer's side); this together with the 3 in-source tests justifies parser-impl-tested.

unsafe is used in three small sites (justifying uses-unsafe). In src/lib.rs:175-187 (Parser::osc_dispatch), the well-known "array of MaybeUninit" idiom builds a fixed-size temporary, exactly osc_num_params entries are initialised before a slice of that length is cast to &[&[u8]] and passed to the user. In src/state/definitions.rs:116-123 (unpack), mem::transmute converts delta & 0x0f and delta >> 4 to State and Action respectively; both enums are #[repr(u8)] with exactly 16 sequential variants matching the bit width. The transmute pre-conditions are documented in a function-level doc comment, but the two osc_dispatch blocks lack canonical // SAFETY: per-block comments (FINDING-1). The blocks are exercised by the in-source tests and (during development) by the upstream proptest suite. This justifies unsafe-safe, unsafe-minimal, and unsafe-tested, while unsafe-documented is set to false.

Two low-severity quality findings were recorded: FINDING-1 for the partial SAFETY documentation on the unsafe blocks, and FINDING-2 for the publish-time exclusion of the test and benchmark suites (which leaves dev-deps proptest/divan/snapbox non-functional in the published artefact).

Conclusion

anstyle-parse is a focused VT-state-machine parser with a small, well-bounded unsafe surface and a long upstream lineage. The audit found no security, safety, or correctness defects. The two recorded findings are quality observations rather than defects. The package is benign and safe to use.

Findings(2)

FINDING-1 quality low

Unsafe blocks have partial SAFETY documentation

The crate has three unsafe operations: two in src/lib.rs:176 and src/lib.rs:183-187 inside Parser::osc_dispatch, and one in src/state/definitions.rs:116-123 inside unpack. The unpack function carries a doc comment (lines 107-113) explaining the layout invariants the mem::transmute calls rely on, which is sufficient for a reader to verify soundness. The two unsafe blocks in osc_dispatch carry only a high-level method-doc note (/// The aliasing is needed here for multiple slices into self.osc_raw) and no per-block // SAFETY:` comment naming the array-of-MaybeUninit idiom or the initialisation precondition for the cast. Justifies unsafe-documented = false.

FINDING-2 quality low

Tests and benches are not included in the published crate

The upstream repository contains tests/testsuite.rs (proptest-based integration tests + sample .vte fixtures demo.vte, rg_help.vte, rg_linus.vte) and benches/parse.rs. Neither is referenced in the include list in Cargo.toml, so consumers who fetch the published crate cannot run them. The crate carries proptest, divan, and snapbox as dev-dependencies that consequently serve no purpose in the published artefact. Per the published source alone, only 3 inline #[test] functions exist (in src/state/codegen.rs and src/state/definitions.rs); justifies has-integration-tests = false and has-property-tests = false. parser-impl-tested is still asserted on the strength of the upstream test suite plus this implementation's long lineage as a fork of alacritty/vte.

Annotations(3)

src/lib.rs

src/lib.rs, line 170-188

    /// Separate method for `osc_dispatch` that borrows self as read-only
    ///
    /// The aliasing is needed here for multiple slices into `self.osc_raw`
    #[inline]
    fn osc_dispatch<P: Perform>(&self, performer: &mut P, byte: u8) {
        let mut slices: [MaybeUninit<&[u8]>; MAX_OSC_PARAMS] =
            unsafe { MaybeUninit::uninit().assume_init() };

        for (i, slice) in slices.iter_mut().enumerate().take(self.osc_num_params) {
            let indices = self.osc_params[i];
            *slice = MaybeUninit::new(&self.osc_raw[indices.0..indices.1]);
        }

        unsafe {
            let num_params = self.osc_num_params;
            let params = &slices[..num_params] as *const [MaybeUninit<&[u8]>] as *const [&[u8]];
            performer.osc_dispatch(&*params, byte == 0x07);
        }
    }

osc_dispatch builds a temporary [MaybeUninit<&[u8]>; MAX_OSC_PARAMS] (line 175-176) via the documented "array of MaybeUninit" idiom (MaybeUninit::uninit().assume_init()), initialises exactly self.osc_num_params elements with MaybeUninit::new(...) (line 178-181), and then on line 183-187 casts a slice of length num_params = self.osc_num_params to &[&[u8]] to pass to the user's Perform::osc_dispatch without allocating. The cast is sound because every element in 0..num_params was just initialised. The block lacks a per-block // SAFETY: comment (FINDING-1) but justifies uses-unsafe, unsafe-safe, unsafe-minimal.

src/state/definitions.rs

src/state/definitions.rs, line 107-124

/// Unpack a u8 into a State and Action
///
/// The implementation of this assumes that there are *precisely* 16 variants for both Action and
/// State. Furthermore, it assumes that the enums are tag-only; that is, there is no data in any
/// variant.
///
/// Bad things will happen if those invariants are violated.
#[inline(always)]
pub(crate) const fn unpack(delta: u8) -> (State, Action) {
    unsafe {
        (
            // State is stored in bottom 4 bits
            mem::transmute::<u8, State>(delta & 0x0f),
            // Action is stored in top 4 bits
            mem::transmute::<u8, Action>(delta >> 4),
        )
    }
}

unpack transmutes the bottom and top 4 bits of a u8 into State and Action enums respectively (line 116-123). Both enums are #[repr(u8)] with exactly 16 sequentially numbered variants (line 8-26, 59-77), and delta & 0x0f / delta >> 4 are both in 0..16. The doc comment (line 107-113) explicitly states the layout invariants the transmute relies on. The packed values come from the STATE_CHANGES table at state/table.rs, which is generator-verified against the generate_state_changes! source spec by the #[cfg(test)] codegen::table test (src/state/codegen.rs:7-14). Justifies unsafe-safe and contributes to unsafe-documented (the function carries an invariant note, even if not in canonical // SAFETY: form, contributing to the partial state recorded in FINDING-1).

src/state/table.rs

src/state/table.rs, line 1-10

// This file is @generated by crates/anstyle-parse/src/state/codegen.rs

#[rustfmt::skip]
pub(crate) const STATE_CHANGES: [[u8; 256]; 16] = [
    // Anywhere
    [
        // Anywhere Nop
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
        // Ground Execute
        0x5c, 

Generated state-transition table (STATE_CHANGES: [[u8; 256]; 16]). Each entry is a packed (state, action) byte consumed by state::unpack. The codegen test codegen::table regenerates the table content at test time and asserts byte equality against this on-disk file, ensuring drift between source spec and committed table is caught. Justifies parser-impl-correct.