cargo / bitflags / audit
cargo : bitflags @ 1.3.2
PE Patrick Elsen signed 2026-05-27 published 2026-05-27

Claims

datastructure-impl-boundsdatastructure-impl-correctdatastructure-impl-safedatastructure-impl-testedhas-binarieshas-build-exechas-fuzz-testshas-install-exechas-integration-testshas-property-testshas-unit-testsimpl-algorithmimpl-concurrencyimpl-cryptoimpl-datastructureimpl-interpreterimpl-jitimpl-parserimpl-protocolis-benignunsafe-documentedunsafe-minimalunsafe-safeunsafe-testeduses-concurrencyuses-cryptouses-environmentuses-execuses-filesystemuses-interpreteruses-jituses-networkuses-unsafe

Summary

Mature no_std declarative-macro library that generates bit-flag set types over primitive integers. Single unsafe API (from_bits_unchecked) is documented and memory-safe by construction. Exhaustive test enumeration over an 11-bit domain establishes set-operation correctness. No findings; safe to deploy.

Report

Subject

bitflags is a declarative-macro library that generates typed bit-flag set types backed by a single primitive integer field. The bitflags! macro expands to a #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] struct plus inherent methods (empty, all, from_bits[_truncate|_unchecked], contains, intersects, insert/remove/toggle/set, union/intersection/difference/symmetric_difference/complement) and operator impls for |, &, ^, -, !. The crate is #![no_std] and exposes a single optional rustc-dep-of-std feature used when building as part of the Rust standard library workspace.

Methodology

The published crate contents were compared against the upstream Git repository at https://github.com/bitflags/bitflags using diff -r; the source revision recorded in .cargo_vcs_info.json resolves correctly and the source and test files match byte-for-byte. The 1729-line src/lib.rs was read in full (doc comments, the bitflags! and __impl_bitflags!/__impl_all_bitflags! macros, the #[cfg(test)] module), along with src/example_generated.rs, tests/basic.rs, and tests/compile.rs. Manifest files, README.md, CHANGELOG.md, and both licence files were reviewed. The test suite was inspected statically; the exhaustive test_set_ops_exhaustive enumeration was reasoned about analytically rather than executed.

Results

The package contains no binary artefacts (justifying has-binaries) and no build.rs; the bitflags! macro is macro_rules!, not procedural, so no consumer compile-time code from this crate runs at the consumer's machine, justifying has-build-exec. Cargo runs no install-time hooks for library crates, justifying has-install-exec. Cargo.toml differences against the upstream repository are confined to cargo's standard manifest normalisation plus the exclude = ["bors.toml"] directive.

The src/lib.rs review found no std::net, std::fs, std::process, std::env, std::thread, or async constructs in the published runtime code (the test-fixture helpers in tests/compile.rs use std::fs/walkdir but run only under cargo test from dev-dependencies), justifying uses-network, uses-filesystem, uses-exec, uses-environment, uses-concurrency, uses-jit, and uses-interpreter. No cryptographic, parsing, interpreting, JIT, protocol, algorithm, or concurrency-primitive code is present, justifying uses-crypto, impl-crypto, impl-parser, impl-interpreter, impl-jit, impl-protocol, impl-algorithm, impl-concurrency. The single non-test occurrence of the unsafe keyword is from_bits_unchecked; it carries a # Safety doc comment and a safe body (a struct literal), so it stands as a logical contract rather than a memory-safety hazard (justifying uses-unsafe, unsafe-documented, unsafe-safe, unsafe-minimal).

The generated bit-flag types are simple wrappers over primitive integers, with all operations expressed as |, &, ^, & !, or ! followed by truncation — O(1) on the underlying integer type, with no allocation or panic, justifying impl-datastructure, datastructure-impl-safe, datastructure-impl-bounds. Correctness is established by test_set_ops_exhaustive, which enumerates all ~4M pairs of values in an 11-bit domain (including bits outside all()) and asserts that named operations agree with operator overloads and that the symmetric operations commute, justifying datastructure-impl-correct, datastructure-impl-tested, unsafe-tested, has-unit-tests, has-integration-tests. Fuzz harnesses are not used, justifying has-fuzz-tests; property-based test frameworks are not used, justifying has-property-tests.

