Codecs: never crash — ac3, audio-dsp, opus, mpeg2v, prores, dnx

- ac3: LFE channel taken without `expect`.
- audio-dsp: the channel matrix match is exhaustive (no `unreachable!`); an unknown internal
  parameter reads as 0 (debug-asserted) instead of `panic!`.
- opus: dual-stereo bands and SILK frames take the optional buffer / range decoder with `if let`.
- mpeg2v: 64-bit peek through `get()` + `first_chunk`; the literal VLC table parser no longer
  panics (debug-asserted).
- prores, dnx: IDCT rows through `as_chunks`; ProRes block store and alpha slices without
  unwrap/expect; DNx header timecode through `get()`.
- dnx: `Encoder::new` returns `Result` instead of panicking on a raster the profile can't code
  (e.g. 0x0), with a regression test; missing CID table is an error.
- Each crate denies unwrap/expect/panic/unimplemented/todo/unreachable outside tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Brandon Thomas 2026-10-04 11:43:19 -04:00
parent 7b96f22684
commit 46b286edaf
19 changed files with 41 additions and 29 deletions

View File

@ -13,6 +13,8 @@
//!
//! E-AC-3 (bsid 11-16) is recognised and refused.
#![cfg_attr(not(test), deny(clippy::unwrap_used, clippy::expect_used, clippy::panic, clippy::unimplemented, clippy::todo, clippy::unreachable))]
mod tables;
use std::sync::OnceLock;
@ -1030,7 +1032,7 @@ impl Decoder {
/// Coded channel order (Table 5.8) → WAV / SMPTE order: fronts L R C, LFE, surrounds.
fn wav_order(mut pcm: Vec<Vec<f32>>, acmod: usize, lfeon: bool) -> Vec<Vec<f32>> {
let nf = NFCHANS[acmod];
let lfe = lfeon.then(|| pcm.pop().expect("lfe"));
let lfe = if lfeon { pcm.pop() } else { None };
let mut fbw = pcm;
// acmod 3, 5, 7 code L C R …: move C after R
if acmod & 1 == 1 && acmod != 1 {

View File

@ -123,7 +123,7 @@ pub fn matrix(from: Layout, to: Layout, mixdown: Mixdown) -> Vec<Vec<f32>> {
let (ni, no) = (from.channels(), to.channels());
let mut m = vec![vec![0.0f32; ni]; no];
match (from, to) {
(a, b) if a == b => {
(Layout::Mono, Layout::Mono) | (Layout::Stereo, Layout::Stereo) | (Layout::Surround51, Layout::Surround51) => {
for (i, row) in m.iter_mut().enumerate() {
row[i] = 1.0;
}
@ -159,7 +159,6 @@ pub fn matrix(from: Layout, to: Layout, mixdown: Mixdown) -> Vec<Vec<f32>> {
m[1][LFE] = K;
}
}
_ => unreachable!("all layout pairs are covered"),
}
m
}

View File

@ -16,6 +16,8 @@
//! denormals never appear (no FTZ/DAZ CPU flags needed, which keeps the crate `unsafe`-free and
//! portable to wasm).
#![cfg_attr(not(test), deny(clippy::unwrap_used, clippy::expect_used, clippy::panic, clippy::unimplemented, clippy::todo, clippy::unreachable))]
pub mod biquad;
pub mod channels;
pub mod design;
@ -169,12 +171,11 @@ impl ParamValues {
pub(crate) fn get(&self, id: &str) -> Option<f32> {
self.index_of(id).map(|i| self.values[i])
}
/// Value of a parameter that is known to exist (panics otherwise — programming error).
/// Value of a parameter that is known to exist (0 otherwise — a programming error caught by
/// debug builds).
pub(crate) fn v(&self, id: &str) -> f32 {
match self.get(id) {
Some(v) => v,
None => panic!("unknown parameter {id}"),
}
debug_assert!(self.get(id).is_some(), "unknown parameter {id}");
self.get(id).unwrap_or(0.0)
}
pub(crate) fn on(&self, id: &str) -> bool {
self.v(id) >= 0.5

View File

@ -38,7 +38,7 @@ let f = decode_frame_with(&sample, &DecodeOptions { threads: false, ..Default::d
let hdr: FrameHeader = probe(&sample)?; // cid, raster, depth, chroma…
let name = cid_name(hdr.cid); // "DNxHR HQX", "DNxHD 1080i 8-bit (CID 1242)"
let mut enc = Encoder::new(Profile::Hq, 1920, 1080); // or Encoder::with_config(EncoderConfig { .. })
let mut enc = Encoder::new(Profile::Hq, 1920, 1080)?; // or Encoder::with_config(EncoderConfig { .. })
let sample: Vec<u8> = enc.encode(&frame)?; // exactly enc.frame_size() bytes (CBR)
```

View File

@ -19,8 +19,7 @@ pub(crate) fn idct8x8(block: &mut [f32; 64]) {
let mut nz = [false; 8];
let mut dc_only = true;
// Horizontal pass: t[r][x] = Σ_u F[r][u] · B[u][x]
for r in 0..8 {
let c: &[f32; 8] = block[r * 8..r * 8 + 8].try_into().unwrap();
for (r, c) in block.as_chunks::<8>().0.iter().enumerate() {
if c[1..].iter().all(|&v| v == 0.0) {
if c[0] != 0.0 {
t[r] = [c[0] * BASIS[0][0]; 8];

View File

@ -61,8 +61,9 @@ struct Prepared {
}
impl Encoder {
pub fn new(profile: Profile, width: u32, height: u32) -> Encoder {
Encoder::with_config(EncoderConfig::new(profile, width, height)).expect("valid default configuration")
/// An encoder with the default configuration; fails on rasters the profile can't code.
pub fn new(profile: Profile, width: u32, height: u32) -> Result<Encoder> {
Encoder::with_config(EncoderConfig::new(profile, width, height))
}
pub fn with_config(cfg: EncoderConfig) -> Result<Encoder> {
@ -79,7 +80,7 @@ impl Encoder {
if !ok_depth {
return Err(Error::Input(format!("{} does not support {}-bit", cfg.profile.name(), cfg.bit_depth)));
}
let info = cid_info(cfg.profile.cid()).expect("RI CID");
let info = cid_info(cfg.profile.cid()).ok_or_else(|| Error::Input(format!("no compression table for {}", cfg.profile.name())))?;
let frame_size = cfg.profile.frame_size(cfg.width, cfg.height);
let header_size = header::header_size_for(cfg.height);
Ok(Encoder { cfg, info, frame_size, header_size, last_q: 8 })

View File

@ -152,7 +152,7 @@ pub fn parse(d: &[u8]) -> Result<FrameHeader> {
2 => ColorVolume::Bt2020Cl,
_ => ColorVolume::OutOfBand,
};
let timecode = (d[0x30] & 0x80 != 0).then(|| d[0x31..0x39].try_into().unwrap());
let timecode = if d[0x30] & 0x80 != 0 { d.get(0x31..).and_then(|t| t.first_chunk::<8>()).copied() } else { None };
let mb_rows = be16(d, 0x16C);
if width == 0 || lines == 0 || width > 16384 || lines > 16384 {
return Err(Error::Invalid("raster size"));

View File

@ -11,6 +11,8 @@
//!
//! See the crate README for accuracy, performance and limitations.
#![cfg_attr(not(test), deny(clippy::unwrap_used, clippy::expect_used, clippy::panic, clippy::unimplemented, clippy::todo, clippy::unreachable))]
mod bits;
mod dct;
mod decode;

View File

@ -68,17 +68,20 @@ fn roundtrip_all_profiles() {
#[test]
fn rejects_bad_input() {
let mut enc = Encoder::new(Profile::Hq, 64, 64);
let mut enc = Encoder::new(Profile::Hq, 64, 64).unwrap();
assert!(enc.encode(&Frame::new(32, 64, ChromaFormat::Yuv422, 8, false)).is_err());
assert!(enc.encode(&Frame::new(64, 64, ChromaFormat::Yuv444, 8, false)).is_err());
assert!(Encoder::with_config(EncoderConfig { bit_depth: 10, ..EncoderConfig::new(Profile::Hq, 64, 64) }).is_err());
assert!(decode_frame(&[0u8; 100]).is_err());
assert!(decode_frame(&[0u8; 1000]).is_err());
// An unsupported raster is an error, not a panic.
assert!(Encoder::new(Profile::Hq, 0, 0).is_err());
assert!(Encoder::new(Profile::Hq, 20_000, 64).is_err());
}
#[test]
fn corrupt_streams_never_panic() {
let mut enc = Encoder::new(Profile::Hq, 256, 128);
let mut enc = Encoder::new(Profile::Hq, 256, 128).unwrap();
let good = enc.encode(&picture(256, 128, ChromaFormat::Yuv422, 8, 1)).unwrap();
let mut seed = 0x1234_5678u32;
let mut rnd = || {

View File

@ -16,8 +16,8 @@ impl<'a> Bits<'a> {
#[inline(always)]
pub fn peek32(&self) -> u32 {
let byte = self.pos >> 3;
let w = if byte + 8 <= self.data.len() {
u64::from_be_bytes(self.data[byte..byte + 8].try_into().expect("8 bytes"))
let w = if let Some(&b) = self.data.get(byte..).and_then(|d| d.first_chunk::<8>()) {
u64::from_be_bytes(b)
} else {
let mut b = [0u8; 8];
if byte < self.data.len() {

View File

@ -23,6 +23,8 @@
//! # Ok::<(), filmcraft_mpeg2v::Error>(())
//! ```
#![cfg_attr(not(test), deny(clippy::unwrap_used, clippy::expect_used, clippy::panic, clippy::unimplemented, clippy::todo, clippy::unreachable))]
mod bits;
pub mod headers;
mod idct;

View File

@ -32,7 +32,8 @@ fn parse_code(s: &str) -> (u32, u32) {
n += 1;
}
' ' => {}
_ => panic!("bad code {s}"),
// Codes are literals in this crate; the table tests cover them.
_ => debug_assert!(false, "bad code {s}"),
}
}
(v, n)

View File

@ -784,8 +784,8 @@ pub fn quant_all_bands(
}
let out_off = xo - norm_offset;
let x = &mut x_buf[xo..xo + n];
if dual_stereo {
let y = &mut y_buf.as_deref_mut().expect("stereo")[xo..xo + n];
if dual_stereo && let Some(y_buf) = y_buf.as_deref_mut() {
let y = &mut y_buf[xo..xo + n];
let lb = effective_lowband.map(|e| {
scratch[..n].copy_from_slice(&norm[e..e + n]);
&mut scratch[..n]

View File

@ -2,6 +2,7 @@
//!
//! Layer L0: no dependencies beyond `std`; no `unsafe`; builds for `wasm32-unknown-unknown`.
#![cfg_attr(not(test), deny(clippy::unwrap_used, clippy::expect_used, clippy::panic, clippy::unimplemented, clippy::todo, clippy::unreachable))]
// The fixed-point parts mirror the normative integer arithmetic; keep C-like shift expressions.
#![allow(clippy::precedence, clippy::int_plus_one)]

View File

@ -211,8 +211,7 @@ impl Channel {
(LostFlag::Lbrr, Some(_)) => self.lbrr_flags[self.n_frames_decoded],
_ => false,
};
if decode {
let dec = dec.expect("range decoder");
if decode && let Some(dec) = dec {
self.decode_indices(dec, self.n_frames_decoded, lost == LostFlag::Lbrr, cond);
let mut pulses = [0i32; MAX_FRAME + 16];
decode_pulses(dec, &mut pulses, self.indices.signal_type, self.indices.quant_offset_type, l);

View File

@ -19,8 +19,7 @@ pub(crate) fn idct8x8(block: &mut [f32; 64]) {
let mut nz = [false; 8];
let mut dc_only = true;
// Horizontal pass: t[r][x] = Σ_u F[r][u] · B[u][x]
for r in 0..8 {
let c: &[f32; 8] = block[r * 8..r * 8 + 8].try_into().unwrap();
for (r, c) in block.as_chunks::<8>().0.iter().enumerate() {
if c[1..].iter().all(|&v| v == 0.0) {
if c[0] != 0.0 {
t[r] = [c[0] * BASIS[0][0]; 8];

View File

@ -476,8 +476,9 @@ fn put_block(f: &[f32; 64], plane: &mut [u16], stride: usize, x0: usize, y0: usi
if xn == 8 && (y0 + 7) * step + parity < rows {
for yy in 0..8 {
let o = ((y0 + yy) * step + parity) * stride + x0;
let row: &mut [u16; 8] = (&mut plane[o..o + 8]).try_into().unwrap();
let src: &[f32; 8] = f[yy * 8..yy * 8 + 8].try_into().unwrap();
let (Some(row), Some(src)) = (plane.get_mut(o..).and_then(|p| p.first_chunk_mut::<8>()), f.as_chunks::<8>().0.get(yy)) else {
return;
};
for x in 0..8 {
row[x] = conv.conv(src[x]);
}

View File

@ -429,7 +429,7 @@ fn write_slice(sd: &SliceData, q: u8) -> Vec<u8> {
/// Encode the slice's alpha samples (16-bit coding) in raster order over its 16 lines.
fn encode_alpha_slice(src: &Source, x0: usize, y0: usize, mbs: usize) -> Vec<u8> {
let a = src.frame.alpha.as_ref().expect("alpha checked");
let Some(a) = src.frame.alpha.as_ref() else { return Vec::new() };
let d = src.frame.bit_depth as u32;
let to16 = |v: u16| -> u32 {
let v = v as u32 & ((1 << d) - 1);

View File

@ -9,6 +9,8 @@
//!
//! See the crate README for bitstream details, accuracy and limitations.
#![cfg_attr(not(test), deny(clippy::unwrap_used, clippy::expect_used, clippy::panic, clippy::unimplemented, clippy::todo, clippy::unreachable))]
mod bits;
mod dct;
mod decode;