From a8bc88209227a8dad6aa5c6d1239b335b2a3f7ee Mon Sep 17 00:00:00 2001 From: adrdsouza <102761952+adrdsouza@users.noreply.github.com> Date: Sat, 17 Oct 2026 21:13:49 +0530 Subject: [PATCH] Correct JPEG XL test target guard & tracked-budget claims (#1517) Pitch-Impact: none Co-authored-by: Codex --- crates/codecs/src/jxl.rs | 14 ++++++-------- crates/codecs/tests/roundtrip.rs | 5 ++--- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/crates/codecs/src/jxl.rs b/crates/codecs/src/jxl.rs index d629a1e10..adf59c0aa 100644 --- a/crates/codecs/src/jxl.rs +++ b/crates/codecs/src/jxl.rs @@ -16,9 +16,8 @@ fn err(e: impl std::fmt::Display) -> Error { Error::Malformed(F, e.to_string()) } -/// Bytes the decoder may hold per allowed pixel: four f32 channels, eight times over for frames, -/// extra channels and working buffers. The budget follows `max_pixels`, so oversized frames, extra -/// channels or modular trees inside an image of allowed size still fail (#1517). +/// Tracked allocation budget per allowed pixel for frames, extra channels and modular trees. +/// This bounds allocations covered by `AllocTracker`, not every allocation made by the decoder (#1517). const TRACKED_BYTES_PER_PIXEL: u64 = 4 * 4 * 8; /// The allocation budget never drops below this, so small images keep the decoder's fixed @@ -28,15 +27,14 @@ const MIN_TRACKED_BYTES: u64 = 64 << 20; /// Bytes handed to the decoder at a time (the size `JxlImageBuilder::read` reads in). const INPUT_CHUNK: usize = 4096; -/// Parses the header, checks the image size against `opts`, and only then reads the frames, with -/// an allocation budget, so a huge image is refused before any of its pixels are decoded -/// (`JxlImageBuilder::read` loads everything first). +/// Initializes from bounded input, checks dimensions before rendering, then loads remaining frames +/// within a tracked allocation budget. fn open(bytes: &[u8], opts: &DecodeOptions) -> Result { let budget = opts.max_pixels.saturating_mul(TRACKED_BYTES_PER_PIXEL).max(MIN_TRACKED_BYTES); let tracker = AllocTracker::with_limit(usize::try_from(budget).unwrap_or(usize::MAX)); let mut uninit = JxlImage::builder().alloc_tracker(tracker).build_uninit(); - // Small slices, as `read` does: whatever the header stage has buffered when it initializes is - // parsed as frame data right away, so feeding the whole file here would skip the size check. + // Initialization can load already-buffered frame data, so small feeds limit work before + // dimension checking. let mut rest = bytes; let mut window = INPUT_CHUNK; let mut img = loop { diff --git a/crates/codecs/tests/roundtrip.rs b/crates/codecs/tests/roundtrip.rs index 2ab55da31..3391e3a91 100644 --- a/crates/codecs/tests/roundtrip.rs +++ b/crates/codecs/tests/roundtrip.rs @@ -734,10 +734,9 @@ fn jxl_lossless_exact_with_metadata() { assert!(max_diff(&d.image, &want.image) < 1e-5, "16-bit lossless differs by {}", max_diff(&d.image, &want.image)); } -/// #1517: the size limit is checked from the header, before any frame is read, and a -/// file cut anywhere is an error, never a panic. +/// #1517: the pixel limit rejects an oversized image; selected truncations never panic. #[test] -#[cfg(feature = "jxl")] +#[cfg(all(feature = "jxl", not(target_arch = "wasm32")))] fn jxl_size_limit_and_truncation() { let b = encode_jxl(&EncodeImage::rgba8(&gradient(97, 61)), &JxlOptions { lossless: true, ..Default::default() }, &EncodeMeta::default()).unwrap(); let r = decode(&b, DecodeOptions { max_pixels: 97 * 61 - 1, ..Default::default() });