Fix bad reads of nested cmpd/vlen types in JNI#6413
Open
mattjala wants to merge 9 commits into
Open
Conversation
There was a problem hiding this comment.
Pull request overview
This PR fixes multiple JNI translation bugs affecting H5DreadVL/H5DwriteVL for nested datatype combinations (notably compound members containing VLEN, and VLEN of nested compounds), which previously could yield empty Java ArrayList results or crash (SIGSEGV). It also adds regression tests in the Java JNI test suite to cover these cases.
Changes:
- Fix compound read translation to always append members via
ArrayList.add()(instead of incorrectly attempting array element assignment when the caller pre-allocates row slots). - Fix VLEN write translation to convert
ArrayListinputs viatoArray()before performing array-style operations. - Add new JUnit tests for compound-of-VLEN reads (preallocated and non-preallocated), VLEN-of-compound reads with null slots, and VLEN-of-nested-compound reads; update expected test output.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
java/src-jni/test/TestH5D.java |
Adds helper + new regression tests covering nested compound/VLEN read/write scenarios. |
java/src-jni/test/testfiles/JUnit-TestH5D.txt |
Updates expected JUnit output list/count for the added tests. |
java/src-jni/jni/h5util.c |
Adjusts JNI buffer translation logic for compound/VLEN read/write paths to correctly use ArrayList semantics and correct member offsets. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
e65f573 to
af4e258
Compare
`translate_rbuf`'s H5T_VLEN case had a similar bug where when `found_jList` was set to false due to an entyr in `ret_buf` being null, `ret_buf.add()` would be invoked on an array of objects without the list .add() method. This would occur whenever a read was invoked of a vlen sequence with a null (non-preallocated) entry. The pre-existing tests only tested the pre-allocated cases. I removed the use of the `found_jList` flag, since it conflated the passing of an unallocated slot with `ret_buf` not being an array. Instead use `ret_buflen == 0` as the check to match the pattern in H5T_INTEGER and other branches. The test for this fix is testH5Dread_vlen_of_compound_nullslot. --- `translate_atomic_rebuf` had two issues related to handling of nested compounds. First, it discarded recursive returns, resulting in the construction of empty lists. Secondly, its member offset (`char_buf + i * typeSize + memb_offset`) was incorrect. In this case, `i` was the member index and `memberSize` was the entire cmpd size, so the offset would be erroneously large. It seems like this came from copying of the offset computation from `translate_rbuf`, which had to advance over entire elements of compound data. This error was duplicated on the write side in `translate_atomic_wbuf`'s H5T_COMPOUND case (h5util.c:4611). I changed `translate_atomic_rbuf` to capture the resultant object, and dropped the `i * typeSize` term in both routines. The new test verifying the fix works is `testH5Dread_vlen_of_nested_compound`.
34382ae to
65bf5a7
Compare
1837946 to
2adb07a
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
When H5DreadVL is called with a
H5T_COMPOUNDmemory type whose members include variable-length data, and the caller pre-allocates each row slot in the outerObject[]buffer, then the read completes without throwing an error, but each pre-allocated row ArrayList comes back empty.If the caller follows the alternate calling pattern, leaving each row slot null and letting the native code allocate the per-row record, then the same read works correctly.
The root cause is the
H5T_COMPOUNDcase intranslate_rbuf. This function walks each row's compound, decodes each member into a Java object viatranslate_atomic_rbuf, and stores the result in the per-row ArrayList. It branches on afound_jListflag based on the result ofGetObjectArrayElement(), i.e. whether the caller pre-allocated the row slot.Later on, this flag is used to decide between using the
.add()method of ArrayList (iffound_jListis false), or trying to directly set an element on a builtin java array (if found_jList is true).The latter case is wrong in two ways.
jListis always an ArrayList, either fetched from ret_buf[i] (where each slot is an ArrayList) or newly created a few lines earlier. The attempt to directly set an element using the built-in array syntax silently no-ops.There are currently no tests covering
H5DreadVL()for H5T_COMPOUND in the src-jni test tree. This was discovered in the context of HDFView attempting to read compound-of-sequence datasets. HDFView's H5Datatype.allocateArray pre-fills each row slot with new ArrayList<>() before handing the buffer to H5DreadVL, which triggered the bug.My fix is to replace the use the ArrayList.add() method in every case, and drop the found_jList tracking since jList is always an ArrayList and we always want to use the .add() method. This also causes it to match the pattern of the H5T_VLEN branch, which works for both pre-allocated and non-pre-allocated slots.
To test this case, I added
test.TestH5D.testH5Dread_compound_of_vlen_preallocated. For the sake of regression testing, I also added testH5Dread_compound_of_vlen which tests the same thing with non-pre-allocated row slots, although this test passed even before I made any changes to the source code.I also fixed a very similar problem in H5DwriteVL that led to a SIGSEGV when writing compound of variable-length sequences. Specifically,
translate_atomic_wbuftreatsin_objas a java array when it is an ArrayList, producing a garbage length value that eventually leads to a crash.I changed the H5T_VLEN case to behave similarly to the H5T_ARRAY and H5T_COMPOUND cases and use
mToArrayon the provided ArrayList to get an array object for the array operations. The test verifying the fix istestH5Dwrite_compound_of_vlen.translate_rbuf's H5T_VLEN case had a similar bug where whenfound_jListwas set to false due to an entry inret_bufbeing null.ret_buf.add()would then be invoked on an array of objects without the list .add() method. This would occur whenever a read was invoked of a vlen sequence with a null (non-preallocated) entry. The pre-existing tests only tested the pre-allocated cases.I removed the use of the
found_jListflag, since it conflated the passing of an unallocated slot withret_bufnot being an array. Instead, it now usesret_buflen == 0as the check to match the pattern in H5T_INTEGER and other branches.The test for this fix is
testH5Dread_vlen_of_compound_nullslot.translate_atomic_rebufhad two issues related to handling of nested compounds. First, it discarded recursive return values, resulting in the construction of empty lists. Secondly, its member offset (char_buf + i * typeSize + memb_offset) was incorrect. In this case,iwas the member index andmemberSizewas the entire cmpd size, so the offset would be erroneously large. It seems like this came from copying of the offset computation fromtranslate_rbuf, which had to advance over entire elements of compound data. This error was duplicated on the write side intranslate_atomic_wbuf's H5T_COMPOUND case (h5util.c:4611).I changed
translate_atomic_rbufto capture the resultant object, and dropped thei * typeSizeterm in both routines.The new test verifying the fix is
testH5Dread_vlen_of_nested_compound.