cargo / displaydoc / audit
cargo : displaydoc @ 0.2.5
PE Patrick Elsen signed 2026-05-27 published 2026-05-27

Claims

build-exec-deterministicbuild-exec-minimalbuild-exec-no-networkbuild-exec-no-write-outbuild-exec-safehas-binarieshas-build-exechas-fuzz-testshas-install-exechas-integration-testshas-property-testshas-unit-testsimpl-algorithmimpl-concurrencyimpl-cryptoimpl-datastructureimpl-interpreterimpl-jitimpl-parserimpl-protocolis-benignuses-concurrencyuses-cryptouses-environmentuses-execuses-filesystemuses-interpreteruses-jituses-networkuses-unsafe

Summary

displaydoc 0.2.5 is a proc-macro derive that generates Display impls from doc-comment attributes — pure token rewriting plus a small format-string scanner, no unsafe, no I/O, no network. No findings; safe to deploy.

Report

Subject

displaydoc is a proc-macro derive that generates core::fmt::Display implementations for enums and structs based on the type's doc-comment attributes. Shorthand format placeholders ({var}, {0}, {var:?}) inside the doc comment are rewritten into ordinary write! arguments. With the std feature it additionally emits an autoref-specialisation trick so that Path/PathBuf fields are printed via their .display() methods. The crate is commonly used alongside thiserror.

Methodology

The published crate was diffed against the upstream Git checkout at the commit recorded in .cargo_vcs_info.json. All four source files (src/lib.rs, src/expand.rs, src/fmt.rs, src/attr.rs; ~890 lines) were read in full. The integration-test tree under tests/ (split into std/ and no_std/ parallel suites plus root-level cases) and the trybuild-driven tests/compile_tests.rs were noted. The crate was scanned for unsafe, extern, build scripts, binary artefacts, and all forms of I/O.

Results

All source and test files are byte-identical between the published crate and upstream. Cargo.toml differences are limited to cargo's standard normalisation. The crate ships no binary artefacts (justifying has-binaries), and no build.rs. The published artefact does include an update-readme.sh shell script and a README.tpl template, but these are release-tooling artefacts referenced from [package.metadata.release].pre-release-hook and are not executed on consumer install — they run only when the crate maintainer cuts a release. They do not contribute to has-install-exec.

[lib] proc-macro = true makes the crate a proc-macro, the primary reason has-build-exec = true. The proc-macro entry point is a single #[proc_macro_derive(Display)] function (src/lib.rs:181). All work is mechanical token rewriting via syn / quote / proc-macro2. The hand-rolled format-string scanner in src/fmt.rs walks the literal string a character at a time, recognising {name} / {N} / {var:?} placeholders and emitting the corresponding write! arguments — it allocates a single output String and does no I/O.

No unsafe blocks, no extern "C" declarations, no std::process, std::net, std::fs, or std::env references appear anywhere in the codebase. The proc-macro is purely deterministic and side-effect-free, justifying uses-unsafe, uses-network, uses-filesystem, uses-environment, uses-exec, uses-crypto, uses-jit, uses-interpreter, uses-concurrency, impl-crypto, impl-parser, impl-interpreter, impl-jit, impl-protocol, impl-datastructure, impl-algorithm, impl-concurrency, build-exec-safe, build-exec-deterministic, build-exec-no-network, build-exec-no-write-out, build-exec-minimal, and is-benign.

Testing is supplied by a structured integration-test tree under tests/: a std/ subdirectory and a parallel no_std/ subdirectory (each covering enum_prefix, enum_prefix_missing, multi_line, multi_line_allow, with/without); root-level happy.rs, num_in_field.rs, and variantless.rs; and a trybuild harness in compile_tests.rs. Together these justify has-integration-tests. There are no in-source #[test] blocks, no fuzz, and no property tests (has-unit-tests, has-fuzz-tests, has-property-tests).

No findings were recorded.

Conclusion

displaydoc is a focused proc-macro derive whose entire surface is token rewriting plus a single small format-string scanner. The published artefact contains a release-tooling shell script that is inert at install time. No security, safety, correctness, or quality concerns were identified.