The audit produced no findings. Nothing in the published code or metadata appears malicious or otherwise concerning, justifying is-benign.

Conclusion

bitflags@1.3.2 is a small, mature, no_std, dependency-free macro library with no I/O, no concurrency, no build-time or install-time code execution, and one well-contained unsafe API whose body cannot cause memory unsafety on its own. The crate is the final release of the 1.x line (2.x is the current major version).

Findings

No findings.

Annotations(4)

Cargo.toml

Cargo.toml, line 27-32

[package.metadata.docs.rs]
features = ["example_generated"]
[dependencies.compiler_builtins]
version = "0.1.2"
optional = true

Runtime dependencies are both optional and only enabled by the rustc-dep-of-std feature: rustc-std-workspace-core (aliased core) and compiler_builtins. Both are internal scaffolding for building the crate as part of the Rust standard library workspace and are inert for ordinary consumers. No [lib] proc-macro = true, no build-dependencies.

src/lib.rs

src/lib.rs, line 278-282

#![cfg_attr(not(test), no_std)]
#![doc(html_root_url = "https://docs.rs/bitflags/1.3.2")]

#[doc(hidden)]
pub extern crate core as _core;

Crate is #![no_std] except in the test build. pub extern crate core as _core re-exports core under the _core alias so the bitflags! macro expansion can reference $crate::_core::... paths from any consumer crate without depending on std. Imports throughout lib.rs are limited to that _core alias plus a single std::collections::hash_map::DefaultHasher in the #[cfg(test)] module; no std::net, std::fs, std::process, std::env, std::thread, or async. Justifies uses-network, uses-filesystem, uses-environment, uses-exec, uses-concurrency, uses-jit, uses-interpreter, uses-crypto, impl-crypto, impl-parser, impl-interpreter, impl-jit, impl-protocol, impl-algorithm, impl-concurrency.

src/lib.rs, line 349-382

