fix(py): a panic in the library raises clawhdf5.InternalError, not PanicException
PanicException derives from BaseException, so `except Exception` let a library bug through. Every call from the bindings into the library now runs under catch_unwind and a panic becomes InternalError (RuntimeError) naming the object. Tests: a hidden hook panics inside the guard; and the v4 chunk indexes are compared with h5py from Python — with the library fix reverted, ds[0:30] of the implicit-index dataset now raises InternalError instead of aborting the test run. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
@@ -42,25 +42,27 @@ impl PyDataset {
|
||||
file: Arc<clawhdf5_rs::File>,
|
||||
path: String,
|
||||
) -> PyResult<Self> {
|
||||
let hdr = node::header(&file, &path)?;
|
||||
let null = node::is_null(&node::dataspace(&file, &hdr)?);
|
||||
let (shape, datatype) = {
|
||||
let ds = file.dataset(&path).map_err(to_py_err)?;
|
||||
let shape = if null {
|
||||
None
|
||||
} else {
|
||||
Some(ds.shape().map_err(to_py_err)?)
|
||||
crate::no_panic(|| {
|
||||
let hdr = node::header(&file, &path)?;
|
||||
let null = node::is_null(&node::dataspace(&file, &hdr)?);
|
||||
let (shape, datatype) = {
|
||||
let ds = file.dataset(&path).map_err(to_py_err)?;
|
||||
let shape = if null {
|
||||
None
|
||||
} else {
|
||||
Some(ds.shape().map_err(to_py_err)?)
|
||||
};
|
||||
(shape, ds.raw_datatype().map_err(to_py_err)?)
|
||||
};
|
||||
(shape, ds.raw_datatype().map_err(to_py_err)?)
|
||||
};
|
||||
let conv = Converter::new(py, &datatype, file.superblock().offset_size)
|
||||
.map_err(|e| e.value(py).to_string());
|
||||
Ok(Self {
|
||||
file,
|
||||
path,
|
||||
shape,
|
||||
datatype,
|
||||
conv,
|
||||
let conv = Converter::new(py, &datatype, file.superblock().offset_size)
|
||||
.map_err(|e| e.value(py).to_string());
|
||||
Ok(Self {
|
||||
file,
|
||||
path,
|
||||
shape,
|
||||
datatype,
|
||||
conv,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -84,39 +86,43 @@ impl PyDataset {
|
||||
let path = self.path.as_str();
|
||||
let (vl, elem_size, unit) = (conv.is_vl(), conv.elem_size, conv.vl_unit);
|
||||
// Everything below touches only Rust data: release the GIL.
|
||||
let blocks: Vec<(Elements, Vec<usize>)> = py
|
||||
.detach(|| -> Result<_, ReadError> {
|
||||
let ds = file.dataset(path)?;
|
||||
let sb = file.superblock();
|
||||
let mut blocks = Vec::with_capacity(reads.len());
|
||||
for (sel, shape) in reads {
|
||||
let raw = ds.read_selection(&sel)?;
|
||||
let n: usize = shape.iter().product();
|
||||
let data = if vl {
|
||||
if raw.len() != n * elem_size {
|
||||
return Err(ReadError::Other(format!(
|
||||
"read {} bytes of variable-length references, expected {}",
|
||||
raw.len(),
|
||||
n * elem_size
|
||||
)));
|
||||
}
|
||||
Elements::Vl(
|
||||
resolve_vl(
|
||||
file.as_bytes(),
|
||||
&raw,
|
||||
n,
|
||||
sb.offset_size,
|
||||
sb.length_size,
|
||||
unit,
|
||||
)
|
||||
.map_err(ReadError::Other)?,
|
||||
let read = || -> Result<_, ReadError> {
|
||||
let ds = file.dataset(path)?;
|
||||
let sb = file.superblock();
|
||||
let mut blocks = Vec::with_capacity(reads.len());
|
||||
for (sel, shape) in reads {
|
||||
let raw = ds.read_selection(&sel)?;
|
||||
let n: usize = shape.iter().product();
|
||||
let data = if vl {
|
||||
if raw.len() != n * elem_size {
|
||||
return Err(ReadError::Other(format!(
|
||||
"read {} bytes of variable-length references, expected {}",
|
||||
raw.len(),
|
||||
n * elem_size
|
||||
)));
|
||||
}
|
||||
Elements::Vl(
|
||||
resolve_vl(
|
||||
file.as_bytes(),
|
||||
&raw,
|
||||
n,
|
||||
sb.offset_size,
|
||||
sb.length_size,
|
||||
unit,
|
||||
)
|
||||
} else {
|
||||
Elements::Bytes(raw)
|
||||
};
|
||||
blocks.push((data, shape));
|
||||
}
|
||||
Ok(blocks)
|
||||
.map_err(ReadError::Other)?,
|
||||
)
|
||||
} else {
|
||||
Elements::Bytes(raw)
|
||||
};
|
||||
blocks.push((data, shape));
|
||||
}
|
||||
Ok(blocks)
|
||||
};
|
||||
let blocks: Vec<(Elements, Vec<usize>)> = py
|
||||
.detach(|| {
|
||||
std::panic::catch_unwind(std::panic::AssertUnwindSafe(read))
|
||||
.unwrap_or_else(|p| Err(ReadError::Panic(crate::panic_text(&*p))))
|
||||
})
|
||||
.map_err(|e| e.into_py(&self.path))?;
|
||||
|
||||
@@ -160,6 +166,7 @@ impl PyDataset {
|
||||
enum ReadError {
|
||||
Lib(clawhdf5_rs::Error),
|
||||
Other(String),
|
||||
Panic(String),
|
||||
}
|
||||
|
||||
impl From<clawhdf5_rs::Error> for ReadError {
|
||||
@@ -173,6 +180,10 @@ impl ReadError {
|
||||
match self {
|
||||
ReadError::Lib(e) => to_py_err(e),
|
||||
ReadError::Other(msg) => PyValueError::new_err(format!("{}: {msg}", node::name(path))),
|
||||
ReadError::Panic(msg) => crate::InternalError::new_err(format!(
|
||||
"{}: clawhdf5 internal error (please report it): {msg}",
|
||||
node::name(path)
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -223,20 +234,22 @@ impl PyDataset {
|
||||
/// The maximum shape (`None` per unlimited dimension), like h5py.
|
||||
#[getter]
|
||||
fn maxshape<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> {
|
||||
let Some(shape) = &self.shape else {
|
||||
return Ok(py.None().into_bound(py));
|
||||
};
|
||||
let max = self
|
||||
.file
|
||||
.dataset(&self.path)
|
||||
.and_then(|ds| ds.max_dimensions())
|
||||
.map_err(to_py_err)?
|
||||
.unwrap_or_else(|| shape.clone());
|
||||
let items: Vec<Option<u64>> = max
|
||||
.into_iter()
|
||||
.map(|d| (d != u64::MAX).then_some(d))
|
||||
.collect();
|
||||
Ok(PyTuple::new(py, items)?.into_any())
|
||||
crate::no_panic(|| {
|
||||
let Some(shape) = &self.shape else {
|
||||
return Ok(py.None().into_bound(py));
|
||||
};
|
||||
let max = self
|
||||
.file
|
||||
.dataset(&self.path)
|
||||
.and_then(|ds| ds.max_dimensions())
|
||||
.map_err(to_py_err)?
|
||||
.unwrap_or_else(|| shape.clone());
|
||||
let items: Vec<Option<u64>> = max
|
||||
.into_iter()
|
||||
.map(|d| (d != u64::MAX).then_some(d))
|
||||
.collect();
|
||||
Ok(PyTuple::new(py, items)?.into_any())
|
||||
})
|
||||
}
|
||||
|
||||
/// The dataset's numpy dtype, as h5py reports it.
|
||||
|
||||
Reference in New Issue
Block a user