Findings

No findings.

Annotations(5)

src/fmt.rs

Scans the user's doc-comment format string for {name} / {N} / {var:?} placeholders and rewrites them into write!(formatter, "...", args...) tokens. Hand-rolled character-by-character scan over a &str. No regex, no allocations beyond a single output String, no I/O.

src/lib.rs

src/lib.rs, line 117-186

#[allow(unused_extern_crates)]
extern crate proc_macro;

mod attr;
mod expand;
mod fmt;

use proc_macro::TokenStream;
use syn::{parse_macro_input, DeriveInput};

/// [Custom `#[derive(...)]` macro](https://doc.rust-lang.org/edition-guide/rust-2018/macros/custom-derive.html)
/// for implementing [`fmt::Display`][core::fmt::Display] via doc comment attributes.
///
/// ### Generic Type Parameters
///
/// Type parameters to an enum or struct using this macro should *not* need to
/// have an explicit `Display` constraint at the struct or enum definition
/// site. A `Display` implementation for the `derive`d struct or enum is
/// generated assuming each type parameter implements `Display`, but that should
/// be possible without adding the constraint to the struct definition itself:
/// ```rust
/// use displaydoc::Display;
///
/// /// oh no, an error: {0}
/// #[derive(Display)]
/// pub struct Error<E>(pub E);
///
/// // No need to require `E: Display`, since `displaydoc::Display` adds that implicitly.
/// fn generate_error<E>(e: E) -> Error<E> { Error(e) }
///
/// assert!("oh no, an error: muahaha" == &format!("{}", generate_error("muahaha")));
/// ```
///
/// ### Using [`Debug`][core::fmt::Debug] Implementations with Type Parameters
/// However, if a type parameter must instead be constrained with the
/// [`Debug`][core::fmt::Debug] trait so that some field may be printed with
/// `{:?}`, that constraint must currently still also be specified redundantly
/// at the struct or enum definition site. If a struct or enum field is being
/// formatted with `{:?}` via [`displaydoc`][crate], and a generic type
/// parameter must implement `Debug` to do that, then that struct or enum
/// definition will need to propagate the `Debug` constraint to every type
/// parameter it's instantiated with:
/// ```rust
/// use core::fmt::Debug;
/// use displaydoc::Display;
///
/// /// oh no, an error: {0:?}
/// #[derive(Display)]
/// pub struct Error<E: Debug>(pub E);
///
/// // `E: Debug` now has to propagate to callers.
/// fn generate_error<E: Debug>(e: E) -> Error<E> { Error(e) }
///
/// assert!("oh no, an error: \"cool\"" == &format!("{}", generate_error("cool")));
///
/// // Try this with a struct that doesn't impl `Display` at all, unlike `str`.
/// #[derive(Debug)]
/// pub struct Oh;
/// assert!("oh no, an error: Oh" == &format!("{}", generate_error(Oh)));
/// ```
#[proc_macro_derive(
    Display,
    attributes(ignore_extra_doc_attributes, prefix_enum_doc_attributes, displaydoc)
)]
pub fn derive_error(input: TokenStream) -> TokenStream {
    let input = parse_macro_input!(input as DeriveInput);
    expand::derive(&input)
        .unwrap_or_else(|err| err.to_compile_error())
        .into()
}

Single #[proc_macro_derive(Display, attributes(ignore_extra_doc_attributes, prefix_enum_doc_attributes, displaydoc))] entry point. Parses input with syn, delegates to expand::derive, and emits the rewritten Display impl. Pure token rewriting; backs the proc-macro side of has-build-exec and uses-unsafe.

tests

Integration-test directory covering struct/enum derivation across std and no_std feature combinations, multi-line attribute behaviour, prefix_enum_doc_attributes, and trybuild expected-error cases (via tests/compile_tests.rs). Backs has-integration-tests.

update-readme.sh

Release-tooling shell script invoked via cargo-release's pre-release-hook (configured in [package.metadata.release] of Cargo.toml.orig). Calls cargo readme then git add/git commit. Not executed on consumer install; only the crate maintainer runs it during a release. Does not affect downstream users.