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:
@@ -6,6 +6,7 @@ extern crate alloc;
|
||||
#[cfg(not(feature = "std"))]
|
||||
use alloc::{format, vec, vec::Vec};
|
||||
|
||||
use crate::addr::to_usize;
|
||||
#[cfg(feature = "std")]
|
||||
use crate::chunk_cache::{CacheAlignedBuffer, ChunkCache};
|
||||
use crate::data_layout::DataLayout;
|
||||
@@ -265,7 +266,7 @@ fn fill_from_chunks(
|
||||
)));
|
||||
}
|
||||
let offsets = &c.offsets[..rank];
|
||||
let c_addr = c.address as usize;
|
||||
let c_addr = to_usize(c.address)?;
|
||||
let size = c.chunk_size as usize;
|
||||
ensure_len(file_data, c_addr, size)?;
|
||||
let raw = &file_data[c_addr..c_addr + size];
|
||||
@@ -825,7 +826,7 @@ fn parse_chunk_node(
|
||||
return Err(FormatError::NestingDepthExceeded);
|
||||
}
|
||||
|
||||
let offset = btree_address as usize;
|
||||
let offset = to_usize(btree_address)?;
|
||||
let os = offset_size as usize;
|
||||
|
||||
// Parse B-tree v1 header
|
||||
@@ -945,7 +946,8 @@ pub fn generate_implicit_chunks(
|
||||
}
|
||||
let total_chunks: u64 = num_chunks_per_dim.iter().product();
|
||||
|
||||
let mut chunks = Vec::with_capacity(total_chunks as usize);
|
||||
// A capacity hint only (a count past `usize::MAX` could not be pushed).
|
||||
let mut chunks = Vec::with_capacity(usize::try_from(total_chunks).unwrap_or(0));
|
||||
for linear_idx in 0..total_chunks {
|
||||
let mut offsets = vec![0u64; rank];
|
||||
let mut remaining = linear_idx;
|
||||
@@ -993,7 +995,7 @@ fn read_btree_v2_chunks(
|
||||
use crate::btree_v2::{BTreeV2Header, collect_btree_v2_records};
|
||||
|
||||
let bad = |what: &str| FormatError::ChunkedReadError(format!("B-tree v2 chunk index: {what}"));
|
||||
let header = BTreeV2Header::parse(file_data, addr as usize, offset_size, length_size)?;
|
||||
let header = BTreeV2Header::parse(file_data, to_usize(addr)?, offset_size, length_size)?;
|
||||
let rank = chunk_dims.len();
|
||||
let os = offset_size as usize;
|
||||
let record_size = header.record_size as usize;
|
||||
@@ -1122,7 +1124,11 @@ pub fn list_chunks(
|
||||
|
||||
// Both v3 and v4 include element size as last dim (rank+1)
|
||||
let (rank, chunk_dims) = chunk_geometry(chunk_dimensions, version, dataspace, elem_size)?;
|
||||
let ds_dims: Vec<usize> = dataspace.dimensions.iter().map(|&d| d as usize).collect();
|
||||
let ds_dims: Vec<usize> = dataspace
|
||||
.dimensions
|
||||
.iter()
|
||||
.map(|&d| to_usize(d))
|
||||
.collect::<Result<_, _>>()?;
|
||||
|
||||
// Collect chunks based on version and index type
|
||||
let mut chunks = match (version, chunk_index_type) {
|
||||
@@ -1158,7 +1164,7 @@ pub fn list_chunks(
|
||||
// Fixed Array — use spatial chunk dims only
|
||||
let spatial_chunk_dims: &[u32] = &chunk_dimensions[..rank];
|
||||
let header =
|
||||
FixedArrayHeader::parse(file_data, addr as usize, offset_size, length_size)?;
|
||||
FixedArrayHeader::parse(file_data, to_usize(addr)?, offset_size, length_size)?;
|
||||
read_fixed_array_chunks(
|
||||
file_data,
|
||||
&header,
|
||||
@@ -1174,7 +1180,7 @@ pub fn list_chunks(
|
||||
// Extensible Array — use spatial chunk dims only
|
||||
let spatial_chunk_dims: &[u32] = &chunk_dimensions[..rank];
|
||||
let header =
|
||||
ExtensibleArrayHeader::parse(file_data, addr as usize, offset_size, length_size)?;
|
||||
ExtensibleArrayHeader::parse(file_data, to_usize(addr)?, offset_size, length_size)?;
|
||||
read_extensible_array_chunks(
|
||||
file_data,
|
||||
&header,
|
||||
@@ -1349,7 +1355,11 @@ pub(crate) fn read_chunked_full<O>(
|
||||
// dimension the total is 0 even if other dimensions are huge.
|
||||
return Ok(output);
|
||||
}
|
||||
let ds_dims: Vec<usize> = dataspace.dimensions.iter().map(|&d| d as usize).collect();
|
||||
let ds_dims: Vec<usize> = dataspace
|
||||
.dimensions
|
||||
.iter()
|
||||
.map(|&d| to_usize(d))
|
||||
.collect::<Result<_, _>>()?;
|
||||
let placer = ChunkPlacer::new(&chunk_dims, &ds_dims, elem_size);
|
||||
let chunk_total_bytes = checked_chunk_byte_len(&chunk_dims, elem_size)?;
|
||||
// Chunks are cached only when the whole dataset fits: pushing a larger
|
||||
@@ -1603,7 +1613,11 @@ pub fn read_chunked_data_sweep(
|
||||
check_chunk_element_size(layout, datatype, offset_size)?;
|
||||
let elem_size = datatype.type_size() as usize;
|
||||
let (rank, chunk_dims) = chunk_geometry(chunk_dimensions, version, dataspace, elem_size)?;
|
||||
let ds_dims: Vec<usize> = dataspace.dimensions.iter().map(|&d| d as usize).collect();
|
||||
let ds_dims: Vec<usize> = dataspace
|
||||
.dimensions
|
||||
.iter()
|
||||
.map(|&d| to_usize(d))
|
||||
.collect::<Result<_, _>>()?;
|
||||
|
||||
// The per-file cache is shared across datasets (and threads); every
|
||||
// lookup is keyed by this dataset's chunk-index address, so another
|
||||
@@ -1659,7 +1673,7 @@ pub fn read_chunked_data_sweep(
|
||||
cached
|
||||
} else {
|
||||
// Decompress from file
|
||||
let c_addr = chunk_info.address as usize;
|
||||
let c_addr = to_usize(chunk_info.address)?;
|
||||
let size = chunk_info.chunk_size as usize;
|
||||
ensure_len(file_data, c_addr, size)?;
|
||||
let raw_chunk = &file_data[c_addr..c_addr + size];
|
||||
@@ -1682,8 +1696,8 @@ pub fn read_chunked_data_sweep(
|
||||
.offsets
|
||||
.iter()
|
||||
.take(rank)
|
||||
.map(|&o| o as usize)
|
||||
.collect();
|
||||
.map(|&o| to_usize(o))
|
||||
.collect::<Result<_, _>>()?;
|
||||
|
||||
if rank == 0 {
|
||||
let copy_len = decompressed.len().min(output.len());
|
||||
@@ -1743,7 +1757,11 @@ pub fn read_chunked_data_indexed(
|
||||
check_chunk_element_size(layout, datatype, offset_size)?;
|
||||
let elem_size = datatype.type_size() as usize;
|
||||
let (rank, chunk_dims) = chunk_geometry(chunk_dimensions, version, dataspace, elem_size)?;
|
||||
let ds_dims: Vec<usize> = dataspace.dimensions.iter().map(|&d| d as usize).collect();
|
||||
let ds_dims: Vec<usize> = dataspace
|
||||
.dimensions
|
||||
.iter()
|
||||
.map(|&d| to_usize(d))
|
||||
.collect::<Result<_, _>>()?;
|
||||
|
||||
// Chunk index and assembly plan for this dataset, built on first access
|
||||
// and kept per dataset (keyed by chunk-index address) in the shared cache.
|
||||
@@ -1776,7 +1794,7 @@ pub fn read_chunked_data_indexed(
|
||||
if let Some(cached) = cache.get_decompressed_in(addr, coord) {
|
||||
chunk_buffers.push(cached);
|
||||
} else {
|
||||
let c_addr = *file_offset as usize;
|
||||
let c_addr = to_usize(*file_offset)?;
|
||||
let size = *file_size as usize;
|
||||
ensure_len(file_data, c_addr, size)?;
|
||||
let raw_chunk = &file_data[c_addr..c_addr + size];
|
||||
|
||||
Reference in New Issue
Block a user