#[macro_export(local_inner_macros)]
macro_rules! bitflags {
    (
        $(#[$outer:meta])*
        $vis:vis struct $BitFlags:ident: $T:ty {
            $(
                $(#[$inner:ident $($args:tt)*])*
                const $Flag:ident = $value:expr;
            )*
        }

        $($t:tt)*
    ) => {
        $(#[$outer])*
        #[derive(Copy, PartialEq, Eq, Clone, PartialOrd, Ord, Hash)]
        $vis struct $BitFlags {
            bits: $T,
        }

        __impl_bitflags! {
            $BitFlags: $T {
                $(
                    $(#[$inner $($args)*])*
                    $Flag = $value;
                )*
            }
        }

        bitflags! {
            $($t)*
        }
    };
    () => {};
}

bitflags! is a declarative macro_rules! macro (not a procedural macro). The expansion is read by rustc at compile time inside the consumer's crate; no code from this crate executes at the consumer's compile time, justifying has-build-exec. The macro generates a #[derive(Copy, PartialEq, Eq, Clone, PartialOrd, Ord, Hash)] struct with a single bits: T field plus an __impl_bitflags! invocation.

src/lib.rs, line 433-495

        impl $crate::_core::fmt::Debug for $BitFlags {
            fn fmt(&self, f: &mut $crate::_core::fmt::Formatter) -> $crate::_core::fmt::Result {
                // This convoluted approach is to handle #[cfg]-based flag
                // omission correctly. For example it needs to support:
                //
                //    #[cfg(unix)] const A: Flag = /* ... */;
                //    #[cfg(windows)] const B: Flag = /* ... */;

                // Unconditionally define a check for every flag, even disabled
                // ones.
                #[allow(non_snake_case)]
                trait __BitFlags {
                    $(
                        #[inline]
                        fn $Flag(&self) -> bool { false }
                    )*
                }

                // Conditionally override the check for just those flags that
                // are not #[cfg]ed away.
                #[allow(non_snake_case)]
                impl __BitFlags for $BitFlags {
                    $(
                        __impl_bitflags! {
                            #[allow(deprecated)]
                            #[inline]
                            $(? #[$attr $($args)*])*
                            fn $Flag(&self) -> bool {
                                if Self::$Flag.bits == 0 && self.bits != 0 {
                                    false
                                } else {
                                    self.bits & Self::$Flag.bits == Self::$Flag.bits
                                }
                            }
                        }
                    )*
                }

                let mut first = true;
                $(
                    if <Self as __BitFlags>::$Flag(self) {
                        if !first {
                            f.write_str(" | ")?;
                        }
                        first = false;
                        f.write_str($crate::_core::stringify!($Flag))?;
                    }
                )*
                let extra_bits = self.bits & !Self::all().bits();
                if extra_bits != 0 {
                    if !first {
                        f.write_str(" | ")?;
                    }
                    first = false;
                    f.write_str("0x")?;
                    $crate::_core::fmt::LowerHex::fmt(&extra_bits, f)?;
                }
                if first {
                    f.write_str("(empty)")?;
                }
                Ok(())
            }
        }

Generated Debug implementation. Uses a helper trait __BitFlags defined per-call-site so that flags omitted by #[cfg] are reported as absent rather than triggering compile errors. Trailing logic at lines 481-489 prints any extra_bits (bits set in self but not in Self::all()) as a hex suffix, which is the source of the "A | 0xb8" formatting exercised by test_debug. Pure-text I/O via fmt::Formatter; no panics in the formatting path. Supports datastructure-impl-correct.

src/lib.rs, line 569-581

            ///
            /// # Safety
            ///
            /// The caller of the `bitflags!` macro can chose to allow or
            /// disallow extra bits for their bitflags type.
            ///
            /// The caller of `from_bits_unchecked()` has to ensure that
            /// all bits correspond to a defined flag or that extra bits
            /// are valid for this bitflags type.
            #[inline]
            pub const unsafe fn from_bits_unchecked(bits: $T) -> Self {
                Self { bits }
            }

Sole unsafe keyword in non-test code, justifying uses-unsafe. The body is Self { bits } — a safe struct literal — but the function is marked unsafe fn so that callers acknowledge that the resulting value may contain bits outside Self::all(), which downstream code may rely on as a (logical, not memory) invariant. The block carries a # Safety doc explaining the contract (justifying unsafe-documented). The body performs no operations that could violate memory safety regardless of input (justifying unsafe-safe), and the API is the documented opt-out for bit validation — there is no safer way to construct a flags value containing unknown bits, justifying unsafe-minimal.

src/lib.rs, line 517-723

        #[allow(dead_code)]
        impl $BitFlags {
            $(
                $(#[$attr $($args)*])*
                pub const $Flag: Self = Self { bits: $value };
            )*

            /// Returns an empty set of flags.
            #[inline]
            pub const fn empty() -> Self {
                Self { bits: 0 }
            }

            /// Returns the set containing all flags.
            #[inline]
            pub const fn all() -> Self {
                __impl_all_bitflags! {
                    $BitFlags: $T {
                        $(
                            $(#[$attr $($args)*])*
                            $Flag = $value;
                        )*
                    }
                }
            }

            /// Returns the raw value of the flags currently stored.
            #[inline]
            pub const fn bits(&self) -> $T {
                self.bits
            }

            /// Convert from underlying bit representation, unless that
            /// representation contains bits that do not correspond to a flag.
            #[inline]
            pub const fn from_bits(bits: $T) -> $crate::_core::option::Option<Self> {
                if (bits & !Self::all().bits()) == 0 {
                    $crate::_core::option::Option::Some(Self { bits })
                } else {
                    $crate::_core::option::Option::None
                }
            }

            /// Convert from underlying bit representation, dropping any bits
            /// that do not correspond to flags.
            #[inline]
            pub const fn from_bits_truncate(bits: $T) -> Self {
                Self { bits: bits & Self::all().bits }
            }

            /// Convert from underlying bit representation, preserving all
            /// bits (even those not corresponding to a defined flag).
            ///
            /// # Safety
            ///
            /// The caller of the `bitflags!` macro can chose to allow or
            /// disallow extra bits for their bitflags type.
            ///
            /// The caller of `from_bits_unchecked()` has to ensure that
            /// all bits correspond to a defined flag or that extra bits
            /// are valid for this bitflags type.
            #[inline]
            pub const unsafe fn from_bits_unchecked(bits: $T) -> Self {
                Self { bits }
            }

            /// Returns `true` if no flags are currently stored.
            #[inline]
            pub const fn is_empty(&self) -> bool {
                self.bits() == Self::empty().bits()
            }

            /// Returns `true` if all flags are currently set.
            #[inline]
            pub const fn is_all(&self) -> bool {
                Self::all().bits | self.bits == self.bits
            }

            /// Returns `true` if there are flags common to both `self` and `other`.
            #[inline]
            pub const fn intersects(&self, other: Self) -> bool {
                !(Self { bits: self.bits & other.bits}).is_empty()
            }

            /// Returns `true` if all of the flags in `other` are contained within `self`.
            #[inline]
            pub const fn contains(&self, other: Self) -> bool {
                (self.bits & other.bits) == other.bits
            }

            /// Inserts the specified flags in-place.
            #[inline]
            pub fn insert(&mut self, other: Self) {
                self.bits |= other.bits;
            }

            /// Removes the specified flags in-place.
            #[inline]
            pub fn remove(&mut self, other: Self) {
                self.bits &= !other.bits;
            }

            /// Toggles the specified flags in-place.
            #[inline]
            pub fn toggle(&mut self, other: Self) {
                self.bits ^= other.bits;
            }

            /// Inserts or removes the specified flags depending on the passed value.
            #[inline]
            pub fn set(&mut self, other: Self, value: bool) {
                if value {
                    self.insert(other);
                } else {
                    self.remove(other);
                }
            }

            /// Returns the intersection between the flags in `self` and
            /// `other`.
            ///
            /// Specifically, the returned set contains only the flags which are
            /// present in *both* `self` *and* `other`.
            ///
            /// This is equivalent to using the `&` operator (e.g.
            /// [`ops::BitAnd`]), as in `flags & other`.
            ///
            /// [`ops::BitAnd`]: https://doc.rust-lang.org/std/ops/trait.BitAnd.html
            #[inline]
            #[must_use]
            pub const fn intersection(self, other: Self) -> Self {
                Self { bits: self.bits & other.bits }
            }

            /// Returns the union of between the flags in `self` and `other`.
            ///
            /// Specifically, the returned set contains all flags which are
            /// present in *either* `self` *or* `other`, including any which are
            /// present in both (see [`Self::symmetric_difference`] if that
            /// is undesirable).
            ///
            /// This is equivalent to using the `|` operator (e.g.
            /// [`ops::BitOr`]), as in `flags | other`.
            ///
            /// [`ops::BitOr`]: https://doc.rust-lang.org/std/ops/trait.BitOr.html
            #[inline]
            #[must_use]
            pub const fn union(self, other: Self) -> Self {
                Self { bits: self.bits | other.bits }
            }

            /// Returns the difference between the flags in `self` and `other`.
            ///
            /// Specifically, the returned set contains all flags present in
            /// `self`, except for the ones present in `other`.
            ///
            /// It is also conceptually equivalent to the "bit-clear" operation:
            /// `flags & !other` (and this syntax is also supported).
            ///
            /// This is equivalent to using the `-` operator (e.g.
            /// [`ops::Sub`]), as in `flags - other`.
            ///
            /// [`ops::Sub`]: https://doc.rust-lang.org/std/ops/trait.Sub.html
            #[inline]
            #[must_use]
            pub const fn difference(self, other: Self) -> Self {
                Self { bits: self.bits & !other.bits }
            }

            /// Returns the [symmetric difference][sym-diff] between the flags
            /// in `self` and `other`.
            ///
            /// Specifically, the returned set contains the flags present which
            /// are present in `self` or `other`, but that are not present in
            /// both. Equivalently, it contains the flags present in *exactly
            /// one* of the sets `self` and `other`.
            ///
            /// This is equivalent to using the `^` operator (e.g.
            /// [`ops::BitXor`]), as in `flags ^ other`.
            ///
            /// [sym-diff]: https://en.wikipedia.org/wiki/Symmetric_difference
            /// [`ops::BitXor`]: https://doc.rust-lang.org/std/ops/trait.BitXor.html
            #[inline]
            #[must_use]
            pub const fn symmetric_difference(self, other: Self) -> Self {
                Self { bits: self.bits ^ other.bits }
            }

            /// Returns the complement of this set of flags.
            ///
            /// Specifically, the returned set contains all the flags which are
            /// not set in `self`, but which are allowed for this type.
            ///
            /// Alternatively, it can be thought of as the set difference
            /// between [`Self::all()`] and `self` (e.g. `Self::all() - self`)
            ///
            /// This is equivalent to using the `!` operator (e.g.
            /// [`ops::Not`]), as in `!flags`.
            ///
            /// [`Self::all()`]: Self::all
            /// [`ops::Not`]: https://doc.rust-lang.org/std/ops/trait.Not.html
            #[inline]
            #[must_use]
            pub const fn complement(self) -> Self {
                Self::from_bits_truncate(!self.bits)
            }

Generated public API on $BitFlags. Every method is a thin wrapper over bitwise ops on the bits field: union is bitwise OR, intersection is bitwise AND, difference is AND-with-complement, symmetric_difference is XOR, complement is from_bits_truncate applied to the bitwise NOT of the bits field. All are #[inline] and most are const fn. The operations are O(1) on the primitive integer type, with no allocation, panic, or unsafe operations, justifying impl-datastructure, datastructure-impl-safe, datastructure-impl-bounds.

src/lib.rs, line 1257-1366

    fn test_set_ops_exhaustive() {
        // Define a flag that contains gaps to help exercise edge-cases,
        // especially around "unknown" flags (e.g. ones outside of `all()`
        // `from_bits_unchecked`).
        // - when lhs and rhs both have different sets of unknown flags.
        // - unknown flags at both ends, and in the middle
        // - cases with "gaps".
        bitflags! {
            struct Test: u16 {
                // Intentionally no `A`
                const B = 0b000000010;
                // Intentionally no `C`
                const D = 0b000001000;
                const E = 0b000010000;
                const F = 0b000100000;
                const G = 0b001000000;
                // Intentionally no `H`
                const I = 0b100000000;
            }
        }
        let iter_test_flags =
            || (0..=0b111_1111_1111).map(|bits| unsafe { Test::from_bits_unchecked(bits) });

        for a in iter_test_flags() {
            assert_eq!(
                a.complement(),
                Test::from_bits_truncate(!a.bits),
                "wrong result: !({:?})",
                a,
            );
            assert_eq!(a.complement(), !a, "named != op: !({:?})", a);
            for b in iter_test_flags() {
                // Check that the named operations produce the expected bitwise
                // values.
                assert_eq!(
                    a.union(b).bits,
                    a.bits | b.bits,
                    "wrong result: `{:?}` | `{:?}`",
                    a,
                    b,
                );
                assert_eq!(
                    a.intersection(b).bits,
                    a.bits & b.bits,
                    "wrong result: `{:?}` & `{:?}`",
                    a,
                    b,
                );
                assert_eq!(
                    a.symmetric_difference(b).bits,
                    a.bits ^ b.bits,
                    "wrong result: `{:?}` ^ `{:?}`",
                    a,
                    b,
                );
                assert_eq!(
                    a.difference(b).bits,
                    a.bits & !b.bits,
                    "wrong result: `{:?}` - `{:?}`",
                    a,
                    b,
                );
                // Note: Difference is checked as both `a - b` and `b - a`
                assert_eq!(
                    b.difference(a).bits,
                    b.bits & !a.bits,
                    "wrong result: `{:?}` - `{:?}`",
                    b,
                    a,
                );
                // Check that the named set operations are equivalent to the
                // bitwise equivalents
                assert_eq!(a.union(b), a | b, "named != op: `{:?}` | `{:?}`", a, b,);
                assert_eq!(
                    a.intersection(b),
                    a & b,
                    "named != op: `{:?}` & `{:?}`",
                    a,
                    b,
                );
                assert_eq!(
                    a.symmetric_difference(b),
                    a ^ b,
                    "named != op: `{:?}` ^ `{:?}`",
                    a,
                    b,
                );
                assert_eq!(a.difference(b), a - b, "named != op: `{:?}` - `{:?}`", a, b,);
                // Note: Difference is checked as both `a - b` and `b - a`
                assert_eq!(b.difference(a), b - a, "named != op: `{:?}` - `{:?}`", b, a,);
                // Verify that the operations which should be symmetric are
                // actually symmetric.
                assert_eq!(a.union(b), b.union(a), "asymmetry: `{:?}` | `{:?}`", a, b,);
                assert_eq!(
                    a.intersection(b),
                    b.intersection(a),
                    "asymmetry: `{:?}` & `{:?}`",
                    a,
                    b,
                );
                assert_eq!(
                    a.symmetric_difference(b),
                    b.symmetric_difference(a),
                    "asymmetry: `{:?}` ^ `{:?}`",
                    a,
                    b,
                );
            }
        }
    }

test_set_ops_exhaustive enumerates all 2048×2048 = ~4M pairs of Test: u16 flag values (constructed via from_bits_unchecked so unknown bits are covered) and asserts the algebraic invariants: union ↔ |, intersection ↔ &, difference ↔ & !, symmetric_difference ↔ ^, the named operations agree with the operator overloads, and the symmetric operations are commutative. This is exhaustive testing over the 11-bit domain and provides stronger evidence than random property tests would for the same surface, justifying datastructure-impl-tested, unsafe-tested. Property-based test frameworks (proptest/quickcheck) are not used, justifying has-property-tests.

src/lib.rs, line 937-980

#[cfg(test)]
mod tests {
    use std::collections::hash_map::DefaultHasher;
    use std::hash::{Hash, Hasher};

    bitflags! {
        #[doc = "> The first principle is that you must not fool yourself — and"]
        #[doc = "> you are the easiest person to fool."]
        #[doc = "> "]
        #[doc = "> - Richard Feynman"]
        #[derive(Default)]
        struct Flags: u32 {
            const A = 0b00000001;
            #[doc = "<pcwalton> macros are way better at generating code than trans is"]
            const B = 0b00000010;
            const C = 0b00000100;
            #[doc = "* cmr bed"]
            #[doc = "* strcat table"]
            #[doc = "<strcat> wait what?"]
            const ABC = Self::A.bits | Self::B.bits | Self::C.bits;
        }

        struct _CfgFlags: u32 {
            #[cfg(unix)]
            const _CFG_A = 0b01;
            #[cfg(windows)]
            const _CFG_B = 0b01;
            #[cfg(unix)]
            const _CFG_C = Self::_CFG_A.bits | 0b10;
        }

        struct AnotherSetOfFlags: i8 {
            const ANOTHER_FLAG = -1_i8;
        }

        struct LongFlags: u32 {
            const LONG_A = 0b1111111111111111;
        }
    }

    bitflags! {
        struct EmptyFlags: u32 {
        }
    }

Test module entry: defines Flags, _CfgFlags, AnotherSetOfFlags: i8 (signed-type coverage), LongFlags: u32, and EmptyFlags (no-const coverage). The AnotherSetOfFlags case asserts behaviour at the signed-integer corner (ANOTHER_FLAG = -1_i8 ↔ all bits set), and test_u128_bitflags further exercises the u128 block type. Supports has-unit-tests.

tests/basic.rs

Integration test compiled in a separate #![no_std] crate, verifying that the macro expansion is usable without std in scope and without the in-crate tests module's helper traits. Supports has-integration-tests.

tests/compile.rs

Dev-only trybuild-based UI tests over tests/compile-fail/ and tests/compile-pass/ that check the macro emits expected compiler errors and accepted forms (visibility, repr, redefinition, impls). Uses std::fs/walkdir to manage .stderr.beta fixtures, but this code runs only during cargo test from dev-dependencies and is not part of the published library surface — does not contribute to uses-filesystem (which is asserted on the published runtime code).