diff --git a/src/main/java/org/apache/commons/io/input/ByteBufferCleaner.java b/src/main/java/org/apache/commons/io/input/ByteBufferCleaner.java index 6debbfb8449..5bc32c2b926 100644 --- a/src/main/java/org/apache/commons/io/input/ByteBufferCleaner.java +++ b/src/main/java/org/apache/commons/io/input/ByteBufferCleaner.java @@ -17,9 +17,11 @@ package org.apache.commons.io.input; +import java.lang.management.ManagementFactory; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.nio.ByteBuffer; +import java.util.List; import org.apache.commons.io.Buffers; @@ -91,14 +93,19 @@ static void clean(final ByteBuffer buffer) { try { if (buffer.isDirect()) { Buffers.clearWritable(buffer); - INSTANCE.clean(buffer); + if (INSTANCE != null) { + INSTANCE.clean(buffer); + } } } catch (final Exception e) { throw new IllegalStateException("Failed to clean direct buffer.", e); } } - private static Cleaner getCleaner() { + static Cleaner getCleaner() { + if (unsafeMemoryAccessDeprecated()) { + return null; + } try { return new Java8Cleaner(); } catch (final Exception e) { @@ -110,6 +117,17 @@ private static Cleaner getCleaner() { } } + private static boolean unsafeMemoryAccessDeprecated() { + final int version; + try { + version = Integer.parseInt(System.getProperty("java.specification.version")); + } catch (final RuntimeException e) { + return false; + } + // see https://openjdk.org/jeps/471 + return version >= 23; + } + /** * Tests if were able to load a suitable cleaner for the current JVM. Attempting to call {@code ByteBufferCleaner#clean(ByteBuffer)} when this method * returns false will result in an exception. diff --git a/src/test/java/org/apache/commons/io/input/BufferedFileChannelInputStreamTest.java b/src/test/java/org/apache/commons/io/input/BufferedFileChannelInputStreamTest.java index c03e0fb14a5..d4237047f0a 100644 --- a/src/test/java/org/apache/commons/io/input/BufferedFileChannelInputStreamTest.java +++ b/src/test/java/org/apache/commons/io/input/BufferedFileChannelInputStreamTest.java @@ -17,15 +17,19 @@ package org.apache.commons.io.input; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import java.io.IOException; import java.io.InputStream; +import java.lang.management.ManagementFactory; import java.nio.channels.FileChannel; import java.nio.file.StandardOpenOption; +import org.apache.commons.lang3.JavaVersion; +import org.apache.commons.lang3.SystemUtils; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -74,12 +78,13 @@ void testBuilderGet() { */ @Test void testCleanCalledOnlyOnce() throws Exception { + final boolean expectedCleanOnClose = !SystemUtils.isJavaVersionAtLeast(JavaVersion.JAVA_23); try (BufferedFileChannelInputStream stream = BufferedFileChannelInputStream.builder().setPath(InputPath).get()) { assertFalse(stream.isClean()); stream.close(); - assertTrue(stream.isClean()); + assertEquals(stream.isClean(), expectedCleanOnClose); stream.close(); - assertTrue(stream.isClean()); + assertEquals(stream.isClean(), expectedCleanOnClose); } } diff --git a/src/test/java/org/apache/commons/io/input/ByteBufferCleanerTest.java b/src/test/java/org/apache/commons/io/input/ByteBufferCleanerTest.java index 4675cd72b10..07f23332a86 100644 --- a/src/test/java/org/apache/commons/io/input/ByteBufferCleanerTest.java +++ b/src/test/java/org/apache/commons/io/input/ByteBufferCleanerTest.java @@ -16,12 +16,18 @@ */ package org.apache.commons.io.input; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.condition.JRE.JAVA_23; import java.nio.ByteBuffer; -import org.apache.commons.lang3.RandomUtils; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.EnabledForJreRange; /** * Tests {@code ByteBufferCleaner}. @@ -30,24 +36,55 @@ class ByteBufferCleanerTest { @Test void testCleanEmpty() { - final ByteBuffer buffer = ByteBuffer.allocateDirect(10); + final ByteBuffer buffer = ByteBuffer.allocateDirect(8); // There is no way verify that the buffer has been cleaned up, we are just verifying that // clean() doesn't blow up ByteBufferCleaner.clean(buffer); + verifyCleared(buffer); } @Test void testCleanFull() { - final ByteBuffer buffer = ByteBuffer.allocateDirect(10); - buffer.put(RandomUtils.insecure().randomBytes(10), 0, 10); + final ByteBuffer buffer = ByteBuffer.allocateDirect(8); + buffer.putLong(Long.MAX_VALUE); + verifyUncleared(buffer); // There is no way verify that the buffer has been cleaned up, we are just verifying that // clean() doesn't blow up ByteBufferCleaner.clean(buffer); + verifyCleared(buffer); } @Test + void testCleanNonDirectBuffer() { + assertDoesNotThrow(() -> ByteBufferCleaner.clean(ByteBuffer.allocate(10))); + } + + @Test + @EnabledForJreRange(max = JAVA_23) + void testCleanNullBuffer() { + assertThrows(IllegalStateException.class, () -> ByteBufferCleaner.clean(null)); + } + + @Test + @EnabledForJreRange(max = JAVA_23) void testSupported() { assertTrue(ByteBufferCleaner.isSupported(), "ByteBufferCleaner does not work on this platform, please investigate and fix"); } + @Test + @EnabledForJreRange(min = JAVA_23) + void testUnsupportedByDefaultOnJava23() { + assertNull(ByteBufferCleaner.getCleaner()); + assertFalse(ByteBufferCleaner.isSupported(), "ByteBufferCleaner does not work on this platform, please investigate and fix"); + } + + private void verifyUncleared(final ByteBuffer buffer) { + buffer.flip(); + assertEquals(Long.MAX_VALUE, buffer.getLong()); + buffer.flip(); + } + + private void verifyCleared(final ByteBuffer buffer) { + assertEquals(0, buffer.getLong()); + } }