From e906a2ee37fba7d1600175eb0ca3f0ea48931989 Mon Sep 17 00:00:00 2001 From: Peter Lawrey Date: Fri, 21 Aug 2026 09:52:09 +0100 Subject: [PATCH 1/5] [#60] Harden JPMS runtime validation through Java 25 --- src/it/README.adoc | 4 +- src/it/module-test/pom.xml | 3 ++ .../net/openhft/it/module/ModuleTest.java | 37 +++++++++++++++++++ src/main/docs/architecture-overview.adoc | 4 +- src/main/docs/project-requirements.adoc | 2 +- src/main/docs/unsafe-and-platform-notes.adoc | 20 +++++----- src/main/java/sun/nio/ch/DirectBuffer.java | 17 ++++----- .../hashing/AutomaticModuleNameIT.java | 13 +++++++ .../openhft/hashing/LongHashFunctionTest.java | 14 +++++++ .../hashing/LongTupleHashFunctionTest.java | 14 +++++++ 10 files changed, 107 insertions(+), 21 deletions(-) diff --git a/src/it/README.adoc b/src/it/README.adoc index 004e7cf..1644467 100644 --- a/src/it/README.adoc +++ b/src/it/README.adoc @@ -51,7 +51,9 @@ module net.openhft.hashing { } ---- -Internal packages (like sun.nio.ch) are not exported and cannot be accessed by consuming modules. +No implementation packages from this library are exported. +The direct-buffer runtime test supplies `--add-exports java.base/sun.nio.ch=net.openhft.hashing` because the performance-sensitive implementation calls `DirectBuffer.address()` directly. +For class-path use, the corresponding target is `ALL-UNNAMED`. == Multi-Release JAR diff --git a/src/it/module-test/pom.xml b/src/it/module-test/pom.xml index 3a90660..b282c10 100644 --- a/src/it/module-test/pom.xml +++ b/src/it/module-test/pom.xml @@ -36,6 +36,9 @@ maven-surefire-plugin 3.5.4 + + --add-exports java.base/sun.nio.ch=net.openhft.hashing + diff --git a/src/it/module-test/src/test/java/net/openhft/it/module/ModuleTest.java b/src/it/module-test/src/test/java/net/openhft/it/module/ModuleTest.java index ed27989..669fe3a 100644 --- a/src/it/module-test/src/test/java/net/openhft/it/module/ModuleTest.java +++ b/src/it/module-test/src/test/java/net/openhft/it/module/ModuleTest.java @@ -4,9 +4,11 @@ package net.openhft.it.module; import net.openhft.hashing.LongHashFunction; +import net.openhft.hashing.LongTupleHashFunction; import org.junit.Test; import java.lang.module.ModuleDescriptor; +import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import java.util.Set; @@ -64,4 +66,39 @@ public void testExplicitDescriptorContract() { assertFalse("jdk.unsupported must be available at runtime", unsupported.modifiers().contains(ModuleDescriptor.Requires.Modifier.STATIC)); } + + @Test + public void testPublicRuntimePathsWithRequiredDirectBufferExport() { + LongHashFunction[] functions = { + LongHashFunction.city_1_1(), + LongHashFunction.farmNa(), + LongHashFunction.farmUo(), + LongHashFunction.murmur_3(), + LongHashFunction.xx(), + LongHashFunction.xx3(), + LongHashFunction.xx128low(), + LongHashFunction.wy_3(), + LongHashFunction.metro() + }; + + for (LongHashFunction function : functions) { + long expected = function.hashBytes(TEST_BYTES); + assertEquals(expected, function.hashBytes(ByteBuffer.wrap(TEST_BYTES))); + + ByteBuffer direct = ByteBuffer.allocateDirect(TEST_BYTES.length); + direct.put(TEST_BYTES).flip(); + assertEquals(expected, function.hashBytes(direct)); + assertEquals("Direct buffer position should be unchanged", 0, direct.position()); + assertEquals("Direct buffer limit should be unchanged", TEST_BYTES.length, direct.limit()); + + assertEquals(function.hashChars(TEST_DATA), + function.hashChars(new StringBuilder(TEST_DATA))); + assertTrue(function.hashInts(new int[] {1, 2, 3, 4}) != 0); + } + + LongTupleHashFunction tuple = LongTupleHashFunction.xx128(); + ByteBuffer direct = ByteBuffer.allocateDirect(TEST_BYTES.length); + direct.put(TEST_BYTES).flip(); + assertArrayEquals(tuple.hashBytes(TEST_BYTES), tuple.hashBytes(direct)); + } } diff --git a/src/main/docs/architecture-overview.adoc b/src/main/docs/architecture-overview.adoc index 44dce67..a9bfd12 100644 --- a/src/main/docs/architecture-overview.adoc +++ b/src/main/docs/architecture-overview.adoc @@ -36,7 +36,9 @@ It currently delivers 128-bit MurmurHash3 and XXH3 outputs and mirrors the singl * `net.openhft.hashing.Util.VALID_STRING_HASH` selects the correct `StringHash` strategy at JVM initialisation time by inspecting `java.vm.name` and `java.version`, covering HotSpot, OpenJ9, Zing, and unknown VMs (`Util.java:29-63`). * `ModernHotSpotStringHash`, `ModernCompactStringHash`, and `HotSpotPrior7u6StringHash` encode the memory layout differences between pre-compact, compact-string, and legacy HotSpot builds. When the VM cannot be recognised, `UnknownJvmStringHash` provides a defensive fallback. -* Direct buffer hashing uses `sun.nio.ch.DirectBuffer` addresses pulled via `LongHashFunction.hashBytes(ByteBuffer)` and `LongHashFunction.hashMemory(long, long)`; `Util.getDirectBufferAddress` centralises the address extraction (`Util.java:65-68`). +* Direct buffer hashing calls `sun.nio.ch.DirectBuffer.address()` and passes the native address to the allocation-free `Unsafe` path (`LongHashFunction.hashByteBuffer`, `LongTupleHashFunction.hashByteBuffer`, and `Util.getDirectBufferAddress`). +The invocation is deliberately direct rather than reflective because this is a performance-sensitive path. +On Java 9 and newer, the application must export `java.base/sun.nio.ch` to the library as described in `unsafe-and-platform-notes.adoc`. === Supporting Utilities diff --git a/src/main/docs/project-requirements.adoc b/src/main/docs/project-requirements.adoc index d6d0aa7..3fb904d 100644 --- a/src/main/docs/project-requirements.adoc +++ b/src/main/docs/project-requirements.adoc @@ -55,7 +55,7 @@ The library must implement the following non-cryptographic hash algorithms: === 3.3. Compatibility (NF-O) * **NF-O-001:** **JDK Version:** The library must support JDK 8 through the current LTS (JDK 21+). -* **NF-O-002:** **Module System:** The library must function in Java 9+ environments. Tests and runtime require `--add-opens java.base/jdk.internal.misc=ALL-UNNAMED --add-opens java.base/sun.nio.ch=ALL-UNNAMED` for `sun.misc.Unsafe` and `sun.nio.ch.DirectBuffer` access (see `unsafe-and-platform-notes.adoc`). +* **NF-O-002:** **Module System:** The library must function in Java 9+ environments as explicit module `net.openhft.hashing`. The descriptor declares the runtime dependency on `jdk.unsupported`. Direct-buffer hashing additionally requires the application to export `java.base/sun.nio.ch` to `net.openhft.hashing`; this preserves the direct, allocation-free `DirectBuffer.address()` call (see `unsafe-and-platform-notes.adoc`). * **NF-O-003:** **VM Support:** The library must support major JVMs including HotSpot, OpenJDK, and OpenJ9/IBM J9. == 4. Interface Constraints diff --git a/src/main/docs/unsafe-and-platform-notes.adoc b/src/main/docs/unsafe-and-platform-notes.adoc index 492a6eb..b493fbc 100644 --- a/src/main/docs/unsafe-and-platform-notes.adoc +++ b/src/main/docs/unsafe-and-platform-notes.adoc @@ -9,20 +9,22 @@ Chronicle Software * `UnsafeAccess` reflects on `sun.misc.Unsafe.theUnsafe` to obtain the singleton and uses it for raw array access, field offsets, and direct memory loads (`UnsafeAccess.java:40-118`). This keeps hashing allocation-free but depends on the `jdk.unsupported` module. -* Direct buffer handling casts to `sun.nio.ch.DirectBuffer` to retrieve native addresses for `hashBytes(ByteBuffer)` and `hashMemory` (`Util.java:65-68`, `LongHashFunction.java:430-470`). -The code path assumes the buffer is direct; heap buffers are copied through array access instead. +* Direct buffer handling casts to `sun.nio.ch.DirectBuffer` and calls `address()` directly before hashing the native memory. +This preserves the established zero-allocation fast path and avoids reflective invocation in a performance-sensitive operation. +There is deliberately no reflective fallback when access to `DirectBuffer` is unavailable. * String hashing inspects `java.lang.String.value` and, on compact-string VMs, treats the backing `byte[]` as Latin-1 using `CompactLatin1CharSequenceAccess` (`ModernCompactStringHash.java:10-62`). Older HotSpot builds and other JVMs fall back to UTF-16 behaviour through `ModernHotSpotStringHash` or `HotSpotPrior7u6StringHash`. === JVM Configuration -* On Java 9 and newer, strong encapsulation may block reflective access to the JDK internals above. -Add the following opens/exports when running with a strict module layer: -** `--add-opens java.base/sun.misc=ALL-UNNAMED` (access to `Unsafe.theUnsafe`). -** `--add-exports java.base/sun.nio.ch=ALL-UNNAMED` (casts to `DirectBuffer`). -** `--add-opens java.base/java.lang=ALL-UNNAMED` (reading `String.value` for compact strings). -* The library does not automatically request these permissions. -Applications embedding Zero-Allocation Hashing must configure the JVM command line or module descriptors accordingly. +* The explicit module descriptor declares `requires jdk.unsupported` for `sun.misc.Unsafe`. +* On Java 9 and newer, direct-buffer hashing also requires one of the following runtime options: +** Named-module use: `--add-exports java.base/sun.nio.ch=net.openhft.hashing` +** Class-path use: `--add-exports java.base/sun.nio.ch=ALL-UNNAMED` +The target differs because class-path code belongs to the unnamed module. +The module descriptor cannot request that another module export one of its packages, so the application launcher must supply this option. +* Java 24 and 25 emit terminal-deprecation warnings for some `sun.misc.Unsafe` memory-access methods. +These warnings do not prevent execution through Java 25, but removal of those methods is a separate compatibility horizon. === Platform Behaviour diff --git a/src/main/java/sun/nio/ch/DirectBuffer.java b/src/main/java/sun/nio/ch/DirectBuffer.java index 01b65e5..76b3a4e 100644 --- a/src/main/java/sun/nio/ch/DirectBuffer.java +++ b/src/main/java/sun/nio/ch/DirectBuffer.java @@ -4,17 +4,16 @@ package sun.nio.ch; /** - * Stub for JDK internal ckass sun.nio.ch.DirectBuffer. + * Stub for JDK internal class sun.nio.ch.DirectBuffer. *

- * - When crossing compiling for Java SE 7 and 8, this stub class bypasses compiler sun-api + * - When cross compiling for Java SE 7 and 8, this stub class bypasses compiler sun-api * warnings. - * - When crossing compiling for Java SE 9+, the package 'sun.nio.ch' is not exported from - * 'java.base' module. This stub class helps to access the class in compile-time without - * '--add-export' arguments and bypasses sun-api warnings. - * - Only used methods are exported. - * - In test and production runtime, the real class is loaded from boot classpath. + * - When cross compiling for Java SE 9+, the package 'sun.nio.ch' is not exported from + * 'java.base'. This stub class helps to access the class at compile time without + * '--add-exports' arguments and bypasses sun-api warnings. + * - Only used methods are declared. + * - In test and production runtime, the real class is loaded from the boot class path. */ - public interface DirectBuffer { - public long address(); + long address(); } diff --git a/src/test/java/net/openhft/hashing/AutomaticModuleNameIT.java b/src/test/java/net/openhft/hashing/AutomaticModuleNameIT.java index 882b808..ad4acd2 100644 --- a/src/test/java/net/openhft/hashing/AutomaticModuleNameIT.java +++ b/src/test/java/net/openhft/hashing/AutomaticModuleNameIT.java @@ -15,6 +15,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; public class AutomaticModuleNameIT { @@ -54,4 +55,16 @@ public void java8BuildProducesJava9ModuleDescriptor() throws IOException { } } } + + @Test + public void packagedJarDoesNotContainJdkCompilationStub() throws IOException { + final String packagedJarPath = System.getProperty("packaged.jar"); + assertNotNull("packaged.jar system property", packagedJarPath); + + try (JarFile jarFile = new JarFile(new File(packagedJarPath))) { + assertNull("The JDK supplies sun.nio.ch.DirectBuffer at runtime; the compile-time stub " + + "must not be packaged", + jarFile.getJarEntry("sun/nio/ch/DirectBuffer.class")); + } + } } diff --git a/src/test/java/net/openhft/hashing/LongHashFunctionTest.java b/src/test/java/net/openhft/hashing/LongHashFunctionTest.java index fa4380a..bafcc80 100644 --- a/src/test/java/net/openhft/hashing/LongHashFunctionTest.java +++ b/src/test/java/net/openhft/hashing/LongHashFunctionTest.java @@ -152,11 +152,25 @@ private static void testByteBuffers(LongHashFunction f, long eh, int len, ByteBu bb2.put(bb); assertEquals("byte buffer little endian off len", eh, f.hashBytes(bb2, 1, len)); + ByteBuffer direct = ByteBuffer.allocateDirect(len + 2).order(LITTLE_ENDIAN); + ((Buffer)direct).position(1); + ByteBuffer directSource = bb.duplicate(); + ((Buffer)directSource).clear(); + direct.put(directSource); + ((Buffer)direct).position(1); + ((Buffer)direct).limit(len + 1); + assertEquals("direct byte buffer", eh, f.hashBytes(direct)); + assertEquals("direct byte buffer off len", eh, f.hashBytes(direct, 1, len)); + assertEquals("direct byte buffer position unchanged", 1, direct.position()); + assertEquals("direct byte buffer limit unchanged", len + 1, direct.limit()); + ((Buffer)bb.order(BIG_ENDIAN)).clear(); assertEquals("byte buffer big endian", eh, f.hashBytes(bb)); bb2.order(BIG_ENDIAN); assertEquals("byte buffer big endian off len", eh, f.hashBytes(bb2, 1, len)); + direct.order(BIG_ENDIAN); + assertEquals("direct byte buffer big endian", eh, f.hashBytes(direct)); ((Buffer)bb.order(nativeOrder())).clear(); } diff --git a/src/test/java/net/openhft/hashing/LongTupleHashFunctionTest.java b/src/test/java/net/openhft/hashing/LongTupleHashFunctionTest.java index 9781aa2..b925e5e 100644 --- a/src/test/java/net/openhft/hashing/LongTupleHashFunctionTest.java +++ b/src/test/java/net/openhft/hashing/LongTupleHashFunctionTest.java @@ -204,11 +204,25 @@ private static void testByteBuffers(LongTupleHashFunction f, long[] eh, int len, bb2.put(bb); assertArrayEquals("byte buffer little endian off len", eh, f.hashBytes(bb2, 1, len)); + ByteBuffer direct = ByteBuffer.allocateDirect(len + 2).order(LITTLE_ENDIAN); + ((Buffer)direct).position(1); + ByteBuffer directSource = bb.duplicate(); + ((Buffer)directSource).clear(); + direct.put(directSource); + ((Buffer)direct).position(1); + ((Buffer)direct).limit(len + 1); + assertArrayEquals("direct byte buffer", eh, f.hashBytes(direct)); + assertArrayEquals("direct byte buffer off len", eh, f.hashBytes(direct, 1, len)); + assertEquals("direct byte buffer position unchanged", 1, direct.position()); + assertEquals("direct byte buffer limit unchanged", len + 1, direct.limit()); + ((Buffer)bb.order(BIG_ENDIAN)).clear(); assertArrayEquals("byte buffer big endian", eh, f.hashBytes(bb)); bb2.order(BIG_ENDIAN); assertArrayEquals("byte buffer big endian off len", eh, f.hashBytes(bb2, 1, len)); + direct.order(BIG_ENDIAN); + assertArrayEquals("direct byte buffer big endian", eh, f.hashBytes(direct)); ((Buffer)bb.order(nativeOrder())).clear(); } From 5f306c21a0362b2c334f8873175a8f7595121496 Mon Sep 17 00:00:00 2001 From: Peter Lawrey Date: Fri, 21 Aug 2026 09:54:40 +0100 Subject: [PATCH 2/5] [#60] Document Java 25 Unsafe warning policy --- src/main/docs/unsafe-and-platform-notes.adoc | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/main/docs/unsafe-and-platform-notes.adoc b/src/main/docs/unsafe-and-platform-notes.adoc index b493fbc..c427dd1 100644 --- a/src/main/docs/unsafe-and-platform-notes.adoc +++ b/src/main/docs/unsafe-and-platform-notes.adoc @@ -24,7 +24,14 @@ Older HotSpot builds and other JVMs fall back to UTF-16 behaviour through `Moder The target differs because class-path code belongs to the unnamed module. The module descriptor cannot request that another module export one of its packages, so the application launcher must supply this option. * Java 24 and 25 emit terminal-deprecation warnings for some `sun.misc.Unsafe` memory-access methods. -These warnings do not prevent execution through Java 25, but removal of those methods is a separate compatibility horizon. +The Java 25 default is `--sun-misc-unsafe-memory-access=warn`, which permits execution and emits at most one warning per JVM. +The first warning from this library normally identifies `UnsafeAccess.arrayBaseOffset`, but suppressing or replacing that one call would not resolve the underlying dependency: hashing also uses `getByte`, `getShort`, `getInt`, and `getLong` throughout its hot paths. +* Applications that have explicitly accepted this temporary compatibility risk can suppress the warning on Java 24 and 25 with `--sun-misc-unsafe-memory-access=allow`. +This is an application-wide launcher policy, not a library setting, and does not mitigate the future-removal risk. +Use `debug` instead when locating every call; `deny` makes the current implementation fail during `UnsafeAccess` initialisation. +* Removing the warning properly requires replacing all affected on-heap and off-heap memory operations with supported APIs, principally `VarHandle` and the Foreign Function and Memory API. +That work requires version-specific implementations and performance validation, and is separate from JPMS packaging. +See https://openjdk.org/jeps/498[JEP 498] and https://openjdk.org/jeps/471[JEP 471]. === Platform Behaviour From 15178cd6abf47399f6cb8f10314f630b4a27cae0 Mon Sep 17 00:00:00 2001 From: Peter Lawrey Date: Fri, 11 Sep 2026 14:14:00 +0100 Subject: [PATCH 3/5] [#60] Compare integer hashing with its native byte representation --- .../src/test/java/net/openhft/it/module/ModuleTest.java | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/it/module-test/src/test/java/net/openhft/it/module/ModuleTest.java b/src/it/module-test/src/test/java/net/openhft/it/module/ModuleTest.java index 669fe3a..52bca90 100644 --- a/src/it/module-test/src/test/java/net/openhft/it/module/ModuleTest.java +++ b/src/it/module-test/src/test/java/net/openhft/it/module/ModuleTest.java @@ -9,6 +9,7 @@ import java.lang.module.ModuleDescriptor; import java.nio.ByteBuffer; +import java.nio.ByteOrder; import java.nio.charset.StandardCharsets; import java.util.Set; @@ -93,7 +94,13 @@ public void testPublicRuntimePathsWithRequiredDirectBufferExport() { assertEquals(function.hashChars(TEST_DATA), function.hashChars(new StringBuilder(TEST_DATA))); - assertTrue(function.hashInts(new int[] {1, 2, 3, 4}) != 0); + int[] integers = {1, 2, 3, 4}; + ByteBuffer integerBytes = ByteBuffer.allocate(integers.length * Integer.BYTES) + .order(ByteOrder.nativeOrder()); + for (int value : integers) + integerBytes.putInt(value); + // Primitive-array hashing uses the bytes as laid out in native memory. + assertEquals(function.hashBytes(integerBytes.array()), function.hashInts(integers)); } LongTupleHashFunction tuple = LongTupleHashFunction.xx128(); From 759583413bd40c8f014e65bdd1c5751c47c15866 Mon Sep 17 00:00:00 2001 From: Peter Lawrey Date: Fri, 11 Sep 2026 14:14:00 +0100 Subject: [PATCH 4/5] [#60] Refresh retired Actions setup using the validated CI repair --- .github/workflows/build.yml | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a0e98de..531a04e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -11,21 +11,22 @@ jobs: name: Build runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v7 with: fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis - - name: Set up JDK 11 - uses: actions/setup-java@v1 + - name: Set up JDK 21 + uses: actions/setup-java@v6 with: - java-version: 11 + distribution: zulu + java-version: '21' - name: Cache SonarCloud packages - uses: actions/cache@v1 + uses: actions/cache@v6 with: path: ~/.sonar/cache key: ${{ runner.os }}-sonar restore-keys: ${{ runner.os }}-sonar - name: Cache Maven packages - uses: actions/cache@v1 + uses: actions/cache@v6 with: path: ~/.m2 key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }} From 07551444fe628db618532666b4d6f19d0d60c39a Mon Sep 17 00:00:00 2001 From: Peter Lawrey Date: Sat, 12 Sep 2026 14:47:52 +0100 Subject: [PATCH 5/5] #60: Distinguish explicit direct-buffer ranges from active windows --- src/it/README.adoc | 6 ++++++ .../net/openhft/hashing/LongHashFunctionTest.java | 11 ++++++++++- .../openhft/hashing/LongTupleHashFunctionTest.java | 11 ++++++++++- 3 files changed, 26 insertions(+), 2 deletions(-) diff --git a/src/it/README.adoc b/src/it/README.adoc index 6c28e04..ce0fb9d 100644 --- a/src/it/README.adoc +++ b/src/it/README.adoc @@ -67,3 +67,9 @@ The library is built as a multi-release JAR with module-info.class in META-INF/v All integration tests use JUnit 4 (same as the main project tests) and do not print to System.out. Test results are reported through Maven Surefire plugin output only. + +The library unit tests also check direct-buffer ranges independently of the active cursor window. +`LongHashFunctionTest` and `LongTupleHashFunctionTest` put distinct guard bytes around the payload, +then request only the payload while position and limit cover the complete buffer. They verify the +hash and unchanged cursor, then restore the payload window before the whole-buffer byte-order check. +Replacing the direct-buffer range with `position()` and `remaining()` must fail these assertions. diff --git a/src/test/java/net/openhft/hashing/LongHashFunctionTest.java b/src/test/java/net/openhft/hashing/LongHashFunctionTest.java index bafcc80..dc3277f 100644 --- a/src/test/java/net/openhft/hashing/LongHashFunctionTest.java +++ b/src/test/java/net/openhft/hashing/LongHashFunctionTest.java @@ -153,6 +153,8 @@ private static void testByteBuffers(LongHashFunction f, long eh, int len, ByteBu assertEquals("byte buffer little endian off len", eh, f.hashBytes(bb2, 1, len)); ByteBuffer direct = ByteBuffer.allocateDirect(len + 2).order(LITTLE_ENDIAN); + direct.put(0, (byte) 0x6D); + direct.put(len + 1, (byte) 0xB7); ((Buffer)direct).position(1); ByteBuffer directSource = bb.duplicate(); ((Buffer)directSource).clear(); @@ -160,10 +162,17 @@ private static void testByteBuffers(LongHashFunction f, long eh, int len, ByteBu ((Buffer)direct).position(1); ((Buffer)direct).limit(len + 1); assertEquals("direct byte buffer", eh, f.hashBytes(direct)); - assertEquals("direct byte buffer off len", eh, f.hashBytes(direct, 1, len)); assertEquals("direct byte buffer position unchanged", 1, direct.position()); assertEquals("direct byte buffer limit unchanged", len + 1, direct.limit()); + // The explicit range excludes guards that are inside the active window. + ((Buffer)direct).clear(); + assertEquals("direct byte buffer off len", eh, f.hashBytes(direct, 1, len)); + assertEquals("direct range position unchanged", 0, direct.position()); + assertEquals("direct range limit unchanged", len + 2, direct.limit()); + ((Buffer)direct).position(1); + ((Buffer)direct).limit(len + 1); + ((Buffer)bb.order(BIG_ENDIAN)).clear(); assertEquals("byte buffer big endian", eh, f.hashBytes(bb)); diff --git a/src/test/java/net/openhft/hashing/LongTupleHashFunctionTest.java b/src/test/java/net/openhft/hashing/LongTupleHashFunctionTest.java index b925e5e..78c2b36 100644 --- a/src/test/java/net/openhft/hashing/LongTupleHashFunctionTest.java +++ b/src/test/java/net/openhft/hashing/LongTupleHashFunctionTest.java @@ -205,6 +205,8 @@ private static void testByteBuffers(LongTupleHashFunction f, long[] eh, int len, assertArrayEquals("byte buffer little endian off len", eh, f.hashBytes(bb2, 1, len)); ByteBuffer direct = ByteBuffer.allocateDirect(len + 2).order(LITTLE_ENDIAN); + direct.put(0, (byte) 0x6D); + direct.put(len + 1, (byte) 0xB7); ((Buffer)direct).position(1); ByteBuffer directSource = bb.duplicate(); ((Buffer)directSource).clear(); @@ -212,10 +214,17 @@ private static void testByteBuffers(LongTupleHashFunction f, long[] eh, int len, ((Buffer)direct).position(1); ((Buffer)direct).limit(len + 1); assertArrayEquals("direct byte buffer", eh, f.hashBytes(direct)); - assertArrayEquals("direct byte buffer off len", eh, f.hashBytes(direct, 1, len)); assertEquals("direct byte buffer position unchanged", 1, direct.position()); assertEquals("direct byte buffer limit unchanged", len + 1, direct.limit()); + // The explicit range excludes guards that are inside the active window. + ((Buffer)direct).clear(); + assertArrayEquals("direct byte buffer off len", eh, f.hashBytes(direct, 1, len)); + assertEquals("direct range position unchanged", 0, direct.position()); + assertEquals("direct range limit unchanged", len + 2, direct.limit()); + ((Buffer)direct).position(1); + ((Buffer)direct).limit(len + 1); + ((Buffer)bb.order(BIG_ENDIAN)).clear(); assertArrayEquals("byte buffer big endian", eh, f.hashBytes(bb));