diff --git a/java/src-jni/jni/h5util.c b/java/src-jni/jni/h5util.c index 684e36db6c4a..1f2525b6f7d4 100644 --- a/java/src-jni/jni/h5util.c +++ b/java/src-jni/jni/h5util.c @@ -4252,6 +4252,7 @@ translate_atomic_rbuf(JNIEnv *env, jlong mem_type_id, H5T_class_t type_class, vo /* retrieve the java.util.ArrayList interface class */ jclass arrCList = ENVPTR->FindClass(ENVONLY, "java/util/ArrayList"); jmethodID arrListMethod = ENVPTR->GetMethodID(ENVONLY, arrCList, "", "(I)V"); + jmethodID arrAddMethod = ENVPTR->GetMethodID(ENVONLY, arrCList, "add", "(Ljava/lang/Object;)Z"); /* Cache class types */ /* jclass cBool = ENVPTR->FindClass(ENVONLY, "java/lang/Boolean"); */ @@ -4308,15 +4309,14 @@ translate_atomic_rbuf(JNIEnv *env, jlong mem_type_id, H5T_class_t type_class, vo case H5T_COMPOUND: { int nmembs = H5Tget_nmembers(mem_type_id); - /* The list we're going to return: */ if (NULL == (jList = (jobjectArray)ENVPTR->NewObject(ENVONLY, arrCList, arrListMethod, 0))) H5_OUT_OF_MEMORY_ERROR(ENVONLY, "translate_atomic_rbuf: failed to allocate list read buffer"); - /* Convert each element to a compound object */ for (i = 0; i < (size_t)nmembs; i++) { H5T_class_t memb_vlClass; size_t memb_vlSize; size_t memb_offset; + jobject memb_jobj; if ((memb = H5Tget_member_type(mem_type_id, (unsigned int)i)) < 0) H5_LIBRARY_ERROR(ENVONLY); @@ -4326,7 +4326,12 @@ translate_atomic_rbuf(JNIEnv *env, jlong mem_type_id, H5T_class_t type_class, vo if (!(memb_vlSize = H5Tget_size(memb))) H5_LIBRARY_ERROR(ENVONLY); - translate_atomic_rbuf(ENVONLY, memb, memb_vlClass, char_buf + i * typeSize + memb_offset); + memb_jobj = translate_atomic_rbuf(ENVONLY, memb, memb_vlClass, char_buf + memb_offset); + if (memb_jobj) { + ENVPTR->CallBooleanMethod(ENVONLY, jList, arrAddMethod, memb_jobj); + CHECK_JNI_EXCEPTION(ENVONLY, JNI_TRUE); + ENVPTR->DeleteLocalRef(ENVONLY, memb_jobj); + } H5Tclose(memb); } jobj = jList; @@ -4471,7 +4476,14 @@ translate_atomic_rbuf(JNIEnv *env, jlong mem_type_id, H5T_class_t type_class, vo /* Convert each element */ if (is_variable) { char **var_str_buf = (char **)raw_buf; - if (NULL == (jobj = ENVPTR->NewStringUTF(ENVONLY, *var_str_buf))) { + /* Passing NULL to NewStringUTF leads to a crash. A NULL pointer here + * usually means H5Dread did not fill this slot (e.g., if + * caller passed an over-sized buffer). If this happens, return null + * rather than dereferencing. */ + if (*var_str_buf == NULL) { + jobj = NULL; + } + else if (NULL == (jobj = ENVPTR->NewStringUTF(ENVONLY, *var_str_buf))) { CHECK_JNI_EXCEPTION(ENVONLY, JNI_TRUE); H5_OUT_OF_MEMORY_ERROR(ENVONLY, "translate_atomic_rbuf: out of memory - unable to " "construct string from UTF characters"); @@ -4576,22 +4588,32 @@ translate_atomic_wbuf(JNIEnv *env, jobject in_obj, jlong mem_type_id, H5T_class_ if (!(vlSize = H5Tget_size(memb))) H5_LIBRARY_ERROR(ENVONLY); - /* Convert element to a vlen element */ - hvl_t vl_elem; - - jsize jnelmts = ENVPTR->GetArrayLength(ENVONLY, in_obj); + /* Convert ArrayList to plain array */ + if (mToArray == NULL) + CHECK_JNI_EXCEPTION(ENVONLY, JNI_FALSE); + if (NULL == in_obj) + H5_NULL_ARGUMENT_ERROR(ENVONLY, "translate_atomic_wbuf: VL in_obj is NULL"); + jobjectArray array = (jobjectArray)ENVPTR->CallObjectMethod(ENVONLY, in_obj, mToArray); + CHECK_JNI_EXCEPTION(ENVONLY, JNI_FALSE); + if (NULL == array) + H5_NULL_ARGUMENT_ERROR(ENVONLY, "translate_atomic_wbuf: ArrayList.toArray returned NULL"); + jsize jnelmts = ENVPTR->GetArrayLength(ENVONLY, array); if (jnelmts < 0) H5_BAD_ARGUMENT_ERROR(ENVONLY, "translate_atomic_wbuf: number of VL elements < 0"); + /* Convert element to a vlen element */ + hvl_t vl_elem; vl_elem.len = (size_t)jnelmts; if (NULL == (vl_elem.p = malloc((size_t)jnelmts * vlSize))) H5_OUT_OF_MEMORY_ERROR(ENVONLY, "translate_atomic_wbuf: failed to allocate vlen ptr buffer"); - translate_wbuf(ENVONLY, (jobjectArray)in_obj, memb, vlClass, (jsize)jnelmts, vl_elem.p); + translate_wbuf(ENVONLY, array, memb, vlClass, (jsize)jnelmts, vl_elem.p); memcpy(char_buf, &vl_elem, sizeof(hvl_t)); + + ENVPTR->DeleteLocalRef(ENVONLY, array); break; } /* H5T_VLEN */ case H5T_COMPOUND: { @@ -4601,8 +4623,13 @@ translate_atomic_wbuf(JNIEnv *env, jobject in_obj, jlong mem_type_id, H5T_class_ /* invoke the toArray method */ if (mToArray == NULL) CHECK_JNI_EXCEPTION(ENVONLY, JNI_FALSE); - jobjectArray array = (jobjectArray)ENVPTR->CallObjectMethod(ENVONLY, in_obj, mToArray); - jsize jnelmts = ENVPTR->GetArrayLength(ENVONLY, array); + if (NULL == in_obj) + H5_NULL_ARGUMENT_ERROR(ENVONLY, "translate_atomic_wbuf: compound in_obj is NULL"); + jobjectArray array = (jobjectArray)ENVPTR->CallObjectMethod(ENVONLY, in_obj, mToArray); + CHECK_JNI_EXCEPTION(ENVONLY, JNI_FALSE); + if (NULL == array) + H5_NULL_ARGUMENT_ERROR(ENVONLY, "translate_atomic_wbuf: ArrayList.toArray returned NULL"); + jsize jnelmts = ENVPTR->GetArrayLength(ENVONLY, array); if (jnelmts != nmembs) H5_BAD_ARGUMENT_ERROR( @@ -4623,11 +4650,12 @@ translate_atomic_wbuf(JNIEnv *env, jobject in_obj, jlong mem_type_id, H5T_class_ H5_LIBRARY_ERROR(ENVONLY); jobject arr_obj = ENVPTR->GetObjectArrayElement(ENVONLY, array, (jsize)i); - translate_atomic_wbuf(ENVONLY, arr_obj, memb, memb_vlClass, - char_buf + i * typeSize + memb_offset); + translate_atomic_wbuf(ENVONLY, arr_obj, memb, memb_vlClass, char_buf + memb_offset); ENVPTR->DeleteLocalRef(ENVONLY, arr_obj); H5Tclose(memb); } + + ENVPTR->DeleteLocalRef(ENVONLY, array); break; } /* H5T_COMPOUND */ case H5T_ARRAY: { @@ -4644,8 +4672,13 @@ translate_atomic_wbuf(JNIEnv *env, jobject in_obj, jlong mem_type_id, H5T_class_ /* invoke the toArray method */ if (mToArray == NULL) CHECK_JNI_EXCEPTION(ENVONLY, JNI_FALSE); - jobjectArray array = (jobjectArray)ENVPTR->CallObjectMethod(ENVONLY, in_obj, mToArray); - jsize jnelmts = ENVPTR->GetArrayLength(ENVONLY, array); + if (NULL == in_obj) + H5_NULL_ARGUMENT_ERROR(ENVONLY, "translate_atomic_wbuf: array in_obj is NULL"); + jobjectArray array = (jobjectArray)ENVPTR->CallObjectMethod(ENVONLY, in_obj, mToArray); + CHECK_JNI_EXCEPTION(ENVONLY, JNI_FALSE); + if (NULL == array) + H5_NULL_ARGUMENT_ERROR(ENVONLY, "translate_atomic_wbuf: ArrayList.toArray returned NULL"); + jsize jnelmts = ENVPTR->GetArrayLength(ENVONLY, array); if (jnelmts < 0) H5_BAD_ARGUMENT_ERROR(ENVONLY, "translate_atomic_wbuf: number of array elements < 0"); @@ -4656,6 +4689,8 @@ translate_atomic_wbuf(JNIEnv *env, jobject in_obj, jlong mem_type_id, H5T_class_ translate_wbuf(ENVONLY, array, memb, vlClass, (jsize)jnelmts, objBuf); memcpy(char_buf, (char *)objBuf, vlSize * (size_t)jnelmts); + + ENVPTR->DeleteLocalRef(ENVONLY, array); break; } /* H5T_ARRAY */ case H5T_ENUM: @@ -4761,8 +4796,13 @@ translate_atomic_wbuf(JNIEnv *env, jobject in_obj, jlong mem_type_id, H5T_class_ /* Convert each array element - invoke the toArray method */ if (mToArray == NULL) CHECK_JNI_EXCEPTION(ENVONLY, JNI_FALSE); - jobjectArray array = (jobjectArray)ENVPTR->CallObjectMethod(ENVONLY, in_obj, mToArray); - jsize jnelmts = ENVPTR->GetArrayLength(ENVONLY, array); + if (NULL == in_obj) + H5_NULL_ARGUMENT_ERROR(ENVONLY, "translate_atomic_wbuf: complex in_obj is NULL"); + jobjectArray array = (jobjectArray)ENVPTR->CallObjectMethod(ENVONLY, in_obj, mToArray); + CHECK_JNI_EXCEPTION(ENVONLY, JNI_FALSE); + if (NULL == array) + H5_NULL_ARGUMENT_ERROR(ENVONLY, "translate_atomic_wbuf: ArrayList.toArray returned NULL"); + jsize jnelmts = ENVPTR->GetArrayLength(ENVONLY, array); if (jnelmts < 0) H5_BAD_ARGUMENT_ERROR(ENVONLY, "translate_atomic_wbuf: number of array elements < 0"); @@ -4777,6 +4817,7 @@ translate_atomic_wbuf(JNIEnv *env, jobject in_obj, jlong mem_type_id, H5T_class_ if (objBuf) free(objBuf); + ENVPTR->DeleteLocalRef(ENVONLY, array); break; } case H5T_TIME: @@ -4796,11 +4837,11 @@ void translate_rbuf(JNIEnv *env, jobjectArray ret_buf, jlong mem_type_id, H5T_class_t type_class, jsize count, void *raw_buf) { - hid_t memb = H5I_INVALID_HID; - int ret_buflen = -1; - jboolean found_jList = JNI_TRUE; - jobjectArray jList = NULL; - jobject jobj = NULL; + hid_t memb = H5I_INVALID_HID; + int ret_buflen = -1; + jboolean retIsList = JNI_FALSE; + jobjectArray jList = NULL; + jobject jobj = NULL; H5T_class_t vlClass; size_t vlSize; size_t i, x; @@ -4816,9 +4857,19 @@ translate_rbuf(JNIEnv *env, jobjectArray ret_buf, jlong mem_type_id, H5T_class_t if (!(typeSize = H5Tget_size(mem_type_id))) H5_LIBRARY_ERROR(ENVONLY); - ret_buflen = ENVPTR->GetArrayLength(ENVONLY, ret_buf); - if (ret_buflen < 0) - H5_JNI_FATAL_ERROR(ENVONLY, "ret_buflen: Array length cannot be negative"); + /* Top-level calls pass a Java Object[] array; recursive calls pass an + * ArrayList. Detect which so we never call GetArrayLength on a non-array + * (undefined behavior) and so the append-vs-set decision is unambiguous. */ + retIsList = ENVPTR->IsInstanceOf(ENVONLY, ret_buf, arrCList); + if (retIsList) { + /* Lists are always appended to; no slot reuse. */ + ret_buflen = 0; + } + else { + ret_buflen = ENVPTR->GetArrayLength(ENVONLY, ret_buf); + if (ret_buflen < 0) + H5_JNI_FATAL_ERROR(ENVONLY, "ret_buflen: Array length cannot be negative"); + } switch (type_class) { case H5T_VLEN: { @@ -4829,28 +4880,22 @@ translate_rbuf(JNIEnv *env, jobjectArray ret_buf, jlong mem_type_id, H5T_class_t if (!(vlSize = H5Tget_size(memb))) H5_LIBRARY_ERROR(ENVONLY); - /* Convert each element to a list */ for (i = 0; i < (size_t)count; i++) { hvl_t vl_elem; + jList = NULL; - found_jList = JNI_TRUE; - jList = NULL; - - /* Get the number of sequence elements */ memcpy(&vl_elem, char_buf + i * sizeof(hvl_t), sizeof(hvl_t)); jsize nelmts = (jsize)vl_elem.len; if (vl_elem.len != (size_t)nelmts) H5_JNI_FATAL_ERROR(ENVONLY, "translate_rbuf: overflow of number of VL elements"); - if (nelmts < 0) H5_BAD_ARGUMENT_ERROR(ENVONLY, "translate_rbuf: number of VL elements < 0"); - /* The list we're going to return: */ - if (i < (size_t)ret_buflen) { + if (i < (size_t)ret_buflen) jList = ENVPTR->GetObjectArrayElement(ENVONLY, (jobjectArray)ret_buf, (jsize)i); - } + if (jList != NULL && !ENVPTR->IsInstanceOf(ENVONLY, jList, arrCList)) + H5_BAD_ARGUMENT_ERROR(ENVONLY, "translate_rbuf: VLEN slot is not a java.util.ArrayList"); if (NULL == jList) { - found_jList = JNI_FALSE; if (NULL == (jList = (jobjectArray)ENVPTR->NewObject(ENVONLY, arrCList, arrListMethod, 0))) H5_OUT_OF_MEMORY_ERROR(ENVONLY, @@ -4858,39 +4903,38 @@ translate_rbuf(JNIEnv *env, jobjectArray ret_buf, jlong mem_type_id, H5T_class_t } translate_rbuf(ENVONLY, jList, memb, vlClass, (jsize)nelmts, vl_elem.p); - if (found_jList == JNI_FALSE) { - jboolean addResult = - ENVPTR->CallBooleanMethod(ENVONLY, ret_buf, arrAddMethod, (jobject)jList); - if (!addResult) - H5_JNI_FATAL_ERROR(ENVONLY, "translate_rbuf: cannot add VL element"); - } - else { + + /* ArrayList (recursive call): append. Java array: install at slot i. */ + if (retIsList) + ENVPTR->CallBooleanMethod(ENVONLY, ret_buf, arrAddMethod, (jobject)jList); + else ENVPTR->SetObjectArrayElement(ENVONLY, ret_buf, (jsize)i, (jobject)jList); - CHECK_JNI_EXCEPTION(ENVONLY, JNI_TRUE); - } + CHECK_JNI_EXCEPTION(ENVONLY, JNI_TRUE); ENVPTR->DeleteLocalRef(ENVONLY, jList); } break; } /* H5T_VLEN */ case H5T_COMPOUND: { - /* Convert each compound element to a list */ + /* Convert each compound element to a list. If a per-row slot was + * pre-allocated by the caller, reuse it, otherwise allocate a new + * ArrayList. */ for (i = 0; i < (size_t)count; i++) { - found_jList = JNI_TRUE; - jList = NULL; + jList = NULL; - /* The list we're going to return: */ if (i < (size_t)ret_buflen) { jList = ENVPTR->GetObjectArrayElement(ENVONLY, (jobjectArray)ret_buf, (jsize)i); } + if (jList != NULL && !ENVPTR->IsInstanceOf(ENVONLY, jList, arrCList)) + H5_BAD_ARGUMENT_ERROR(ENVONLY, + "translate_rbuf: COMPOUND slot is not a java.util.ArrayList"); if (NULL == jList) { - found_jList = JNI_FALSE; if (NULL == (jList = (jobjectArray)ENVPTR->NewObject(ENVONLY, arrCList, arrListMethod, 0))) H5_OUT_OF_MEMORY_ERROR(ENVONLY, "translate_rbuf: failed to allocate list read buffer"); } int nmembs = H5Tget_nmembers(mem_type_id); - /* Convert each element to a list */ + /* Append each member's value to this row's ArrayList */ for (x = 0; x < (size_t)nmembs; x++) { H5T_class_t memb_vlClass; size_t memb_vlSize; @@ -4908,17 +4952,14 @@ translate_rbuf(JNIEnv *env, jobjectArray ret_buf, jlong mem_type_id, H5T_class_t jobj = translate_atomic_rbuf(ENVONLY, memb, memb_vlClass, char_buf + i * typeSize + memb_offset); if (jobj) { - if (found_jList == JNI_FALSE) - ENVPTR->CallBooleanMethod(ENVONLY, jList, arrAddMethod, (jobject)jobj); - else - ENVPTR->SetObjectArrayElement(ENVONLY, jList, (jsize)i, (jobject)jobj); + ENVPTR->CallBooleanMethod(ENVONLY, jList, arrAddMethod, (jobject)jobj); CHECK_JNI_EXCEPTION(ENVONLY, JNI_TRUE); ENVPTR->DeleteLocalRef(ENVONLY, jobj); } H5Tclose(memb); } - if (ret_buflen == 0) + if (retIsList) ENVPTR->CallBooleanMethod(ENVONLY, ret_buf, arrAddMethod, jList); else ENVPTR->SetObjectArrayElement(ENVONLY, ret_buf, (jsize)i, jList); @@ -4947,8 +4988,7 @@ translate_rbuf(JNIEnv *env, jobjectArray ret_buf, jlong mem_type_id, H5T_class_t /* Convert each element to a list */ for (i = 0; i < (size_t)count; i++) { - found_jList = JNI_TRUE; - jList = NULL; + jList = NULL; /* Get the object element */ memcpy((char *)objBuf, char_buf + i * typeSize, typeSize); @@ -4959,6 +4999,8 @@ translate_rbuf(JNIEnv *env, jobjectArray ret_buf, jlong mem_type_id, H5T_class_t (jList = ENVPTR->GetObjectArrayElement(ENVONLY, (jobjectArray)ret_buf, (jsize)i))) found_jList = JNI_FALSE; } + if (jList != NULL && !ENVPTR->IsInstanceOf(ENVONLY, jList, arrCList)) + H5_BAD_ARGUMENT_ERROR(ENVONLY, "translate_rbuf: ARRAY slot is not a java.util.ArrayList"); if (NULL == jList) { if (NULL == (jList = (jobjectArray)ENVPTR->NewObject(ENVONLY, arrCList, arrListMethod, 0))) @@ -4967,7 +5009,7 @@ translate_rbuf(JNIEnv *env, jobjectArray ret_buf, jlong mem_type_id, H5T_class_t } translate_rbuf(ENVONLY, jList, memb, vlClass, (jsize)typeCount, objBuf); - if (found_jList == JNI_FALSE) + if (retIsList) ENVPTR->CallBooleanMethod(ENVONLY, ret_buf, arrAddMethod, jList); else ENVPTR->SetObjectArrayElement(ENVONLY, ret_buf, (jsize)i, jList); @@ -4991,7 +5033,7 @@ translate_rbuf(JNIEnv *env, jobjectArray ret_buf, jlong mem_type_id, H5T_class_t for (i = 0; i < (size_t)count; i++) { jobj = translate_atomic_rbuf(ENVONLY, mem_type_id, type_class, char_buf + i * typeSize); if (jobj) { - if (ret_buflen == 0) + if (retIsList) ENVPTR->CallBooleanMethod(ENVONLY, ret_buf, arrAddMethod, (jobject)jobj); else ENVPTR->SetObjectArrayElement(ENVONLY, ret_buf, (jsize)i, (jobject)jobj); @@ -5022,8 +5064,7 @@ translate_rbuf(JNIEnv *env, jobjectArray ret_buf, jlong mem_type_id, H5T_class_t /* Convert each element to a list of 2 floating-point elements */ for (i = 0; i < (size_t)count; i++) { - found_jList = JNI_TRUE; - jList = NULL; + jList = NULL; /* Get the object element */ memcpy((char *)objBuf, char_buf + i * typeSize, typeSize); @@ -5034,6 +5075,9 @@ translate_rbuf(JNIEnv *env, jobjectArray ret_buf, jlong mem_type_id, H5T_class_t (jList = ENVPTR->GetObjectArrayElement(ENVONLY, (jobjectArray)ret_buf, (jsize)i))) found_jList = JNI_FALSE; } + if (jList != NULL && !ENVPTR->IsInstanceOf(ENVONLY, jList, arrCList)) + H5_BAD_ARGUMENT_ERROR(ENVONLY, + "translate_rbuf: COMPLEX slot is not a java.util.ArrayList"); if (NULL == jList) { if (NULL == (jList = (jobjectArray)ENVPTR->NewObject(ENVONLY, arrCList, arrListMethod, 0))) @@ -5042,7 +5086,7 @@ translate_rbuf(JNIEnv *env, jobjectArray ret_buf, jlong mem_type_id, H5T_class_t } translate_rbuf(ENVONLY, jList, memb, base_class, (jsize)typeCount, objBuf); - if (found_jList == JNI_FALSE) + if (retIsList) ENVPTR->CallBooleanMethod(ENVONLY, ret_buf, arrAddMethod, jList); else ENVPTR->SetObjectArrayElement(ENVONLY, ret_buf, (jsize)i, jList); @@ -5103,14 +5147,23 @@ translate_wbuf(JNIEnv *env, jobjectArray in_buf, jlong mem_type_id, H5T_class_t for (i = 0; i < (size_t)count; i++) { hvl_t vl_elem; - if (NULL == (jList = ENVPTR->GetObjectArrayElement(ENVONLY, (jobjectArray)in_buf, (jsize)i))) + if (NULL == + (jList = ENVPTR->GetObjectArrayElement(ENVONLY, (jobjectArray)in_buf, (jsize)i))) { CHECK_JNI_EXCEPTION(ENVONLY, JNI_FALSE); + H5_NULL_ARGUMENT_ERROR(ENVONLY, "translate_wbuf: in_buf element is NULL"); + } + + if (!ENVPTR->IsInstanceOf(ENVONLY, jList, arrCList)) + H5_BAD_ARGUMENT_ERROR(ENVONLY, "translate_wbuf: VLEN slot is not a java.util.ArrayList"); /* invoke the toArray method */ if (mToArray == NULL) CHECK_JNI_EXCEPTION(ENVONLY, JNI_FALSE); - jobjectArray array = (jobjectArray)ENVPTR->CallObjectMethod(ENVONLY, jList, mToArray); - jsize jnelmts = ENVPTR->GetArrayLength(ENVONLY, array); + jobjectArray array = (jobjectArray)ENVPTR->CallObjectMethod(ENVONLY, jList, mToArray); + CHECK_JNI_EXCEPTION(ENVONLY, JNI_FALSE); + if (NULL == array) + H5_NULL_ARGUMENT_ERROR(ENVONLY, "translate_wbuf: ArrayList.toArray returned NULL"); + jsize jnelmts = ENVPTR->GetArrayLength(ENVONLY, array); if (jnelmts < 0) H5_BAD_ARGUMENT_ERROR(ENVONLY, "translate_wbuf: number of VL elements < 0"); @@ -5124,6 +5177,7 @@ translate_wbuf(JNIEnv *env, jobjectArray in_buf, jlong mem_type_id, H5T_class_t memcpy(char_buf + i * sizeof(hvl_t), &vl_elem, sizeof(hvl_t)); + ENVPTR->DeleteLocalRef(ENVONLY, array); ENVPTR->DeleteLocalRef(ENVONLY, jList); } /* end for (i = 0; i < count; i++) */ break; @@ -5131,16 +5185,26 @@ translate_wbuf(JNIEnv *env, jobjectArray in_buf, jlong mem_type_id, H5T_class_t case H5T_COMPOUND: { /* Convert each list to a compound element */ for (i = 0; i < (size_t)count; i++) { - if (NULL == (jList = ENVPTR->GetObjectArrayElement(ENVONLY, (jobjectArray)in_buf, (jsize)i))) + if (NULL == + (jList = ENVPTR->GetObjectArrayElement(ENVONLY, (jobjectArray)in_buf, (jsize)i))) { CHECK_JNI_EXCEPTION(ENVONLY, JNI_FALSE); + H5_NULL_ARGUMENT_ERROR(ENVONLY, "translate_wbuf: in_buf element is NULL"); + } + + if (!ENVPTR->IsInstanceOf(ENVONLY, jList, arrCList)) + H5_BAD_ARGUMENT_ERROR(ENVONLY, + "translate_wbuf: COMPOUND slot is not a java.util.ArrayList"); int nmembs = H5Tget_nmembers(mem_type_id); /* invoke the toArray method */ if (mToArray == NULL) CHECK_JNI_EXCEPTION(ENVONLY, JNI_FALSE); - jobjectArray array = (jobjectArray)ENVPTR->CallObjectMethod(ENVONLY, jList, mToArray); - jsize jnelmts = ENVPTR->GetArrayLength(ENVONLY, array); + jobjectArray array = (jobjectArray)ENVPTR->CallObjectMethod(ENVONLY, jList, mToArray); + CHECK_JNI_EXCEPTION(ENVONLY, JNI_FALSE); + if (NULL == array) + H5_NULL_ARGUMENT_ERROR(ENVONLY, "translate_wbuf: ArrayList.toArray returned NULL"); + jsize jnelmts = ENVPTR->GetArrayLength(ENVONLY, array); if (jnelmts != nmembs) H5_BAD_ARGUMENT_ERROR( @@ -5168,6 +5232,7 @@ translate_wbuf(JNIEnv *env, jobjectArray in_buf, jlong mem_type_id, H5T_class_t H5Tclose(memb); } + ENVPTR->DeleteLocalRef(ENVONLY, array); ENVPTR->DeleteLocalRef(ENVONLY, jList); } /* end for (i = 0; i < count; i++) */ break; @@ -5182,14 +5247,23 @@ translate_wbuf(JNIEnv *env, jobjectArray in_buf, jlong mem_type_id, H5T_class_t /* Convert each list to an array element */ for (i = 0; i < (size_t)count; i++) { - if (NULL == (jList = ENVPTR->GetObjectArrayElement(ENVONLY, (jobjectArray)in_buf, (jsize)i))) + if (NULL == + (jList = ENVPTR->GetObjectArrayElement(ENVONLY, (jobjectArray)in_buf, (jsize)i))) { CHECK_JNI_EXCEPTION(ENVONLY, JNI_FALSE); + H5_NULL_ARGUMENT_ERROR(ENVONLY, "translate_wbuf: in_buf element is NULL"); + } + + if (!ENVPTR->IsInstanceOf(ENVONLY, jList, arrCList)) + H5_BAD_ARGUMENT_ERROR(ENVONLY, "translate_wbuf: ARRAY slot is not a java.util.ArrayList"); /* invoke the toArray method */ if (mToArray == NULL) CHECK_JNI_EXCEPTION(ENVONLY, JNI_FALSE); - jobjectArray array = (jobjectArray)ENVPTR->CallObjectMethod(ENVONLY, jList, mToArray); - jsize jnelmts = ENVPTR->GetArrayLength(ENVONLY, array); + jobjectArray array = (jobjectArray)ENVPTR->CallObjectMethod(ENVONLY, jList, mToArray); + CHECK_JNI_EXCEPTION(ENVONLY, JNI_FALSE); + if (NULL == array) + H5_NULL_ARGUMENT_ERROR(ENVONLY, "translate_wbuf: ArrayList.toArray returned NULL"); + jsize jnelmts = ENVPTR->GetArrayLength(ENVONLY, array); if (jnelmts < 0) H5_BAD_ARGUMENT_ERROR(ENVONLY, "translate_wbuf: number of array elements < 0"); @@ -5197,6 +5271,7 @@ translate_wbuf(JNIEnv *env, jobjectArray in_buf, jlong mem_type_id, H5T_class_t translate_wbuf(ENVONLY, array, memb, vlClass, jnelmts, char_buf + i * vlSize * (size_t)jnelmts); + ENVPTR->DeleteLocalRef(ENVONLY, array); ENVPTR->DeleteLocalRef(ENVONLY, jList); } /* end for (i = 0; i < count; i++) */ break; @@ -5230,14 +5305,24 @@ translate_wbuf(JNIEnv *env, jobjectArray in_buf, jlong mem_type_id, H5T_class_t /* Convert each list to an array element */ for (i = 0; i < (size_t)count; i++) { - if (NULL == (jList = ENVPTR->GetObjectArrayElement(ENVONLY, (jobjectArray)in_buf, (jsize)i))) + if (NULL == + (jList = ENVPTR->GetObjectArrayElement(ENVONLY, (jobjectArray)in_buf, (jsize)i))) { CHECK_JNI_EXCEPTION(ENVONLY, JNI_FALSE); + H5_NULL_ARGUMENT_ERROR(ENVONLY, "translate_wbuf: in_buf element is NULL"); + } + + if (!ENVPTR->IsInstanceOf(ENVONLY, jList, arrCList)) + H5_BAD_ARGUMENT_ERROR(ENVONLY, + "translate_wbuf: COMPLEX slot is not a java.util.ArrayList"); /* invoke the toArray method */ if (mToArray == NULL) CHECK_JNI_EXCEPTION(ENVONLY, JNI_FALSE); - jobjectArray array = (jobjectArray)ENVPTR->CallObjectMethod(ENVONLY, jList, mToArray); - jsize jnelmts = ENVPTR->GetArrayLength(ENVONLY, array); + jobjectArray array = (jobjectArray)ENVPTR->CallObjectMethod(ENVONLY, jList, mToArray); + CHECK_JNI_EXCEPTION(ENVONLY, JNI_FALSE); + if (NULL == array) + H5_NULL_ARGUMENT_ERROR(ENVONLY, "translate_wbuf: ArrayList.toArray returned NULL"); + jsize jnelmts = ENVPTR->GetArrayLength(ENVONLY, array); if (jnelmts < 0) H5_BAD_ARGUMENT_ERROR(ENVONLY, "translate_wbuf: number of array elements < 0"); @@ -5245,6 +5330,7 @@ translate_wbuf(JNIEnv *env, jobjectArray in_buf, jlong mem_type_id, H5T_class_t translate_wbuf(ENVONLY, array, memb, base_class, jnelmts, char_buf + i * base_size * (size_t)jnelmts); + ENVPTR->DeleteLocalRef(ENVONLY, array); ENVPTR->DeleteLocalRef(ENVONLY, jList); } /* end for (i = 0; i < count; i++) */ diff --git a/java/src-jni/test/TestH5D.java b/java/src-jni/test/TestH5D.java index 794c7c39f727..e8a58589a3c3 100644 --- a/java/src-jni/test/TestH5D.java +++ b/java/src-jni/test/TestH5D.java @@ -12,7 +12,9 @@ package test; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -1755,6 +1757,166 @@ public void testH5DArray_string_buffer() throws Throwable arr_str_data[3].get(0).equals(arr_readbuf[3].get(0))); } + /* + * Verify H5DreadVL safe throws a Java exception + * when called with a malformed buffer shape for an ARRAY-of-varstr dataset. + */ + @Test + public void testH5DArray_string_buffer_flat_StringArray() throws Throwable + { + String dset_str_name = "ArrayStringdata_flat"; + long dset_str_id = HDF5Constants.H5I_INVALID_HID; + long dtype_str_id = HDF5Constants.H5I_INVALID_HID; + long varstr_id = HDF5Constants.H5I_INVALID_HID; + long dspace_id = HDF5Constants.H5I_INVALID_HID; + long[] strdims = {3}; + long[] dims = {2}; + long lsize = 1; + + String[] row0 = {"a", "bb", "ccc"}; + String[] row1 = {"dd", "ee", "fff"}; + + ArrayList[] arr_str_data = new ArrayList[2]; + arr_str_data[0] = new ArrayList(Arrays.asList(row0)); + arr_str_data[1] = new ArrayList(Arrays.asList(row1)); + + try { + varstr_id = H5.H5Tcopy(HDF5Constants.H5T_C_S1); + H5.H5Tset_size(varstr_id, HDF5Constants.H5T_VARIABLE); + dtype_str_id = H5.H5Tarray_create(varstr_id, 1, strdims); + + dspace_id = H5.H5Screate_simple(1, dims, null); + dset_str_id = + H5.H5Dcreate(H5fid, dset_str_name, dtype_str_id, dspace_id, HDF5Constants.H5P_DEFAULT, + HDF5Constants.H5P_DEFAULT, HDF5Constants.H5P_DEFAULT); + H5.H5DwriteVL(dset_str_id, dtype_str_id, HDF5Constants.H5S_ALL, HDF5Constants.H5S_ALL, + HDF5Constants.H5P_DEFAULT, arr_str_data); + H5.H5Fflush(H5fid, HDF5Constants.H5F_SCOPE_LOCAL); + + for (int j = 0; j < dims.length; j++) + lsize *= dims[j]; + + // Malformed buffer: a flat String[dims*array_dim], not + // ArrayList[dims] of array_dim Strings. + int flatLen = (int)lsize * (int)strdims[0]; + String[] badBuf = new String[flatLen]; + for (int j = 0; j < flatLen; j++) + badBuf[j] = ""; + + // The JNI must not segfault here regardless of what badBuf looks + // like. Either it correctly populates the slots or it throws. + try { + H5.H5DreadVL(dset_str_id, dtype_str_id, HDF5Constants.H5S_ALL, HDF5Constants.H5S_ALL, + HDF5Constants.H5P_DEFAULT, badBuf); + } + catch (Exception ex) { + // Accepted outcome: graceful Java-level error. + return; + } + + assertNotNull("badBuf[0] should not be null after H5DreadVL", badBuf[0]); + } + finally { + if (dset_str_id > 0) + try { + H5.H5Dclose(dset_str_id); + } + catch (Exception ex) { + } + if (dspace_id > 0) + try { + H5.H5Sclose(dspace_id); + } + catch (Exception ex) { + } + if (dtype_str_id > 0) + try { + H5.H5Tclose(dtype_str_id); + } + catch (Exception ex) { + } + if (varstr_id > 0) + try { + H5.H5Tclose(varstr_id); + } + catch (Exception ex) { + } + } + } + + /* + * Verify H5DwriteVL safely throws a Java exception + * when called with a malformed buffer shape for an ARRAY-of-varstr dataset. + */ + @Test + public void testH5DArray_string_buffer_flat_StringArray_write() throws Throwable + { + String dset_str_name = "ArrayStringdata_flat_write"; + long dset_str_id = HDF5Constants.H5I_INVALID_HID; + long dtype_str_id = HDF5Constants.H5I_INVALID_HID; + long varstr_id = HDF5Constants.H5I_INVALID_HID; + long dspace_id = HDF5Constants.H5I_INVALID_HID; + long[] strdims = {3}; + long[] dims = {2}; + + try { + varstr_id = H5.H5Tcopy(HDF5Constants.H5T_C_S1); + H5.H5Tset_size(varstr_id, HDF5Constants.H5T_VARIABLE); + dtype_str_id = H5.H5Tarray_create(varstr_id, 1, strdims); + + dspace_id = H5.H5Screate_simple(1, dims, null); + dset_str_id = + H5.H5Dcreate(H5fid, dset_str_name, dtype_str_id, dspace_id, HDF5Constants.H5P_DEFAULT, + HDF5Constants.H5P_DEFAULT, HDF5Constants.H5P_DEFAULT); + + // Malformed buffer: a flat String[dims*array_dim], not + // ArrayList[dims] of array_dim Strings. + int flatLen = (int)dims[0] * (int)strdims[0]; + String[] flatBuf = new String[flatLen]; + for (int j = 0; j < flatLen; j++) + flatBuf[j] = "s" + j; + + try { + H5.H5DwriteVL(dset_str_id, dtype_str_id, HDF5Constants.H5S_ALL, HDF5Constants.H5S_ALL, + HDF5Constants.H5P_DEFAULT, flatBuf); + } + catch (Exception ex) { + // Accepted outcome: graceful Java-level error. + return; + } + + // If we reach here, no exception was thrown. Worst case is the JVM + // segfaults before this line. If it accepted the write silently, + // that itself indicates the JNI doesn't validate the buffer shape. + } + finally { + if (dset_str_id > 0) + try { + H5.H5Dclose(dset_str_id); + } + catch (Exception ex) { + } + if (dspace_id > 0) + try { + H5.H5Sclose(dspace_id); + } + catch (Exception ex) { + } + if (dtype_str_id > 0) + try { + H5.H5Tclose(dtype_str_id); + } + catch (Exception ex) { + } + if (varstr_id > 0) + try { + H5.H5Tclose(varstr_id); + } + catch (Exception ex) { + } + } + } + @Test public void testH5DArrayenum_rw() { @@ -1947,4 +2109,521 @@ public void testH5DArrayenum_rw() } } } + + /* + * Build a 1-D dataset of type COMPOUND { seq: VLEN { int32 }, n: int32 } + * inside the per-test file (H5fid) and write canonical data via H5DwriteVL. + * Closes the vlen/compound/dataspace ids internally and returns only the + * open dataset id; the caller closes the dataset and re-fetches the type + * via H5Dget_type if needed. + */ + private long writeCompoundOfVlenDataset(String dsetName) throws Exception + { + long vlen_tid = HDF5Constants.H5I_INVALID_HID; + long cmpd_tid = HDF5Constants.H5I_INVALID_HID; + long dspace_id = HDF5Constants.H5I_INVALID_HID; + long dset_id = HDF5Constants.H5I_INVALID_HID; + + try { + vlen_tid = H5.H5Tvlen_create(HDF5Constants.H5T_NATIVE_INT); + assertTrue("writeCompoundOfVlenDataset: H5Tvlen_create: ", vlen_tid >= 0); + + long hvlSize = H5.H5Tget_size(vlen_tid); + long intSize = H5.H5Tget_size(HDF5Constants.H5T_NATIVE_INT); + long packedSize = hvlSize + intSize; + cmpd_tid = H5.H5Tcreate(HDF5Constants.H5T_COMPOUND, packedSize); + assertTrue("writeCompoundOfVlenDataset: H5Tcreate compound: ", cmpd_tid >= 0); + H5.H5Tinsert(cmpd_tid, "seq", 0, vlen_tid); + H5.H5Tinsert(cmpd_tid, "n", hvlSize, HDF5Constants.H5T_NATIVE_INT); + H5.H5Tpack(cmpd_tid); + + long[] dims = {2}; + dspace_id = H5.H5Screate_simple(1, dims, null); + assertTrue("writeCompoundOfVlenDataset: H5Screate_simple: ", dspace_id >= 0); + + dset_id = H5.H5Dcreate(H5fid, dsetName, cmpd_tid, dspace_id, HDF5Constants.H5P_DEFAULT, + HDF5Constants.H5P_DEFAULT, HDF5Constants.H5P_DEFAULT); + assertTrue("writeCompoundOfVlenDataset: H5Dcreate: ", dset_id >= 0); + + ArrayList seq0 = new ArrayList<>(); + seq0.add(1); + seq0.add(2); + seq0.add(3); + ArrayList seq1 = new ArrayList<>(); + seq1.add(5); + seq1.add(6); + + ArrayList[] write_data = new ArrayList[2]; + ArrayList rec0 = new ArrayList<>(); + rec0.add(seq0); + rec0.add(Integer.valueOf(4)); + write_data[0] = rec0; + ArrayList rec1 = new ArrayList<>(); + rec1.add(seq1); + rec1.add(Integer.valueOf(7)); + write_data[1] = rec1; + + H5.H5DwriteVL(dset_id, cmpd_tid, HDF5Constants.H5S_ALL, HDF5Constants.H5S_ALL, + HDF5Constants.H5P_DEFAULT, write_data); + H5.H5Fflush(H5fid, HDF5Constants.H5F_SCOPE_LOCAL); + } + finally { + if (dspace_id >= 0) + try { + H5.H5Sclose(dspace_id); + } + catch (Exception ex) { + } + if (cmpd_tid >= 0) + try { + H5.H5Tclose(cmpd_tid); + } + catch (Exception ex) { + } + if (vlen_tid >= 0) + try { + H5.H5Tclose(vlen_tid); + } + catch (Exception ex) { + } + } + + return dset_id; + } + + /* + * Read a 1-D dataset whose type is COMPOUND { seq: VLEN { int32 }, n: int32 }. + * Uses the canonical calling pattern: pass ArrayList[] with null slots + * and let the native code allocate each per-row record. + */ + @Test + public void testH5Dread_compound_of_vlen() + { + long dset_id = HDF5Constants.H5I_INVALID_HID; + long file_type_id = HDF5Constants.H5I_INVALID_HID; + final int N_ROWS = 2; + + try { + dset_id = writeCompoundOfVlenDataset("cmpd_of_vlen_rd"); + file_type_id = H5.H5Dget_type(dset_id); + assertTrue("testH5Dread_compound_of_vlen: H5Dget_type: ", file_type_id >= 0); + + ArrayList[] read_data = new ArrayList[N_ROWS]; + H5.H5DreadVL(dset_id, file_type_id, HDF5Constants.H5S_ALL, HDF5Constants.H5S_ALL, + HDF5Constants.H5P_DEFAULT, read_data); + + assertNotNull("testH5Dread_compound_of_vlen: read_data[0] not null", read_data[0]); + assertNotNull("testH5Dread_compound_of_vlen: read_data[1] not null", read_data[1]); + assertEquals("testH5Dread_compound_of_vlen: row 0 record has 2 members", 2, read_data[0].size()); + assertEquals("testH5Dread_compound_of_vlen: row 1 record has 2 members", 2, read_data[1].size()); + + Object seq0obj = read_data[0].get(0); + Object seq1obj = read_data[1].get(0); + assertTrue("testH5Dread_compound_of_vlen: row 0 seq is ArrayList, got " + + (seq0obj == null ? "null" : seq0obj.getClass().getName()), + seq0obj instanceof ArrayList); + assertTrue("testH5Dread_compound_of_vlen: row 1 seq is ArrayList, got " + + (seq1obj == null ? "null" : seq1obj.getClass().getName()), + seq1obj instanceof ArrayList); + + ArrayList seq0_read = (ArrayList)seq0obj; + ArrayList seq1_read = (ArrayList)seq1obj; + assertEquals("testH5Dread_compound_of_vlen: row 0 seq length", 3, seq0_read.size()); + assertEquals("testH5Dread_compound_of_vlen: row 1 seq length", 2, seq1_read.size()); + assertEquals("testH5Dread_compound_of_vlen: row 0 seq[0]", Integer.valueOf(1), seq0_read.get(0)); + assertEquals("testH5Dread_compound_of_vlen: row 0 seq[1]", Integer.valueOf(2), seq0_read.get(1)); + assertEquals("testH5Dread_compound_of_vlen: row 0 seq[2]", Integer.valueOf(3), seq0_read.get(2)); + assertEquals("testH5Dread_compound_of_vlen: row 1 seq[0]", Integer.valueOf(5), seq1_read.get(0)); + assertEquals("testH5Dread_compound_of_vlen: row 1 seq[1]", Integer.valueOf(6), seq1_read.get(1)); + assertEquals("testH5Dread_compound_of_vlen: row 0 n", Integer.valueOf(4), read_data[0].get(1)); + assertEquals("testH5Dread_compound_of_vlen: row 1 n", Integer.valueOf(7), read_data[1].get(1)); + } + catch (Throwable err) { + err.printStackTrace(); + fail("testH5Dread_compound_of_vlen: " + err); + } + finally { + if (file_type_id >= 0) + try { + H5.H5Tclose(file_type_id); + } + catch (Exception ex) { + } + if (dset_id >= 0) + try { + H5.H5Dclose(dset_id); + } + catch (Exception ex) { + } + } + } + + /* + * Same compound-of-vlen read as testH5Dread_compound_of_vlen, but the caller + * pre-allocates each row's ArrayList. Should behave the same as the + * no-pre-allocation test. + */ + @Test + public void testH5Dread_compound_of_vlen_preallocated() + { + long dset_id = HDF5Constants.H5I_INVALID_HID; + long file_type_id = HDF5Constants.H5I_INVALID_HID; + final int N_ROWS = 2; + + try { + dset_id = writeCompoundOfVlenDataset("cmpd_of_vlen_rd_prealloc"); + file_type_id = H5.H5Dget_type(dset_id); + assertTrue("testH5Dread_compound_of_vlen_preallocated: H5Dget_type: ", file_type_id >= 0); + + ArrayList[] read_data = new ArrayList[N_ROWS]; + for (int i = 0; i < N_ROWS; i++) + read_data[i] = new ArrayList(); + + H5.H5DreadVL(dset_id, file_type_id, HDF5Constants.H5S_ALL, HDF5Constants.H5S_ALL, + HDF5Constants.H5P_DEFAULT, read_data); + + assertEquals("testH5Dread_compound_of_vlen_preallocated: row 0 record has 2 members", 2, + read_data[0].size()); + assertEquals("testH5Dread_compound_of_vlen_preallocated: row 1 record has 2 members", 2, + read_data[1].size()); + + Object seq0obj = read_data[0].get(0); + assertTrue("testH5Dread_compound_of_vlen_preallocated: row 0 seq is ArrayList, got " + + (seq0obj == null ? "null" : seq0obj.getClass().getName()), + seq0obj instanceof ArrayList); + ArrayList seq0_read = (ArrayList)seq0obj; + assertEquals("testH5Dread_compound_of_vlen_preallocated: row 0 seq length", 3, seq0_read.size()); + assertEquals("testH5Dread_compound_of_vlen_preallocated: row 0 seq[0]", Integer.valueOf(1), + seq0_read.get(0)); + } + catch (Throwable err) { + err.printStackTrace(); + fail("testH5Dread_compound_of_vlen_preallocated: " + err); + } + finally { + if (file_type_id >= 0) + try { + H5.H5Tclose(file_type_id); + } + catch (Exception ex) { + } + if (dset_id >= 0) + try { + H5.H5Dclose(dset_id); + } + catch (Exception ex) { + } + } + } + + /* + * Round-trip a compound-of-vlen dataset through H5DwriteVL and H5DreadVL. + * Reuses the writeCompoundOfVlenDataset helper to exercise the write path, + * then reads back and asserts the full row contents. + */ + @Test + public void testH5Dwrite_compound_of_vlen() + { + long dset_id = HDF5Constants.H5I_INVALID_HID; + long file_type_id = HDF5Constants.H5I_INVALID_HID; + final int N_ROWS = 2; + + try { + dset_id = writeCompoundOfVlenDataset("cmpd_of_vlen_wr"); + file_type_id = H5.H5Dget_type(dset_id); + assertTrue("testH5Dwrite_compound_of_vlen: H5Dget_type: ", file_type_id >= 0); + + ArrayList[] read_data = new ArrayList[N_ROWS]; + H5.H5DreadVL(dset_id, file_type_id, HDF5Constants.H5S_ALL, HDF5Constants.H5S_ALL, + HDF5Constants.H5P_DEFAULT, read_data); + + assertNotNull("testH5Dwrite_compound_of_vlen: read_data[0] not null", read_data[0]); + assertNotNull("testH5Dwrite_compound_of_vlen: read_data[1] not null", read_data[1]); + assertEquals("testH5Dwrite_compound_of_vlen: row 0 record has 2 members", 2, read_data[0].size()); + assertEquals("testH5Dwrite_compound_of_vlen: row 1 record has 2 members", 2, read_data[1].size()); + + ArrayList seq0_read = (ArrayList)read_data[0].get(0); + ArrayList seq1_read = (ArrayList)read_data[1].get(0); + assertEquals("testH5Dwrite_compound_of_vlen: row 0 seq length", 3, seq0_read.size()); + assertEquals("testH5Dwrite_compound_of_vlen: row 1 seq length", 2, seq1_read.size()); + assertEquals("testH5Dwrite_compound_of_vlen: row 0 seq[0]", Integer.valueOf(1), seq0_read.get(0)); + assertEquals("testH5Dwrite_compound_of_vlen: row 0 seq[1]", Integer.valueOf(2), seq0_read.get(1)); + assertEquals("testH5Dwrite_compound_of_vlen: row 0 seq[2]", Integer.valueOf(3), seq0_read.get(2)); + assertEquals("testH5Dwrite_compound_of_vlen: row 1 seq[0]", Integer.valueOf(5), seq1_read.get(0)); + assertEquals("testH5Dwrite_compound_of_vlen: row 1 seq[1]", Integer.valueOf(6), seq1_read.get(1)); + assertEquals("testH5Dwrite_compound_of_vlen: row 0 n", Integer.valueOf(4), read_data[0].get(1)); + assertEquals("testH5Dwrite_compound_of_vlen: row 1 n", Integer.valueOf(7), read_data[1].get(1)); + } + catch (Throwable err) { + err.printStackTrace(); + fail("testH5Dwrite_compound_of_vlen: " + err); + } + finally { + if (file_type_id >= 0) + try { + H5.H5Tclose(file_type_id); + } + catch (Exception ex) { + } + if (dset_id >= 0) + try { + H5.H5Dclose(dset_id); + } + catch (Exception ex) { + } + } + } + + /* + * Read a 1-D dataset whose type is VLEN { COMPOUND { A: int, B: int } } using the + * canonical null-row-slot read pattern. Exercises the translate_rbuf H5T_VLEN + * top-level case; pre-fix this read SIGSEGVed because the function's + * found_jList-FALSE branch called ArrayList.add() on the caller's Java array. + */ + @Test + public void testH5Dread_vlen_of_compound_nullslots() + { + long cmpd_tid = HDF5Constants.H5I_INVALID_HID; + long vlen_tid = HDF5Constants.H5I_INVALID_HID; + long dspace_id = HDF5Constants.H5I_INVALID_HID; + long dset_id = HDF5Constants.H5I_INVALID_HID; + final int N_ROWS = 2; + + try { + long intSize = H5.H5Tget_size(HDF5Constants.H5T_NATIVE_INT); + + cmpd_tid = H5.H5Tcreate(HDF5Constants.H5T_COMPOUND, 2 * intSize); + H5.H5Tinsert(cmpd_tid, "A", 0, HDF5Constants.H5T_NATIVE_INT); + H5.H5Tinsert(cmpd_tid, "B", intSize, HDF5Constants.H5T_NATIVE_INT); + H5.H5Tpack(cmpd_tid); + + vlen_tid = H5.H5Tvlen_create(cmpd_tid); + long[] dims = {N_ROWS}; + dspace_id = H5.H5Screate_simple(1, dims, null); + + dset_id = H5.H5Dcreate(H5fid, "vlen_of_cmpd_rd", vlen_tid, dspace_id, HDF5Constants.H5P_DEFAULT, + HDF5Constants.H5P_DEFAULT, HDF5Constants.H5P_DEFAULT); + assertTrue("testH5Dread_vlen_of_compound_nullslots: H5Dcreate: ", dset_id >= 0); + + // row 0: 1 element [ { A=1, B=2 } ] + // row 1: 2 elements [ { A=3, B=4 }, { A=5, B=6 } ] + ArrayList row0_elem0 = new ArrayList<>(); + row0_elem0.add(Integer.valueOf(1)); + row0_elem0.add(Integer.valueOf(2)); + ArrayList row0 = new ArrayList<>(); + row0.add(row0_elem0); + + ArrayList row1_elem0 = new ArrayList<>(); + row1_elem0.add(Integer.valueOf(3)); + row1_elem0.add(Integer.valueOf(4)); + ArrayList row1_elem1 = new ArrayList<>(); + row1_elem1.add(Integer.valueOf(5)); + row1_elem1.add(Integer.valueOf(6)); + ArrayList row1 = new ArrayList<>(); + row1.add(row1_elem0); + row1.add(row1_elem1); + + ArrayList[] write_data = new ArrayList[N_ROWS]; + write_data[0] = row0; + write_data[1] = row1; + + H5.H5DwriteVL(dset_id, vlen_tid, HDF5Constants.H5S_ALL, HDF5Constants.H5S_ALL, + HDF5Constants.H5P_DEFAULT, write_data); + H5.H5Fflush(H5fid, HDF5Constants.H5F_SCOPE_LOCAL); + + // Null row slots: pre-fix this would SIGSEGV inside translate_rbuf. + ArrayList[] read_data = new ArrayList[N_ROWS]; + H5.H5DreadVL(dset_id, vlen_tid, HDF5Constants.H5S_ALL, HDF5Constants.H5S_ALL, + HDF5Constants.H5P_DEFAULT, read_data); + + assertNotNull("testH5Dread_vlen_of_compound_nullslots: row 0 not null", read_data[0]); + assertNotNull("testH5Dread_vlen_of_compound_nullslots: row 1 not null", read_data[1]); + assertEquals("testH5Dread_vlen_of_compound_nullslots: row 0 has 1 element", 1, + read_data[0].size()); + assertEquals("testH5Dread_vlen_of_compound_nullslots: row 1 has 2 elements", 2, + read_data[1].size()); + + ArrayList r0e0 = (ArrayList)read_data[0].get(0); + assertEquals("row 0 elem 0 A", Integer.valueOf(1), r0e0.get(0)); + assertEquals("row 0 elem 0 B", Integer.valueOf(2), r0e0.get(1)); + + ArrayList r1e0 = (ArrayList)read_data[1].get(0); + assertEquals("row 1 elem 0 A", Integer.valueOf(3), r1e0.get(0)); + assertEquals("row 1 elem 0 B", Integer.valueOf(4), r1e0.get(1)); + + ArrayList r1e1 = (ArrayList)read_data[1].get(1); + assertEquals("row 1 elem 1 A", Integer.valueOf(5), r1e1.get(0)); + assertEquals("row 1 elem 1 B", Integer.valueOf(6), r1e1.get(1)); + } + catch (Throwable err) { + err.printStackTrace(); + fail("testH5Dread_vlen_of_compound_nullslots: " + err); + } + finally { + if (dset_id >= 0) + try { + H5.H5Dclose(dset_id); + } + catch (Exception ex) { + } + if (dspace_id >= 0) + try { + H5.H5Sclose(dspace_id); + } + catch (Exception ex) { + } + if (vlen_tid >= 0) + try { + H5.H5Tclose(vlen_tid); + } + catch (Exception ex) { + } + if (cmpd_tid >= 0) + try { + H5.H5Tclose(cmpd_tid); + } + catch (Exception ex) { + } + } + } + + /* + * Read a 1-D dataset whose type is VLEN { COMPOUND { id: int, sub: COMPOUND { P: int, Q: int } } }. + * Per-row canonical shape (per testH5Dwrite_readCompound contract): + * row r ArrayList of vlen elements + * each element is ArrayList of 2 members [Integer id, ArrayList sub] + * sub is ArrayList of 2 members [Integer P, Integer Q] + */ + @Test + public void testH5Dread_vlen_of_nested_compound() + { + long inner_tid = HDF5Constants.H5I_INVALID_HID; + long outer_tid = HDF5Constants.H5I_INVALID_HID; + long vlen_tid = HDF5Constants.H5I_INVALID_HID; + long dspace_id = HDF5Constants.H5I_INVALID_HID; + long dset_id = HDF5Constants.H5I_INVALID_HID; + final int N_ROWS = 2; + + try { + long intSize = H5.H5Tget_size(HDF5Constants.H5T_NATIVE_INT); + + // inner = compound { P:int, Q:int } + inner_tid = H5.H5Tcreate(HDF5Constants.H5T_COMPOUND, 2 * intSize); + H5.H5Tinsert(inner_tid, "P", 0, HDF5Constants.H5T_NATIVE_INT); + H5.H5Tinsert(inner_tid, "Q", intSize, HDF5Constants.H5T_NATIVE_INT); + H5.H5Tpack(inner_tid); + + // outer = compound { id:int, sub:inner } + long innerSize = H5.H5Tget_size(inner_tid); + outer_tid = H5.H5Tcreate(HDF5Constants.H5T_COMPOUND, intSize + innerSize); + H5.H5Tinsert(outer_tid, "id", 0, HDF5Constants.H5T_NATIVE_INT); + H5.H5Tinsert(outer_tid, "sub", intSize, inner_tid); + H5.H5Tpack(outer_tid); + + // vlen of outer + vlen_tid = H5.H5Tvlen_create(outer_tid); + long[] dims = {N_ROWS}; + dspace_id = H5.H5Screate_simple(1, dims, null); + + dset_id = + H5.H5Dcreate(H5fid, "vlen_of_nested_cmpd", vlen_tid, dspace_id, HDF5Constants.H5P_DEFAULT, + HDF5Constants.H5P_DEFAULT, HDF5Constants.H5P_DEFAULT); + assertTrue("testH5Dread_vlen_of_nested_compound: H5Dcreate: ", dset_id >= 0); + + // row 0: 1 element [ { id=1, sub={ P=2, Q=3 } } ] + // row 1: 2 elements [ { id=4, sub={ P=5, Q=6 } }, { id=7, sub={ P=8, Q=9 } } ] + ArrayList row0_elem0_sub = new ArrayList<>(); + row0_elem0_sub.add(Integer.valueOf(2)); + row0_elem0_sub.add(Integer.valueOf(3)); + ArrayList row0_elem0 = new ArrayList<>(); + row0_elem0.add(Integer.valueOf(1)); + row0_elem0.add(row0_elem0_sub); + ArrayList row0 = new ArrayList<>(); + row0.add(row0_elem0); + + ArrayList row1_elem0_sub = new ArrayList<>(); + row1_elem0_sub.add(Integer.valueOf(5)); + row1_elem0_sub.add(Integer.valueOf(6)); + ArrayList row1_elem0 = new ArrayList<>(); + row1_elem0.add(Integer.valueOf(4)); + row1_elem0.add(row1_elem0_sub); + ArrayList row1_elem1_sub = new ArrayList<>(); + row1_elem1_sub.add(Integer.valueOf(8)); + row1_elem1_sub.add(Integer.valueOf(9)); + ArrayList row1_elem1 = new ArrayList<>(); + row1_elem1.add(Integer.valueOf(7)); + row1_elem1.add(row1_elem1_sub); + ArrayList row1 = new ArrayList<>(); + row1.add(row1_elem0); + row1.add(row1_elem1); + + ArrayList[] write_data = new ArrayList[N_ROWS]; + write_data[0] = row0; + write_data[1] = row1; + + H5.H5DwriteVL(dset_id, vlen_tid, HDF5Constants.H5S_ALL, HDF5Constants.H5S_ALL, + HDF5Constants.H5P_DEFAULT, write_data); + H5.H5Fflush(H5fid, HDF5Constants.H5F_SCOPE_LOCAL); + + ArrayList[] read_data = new ArrayList[N_ROWS]; + H5.H5DreadVL(dset_id, vlen_tid, HDF5Constants.H5S_ALL, HDF5Constants.H5S_ALL, + HDF5Constants.H5P_DEFAULT, read_data); + + assertNotNull("row 0 not null", read_data[0]); + assertNotNull("row 1 not null", read_data[1]); + assertEquals("row 0 has 1 element", 1, read_data[0].size()); + assertEquals("row 1 has 2 elements", 2, read_data[1].size()); + + ArrayList r0e0 = (ArrayList)read_data[0].get(0); + assertEquals("row 0 elem 0 has 2 members", 2, r0e0.size()); + assertEquals("row 0 elem 0 id", Integer.valueOf(1), r0e0.get(0)); + ArrayList r0e0sub = (ArrayList)r0e0.get(1); + assertEquals("row 0 elem 0 sub has 2 members", 2, r0e0sub.size()); + assertEquals("row 0 elem 0 sub.P", Integer.valueOf(2), r0e0sub.get(0)); + assertEquals("row 0 elem 0 sub.Q", Integer.valueOf(3), r0e0sub.get(1)); + + ArrayList r1e1 = (ArrayList)read_data[1].get(1); + assertEquals("row 1 elem 1 has 2 members", 2, r1e1.size()); + assertEquals("row 1 elem 1 id", Integer.valueOf(7), r1e1.get(0)); + ArrayList r1e1sub = (ArrayList)r1e1.get(1); + assertEquals("row 1 elem 1 sub.P", Integer.valueOf(8), r1e1sub.get(0)); + assertEquals("row 1 elem 1 sub.Q", Integer.valueOf(9), r1e1sub.get(1)); + } + catch (Throwable err) { + err.printStackTrace(); + fail("testH5Dread_vlen_of_nested_compound: " + err); + } + finally { + if (dset_id >= 0) + try { + H5.H5Dclose(dset_id); + } + catch (Exception ex) { + } + if (dspace_id >= 0) + try { + H5.H5Sclose(dspace_id); + } + catch (Exception ex) { + } + if (vlen_tid >= 0) + try { + H5.H5Tclose(vlen_tid); + } + catch (Exception ex) { + } + if (outer_tid >= 0) + try { + H5.H5Tclose(outer_tid); + } + catch (Exception ex) { + } + if (inner_tid >= 0) + try { + H5.H5Tclose(inner_tid); + } + catch (Exception ex) { + } + } + } } diff --git a/java/src-jni/test/testfiles/JUnit-TestH5D.txt b/java/src-jni/test/testfiles/JUnit-TestH5D.txt index 1651176c1ee3..484395c54269 100644 --- a/java/src-jni/test/testfiles/JUnit-TestH5D.txt +++ b/java/src-jni/test/testfiles/JUnit-TestH5D.txt @@ -15,14 +15,21 @@ JUnit version 4.13.2 .testH5Dget_storage_size_empty .testH5Diterate .testH5Dget_access_plist +.testH5Dread_vlen_of_nested_compound +.testH5DArray_string_buffer_flat_StringArray .testH5Dget_space_closed +.testH5DArray_string_buffer_flat_StringArray_write .testH5DArray_string_buffer +.testH5Dread_vlen_of_compound_nullslots +.testH5Dread_compound_of_vlen .testH5Dget_space_status .testH5Dvlen_write_read +.testH5Dread_compound_of_vlen_preallocated .testH5Dget_space .testH5Dget_type_closed +.testH5Dwrite_compound_of_vlen Time: XXXX -OK (22 tests) +OK (29 tests) diff --git a/java/test/testfiles/JUnit-TestH5D.txt b/java/test/testfiles/JUnit-TestH5D.txt index a451d9671841..484395c54269 100644 --- a/java/test/testfiles/JUnit-TestH5D.txt +++ b/java/test/testfiles/JUnit-TestH5D.txt @@ -3,26 +3,33 @@ JUnit version 4.13.2 .testH5DVLwrVL .testH5Dget_storage_size .testH5DArraywr +.testH5Diterate_write .testH5Dcreate .testH5Dget_offset -I.testH5Dget_type +.testH5Dget_type .testH5DVLwr .testH5Dfill .testH5Dopen .testH5Dcreate_anon .testH5Dfill_null .testH5Dget_storage_size_empty +.testH5Diterate .testH5Dget_access_plist -.testH5Dvlen_get_buf_size +.testH5Dread_vlen_of_nested_compound +.testH5DArray_string_buffer_flat_StringArray .testH5Dget_space_closed +.testH5DArray_string_buffer_flat_StringArray_write .testH5DArray_string_buffer +.testH5Dread_vlen_of_compound_nullslots +.testH5Dread_compound_of_vlen .testH5Dget_space_status .testH5Dvlen_write_read +.testH5Dread_compound_of_vlen_preallocated .testH5Dget_space .testH5Dget_type_closed -.testH5Dwrite_readCompound +.testH5Dwrite_compound_of_vlen Time: XXXX -OK (22 tests) +OK (29 tests)