Files
hdf5/java/jtest/TestH5Affm.java
T
bmribler 8a48c7ced8 Fix NULL pointer access when H5A_operator2_t is NULL (#6541)
* Fix NULL pointer access when H5A_operator2_t is NULL

Passing NULL for the callback function pointer to H5Aiterate2 and
H5Aiterate_by_name was not detected, leading to a subsequent access
of an uninitialized pointer. Add a check for this "no operator
specified" case in both functions so they fail gracefully instead.

Fixes GHSA-r7g4-hv2f-5c66 - CVE-2025-9274

* Fix format

* Fix Java test to handle NULL callback to H5Aiterate2
2026-07-21 14:21:38 -04:00

1045 lines
42 KiB
Java

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Copyright by The HDF Group. *
* All rights reserved. *
* *
* This file is part of HDF5. The full HDF5 copyright notice, including *
* terms governing use, modification, and redistribution, is contained in *
* the COPYING file, which can be found at the root of the source code *
* distribution tree, or in https://www.hdfgroup.org/licenses. *
* If you do not have access to either file, you may request a copy from *
* help@hdfgroup.org. *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
package jtest;
import static org.junit.Assert.*;
import static jtest.FfmTestSupport.*;
import java.lang.foreign.Arena;
import java.lang.foreign.MemorySegment;
import java.lang.foreign.ValueLayout;
import org.hdfgroup.javahdf5.H5A_info_t;
import org.hdfgroup.javahdf5.H5A_operator2_t;
import org.hdfgroup.javahdf5.hdf5_h;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TestName;
/**
* TestH5Affm - FFM-based tests for HDF5 Attribute operations.
* Tests the H5A* API using Foreign Function & Memory (FFM) bindings.
*/
public class TestH5Affm {
private static final String H5_FILE = "testA.h5";
private static final int DIM_X = 4;
private static final int DIM_Y = 6;
private static final int RANK = 2;
@Rule
public TestName testname = new TestName();
long H5fid = hdf5_h.H5I_INVALID_HID();
long H5did = hdf5_h.H5I_INVALID_HID();
long H5sid = hdf5_h.H5I_INVALID_HID();
long H5aid = hdf5_h.H5I_INVALID_HID();
@Before
public void createH5file() throws Exception
{
try (Arena arena = Arena.ofConfined()) {
// Create file
MemorySegment fileName = stringToSegment(arena, H5_FILE);
H5fid = hdf5_h.H5Fcreate(fileName, hdf5_h.H5F_ACC_TRUNC(), hdf5_h.H5P_DEFAULT(),
hdf5_h.H5P_DEFAULT());
assertTrue("H5Fcreate failed", isValidId(H5fid));
// Create dataspace
long[] dims = {DIM_X, DIM_Y};
MemorySegment dimsSegment = allocateLongArray(arena, RANK);
copyToSegment(dimsSegment, dims);
H5sid = hdf5_h.H5Screate_simple(RANK, dimsSegment, MemorySegment.NULL);
assertTrue("H5Screate_simple failed", isValidId(H5sid));
// Create dataset for attaching attributes
MemorySegment dsetName = stringToSegment(arena, "dset");
H5did = hdf5_h.H5Dcreate2(H5fid, dsetName, hdf5_h.H5T_NATIVE_INT_g(), H5sid, hdf5_h.H5P_DEFAULT(),
hdf5_h.H5P_DEFAULT(), hdf5_h.H5P_DEFAULT());
assertTrue("H5Dcreate2 failed", isValidId(H5did));
}
}
@After
public void deleteH5file() throws Exception
{
closeQuietly(H5aid, hdf5_h::H5Aclose);
closeQuietly(H5did, hdf5_h::H5Dclose);
closeQuietly(H5sid, hdf5_h::H5Sclose);
closeQuietly(H5fid, hdf5_h::H5Fclose);
H5aid = hdf5_h.H5I_INVALID_HID();
H5did = hdf5_h.H5I_INVALID_HID();
H5sid = hdf5_h.H5I_INVALID_HID();
H5fid = hdf5_h.H5I_INVALID_HID();
}
static
{
try {
System.loadLibrary("hdf5");
hdf5_h.H5open();
}
catch (UnsatisfiedLinkError e) {
System.err.println("Failed to load HDF5 library: " + e.getMessage());
}
}
@Test
public void testH5Acreate()
{
System.out.print(testname.getMethodName());
try (Arena arena = Arena.ofConfined()) {
// Create scalar attribute space
long attr_sid = hdf5_h.H5Screate(hdf5_h.H5S_SCALAR());
assertTrue("H5Screate scalar failed", isValidId(attr_sid));
// Create attribute
MemorySegment attrName = stringToSegment(arena, "attr1");
H5aid = hdf5_h.H5Acreate2(H5did, attrName, hdf5_h.H5T_NATIVE_INT_g(), attr_sid,
hdf5_h.H5P_DEFAULT(), hdf5_h.H5P_DEFAULT());
assertTrue("H5Acreate2 failed", isValidId(H5aid));
hdf5_h.H5Sclose(attr_sid);
}
}
@Test
public void testH5Awrite_read()
{
System.out.print(testname.getMethodName());
try (Arena arena = Arena.ofConfined()) {
// Create attribute with array dataspace
long[] attr_dims = {3};
MemorySegment attrDimsSegment = allocateLongArray(arena, 1);
attrDimsSegment.setAtIndex(ValueLayout.JAVA_LONG, 0, attr_dims[0]);
long attr_sid = hdf5_h.H5Screate_simple(1, attrDimsSegment, MemorySegment.NULL);
assertTrue("H5Screate_simple failed", isValidId(attr_sid));
MemorySegment attrName = stringToSegment(arena, "int_array_attr");
H5aid = hdf5_h.H5Acreate2(H5did, attrName, hdf5_h.H5T_NATIVE_INT_g(), attr_sid,
hdf5_h.H5P_DEFAULT(), hdf5_h.H5P_DEFAULT());
assertTrue("H5Acreate2 failed", isValidId(H5aid));
// Write data
int[] write_data = {10, 20, 30};
MemorySegment writeSegment = allocateIntArray(arena, 3);
copyToSegment(writeSegment, write_data);
int result = hdf5_h.H5Awrite(H5aid, hdf5_h.H5T_NATIVE_INT_g(), writeSegment);
assertTrue("H5Awrite failed", isSuccess(result));
// Read back
MemorySegment readSegment = allocateIntArray(arena, 3);
result = hdf5_h.H5Aread(H5aid, hdf5_h.H5T_NATIVE_INT_g(), readSegment);
assertTrue("H5Aread failed", isSuccess(result));
// Verify
int[] read_data = new int[3];
copyFromSegment(readSegment, read_data);
assertArrayEquals("Data mismatch", write_data, read_data);
hdf5_h.H5Sclose(attr_sid);
}
}
@Test
public void testH5Aopen()
{
System.out.print(testname.getMethodName());
try (Arena arena = Arena.ofConfined()) {
// Create attribute first
long attr_sid = hdf5_h.H5Screate(hdf5_h.H5S_SCALAR());
MemorySegment attrName = stringToSegment(arena, "test_attr");
long aid = hdf5_h.H5Acreate2(H5did, attrName, hdf5_h.H5T_NATIVE_INT_g(), attr_sid,
hdf5_h.H5P_DEFAULT(), hdf5_h.H5P_DEFAULT());
assertTrue("H5Acreate2 failed", isValidId(aid));
hdf5_h.H5Aclose(aid);
// Open attribute
H5aid = hdf5_h.H5Aopen(H5did, attrName, hdf5_h.H5P_DEFAULT());
assertTrue("H5Aopen failed", isValidId(H5aid));
hdf5_h.H5Sclose(attr_sid);
}
}
@Test
public void testH5Aclose()
{
System.out.print(testname.getMethodName());
try (Arena arena = Arena.ofConfined()) {
long attr_sid = hdf5_h.H5Screate(hdf5_h.H5S_SCALAR());
MemorySegment attrName = stringToSegment(arena, "temp_attr");
H5aid = hdf5_h.H5Acreate2(H5did, attrName, hdf5_h.H5T_NATIVE_INT_g(), attr_sid,
hdf5_h.H5P_DEFAULT(), hdf5_h.H5P_DEFAULT());
assertTrue("H5Acreate2 failed", isValidId(H5aid));
int result = hdf5_h.H5Aclose(H5aid);
assertTrue("H5Aclose failed", isSuccess(result));
H5aid = hdf5_h.H5I_INVALID_HID();
hdf5_h.H5Sclose(attr_sid);
}
}
@Test
public void testH5Aget_name()
{
System.out.print(testname.getMethodName());
try (Arena arena = Arena.ofConfined()) {
String expectedName = "my_attribute";
long attr_sid = hdf5_h.H5Screate(hdf5_h.H5S_SCALAR());
MemorySegment attrName = stringToSegment(arena, expectedName);
H5aid = hdf5_h.H5Acreate2(H5did, attrName, hdf5_h.H5T_NATIVE_INT_g(), attr_sid,
hdf5_h.H5P_DEFAULT(), hdf5_h.H5P_DEFAULT());
assertTrue("H5Acreate2 failed", isValidId(H5aid));
// Get name size
long nameSize = hdf5_h.H5Aget_name(H5aid, 0, MemorySegment.NULL);
assertTrue("H5Aget_name size query failed", nameSize > 0);
// Get name
MemorySegment nameBuffer = arena.allocate(nameSize + 1);
hdf5_h.H5Aget_name(H5aid, nameSize + 1, nameBuffer);
String retrievedName = nameBuffer.getString(0);
assertEquals("Attribute name mismatch", expectedName, retrievedName);
hdf5_h.H5Sclose(attr_sid);
}
}
@Test
public void testH5Aget_space()
{
System.out.print(testname.getMethodName());
try (Arena arena = Arena.ofConfined()) {
// Create attribute with specific dimensions
long[] dims = {5, 3};
MemorySegment dimsSegment = allocateLongArray(arena, 2);
copyToSegment(dimsSegment, dims);
long attr_sid = hdf5_h.H5Screate_simple(2, dimsSegment, MemorySegment.NULL);
MemorySegment attrName = stringToSegment(arena, "array_attr");
H5aid = hdf5_h.H5Acreate2(H5did, attrName, hdf5_h.H5T_NATIVE_INT_g(), attr_sid,
hdf5_h.H5P_DEFAULT(), hdf5_h.H5P_DEFAULT());
assertTrue("H5Acreate2 failed", isValidId(H5aid));
// Get dataspace
long retrieved_sid = hdf5_h.H5Aget_space(H5aid);
assertTrue("H5Aget_space failed", isValidId(retrieved_sid));
// Verify dimensions
MemorySegment retrievedDims = allocateLongArray(arena, 2);
hdf5_h.H5Sget_simple_extent_dims(retrieved_sid, retrievedDims, MemorySegment.NULL);
long[] readDims = new long[2];
copyFromSegment(retrievedDims, readDims);
assertArrayEquals("Dimensions mismatch", dims, readDims);
hdf5_h.H5Sclose(retrieved_sid);
hdf5_h.H5Sclose(attr_sid);
}
}
@Test
public void testH5Aget_type()
{
System.out.print(testname.getMethodName());
try (Arena arena = Arena.ofConfined()) {
long attr_sid = hdf5_h.H5Screate(hdf5_h.H5S_SCALAR());
MemorySegment attrName = stringToSegment(arena, "type_attr");
H5aid = hdf5_h.H5Acreate2(H5did, attrName, hdf5_h.H5T_NATIVE_DOUBLE_g(), attr_sid,
hdf5_h.H5P_DEFAULT(), hdf5_h.H5P_DEFAULT());
assertTrue("H5Acreate2 failed", isValidId(H5aid));
// Get type
long retrieved_tid = hdf5_h.H5Aget_type(H5aid);
assertTrue("H5Aget_type failed", isValidId(retrieved_tid));
// Verify it's a double type
int equal = hdf5_h.H5Tequal(retrieved_tid, hdf5_h.H5T_NATIVE_DOUBLE_g());
assertTrue("Type should be H5T_NATIVE_DOUBLE", equal > 0);
hdf5_h.H5Tclose(retrieved_tid);
hdf5_h.H5Sclose(attr_sid);
}
}
@Test
public void testH5Aexists()
{
System.out.print(testname.getMethodName());
try (Arena arena = Arena.ofConfined()) {
MemorySegment existingName = stringToSegment(arena, "existing_attr");
MemorySegment missingName = stringToSegment(arena, "missing_attr");
// Create one attribute
long attr_sid = hdf5_h.H5Screate(hdf5_h.H5S_SCALAR());
long aid = hdf5_h.H5Acreate2(H5did, existingName, hdf5_h.H5T_NATIVE_INT_g(), attr_sid,
hdf5_h.H5P_DEFAULT(), hdf5_h.H5P_DEFAULT());
assertTrue("H5Acreate2 failed", isValidId(aid));
hdf5_h.H5Aclose(aid);
// Check existing
int exists = hdf5_h.H5Aexists(H5did, existingName);
assertTrue("Attribute should exist", exists > 0);
// Check non-existing
exists = hdf5_h.H5Aexists(H5did, missingName);
assertEquals("Attribute should not exist", 0, exists);
hdf5_h.H5Sclose(attr_sid);
}
}
@Test
public void testH5Adelete()
{
System.out.print(testname.getMethodName());
try (Arena arena = Arena.ofConfined()) {
MemorySegment attrName = stringToSegment(arena, "deletable_attr");
// Create attribute
long attr_sid = hdf5_h.H5Screate(hdf5_h.H5S_SCALAR());
long aid = hdf5_h.H5Acreate2(H5did, attrName, hdf5_h.H5T_NATIVE_INT_g(), attr_sid,
hdf5_h.H5P_DEFAULT(), hdf5_h.H5P_DEFAULT());
assertTrue("H5Acreate2 failed", isValidId(aid));
hdf5_h.H5Aclose(aid);
// Verify it exists
int exists = hdf5_h.H5Aexists(H5did, attrName);
assertTrue("Attribute should exist before delete", exists > 0);
// Delete
int result = hdf5_h.H5Adelete(H5did, attrName);
assertTrue("H5Adelete failed", isSuccess(result));
// Verify it's gone
exists = hdf5_h.H5Aexists(H5did, attrName);
assertEquals("Attribute should not exist after delete", 0, exists);
hdf5_h.H5Sclose(attr_sid);
}
}
@Test
public void testH5Aget_storage_size()
{
System.out.print(testname.getMethodName());
try (Arena arena = Arena.ofConfined()) {
// Create attribute with 10 integers
long[] attr_dims = {10};
MemorySegment attrDimsSegment = allocateLongArray(arena, 1);
attrDimsSegment.setAtIndex(ValueLayout.JAVA_LONG, 0, attr_dims[0]);
long attr_sid = hdf5_h.H5Screate_simple(1, attrDimsSegment, MemorySegment.NULL);
MemorySegment attrName = stringToSegment(arena, "storage_attr");
H5aid = hdf5_h.H5Acreate2(H5did, attrName, hdf5_h.H5T_NATIVE_INT_g(), attr_sid,
hdf5_h.H5P_DEFAULT(), hdf5_h.H5P_DEFAULT());
assertTrue("H5Acreate2 failed", isValidId(H5aid));
// Write data
int[] data = new int[10];
MemorySegment dataSegment = allocateIntArray(arena, 10);
copyToSegment(dataSegment, data);
hdf5_h.H5Awrite(H5aid, hdf5_h.H5T_NATIVE_INT_g(), dataSegment);
// Get storage size
long storage_size = hdf5_h.H5Aget_storage_size(H5aid);
assertEquals("Storage size should be 10 * sizeof(int)", 40L, storage_size);
hdf5_h.H5Sclose(attr_sid);
}
}
@Test
public void testH5Awrite_readStr()
{
System.out.print(testname.getMethodName());
try (Arena arena = Arena.ofConfined()) {
String testString = "Hello HDF5 Attributes!";
// Create string type
long str_tid = hdf5_h.H5Tcopy(hdf5_h.H5T_C_S1_g());
assertTrue("H5Tcopy failed", isValidId(str_tid));
hdf5_h.H5Tset_size(str_tid, testString.length() + 1);
hdf5_h.H5Tset_strpad(str_tid, hdf5_h.H5T_STR_NULLTERM());
// Create attribute
long attr_sid = hdf5_h.H5Screate(hdf5_h.H5S_SCALAR());
MemorySegment attrName = stringToSegment(arena, "str_attr");
H5aid = hdf5_h.H5Acreate2(H5did, attrName, str_tid, attr_sid, hdf5_h.H5P_DEFAULT(),
hdf5_h.H5P_DEFAULT());
assertTrue("H5Acreate2 failed", isValidId(H5aid));
// Write string
MemorySegment writeData = stringToSegment(arena, testString);
int writeResult = hdf5_h.H5Awrite(H5aid, str_tid, writeData);
assertTrue("H5Awrite failed", isSuccess(writeResult));
// Read string back
MemorySegment readData = arena.allocate(testString.length() + 1);
int readResult = hdf5_h.H5Aread(H5aid, str_tid, readData);
assertTrue("H5Aread failed", isSuccess(readResult));
String retrievedString = readData.getString(0);
assertEquals("String mismatch", testString, retrievedString);
hdf5_h.H5Tclose(str_tid);
hdf5_h.H5Sclose(attr_sid);
}
}
@Test
public void testH5Arename()
{
System.out.print(testname.getMethodName());
try (Arena arena = Arena.ofConfined()) {
MemorySegment oldName = stringToSegment(arena, "old_name");
MemorySegment newName = stringToSegment(arena, "new_name");
// Create attribute
long attr_sid = hdf5_h.H5Screate(hdf5_h.H5S_SCALAR());
long aid = hdf5_h.H5Acreate2(H5did, oldName, hdf5_h.H5T_NATIVE_INT_g(), attr_sid,
hdf5_h.H5P_DEFAULT(), hdf5_h.H5P_DEFAULT());
assertTrue("H5Acreate2 failed", isValidId(aid));
hdf5_h.H5Aclose(aid);
// Verify old name exists
int exists = hdf5_h.H5Aexists(H5did, oldName);
assertTrue("Old name should exist", exists > 0);
// Rename
int result = hdf5_h.H5Arename(H5did, oldName, newName);
assertTrue("H5Arename failed", isSuccess(result));
// Verify new name exists and old doesn't
exists = hdf5_h.H5Aexists(H5did, newName);
assertTrue("New name should exist", exists > 0);
exists = hdf5_h.H5Aexists(H5did, oldName);
assertEquals("Old name should not exist", 0, exists);
hdf5_h.H5Sclose(attr_sid);
}
}
@Test
public void testH5Aget_num_attrs()
{
System.out.print(testname.getMethodName());
try (Arena arena = Arena.ofConfined()) {
// Create 3 attributes
long attr_sid = hdf5_h.H5Screate(hdf5_h.H5S_SCALAR());
for (int i = 0; i < 3; i++) {
MemorySegment attrName = stringToSegment(arena, "attr_" + i);
long aid = hdf5_h.H5Acreate2(H5did, attrName, hdf5_h.H5T_NATIVE_INT_g(), attr_sid,
hdf5_h.H5P_DEFAULT(), hdf5_h.H5P_DEFAULT());
assertTrue("H5Acreate2 failed for attr_" + i, isValidId(aid));
hdf5_h.H5Aclose(aid);
}
// Get number of attributes
int num_attrs = hdf5_h.H5Aget_num_attrs(H5did);
assertTrue("Should have at least 3 attributes", num_attrs >= 3);
hdf5_h.H5Sclose(attr_sid);
}
}
// =========================
// Phase 1: Essential Query Operations
// =========================
@Test
public void testH5Aget_info()
{
System.out.print(testname.getMethodName());
try (Arena arena = Arena.ofConfined()) {
// Create attribute with scalar dataspace
long attr_sid = hdf5_h.H5Screate(hdf5_h.H5S_SCALAR());
MemorySegment attrName = stringToSegment(arena, "info_test_attr");
long aid = hdf5_h.H5Acreate2(H5did, attrName, hdf5_h.H5T_NATIVE_INT_g(), attr_sid,
hdf5_h.H5P_DEFAULT(), hdf5_h.H5P_DEFAULT());
assertTrue("H5Acreate2 failed", isValidId(aid));
// Write some data
MemorySegment data = allocateInt(arena);
setInt(data, 42);
int writeResult = hdf5_h.H5Awrite(aid, hdf5_h.H5T_NATIVE_INT_g(), data);
assertTrue("H5Awrite failed", isSuccess(writeResult));
// Get attribute info
MemorySegment info = H5A_info_t.allocate(arena);
int result = hdf5_h.H5Aget_info(aid, info);
assertTrue("H5Aget_info failed", isSuccess(result));
// Verify info fields
long data_size = H5A_info_t.data_size(info);
assertEquals("Data size should be 4 bytes (sizeof int)", 4L, data_size);
// corder_valid may be false if creation order tracking is not enabled
boolean corder_valid = H5A_info_t.corder_valid(info);
// Just verify we can read it (no assertion on value)
assertNotNull("corder_valid should be readable", Boolean.valueOf(corder_valid));
// cset should be ASCII by default
int cset = H5A_info_t.cset(info);
assertEquals("Character set should be ASCII", hdf5_h.H5T_CSET_ASCII(), cset);
hdf5_h.H5Aclose(aid);
hdf5_h.H5Sclose(attr_sid);
}
}
@Test
public void testH5Aget_info_by_idx()
{
System.out.print(testname.getMethodName());
try (Arena arena = Arena.ofConfined()) {
// Create multiple attributes to test indexing
long attr_sid = hdf5_h.H5Screate(hdf5_h.H5S_SCALAR());
String[] attrNames = {"first_attr", "second_attr", "third_attr"};
for (String name : attrNames) {
MemorySegment attrName = stringToSegment(arena, name);
long aid = hdf5_h.H5Acreate2(H5did, attrName, hdf5_h.H5T_NATIVE_DOUBLE_g(), attr_sid,
hdf5_h.H5P_DEFAULT(), hdf5_h.H5P_DEFAULT());
assertTrue("H5Acreate2 failed for " + name, isValidId(aid));
hdf5_h.H5Aclose(aid);
}
// Get info for the second attribute (index 1) by name order
MemorySegment objName = stringToSegment(arena, ".");
MemorySegment info = H5A_info_t.allocate(arena);
int result = hdf5_h.H5Aget_info_by_idx(H5did, objName, hdf5_h.H5_INDEX_NAME(),
hdf5_h.H5_ITER_INC(), 1, info, hdf5_h.H5P_DEFAULT());
assertTrue("H5Aget_info_by_idx failed", isSuccess(result));
// Verify we got valid info
long data_size = H5A_info_t.data_size(info);
assertEquals("Data size should be 8 bytes (sizeof double)", 8L, data_size);
hdf5_h.H5Sclose(attr_sid);
}
}
@Test
public void testH5Aget_info_by_name()
{
System.out.print(testname.getMethodName());
try (Arena arena = Arena.ofConfined()) {
// Create attribute on dataset
String attrNameStr = "named_info_attr";
long attr_sid = hdf5_h.H5Screate(hdf5_h.H5S_SCALAR());
MemorySegment attrName = stringToSegment(arena, attrNameStr);
long aid = hdf5_h.H5Acreate2(H5did, attrName, hdf5_h.H5T_NATIVE_FLOAT_g(), attr_sid,
hdf5_h.H5P_DEFAULT(), hdf5_h.H5P_DEFAULT());
assertTrue("H5Acreate2 failed", isValidId(aid));
hdf5_h.H5Aclose(aid);
// Get info by name from the file (using dataset path)
MemorySegment objName = stringToSegment(arena, "dset");
MemorySegment info = H5A_info_t.allocate(arena);
int result = hdf5_h.H5Aget_info_by_name(H5fid, objName, attrName, info, hdf5_h.H5P_DEFAULT());
assertTrue("H5Aget_info_by_name failed", isSuccess(result));
// Verify info
long data_size = H5A_info_t.data_size(info);
assertEquals("Data size should be 4 bytes (sizeof float)", 4L, data_size);
hdf5_h.H5Sclose(attr_sid);
}
}
@Test
public void testH5Aget_name_by_idx()
{
System.out.print(testname.getMethodName());
try (Arena arena = Arena.ofConfined()) {
// Create multiple attributes with known names
long attr_sid = hdf5_h.H5Screate(hdf5_h.H5S_SCALAR());
String[] attrNames = {"alpha", "beta", "gamma"};
String[] sortedNames = {"alpha", "beta", "gamma"}; // Already sorted alphabetically
for (String name : attrNames) {
MemorySegment attrName = stringToSegment(arena, name);
long aid = hdf5_h.H5Acreate2(H5did, attrName, hdf5_h.H5T_NATIVE_INT_g(), attr_sid,
hdf5_h.H5P_DEFAULT(), hdf5_h.H5P_DEFAULT());
assertTrue("H5Acreate2 failed for " + name, isValidId(aid));
hdf5_h.H5Aclose(aid);
}
// Get name of each attribute by index (alphabetical order)
MemorySegment objName = stringToSegment(arena, ".");
for (int i = 0; i < sortedNames.length; i++) {
// Get name size first
long nameSize =
hdf5_h.H5Aget_name_by_idx(H5did, objName, hdf5_h.H5_INDEX_NAME(), hdf5_h.H5_ITER_INC(), i,
MemorySegment.NULL, 0, hdf5_h.H5P_DEFAULT());
assertTrue("H5Aget_name_by_idx size query failed for index " + i, nameSize > 0);
// Get actual name
MemorySegment nameBuffer = arena.allocate(nameSize + 1);
long result =
hdf5_h.H5Aget_name_by_idx(H5did, objName, hdf5_h.H5_INDEX_NAME(), hdf5_h.H5_ITER_INC(), i,
nameBuffer, nameSize + 1, hdf5_h.H5P_DEFAULT());
assertTrue("H5Aget_name_by_idx failed for index " + i, result > 0);
String retrievedName = nameBuffer.getString(0);
assertEquals("Name at index " + i + " should match", sortedNames[i], retrievedName);
}
hdf5_h.H5Sclose(attr_sid);
}
}
@Test
public void testH5Aget_create_plist()
{
System.out.print(testname.getMethodName());
try (Arena arena = Arena.ofConfined()) {
// Create attribute creation property list with specific settings
long acpl = hdf5_h.H5Pcreate(hdf5_h.H5P_CLS_ATTRIBUTE_CREATE_ID_g());
assertTrue("H5Pcreate acpl failed", isValidId(acpl));
// Set character encoding to UTF-8
int setResult = hdf5_h.H5Pset_char_encoding(acpl, hdf5_h.H5T_CSET_UTF8());
assertTrue("H5Pset_char_encoding failed", isSuccess(setResult));
// Create attribute with this property list
long attr_sid = hdf5_h.H5Screate(hdf5_h.H5S_SCALAR());
MemorySegment attrName = stringToSegment(arena, "plist_attr");
long aid = hdf5_h.H5Acreate2(H5did, attrName, hdf5_h.H5T_NATIVE_INT_g(), attr_sid, acpl,
hdf5_h.H5P_DEFAULT());
assertTrue("H5Acreate2 failed", isValidId(aid));
// Get the creation property list back
long retrieved_acpl = hdf5_h.H5Aget_create_plist(aid);
assertTrue("H5Aget_create_plist failed", isValidId(retrieved_acpl));
// Verify the encoding is UTF-8
MemorySegment encoding = allocateInt(arena);
int getResult = hdf5_h.H5Pget_char_encoding(retrieved_acpl, encoding);
assertTrue("H5Pget_char_encoding failed", isSuccess(getResult));
int retrievedEncoding = getInt(encoding);
assertEquals("Character encoding should be UTF-8", hdf5_h.H5T_CSET_UTF8(), retrievedEncoding);
hdf5_h.H5Pclose(retrieved_acpl);
hdf5_h.H5Pclose(acpl);
hdf5_h.H5Aclose(aid);
hdf5_h.H5Sclose(attr_sid);
}
}
@Test
public void testH5Aexists_by_name()
{
System.out.print(testname.getMethodName());
try (Arena arena = Arena.ofConfined()) {
// Create attribute on dataset
String existingAttrName = "existing_by_name";
String nonExistingAttrName = "nonexistent_by_name";
long attr_sid = hdf5_h.H5Screate(hdf5_h.H5S_SCALAR());
MemorySegment attrName = stringToSegment(arena, existingAttrName);
long aid = hdf5_h.H5Acreate2(H5did, attrName, hdf5_h.H5T_NATIVE_INT_g(), attr_sid,
hdf5_h.H5P_DEFAULT(), hdf5_h.H5P_DEFAULT());
assertTrue("H5Acreate2 failed", isValidId(aid));
hdf5_h.H5Aclose(aid);
// Check existence from file using dataset path
MemorySegment objName = stringToSegment(arena, "dset");
MemorySegment existingAttrNameSeg = stringToSegment(arena, existingAttrName);
MemorySegment missingAttrNameSeg = stringToSegment(arena, nonExistingAttrName);
// Should exist
int exists = hdf5_h.H5Aexists_by_name(H5fid, objName, existingAttrNameSeg, hdf5_h.H5P_DEFAULT());
assertTrue("Attribute should exist", exists > 0);
// Should not exist
exists = hdf5_h.H5Aexists_by_name(H5fid, objName, missingAttrNameSeg, hdf5_h.H5P_DEFAULT());
assertEquals("Attribute should not exist", 0, exists);
hdf5_h.H5Sclose(attr_sid);
}
}
// =========================
// Phase 2: Advanced Operations
// =========================
@Test
public void testH5Aopen_by_idx()
{
System.out.print(testname.getMethodName());
try (Arena arena = Arena.ofConfined()) {
// Create multiple attributes with known names
long attr_sid = hdf5_h.H5Screate(hdf5_h.H5S_SCALAR());
String[] attrNames = {"apple", "banana", "cherry"};
String[] sortedNames = {"apple", "banana", "cherry"}; // Already sorted
for (String name : attrNames) {
MemorySegment attrName = stringToSegment(arena, name);
long aid = hdf5_h.H5Acreate2(H5did, attrName, hdf5_h.H5T_NATIVE_INT_g(), attr_sid,
hdf5_h.H5P_DEFAULT(), hdf5_h.H5P_DEFAULT());
assertTrue("H5Acreate2 failed for " + name, isValidId(aid));
hdf5_h.H5Aclose(aid);
}
// Open second attribute by index (index 1)
MemorySegment objName = stringToSegment(arena, ".");
long aid = hdf5_h.H5Aopen_by_idx(H5did, objName, hdf5_h.H5_INDEX_NAME(), hdf5_h.H5_ITER_INC(), 1,
hdf5_h.H5P_DEFAULT(), hdf5_h.H5P_DEFAULT());
assertTrue("H5Aopen_by_idx failed", isValidId(aid));
// Verify we opened the correct attribute by checking its name
long nameSize = hdf5_h.H5Aget_name(aid, 0, MemorySegment.NULL);
MemorySegment nameBuf = arena.allocate(nameSize + 1);
hdf5_h.H5Aget_name(aid, nameSize + 1, nameBuf);
String retrievedName = nameBuf.getString(0);
assertEquals("Should have opened 'banana' (index 1)", sortedNames[1], retrievedName);
hdf5_h.H5Aclose(aid);
hdf5_h.H5Sclose(attr_sid);
}
}
@Test
public void testH5Adelete_by_idx()
{
System.out.print(testname.getMethodName());
try (Arena arena = Arena.ofConfined()) {
// Create 3 attributes
long attr_sid = hdf5_h.H5Screate(hdf5_h.H5S_SCALAR());
String[] attrNames = {"first", "second", "third"};
for (String name : attrNames) {
MemorySegment attrName = stringToSegment(arena, name);
long aid = hdf5_h.H5Acreate2(H5did, attrName, hdf5_h.H5T_NATIVE_INT_g(), attr_sid,
hdf5_h.H5P_DEFAULT(), hdf5_h.H5P_DEFAULT());
assertTrue("H5Acreate2 failed for " + name, isValidId(aid));
hdf5_h.H5Aclose(aid);
}
// Delete middle attribute (index 1 = "second")
MemorySegment objName = stringToSegment(arena, ".");
int result = hdf5_h.H5Adelete_by_idx(H5did, objName, hdf5_h.H5_INDEX_NAME(), hdf5_h.H5_ITER_INC(),
1, hdf5_h.H5P_DEFAULT());
assertTrue("H5Adelete_by_idx failed", isSuccess(result));
// Verify "second" is gone
MemorySegment secondName = stringToSegment(arena, "second");
int exists = hdf5_h.H5Aexists(H5did, secondName);
assertEquals("'second' should not exist after deletion", 0, exists);
// Verify others still exist
MemorySegment firstName = stringToSegment(arena, "first");
MemorySegment thirdName = stringToSegment(arena, "third");
exists = hdf5_h.H5Aexists(H5did, firstName);
assertTrue("'first' should still exist", exists > 0);
exists = hdf5_h.H5Aexists(H5did, thirdName);
assertTrue("'third' should still exist", exists > 0);
hdf5_h.H5Sclose(attr_sid);
}
}
@Test
public void testH5Adelete_by_name()
{
System.out.print(testname.getMethodName());
try (Arena arena = Arena.ofConfined()) {
// Create attribute on dataset
String attrNameStr = "deletable_by_name";
long attr_sid = hdf5_h.H5Screate(hdf5_h.H5S_SCALAR());
MemorySegment attrName = stringToSegment(arena, attrNameStr);
long aid = hdf5_h.H5Acreate2(H5did, attrName, hdf5_h.H5T_NATIVE_INT_g(), attr_sid,
hdf5_h.H5P_DEFAULT(), hdf5_h.H5P_DEFAULT());
assertTrue("H5Acreate2 failed", isValidId(aid));
hdf5_h.H5Aclose(aid);
// Verify it exists
int exists = hdf5_h.H5Aexists(H5did, attrName);
assertTrue("Attribute should exist before delete", exists > 0);
// Delete by name from file using dataset path
MemorySegment objName = stringToSegment(arena, "dset");
int result = hdf5_h.H5Adelete_by_name(H5fid, objName, attrName, hdf5_h.H5P_DEFAULT());
assertTrue("H5Adelete_by_name failed", isSuccess(result));
// Verify it's gone
exists = hdf5_h.H5Aexists(H5did, attrName);
assertEquals("Attribute should not exist after delete", 0, exists);
hdf5_h.H5Sclose(attr_sid);
}
}
@Test
public void testH5Arename_by_name()
{
System.out.print(testname.getMethodName());
try (Arena arena = Arena.ofConfined()) {
// Create attribute on dataset
String oldNameStr = "old_name_by_path";
String newNameStr = "new_name_by_path";
long attr_sid = hdf5_h.H5Screate(hdf5_h.H5S_SCALAR());
MemorySegment oldName = stringToSegment(arena, oldNameStr);
MemorySegment newName = stringToSegment(arena, newNameStr);
long aid = hdf5_h.H5Acreate2(H5did, oldName, hdf5_h.H5T_NATIVE_INT_g(), attr_sid,
hdf5_h.H5P_DEFAULT(), hdf5_h.H5P_DEFAULT());
assertTrue("H5Acreate2 failed", isValidId(aid));
hdf5_h.H5Aclose(aid);
// Verify old name exists
int exists = hdf5_h.H5Aexists(H5did, oldName);
assertTrue("Old name should exist", exists > 0);
// Rename by name from file using dataset path
MemorySegment objName = stringToSegment(arena, "dset");
int result = hdf5_h.H5Arename_by_name(H5fid, objName, oldName, newName, hdf5_h.H5P_DEFAULT());
assertTrue("H5Arename_by_name failed", isSuccess(result));
// Verify new name exists and old doesn't
exists = hdf5_h.H5Aexists(H5did, newName);
assertTrue("New name should exist", exists > 0);
exists = hdf5_h.H5Aexists(H5did, oldName);
assertEquals("Old name should not exist", 0, exists);
hdf5_h.H5Sclose(attr_sid);
}
}
@Test
public void testH5Aiterate2()
{
System.out.print(testname.getMethodName());
try (Arena arena = Arena.ofConfined()) {
// Create multiple attributes
long attr_sid = hdf5_h.H5Screate(hdf5_h.H5S_SCALAR());
String[] attrNames = {"attr_A", "attr_B", "attr_C", "attr_D"};
for (String name : attrNames) {
MemorySegment attrName = stringToSegment(arena, name);
long aid = hdf5_h.H5Acreate2(H5did, attrName, hdf5_h.H5T_NATIVE_INT_g(), attr_sid,
hdf5_h.H5P_DEFAULT(), hdf5_h.H5P_DEFAULT());
assertTrue("H5Acreate2 failed for " + name, isValidId(aid));
hdf5_h.H5Aclose(aid);
}
// Create iteration callback
final int[] count = {0};
final String[] names = new String[10];
H5A_operator2_t.Function cb = (loc_id, attr_name, ainfo, op_data) ->
{
try {
String name = attr_name.getString(0);
names[count[0]] = name;
count[0]++;
return 0; // Continue iteration
}
catch (Exception e) {
return -1; // Stop on error
}
};
MemorySegment callback = H5A_operator2_t.allocate(cb, arena);
MemorySegment idx = allocateLong(arena);
setLong(idx, 0);
// Iterate
int result = hdf5_h.H5Aiterate2(H5did, hdf5_h.H5_INDEX_NAME(), hdf5_h.H5_ITER_INC(), idx,
callback, MemorySegment.NULL);
assertTrue("H5Aiterate2 failed", isSuccess(result));
// Verify we iterated all attributes (at least the 4 we created)
assertTrue("Should have iterated at least 4 attributes", count[0] >= 4);
// Verify all our attributes were seen
boolean foundA = false, foundB = false, foundC = false, foundD = false;
for (int i = 0; i < count[0]; i++) {
if ("attr_A".equals(names[i]))
foundA = true;
if ("attr_B".equals(names[i]))
foundB = true;
if ("attr_C".equals(names[i]))
foundC = true;
if ("attr_D".equals(names[i]))
foundD = true;
}
assertTrue("Should have found attr_A", foundA);
assertTrue("Should have found attr_B", foundB);
assertTrue("Should have found attr_C", foundC);
assertTrue("Should have found attr_D", foundD);
hdf5_h.H5Sclose(attr_sid);
}
}
@Test
public void testH5Aiterate_by_name()
{
System.out.print(testname.getMethodName());
try (Arena arena = Arena.ofConfined()) {
// Create attributes on dataset
long attr_sid = hdf5_h.H5Screate(hdf5_h.H5S_SCALAR());
String[] attrNames = {"iter_1", "iter_2", "iter_3"};
for (String name : attrNames) {
MemorySegment attrName = stringToSegment(arena, name);
long aid = hdf5_h.H5Acreate2(H5did, attrName, hdf5_h.H5T_NATIVE_INT_g(), attr_sid,
hdf5_h.H5P_DEFAULT(), hdf5_h.H5P_DEFAULT());
assertTrue("H5Acreate2 failed for " + name, isValidId(aid));
hdf5_h.H5Aclose(aid);
}
// Create iteration callback
final int[] count = {0};
H5A_operator2_t.Function cb = (loc_id, attr_name, ainfo, op_data) ->
{
count[0]++;
return 0; // Continue
};
MemorySegment callback = H5A_operator2_t.allocate(cb, arena);
MemorySegment idx = allocateLong(arena);
setLong(idx, 0);
// Iterate from file using dataset path
MemorySegment objName = stringToSegment(arena, "dset");
int result =
hdf5_h.H5Aiterate_by_name(H5fid, objName, hdf5_h.H5_INDEX_NAME(), hdf5_h.H5_ITER_INC(), idx,
callback, MemorySegment.NULL, hdf5_h.H5P_DEFAULT());
assertTrue("H5Aiterate_by_name failed", isSuccess(result));
// Verify we iterated at least our 3 attributes
assertTrue("Should have iterated at least 3 attributes", count[0] >= 3);
hdf5_h.H5Sclose(attr_sid);
}
}
@Test
public void testH5Acreate_by_name_comprehensive()
{
System.out.print(testname.getMethodName());
try (Arena arena = Arena.ofConfined()) {
// Create attribute on dataset using path from file
String attrNameStr = "created_by_path_comprehensive";
long attr_sid = hdf5_h.H5Screate(hdf5_h.H5S_SCALAR());
MemorySegment objName = stringToSegment(arena, "dset");
MemorySegment attrName = stringToSegment(arena, attrNameStr);
long aid =
hdf5_h.H5Acreate_by_name(H5fid, objName, attrName, hdf5_h.H5T_NATIVE_DOUBLE_g(), attr_sid,
hdf5_h.H5P_DEFAULT(), hdf5_h.H5P_DEFAULT(), hdf5_h.H5P_DEFAULT());
assertTrue("H5Acreate_by_name failed", isValidId(aid));
// Write data
MemorySegment data = allocateDoubleArray(arena, 1);
data.setAtIndex(ValueLayout.JAVA_DOUBLE, 0, 3.14159);
int writeResult = hdf5_h.H5Awrite(aid, hdf5_h.H5T_NATIVE_DOUBLE_g(), data);
assertTrue("H5Awrite failed", isSuccess(writeResult));
hdf5_h.H5Aclose(aid);
// Verify attribute is accessible from dataset
MemorySegment attrNameCheck = stringToSegment(arena, attrNameStr);
int exists = hdf5_h.H5Aexists(H5did, attrNameCheck);
assertTrue("Attribute should exist on dataset", exists > 0);
// Open and read back to verify data
long aid2 = hdf5_h.H5Aopen(H5did, attrNameCheck, hdf5_h.H5P_DEFAULT());
assertTrue("H5Aopen failed", isValidId(aid2));
MemorySegment readData = allocateDoubleArray(arena, 1);
int readResult = hdf5_h.H5Aread(aid2, hdf5_h.H5T_NATIVE_DOUBLE_g(), readData);
assertTrue("H5Aread failed", isSuccess(readResult));
double value = readData.getAtIndex(ValueLayout.JAVA_DOUBLE, 0);
assertEquals("Data should match", 3.14159, value, 0.00001);
hdf5_h.H5Aclose(aid2);
hdf5_h.H5Sclose(attr_sid);
}
}
@Test
public void testH5Aiterate_nullCallback()
{
System.out.print(testname.getMethodName());
try (Arena arena = Arena.ofConfined()) {
MemorySegment idx = allocateLongArray(arena, 1);
copyToSegment(idx, new long[] {0});
// NULL callback must be rejected gracefully (H5E_BADVALUE), not crash
long result = hdf5_h.H5Aiterate2(H5did, hdf5_h.H5_INDEX_NAME(), hdf5_h.H5_ITER_INC(), idx,
MemorySegment.NULL, MemorySegment.NULL);
assertTrue("H5Aiterate2 should fail when callback is NULL", result < 0);
}
}
}