GitoxideLabs/gitoxide · info

io::sink() to never fail

Error message

io::sink() to never fail

What it means

`Entry::header::size()` computes a pack entry header length by writing to `io::sink()`, which can never fail; any error from `write_to` would indicate a bug, so it `.expect()`s success. Users see this panic only if `write_to` unexpectedly errors, i.e. an internal invariant broken (e.g. a size that cannot be encoded).

Solutions

  1. Report upstream with the decompressed_size value that triggered it
  2. Use `checked_*` / `Entry::header_size()` on decoded entries as a safer alternative
  3. Upgrade to the latest gitoxide version in case the bug is already fixed

Example fix

// before
let sz = header::Entry::Base.size(size); // panics on internal bug
// after
let mut buf = Vec::new();
let sz = header.write_to(size, &mut buf)?; // propagate any error instead of panicking
Defensive patterns

Strategy: try-catch

Try / catch

let size = std::panic::catch_unwind(|| header::Entry::Base.size(decompressed_size)).unwrap_or(0);

Prevention

When it happens

Trigger: Calling `gix_pack::data::entry::header::Entry::size(decompressed_size)` — panics only if the internal LEB64 encoder fails, which should be impossible for valid u64 sizes.

Common situations: Practically never hit in the field; encountered only during development, fuzzing, or after library modifications.

Understand the failure class

Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.

Related errors


AI-assisted analysis of GitoxideLabs/gitoxide@e73179060b (2026-09-08). Data as JSON: /api/errors/b733356388258f91. Report an issue: GitHub.

Appendix: source

Thrown at gix-pack/src/data/entry/header.rs:120

                let mut buf = [0u8; 10];
                let buf = leb64_encode(*base_distance, &mut buf);
                out.write_all(buf)?;
                written += buf.len();
            }
            Blob | Tree | Commit | Tag => {}
        }
        Ok(written)
    }

    /// The size of the header in bytes when written in canonical form.
    ///
    /// This is the number of bytes [`Self::write_to()`] would emit for `decompressed_size`.
    /// It does not inspect existing pack bytes and therefore does not preserve non-canonical
    /// overlong size encodings. Use [`data::Entry::header_size()`] for decoded entries when the
    /// result has to match the header length present in the pack.
    pub fn size(&self, decompressed_size: u64) -> usize {
        self.write_to(decompressed_size, &mut io::sink())
            .expect("io::sink() to never fail")
    }
}

#[inline]
fn leb64_encode(mut n: u64, buf: &mut [u8; 10]) -> &[u8] {
    let mut bytes_written = 1;
    buf[buf.len() - 1] = n as u8 & 0b0111_1111;
    for out in buf.iter_mut().rev().skip(1) {
        n >>= 7;
        if n == 0 {
            break;
        }
        n -= 1;
        *out = 0b1000_0000 | (n as u8 & 0b0111_1111);
        bytes_written += 1;
    }
    debug_assert_eq!(n, 0, "BUG: buffer must be large enough to hold a 64 bit integer");
    &buf[buf.len() - bytes_written..]

View on GitHub (pinned to e73179060b)