test: compare header and datatype damage with what h5py refuses
h5py writes a dataset (libver earliest, so version-1 object headers) and the script damages one field of a copy: a layout message flagged shareable, a message size that is not a multiple of 8, a compound field that repeats an earlier name or overlaps it, an empty enum member name, a float exponent overlapping the mantissa. h5py refuses every damaged copy, and clawhdf5 must refuse exactly those and read the valid files. All of them but the unaligned one (then an UnexpectedEof) read before this branch. The helper now reads any datatype (File::read_multi) rather than only integers. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
@@ -85,7 +85,9 @@ fn clawhdf5_reads(path: &Path) -> Result<(), String> {
|
|||||||
let ds = file.dataset("d").map_err(|e| format!("dataset: {e}"))?;
|
let ds = file.dataset("d").map_err(|e| format!("dataset: {e}"))?;
|
||||||
ds.dtype().map_err(|e| format!("dtype: {e}"))?;
|
ds.dtype().map_err(|e| format!("dtype: {e}"))?;
|
||||||
ds.shape().map_err(|e| format!("shape: {e}"))?;
|
ds.shape().map_err(|e| format!("shape: {e}"))?;
|
||||||
ds.read_i32().map(|_| ()).map_err(|e| format!("read: {e}"))
|
file.read_multi(&["d"])
|
||||||
|
.map(|_| ())
|
||||||
|
.map_err(|e| format!("read: {e}"))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// h5py's verdict for each file must be `expected`, and clawhdf5 must read
|
/// h5py's verdict for each file must be `expected`, and clawhdf5 must read
|
||||||
@@ -198,3 +200,72 @@ for libver in ("earliest", "latest"):
|
|||||||
assert!(err.to_string().contains("truncated file"), "{name}: {err}");
|
assert!(err.to_string().contains("truncated file"), "{name}: {err}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn header_and_datatype_damage_libhdf5_refuses_is_refused() {
|
||||||
|
skip_if_no_python!();
|
||||||
|
let dir = tempfile::tempdir().unwrap();
|
||||||
|
// Each case damages one field of a valid file h5py wrote (libver
|
||||||
|
// earliest, so version-1 object headers); all of these read before.
|
||||||
|
let verdicts = h5py_verdicts(
|
||||||
|
dir.path(),
|
||||||
|
r#"
|
||||||
|
def write(name, make):
|
||||||
|
path = os.path.join(d, name + ".h5")
|
||||||
|
with h5py.File(path, "w", libver="earliest") as f:
|
||||||
|
make(f)
|
||||||
|
return bytearray(open(path, "rb").read())
|
||||||
|
|
||||||
|
def save(name, data):
|
||||||
|
open(os.path.join(d, name + ".h5"), "wb").write(data)
|
||||||
|
|
||||||
|
# A layout message (type 8, 24 bytes, v1 header) flagged shareable.
|
||||||
|
data = write("layout_good", lambda f: f.create_dataset("d", data=np.arange(4, dtype="<i4")))
|
||||||
|
at = data.find(bytes([8, 0, 24, 0]))
|
||||||
|
assert at > 0
|
||||||
|
bad = bytearray(data); bad[at + 4] |= 0x40
|
||||||
|
save("layout_shareable", bad)
|
||||||
|
# A message size that is not a multiple of 8 in a v1 header.
|
||||||
|
bad = bytearray(data); bad[at + 2] = 23
|
||||||
|
save("layout_unaligned", bad)
|
||||||
|
|
||||||
|
# A compound whose second field repeats the first's name, or overlaps it.
|
||||||
|
dt = np.dtype([("aa", "<i4"), ("bb", "<i4")])
|
||||||
|
data = write("compound_good", lambda f: f.create_dataset("d", data=np.zeros(2, dt)))
|
||||||
|
at = data.find(b"bb\0")
|
||||||
|
bad = bytearray(data); bad[at:at + 2] = b"aa"
|
||||||
|
save("compound_duplicate", bad)
|
||||||
|
bad = bytearray(data); bad[at + 8:at + 12] = struct.pack("<I", 2)
|
||||||
|
save("compound_overlap", bad)
|
||||||
|
|
||||||
|
# An enum member with an empty name.
|
||||||
|
et = h5py.enum_dtype({"RED": 0, "GREEN": 1}, basetype="i4")
|
||||||
|
data = write("enum_good", lambda f: f.create_dataset("d", data=np.zeros(2, "<i4"), dtype=et))
|
||||||
|
at = data.find(b"RED\0")
|
||||||
|
bad = bytearray(data); bad[at:at + 3] = b"\0\0\0"
|
||||||
|
save("enum_empty_name", bad)
|
||||||
|
|
||||||
|
# A float whose exponent overlaps its mantissa (exponent at bit 20).
|
||||||
|
data = write("float_good", lambda f: f.create_dataset("d", data=np.zeros(3, "<f4")))
|
||||||
|
at = data.find(bytes([0, 0, 32, 0, 23, 8, 0, 23]))
|
||||||
|
bad = bytearray(data); bad[at + 4] = 20
|
||||||
|
save("float_overlap", bad)
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
assert_agrees_with_h5py(
|
||||||
|
dir.path(),
|
||||||
|
&verdicts,
|
||||||
|
&[
|
||||||
|
"compound_duplicate ERROR",
|
||||||
|
"compound_good ok",
|
||||||
|
"compound_overlap ERROR",
|
||||||
|
"enum_empty_name ERROR",
|
||||||
|
"enum_good ok",
|
||||||
|
"float_good ok",
|
||||||
|
"float_overlap ERROR",
|
||||||
|
"layout_good ok",
|
||||||
|
"layout_shareable ERROR",
|
||||||
|
"layout_unaligned ERROR",
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user