format: no truncating u64 -> usize casts
Every `u64 as usize` cast in clawhdf5-format (115 on wasm32) now goes through addr::to_usize for values read from the file — addresses, lengths, counts, dimensions: FormatError::Overflow where the value does not fit instead of wrapping onto another part of the file on a 32-bit target — or addr::saturating_usize for counts bounded by something in memory (codec progress counters, writer sizes), which fail a bounds check or allocation rather than wrap. A chunk whose offset does not fit lies outside the dataset and is skipped; partial reads treat such an offset as out of the buffers. On 64-bit targets nothing changes. scripts/check-32bit-casts.sh (run by ci-test.sh) lints the wasm32 build with clippy's cast_possible_truncation and fails on any u64 -> usize finding; before this commit it listed 115. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
@@ -3,6 +3,7 @@
|
||||
//! Produces valid HDF5 files with v3 superblock, v2 object headers,
|
||||
//! link messages, contiguous datasets, inline and dense attributes.
|
||||
|
||||
use crate::addr::saturating_usize;
|
||||
#[cfg(not(feature = "std"))]
|
||||
use alloc::{format, vec, vec::Vec};
|
||||
|
||||
@@ -336,7 +337,7 @@ pub(crate) fn build_single_block_fractal_heap(
|
||||
|
||||
// An object must fit one direct block: the writer has no huge-object
|
||||
// path, and libhdf5 cannot read an object that overruns its block.
|
||||
let max_managed = max_direct_block_size as usize - dblock_header_size;
|
||||
let max_managed = saturating_usize(max_direct_block_size) - dblock_header_size;
|
||||
if let Some(big) = serialized.iter().find(|s| s.len() > max_managed) {
|
||||
return Err(FormatError::SerializationError(format!(
|
||||
"a {}-byte message cannot go in dense storage: a fractal heap \
|
||||
@@ -392,7 +393,7 @@ pub(crate) fn build_single_block_fractal_heap(
|
||||
let dblock_addr = frhp_addr + frhp_size as u64;
|
||||
let btree_addr = dblock_addr + starting_block_size;
|
||||
|
||||
let data_space = starting_block_size as usize - dblock_header_size;
|
||||
let data_space = saturating_usize(starting_block_size) - dblock_header_size;
|
||||
let free_space = data_space - total_data_size;
|
||||
|
||||
// Build fractal heap header
|
||||
@@ -428,7 +429,7 @@ pub(crate) fn build_single_block_fractal_heap(
|
||||
debug_assert_eq!(frhp.len(), frhp_size);
|
||||
|
||||
// Build direct block: header (with checksum) + data + padding
|
||||
let mut dblock = Vec::with_capacity(starting_block_size as usize);
|
||||
let mut dblock = Vec::with_capacity(saturating_usize(starting_block_size));
|
||||
dblock.extend_from_slice(b"FHDB");
|
||||
dblock.push(0); // version
|
||||
write_offset(&mut dblock, frhp_addr, OFFSET_SIZE);
|
||||
@@ -446,12 +447,12 @@ pub(crate) fn build_single_block_fractal_heap(
|
||||
}
|
||||
|
||||
// Pad to full block size
|
||||
dblock.resize(starting_block_size as usize, 0);
|
||||
dblock.resize(saturating_usize(starting_block_size), 0);
|
||||
|
||||
// Checksum: computed over entire block with checksum field zeroed
|
||||
let dblock_checksum = crate::checksum::jenkins_lookup3(&dblock);
|
||||
dblock[cksum_pos..cksum_pos + 4].copy_from_slice(&dblock_checksum.to_le_bytes());
|
||||
debug_assert_eq!(dblock.len(), starting_block_size as usize);
|
||||
debug_assert_eq!(dblock.len(), saturating_usize(starting_block_size));
|
||||
|
||||
// Build heap IDs
|
||||
let heap_ids: Vec<Vec<u8>> = obj_offsets
|
||||
@@ -706,7 +707,7 @@ impl HeapIndirectBlock {
|
||||
let cksum_pos = out.len();
|
||||
out.extend_from_slice(&[0u8; 4]); // checksum placeholder
|
||||
out.extend_from_slice(&b.data);
|
||||
out.resize(d + b.size as usize, 0);
|
||||
out.resize(d + saturating_usize(b.size), 0);
|
||||
let cksum = crate::checksum::jenkins_lookup3(&out[d..]);
|
||||
out[cksum_pos..cksum_pos + 4].copy_from_slice(&cksum.to_le_bytes());
|
||||
child += b.size;
|
||||
@@ -740,7 +741,7 @@ impl HeapPacker<'_> {
|
||||
nrows: Option<usize>,
|
||||
) -> Result<HeapIndirectBlock, FormatError> {
|
||||
let geom = self.geom;
|
||||
let width = geom.width as usize;
|
||||
let width = saturating_usize(geom.width);
|
||||
let mut slots = Vec::new();
|
||||
let mut off = heap_offset;
|
||||
let mut row = 0usize;
|
||||
@@ -763,7 +764,8 @@ impl HeapPacker<'_> {
|
||||
// A child whose biggest direct block cannot hold the
|
||||
// next object is skipped whole, not walked.
|
||||
let biggest = geom.row_size(child_rows.min(geom.max_direct_rows()) - 1);
|
||||
if self.objects[self.next].len() > (biggest as usize - geom.dblock_header_size)
|
||||
if self.objects[self.next].len()
|
||||
> (saturating_usize(biggest) - geom.dblock_header_size)
|
||||
{
|
||||
slots.push(HeapSlot::Empty);
|
||||
off += size;
|
||||
@@ -794,7 +796,7 @@ impl HeapPacker<'_> {
|
||||
/// objects as fit; leave it unallocated if not even the next one does.
|
||||
fn fill_direct(&mut self, heap_offset: u64, size: u64) -> HeapSlot {
|
||||
let header = self.geom.dblock_header_size;
|
||||
let capacity = size as usize - header;
|
||||
let capacity = saturating_usize(size) - header;
|
||||
let mut data = Vec::new();
|
||||
while let Some(obj) = self.objects.get(self.next) {
|
||||
if data.len() + obj.len() > capacity {
|
||||
|
||||
Reference in New Issue
Block a user