/* * Copyright (c) 2019, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ /* * @test * @modules java.base/sun.nio.ch java.base/jdk.internal.foreign * @run testng/othervm/timeout=600 --enable-native-access=ALL-UNNAMED TestByteBuffer */ import java.lang.foreign.*; import java.lang.foreign.MemoryLayout.PathElement; import java.io.File; import java.io.IOException; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.invoke.VarHandle; import java.lang.ref.Cleaner; import java.lang.ref.Reference; import java.lang.ref.WeakReference; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.net.URI; import java.nio.Buffer; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.CharBuffer; import java.nio.DoubleBuffer; import java.nio.FloatBuffer; import java.nio.IntBuffer; import java.nio.LongBuffer; import java.nio.MappedByteBuffer; import java.nio.ShortBuffer; import java.nio.channels.FileChannel; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardOpenOption; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.BiConsumer; import java.util.function.BiFunction; import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Predicate; import java.util.function.Supplier; import java.util.stream.Stream; import org.testng.SkipException; import org.testng.annotations.*; import sun.nio.ch.DirectBuffer; import static java.lang.foreign.ValueLayout.*; import static org.testng.Assert.*; public class TestByteBuffer { static final Path tempPath; static { try { File file = File.createTempFile("buffer", "txt"); file.deleteOnExit(); tempPath = file.toPath(); Files.write(file.toPath(), new byte[256], StandardOpenOption.WRITE); } catch (IOException ex) { throw new ExceptionInInitializerError(ex); } } static final ValueLayout.OfChar BB_CHAR = JAVA_CHAR_UNALIGNED.withOrder(ByteOrder.BIG_ENDIAN); static final ValueLayout.OfShort BB_SHORT = JAVA_SHORT_UNALIGNED.withOrder(ByteOrder.BIG_ENDIAN); static final ValueLayout.OfInt BB_INT = JAVA_INT_UNALIGNED.withOrder(ByteOrder.BIG_ENDIAN); static final ValueLayout.OfLong BB_LONG = JAVA_LONG_UNALIGNED.withOrder(ByteOrder.BIG_ENDIAN); static final ValueLayout.OfFloat BB_FLOAT = JAVA_FLOAT_UNALIGNED.withOrder(ByteOrder.BIG_ENDIAN); static final ValueLayout.OfDouble BB_DOUBLE = JAVA_DOUBLE_UNALIGNED.withOrder(ByteOrder.BIG_ENDIAN); static SequenceLayout tuples = MemoryLayout.sequenceLayout(500, MemoryLayout.structLayout( BB_INT.withName("index"), BB_FLOAT.withName("value") )); static final SequenceLayout bytes = MemoryLayout.sequenceLayout(100, JAVA_BYTE); static final SequenceLayout chars = MemoryLayout.sequenceLayout(100, BB_CHAR); static final SequenceLayout shorts = MemoryLayout.sequenceLayout(100, BB_SHORT); static final SequenceLayout ints = MemoryLayout.sequenceLayout(100, BB_INT); static final SequenceLayout floats = MemoryLayout.sequenceLayout(100, BB_FLOAT); static final SequenceLayout longs = MemoryLayout.sequenceLayout(100, BB_LONG); static final SequenceLayout doubles = MemoryLayout.sequenceLayout(100, BB_DOUBLE); static final VarHandle indexHandle = tuples.varHandle(PathElement.sequenceElement(), PathElement.groupElement("index")); static final VarHandle valueHandle = tuples.varHandle(PathElement.sequenceElement(), PathElement.groupElement("value")); static void initTuples(MemorySegment base, long count) { for (long i = 0; i < count ; i++) { indexHandle.set(base, 0L, i, (int)i); valueHandle.set(base, 0L, i, (float)(i / 500f)); } } static void checkTuples(MemorySegment base, ByteBuffer bb, long count) { for (long i = 0; i < count ; i++) { int index; float value; assertEquals(index = bb.getInt(), (int)indexHandle.get(base, 0L, i)); assertEquals(value = bb.getFloat(), (float)valueHandle.get(base, 0L, i)); assertEquals(value, index / 500f); } } static void initBytes(MemorySegment base, SequenceLayout seq, BiConsumer handleSetter) { for (long i = 0; i < seq.elementCount() ; i++) { handleSetter.accept(base, i); } } static void checkBytes(MemorySegment base, SequenceLayout layout, Function bufFactory, BiFunction handleExtractor, Function bufferExtractor) { long nelems = layout.elementCount(); long elemSize = layout.elementLayout().byteSize(); for (long i = 0 ; i < nelems ; i++) { long limit = nelems - i; MemorySegment resizedSegment = base.asSlice(i * elemSize, limit * elemSize); ByteBuffer bb = resizedSegment.asByteBuffer(); Z z = bufFactory.apply(bb); MemorySegment segmentBufferView = MemorySegment.ofBuffer(z); for (long j = i ; j < limit ; j++) { Object handleValue = handleExtractor.apply(resizedSegment, j - i); Object bufferValue = bufferExtractor.apply(z); Object handleViewValue = handleExtractor.apply(segmentBufferView, j - i); if (handleValue instanceof Number) { assertEquals(((Number)handleValue).longValue(), j); assertEquals(((Number)bufferValue).longValue(), j); assertEquals(((Number)handleViewValue).longValue(), j); } else { assertEquals((long)(char)handleValue, j); assertEquals((long)(char)bufferValue, j); assertEquals((long)(char)handleViewValue, j); } } } } @Test public void testOffheap() { try (Arena arena = Arena.ofConfined()) { MemorySegment segment = arena.allocate(tuples); initTuples(segment, tuples.elementCount()); ByteBuffer bb = segment.asByteBuffer(); checkTuples(segment, bb, tuples.elementCount()); } } @Test public void testHeap() { byte[] arr = new byte[(int) tuples.byteSize()]; MemorySegment region = MemorySegment.ofArray(arr); initTuples(region, tuples.elementCount()); ByteBuffer bb = region.asByteBuffer(); checkTuples(region, bb, tuples.elementCount()); } @Test public void testChannel() throws Throwable { File f = new File("test.out"); assertTrue(f.createNewFile()); f.deleteOnExit(); //write to channel try (FileChannel channel = FileChannel.open(f.toPath(), StandardOpenOption.READ, StandardOpenOption.WRITE)) { withMappedBuffer(channel, FileChannel.MapMode.READ_WRITE, 0, tuples.byteSize(), mbb -> { MemorySegment segment = MemorySegment.ofBuffer(mbb); initTuples(segment, tuples.elementCount()); mbb.force(); }); } //read from channel try (FileChannel channel = FileChannel.open(f.toPath(), StandardOpenOption.READ)) { withMappedBuffer(channel, FileChannel.MapMode.READ_ONLY, 0, tuples.byteSize(), mbb -> { MemorySegment segment = MemorySegment.ofBuffer(mbb); checkTuples(segment, mbb, tuples.elementCount()); }); } } @Test public void testDefaultAccessModesMappedSegment() throws Throwable { try (Arena arena = Arena.ofConfined(); FileChannel fileChannel = FileChannel.open(tempPath, StandardOpenOption.READ, StandardOpenOption.WRITE)) { MemorySegment segment = fileChannel.map(FileChannel.MapMode.READ_WRITE, 0L, 8L, arena); assertFalse(segment.isReadOnly()); } try (Arena arena = Arena.ofConfined(); FileChannel fileChannel = FileChannel.open(tempPath, StandardOpenOption.READ)) { MemorySegment segment = fileChannel.map(FileChannel.MapMode.READ_ONLY, 0L, 8L, arena); assertTrue(segment.isReadOnly()); } } @Test public void testMappedSegment() throws Throwable { File f = new File("test2.out"); f.createNewFile(); f.deleteOnExit(); try (Arena arena = Arena.ofConfined(); FileChannel fileChannel = FileChannel.open(f.toPath(), StandardOpenOption.READ, StandardOpenOption.WRITE)) { //write to channel MemorySegment segment = fileChannel.map(FileChannel.MapMode.READ_WRITE, 0L, tuples.byteSize(), arena); initTuples(segment, tuples.elementCount()); segment.force(); } try (Arena arena = Arena.ofConfined(); FileChannel fileChannel = FileChannel.open(f.toPath(), StandardOpenOption.READ)) { //read from channel MemorySegment segment = fileChannel.map(FileChannel.MapMode.READ_ONLY, 0L, tuples.byteSize(), arena); checkTuples(segment, segment.asByteBuffer(), tuples.elementCount()); } } @Test(dataProvider = "mappedOps", expectedExceptions = IllegalStateException.class) public void testMappedSegmentOperations(MappedSegmentOp mappedBufferOp) throws Throwable { File f = new File("test3.out"); f.createNewFile(); f.deleteOnExit(); Arena arena = Arena.ofConfined(); try (FileChannel fileChannel = FileChannel.open(f.toPath(), StandardOpenOption.READ, StandardOpenOption.WRITE)) { MemorySegment segment = fileChannel.map(FileChannel.MapMode.READ_WRITE, 0L, 8L, arena); assertTrue(segment.isMapped()); arena.close(); mappedBufferOp.apply(segment); } } @Test public void testMappedSegmentOffset() throws Throwable { File f = new File("test3.out"); f.createNewFile(); f.deleteOnExit(); MemoryLayout tupleLayout = tuples.elementLayout(); // write one at a time for (int i = 0 ; i < tuples.byteSize() ; i += tupleLayout.byteSize()) { try (Arena arena = Arena.ofConfined(); FileChannel fileChannel = FileChannel.open(f.toPath(), StandardOpenOption.READ, StandardOpenOption.WRITE)) { //write to channel MemorySegment segment = fileChannel.map(FileChannel.MapMode.READ_WRITE, i, tuples.byteSize(), arena); initTuples(segment, 1); segment.force(); } } // check one at a time for (int i = 0 ; i < tuples.byteSize() ; i += tupleLayout.byteSize()) { try (Arena arena = Arena.ofConfined(); FileChannel fileChannel = FileChannel.open(f.toPath(), StandardOpenOption.READ)) { //read from channel MemorySegment segment = fileChannel.map(FileChannel.MapMode.READ_ONLY, 0L, tuples.byteSize(), arena); checkTuples(segment, segment.asByteBuffer(), 1); } } } @Test(dataProvider = "fromArrays") public void testAsByteBufferFromNonByteArray(MemorySegment segment) { if (!segment.heapBase().map(a -> a instanceof byte[]).get()) { // This should not work as the segment is not backed by a byte array assertThrows(UnsupportedOperationException.class, segment::asByteBuffer); } } @Test public void testMappedSegmentAsByteBuffer() throws Throwable { File f = new File("test4.out"); assertTrue(f.createNewFile()); f.deleteOnExit(); for (var mapOption : List.of(FileChannel.MapMode.READ_WRITE, FileChannel.MapMode.READ_ONLY, FileChannel.MapMode.PRIVATE)) { for (var arena : List.of(Arena.ofConfined(), Arena.global())) { try (FileChannel fileChannel = FileChannel.open(f.toPath(), StandardOpenOption.READ, StandardOpenOption.WRITE)) { //write to channel MemorySegment segment = fileChannel.map(mapOption, 0L, 32L, arena); segment.force(); segment.load(); segment.isLoaded(); segment.unload(); ByteBuffer byteBuffer = segment.asByteBuffer(); assertEquals(byteBuffer.capacity(), segment.byteSize()); assertEquals(byteBuffer.isReadOnly(), segment.isReadOnly()); assertTrue(byteBuffer.isDirect()); } catch (IOException e) { if (e.getMessage().equals("Function not implemented")) throw new SkipException(e.getMessage(), e); } finally { if (arena.scope() != Arena.global().scope()) { arena.close(); } } } } } static final long LARGE_SIZE = (2L * 1024L + 512L) * 1024L * 1024L; // 2.5 GiB @Test public void testLargeMappedSegment() throws Throwable { if (System.getProperty("sun.arch.data.model").equals("32")) { throw new SkipException("large mapped files not supported on 32-bit systems"); } File f = new File("testLargeMappedSegment.out"); f.createNewFile(); f.deleteOnExit(); try (Arena arena = Arena.ofConfined(); FileChannel fileChannel = FileChannel.open(f.toPath(), StandardOpenOption.READ, StandardOpenOption.WRITE)) { MemorySegment segment = fileChannel.map(FileChannel.MapMode.READ_WRITE, 0L, LARGE_SIZE, arena); segment.isLoaded(); segment.load(); segment.isLoaded(); segment.force(); segment.isLoaded(); segment.unload(); segment.isLoaded(); } catch(IOException e) { if (e.getMessage().equals("Function not implemented")) throw new SkipException(e.getMessage(), e); } } static void withMappedBuffer(FileChannel channel, FileChannel.MapMode mode, long pos, long size, Consumer action) throws Throwable { MappedByteBuffer mbb = channel.map(mode, pos, size); var ref = new WeakReference<>(mbb); action.accept(mbb); mbb = null; //wait for it to be GCed System.gc(); while (ref.get() != null) { Thread.sleep(20); } } static void checkByteArrayAlignment(MemoryLayout layout) { if (layout.byteSize() > 4 && System.getProperty("sun.arch.data.model").equals("32")) { throw new SkipException("avoid unaligned access on 32-bit system"); } } @Test(dataProvider = "bufferOps") public void testScopedBuffer(Function bufferFactory, @NoInjection Method method, Object[] args) { Buffer bb; try (Arena arena = Arena.ofConfined()) { MemorySegment segment = arena.allocate(bytes); bb = bufferFactory.apply(segment.asByteBuffer()); } //outside of session!! try { method.invoke(bb, args); fail("Exception expected"); } catch (InvocationTargetException ex) { Throwable cause = ex.getCause(); if (cause instanceof IllegalStateException) { //all get/set buffer operation should fail because of the session check assertTrue(ex.getCause().getMessage().contains("Already closed")); } else { //all other exceptions were unexpected - fail fail("Unexpected exception", cause); } } catch (Throwable ex) { //unexpected exception - fail fail("Unexpected exception", ex); } } @Test(dataProvider = "bufferHandleOps") public void testScopedBufferAndVarHandle(VarHandle bufferHandle) { ByteBuffer bb; try (Arena arena = Arena.ofConfined()) { MemorySegment segment = arena.allocate(bytes, Long.BYTES); bb = segment.asByteBuffer(); for (Map.Entry e : varHandleMembers(bb, bufferHandle).entrySet()) { MethodHandle handle = e.getKey().bindTo(bufferHandle) .asSpreader(Object[].class, e.getValue().length); try { handle.invoke(e.getValue()); } catch (UnsupportedOperationException ex) { //skip } catch (Throwable ex) { //should not fail - segment is alive! fail(); } } } for (Map.Entry e : varHandleMembers(bb, bufferHandle).entrySet()) { try { MethodHandle handle = e.getKey().bindTo(bufferHandle) .asSpreader(Object[].class, e.getValue().length); handle.invoke(e.getValue()); fail(); } catch (IllegalStateException ex) { assertTrue(ex.getMessage().contains("Already closed")); } catch (UnsupportedOperationException ex) { //skip } catch (Throwable ex) { fail(); } } } @Test(dataProvider = "bufferOps") public void testDirectBuffer(Function bufferFactory, @NoInjection Method method, Object[] args) { try (Arena arena = Arena.ofConfined()) { MemorySegment segment = arena.allocate(bytes); Buffer bb = bufferFactory.apply(segment.asByteBuffer()); assertTrue(bb.isDirect()); DirectBuffer directBuffer = ((DirectBuffer)bb); assertEquals(directBuffer.address(), segment.address()); assertTrue((directBuffer.attachment() == null) == (bb instanceof ByteBuffer)); assertTrue(directBuffer.cleaner() == null); } } @Test(dataProvider="resizeOps") public void testResizeOffheap(Consumer checker, Consumer initializer, SequenceLayout seq) { try (Arena arena = Arena.ofConfined()) { MemorySegment segment = arena.allocate(seq); initializer.accept(segment); checker.accept(segment); } } @Test(dataProvider="resizeOps") public void testResizeHeap(Consumer checker, Consumer initializer, SequenceLayout seq) { checkByteArrayAlignment(seq.elementLayout()); int capacity = (int)seq.byteSize(); MemorySegment base = MemorySegment.ofArray(new byte[capacity]); initializer.accept(base); checker.accept(base); } @Test(dataProvider="resizeOps") public void testResizeBuffer(Consumer checker, Consumer initializer, SequenceLayout seq) { checkByteArrayAlignment(seq.elementLayout()); int capacity = (int)seq.byteSize(); MemorySegment base = MemorySegment.ofBuffer(ByteBuffer.wrap(new byte[capacity])); initializer.accept(base); checker.accept(base); } @Test(dataProvider="resizeOps") public void testResizeRoundtripHeap(Consumer checker, Consumer initializer, SequenceLayout seq) { checkByteArrayAlignment(seq.elementLayout()); int capacity = (int)seq.byteSize(); byte[] arr = new byte[capacity]; MemorySegment segment = MemorySegment.ofArray(arr); initializer.accept(segment); MemorySegment second = MemorySegment.ofBuffer(segment.asByteBuffer()); checker.accept(second); } @Test(dataProvider="resizeOps") public void testResizeRoundtripNative(Consumer checker, Consumer initializer, SequenceLayout seq) { try (Arena arena = Arena.ofConfined()) { MemorySegment segment = arena.allocate(seq); initializer.accept(segment); MemorySegment second = MemorySegment.ofBuffer(segment.asByteBuffer()); checker.accept(second); } } @Test(expectedExceptions = IllegalStateException.class) public void testBufferOnClosedSession() { MemorySegment leaked; try (Arena arena = Arena.ofConfined()) { leaked = arena.allocate(bytes); } ByteBuffer byteBuffer = leaked.asByteBuffer(); // ok byteBuffer.get(); // should throw } @Test(expectedExceptions = IllegalStateException.class) public void testTooBigForByteBuffer() { MemorySegment segment = MemorySegment.NULL.reinterpret(Integer.MAX_VALUE + 10L); segment.asByteBuffer(); } @Test(expectedExceptions = IllegalArgumentException.class) public void testBadMapNegativeSize() throws IOException { File f = new File("testNeg1.out"); f.createNewFile(); f.deleteOnExit(); try (FileChannel fileChannel = FileChannel.open(f.toPath(), StandardOpenOption.READ, StandardOpenOption.WRITE)) { fileChannel.map(FileChannel.MapMode.READ_WRITE, 0L, -1L, Arena.ofAuto()); } } @Test(expectedExceptions = IllegalArgumentException.class) public void testBadMapNegativeOffset() throws IOException { File f = new File("testNeg2.out"); f.createNewFile(); f.deleteOnExit(); try (FileChannel fileChannel = FileChannel.open(f.toPath(), StandardOpenOption.READ, StandardOpenOption.WRITE)) { fileChannel.map(FileChannel.MapMode.READ_WRITE, -1L, 1L, Arena.ofAuto()); } } @Test public void testMapOffset() throws IOException { File f = new File("testMapOffset.out"); f.createNewFile(); f.deleteOnExit(); int SIZE = Byte.MAX_VALUE; try (Arena arena = Arena.ofConfined(); FileChannel fileChannel = FileChannel.open(f.toPath(), StandardOpenOption.READ, StandardOpenOption.WRITE)) { MemorySegment segment = fileChannel.map(FileChannel.MapMode.READ_WRITE, 0L, SIZE, arena); for (byte offset = 0; offset < SIZE; offset++) { segment.set(JAVA_BYTE, offset, offset); } segment.force(); } for (int offset = 0 ; offset < SIZE ; offset++) { try (Arena arena = Arena.ofConfined(); FileChannel fileChannel = FileChannel.open(f.toPath(), StandardOpenOption.READ)) { MemorySegment segment = fileChannel.map(FileChannel.MapMode.READ_ONLY, offset, SIZE - offset, arena); assertEquals(segment.get(JAVA_BYTE, 0), offset); } } } @Test public void testMapZeroSize() throws IOException { File f = new File("testPos1.out"); f.createNewFile(); f.deleteOnExit(); //RW try (Arena arena = Arena.ofConfined(); FileChannel fileChannel = FileChannel.open(f.toPath(), StandardOpenOption.READ, StandardOpenOption.WRITE)) { MemorySegment segment = fileChannel.map(FileChannel.MapMode.READ_WRITE, 0L, 0L, arena); assertEquals(segment.byteSize(), 0); assertEquals(segment.isMapped(), true); assertFalse(segment.isReadOnly()); segment.force(); segment.load(); segment.isLoaded(); segment.unload(); ByteBuffer byteBuffer = segment.asByteBuffer(); assertEquals(byteBuffer.capacity(), 0); assertFalse(byteBuffer.isReadOnly()); } //RO try (Arena arena = Arena.ofConfined(); FileChannel fileChannel = FileChannel.open(f.toPath(), StandardOpenOption.READ)) { MemorySegment segment = fileChannel.map(FileChannel.MapMode.READ_ONLY, 0L, 0L, arena); assertEquals(segment.byteSize(), 0); assertEquals(segment.isMapped(), true); assertTrue(segment.isReadOnly()); segment.force(); segment.load(); segment.isLoaded(); segment.unload(); ByteBuffer byteBuffer = segment.asByteBuffer(); assertEquals(byteBuffer.capacity(), 0); assertTrue(byteBuffer.isReadOnly()); } } @Test(expectedExceptions = UnsupportedOperationException.class) public void testMapCustomPath() throws IOException { Path path = Path.of(URI.create("jrt:/")); try (FileChannel fileChannel = FileChannel.open(path, StandardOpenOption.READ, StandardOpenOption.WRITE)) { fileChannel.map(FileChannel.MapMode.READ_WRITE, 0L, 0L, Arena.ofAuto()); } } @Test(dataProvider="resizeOps") public void testCopyHeapToNative(Consumer checker, Consumer initializer, SequenceLayout seq) { checkByteArrayAlignment(seq.elementLayout()); int bytes = (int)seq.byteSize(); try (Arena arena = Arena.ofConfined()) { MemorySegment nativeArray = arena.allocate(bytes, 1); MemorySegment heapArray = MemorySegment.ofArray(new byte[bytes]); initializer.accept(heapArray); nativeArray.copyFrom(heapArray); checker.accept(nativeArray); } } @Test(dataProvider="resizeOps") public void testCopyNativeToHeap(Consumer checker, Consumer initializer, SequenceLayout seq) { checkByteArrayAlignment(seq.elementLayout()); int bytes = (int)seq.byteSize(); try (Arena arena = Arena.ofConfined()) { MemorySegment nativeArray = arena.allocate(seq); MemorySegment heapArray = MemorySegment.ofArray(new byte[bytes]); initializer.accept(nativeArray); heapArray.copyFrom(nativeArray); checker.accept(heapArray); } } @Test public void testDefaultAccessModesOfBuffer() { ByteBuffer rwBuffer = ByteBuffer.wrap(new byte[4]); { MemorySegment segment = MemorySegment.ofBuffer(rwBuffer); assertFalse(segment.isReadOnly()); } { ByteBuffer roBuffer = rwBuffer.asReadOnlyBuffer(); MemorySegment segment = MemorySegment.ofBuffer(roBuffer); assertTrue(segment.isReadOnly()); } } @Test public void testOfBufferScopeReachable() throws InterruptedException { ByteBuffer buffer = ByteBuffer.allocateDirect(1000); MemorySegment segment = MemorySegment.ofBuffer(buffer); try { AtomicBoolean reachable = new AtomicBoolean(true); Cleaner.create().register(buffer, () -> { reachable.set(false); }); buffer = null; System.gc(); // let's sleep to let cleaner run Thread.sleep(100); segment.get(JAVA_BYTE, 0); if (!reachable.get()) { throw new IllegalStateException(); } } finally { Reference.reachabilityFence(segment); } } @Test(dataProvider="bufferSources") public void testBufferToSegment(ByteBuffer bb, Predicate segmentChecker) { MemorySegment segment = MemorySegment.ofBuffer(bb); assertEquals(segment.isReadOnly(), bb.isReadOnly()); assertTrue(segmentChecker.test(segment)); assertTrue(segmentChecker.test(segment.asSlice(0, segment.byteSize()))); assertEquals(bb.capacity(), segment.byteSize()); //another round trip segment = MemorySegment.ofBuffer(segment.asByteBuffer()); assertEquals(segment.isReadOnly(), bb.isReadOnly()); assertTrue(segmentChecker.test(segment)); assertTrue(segmentChecker.test(segment.asSlice(0, segment.byteSize()))); assertEquals(bb.capacity(), segment.byteSize()); } @Test(dataProvider="bufferSources") public void bufferProperties(ByteBuffer bb, Predicate _unused) { MemorySegment segment = MemorySegment.ofBuffer(bb); ByteBuffer buffer = segment.asByteBuffer(); assertEquals(buffer.position(), 0); assertEquals(buffer.capacity(), segment.byteSize()); assertEquals(buffer.limit(), segment.byteSize()); } @Test public void testRoundTripAccess() { try (Arena arena = Arena.ofConfined()) { MemorySegment ms = arena.allocate(4, 1); MemorySegment msNoAccess = ms.asReadOnly(); MemorySegment msRoundTrip = MemorySegment.ofBuffer(msNoAccess.asByteBuffer()); assertEquals(msRoundTrip.scope(), ms.scope()); assertEquals(msNoAccess.isReadOnly(), msRoundTrip.isReadOnly()); } } @Test(dataProvider = "bufferFactories") public void testDerivedBufferScopes(Supplier bufferFactory) { MemorySegment segment = MemorySegment.ofBuffer(bufferFactory.get()); assertEquals(segment.scope(), segment.scope()); // one level assertEquals(segment.asSlice(0).scope(), segment.scope()); assertEquals(segment.asReadOnly().scope(), segment.scope()); // two levels assertEquals(segment.asSlice(0).asReadOnly().scope(), segment.scope()); assertEquals(segment.asReadOnly().asSlice(0).scope(), segment.scope()); // check fresh every time MemorySegment another = MemorySegment.ofBuffer(bufferFactory.get()); assertNotEquals(segment.scope(), another.scope()); } @Test(expectedExceptions = IllegalStateException.class) public void testDeadAccessOnClosedBufferSegment() { Arena arena = Arena.ofConfined(); MemorySegment s1 = arena.allocate(JAVA_INT); MemorySegment s2 = MemorySegment.ofBuffer(s1.asByteBuffer()); // memory freed arena.close(); s2.set(JAVA_INT, 0, 10); // Dead access! } @Test(dataProvider = "closeableArenas") public void closeableArenas(Supplier arenaSupplier) throws IOException { File tmp = File.createTempFile("tmp", "txt"); tmp.deleteOnExit(); try (FileChannel channel = FileChannel.open(tmp.toPath(), StandardOpenOption.READ, StandardOpenOption.WRITE) ; Arena arena = arenaSupplier.get()) { MemorySegment segment = arena.allocate(10, 1); for (int i = 0; i < 10; i++) { segment.set(JAVA_BYTE, i, (byte) i); } ByteBuffer bb = segment.asByteBuffer(); assertEquals(channel.write(bb), 10); segment.fill((byte)0x00); assertEquals(bb.clear(), ByteBuffer.wrap(new byte[] {0, 0, 0, 0, 0, 0, 0, 0, 0, 0})); assertEquals(channel.position(0).read(bb.clear()), 10); assertEquals(bb.flip(), ByteBuffer.wrap(new byte[] {0, 1, 2, 3, 4, 5, 6, 7, 8, 9})); } } static final Class ISE = IllegalStateException.class; @Test(dataProvider = "closeableArenas") public void testIOOnClosedSegmentBuffer(Supplier arenaSupplier) throws IOException { File tmp = File.createTempFile("tmp", "txt"); tmp.deleteOnExit(); Arena arena = arenaSupplier.get(); try (FileChannel channel = FileChannel.open(tmp.toPath(), StandardOpenOption.READ, StandardOpenOption.WRITE)) { MemorySegment segment = arena.allocate(10, 1); for (int i = 0; i < 10; i++) { segment.set(JAVA_BYTE, i, (byte) i); } ByteBuffer bb = segment.asByteBuffer(); arena.close(); assertThrows(ISE, () -> channel.read(bb)); assertThrows(ISE, () -> channel.read(new ByteBuffer[] {bb})); assertThrows(ISE, () -> channel.read(new ByteBuffer[] {bb}, 0, 1)); assertThrows(ISE, () -> channel.write(bb)); assertThrows(ISE, () -> channel.write(new ByteBuffer[] {bb})); assertThrows(ISE, () -> channel.write(new ByteBuffer[] {bb}, 0 ,1)); } } @Test public void buffersAndArraysFromSlices() { try (Arena arena = Arena.ofShared()) { MemorySegment segment = arena.allocate(16, 1); int newSize = 8; var slice = segment.asSlice(4, newSize); var bytes = slice.toArray(JAVA_BYTE); assertEquals(newSize, bytes.length); var buffer = slice.asByteBuffer(); // Fails for heap segments, but passes for native segments: assertEquals(0, buffer.position()); assertEquals(newSize, buffer.limit()); assertEquals(newSize, buffer.capacity()); } } @Test public void viewsFromSharedSegment() { try (Arena arena = Arena.ofShared()) { MemorySegment segment = arena.allocate(16, 1); var byteBuffer = segment.asByteBuffer(); byteBuffer.asReadOnlyBuffer(); byteBuffer.slice(0, 8); } } @DataProvider(name = "segments") public static Object[][] segments() throws Throwable { return new Object[][] { { (Supplier) () -> Arena.ofAuto().allocate(16, 1)}, { (Supplier) () -> Arena.ofConfined().allocate(16, 1)}, { (Supplier) () -> MemorySegment.ofArray(new byte[16]) } }; } @DataProvider(name = "closeableArenas") public static Object[][] closeableArenas() { return new Object[][] { { (Supplier) Arena::ofConfined}, { (Supplier) Arena::ofShared}, }; } @DataProvider(name = "bufferOps") public static Object[][] bufferOps() throws Throwable { List args = new ArrayList<>(); bufferOpsArgs(args, bb -> bb, ByteBuffer.class); bufferOpsArgs(args, ByteBuffer::asCharBuffer, CharBuffer.class); bufferOpsArgs(args, ByteBuffer::asShortBuffer, ShortBuffer.class); bufferOpsArgs(args, ByteBuffer::asIntBuffer, IntBuffer.class); bufferOpsArgs(args, ByteBuffer::asFloatBuffer, FloatBuffer.class); bufferOpsArgs(args, ByteBuffer::asLongBuffer, LongBuffer.class); bufferOpsArgs(args, ByteBuffer::asDoubleBuffer, DoubleBuffer.class); return args.toArray(Object[][]::new); } static void bufferOpsArgs(List argsList, Function factory, Class bufferClass) { for (Method m : bufferClass.getMethods()) { //skip statics and method declared in j.l.Object if (m.getDeclaringClass().equals(Object.class) || ((m.getModifiers() & Modifier.STATIC) != 0) || (!m.getName().contains("get") && !m.getName().contains("put")) || m.getParameterCount() > 2) continue; Object[] args = Stream.of(m.getParameterTypes()) .map(TestByteBuffer::defaultValue) .toArray(); argsList.add(new Object[] { factory, m, args }); } } @DataProvider(name = "bufferHandleOps") public static Object[][] bufferHandleOps() throws Throwable { return new Object[][]{ { MethodHandles.byteBufferViewVarHandle(char[].class, ByteOrder.nativeOrder()) }, { MethodHandles.byteBufferViewVarHandle(short[].class, ByteOrder.nativeOrder()) }, { MethodHandles.byteBufferViewVarHandle(int[].class, ByteOrder.nativeOrder()) }, { MethodHandles.byteBufferViewVarHandle(long[].class, ByteOrder.nativeOrder()) }, { MethodHandles.byteBufferViewVarHandle(float[].class, ByteOrder.nativeOrder()) }, { MethodHandles.byteBufferViewVarHandle(double[].class, ByteOrder.nativeOrder()) } }; } static Map varHandleMembers(ByteBuffer bb, VarHandle handle) { Map members = new HashMap<>(); for (VarHandle.AccessMode mode : VarHandle.AccessMode.values()) { Class[] params = handle.accessModeType(mode).parameterArray(); Object[] args = Stream.concat(Stream.of(bb), Stream.of(params).skip(1) .map(TestByteBuffer::defaultValue)) .toArray(); try { members.put(MethodHandles.varHandleInvoker(mode, handle.accessModeType(mode)), args); } catch (Throwable ex) { throw new AssertionError(ex); } } return members; } @DataProvider(name = "resizeOps") public Object[][] resizeOps() { Consumer byteInitializer = (base) -> initBytes(base, bytes, (addr, pos) -> addr.set(JAVA_BYTE, pos, (byte)(long)pos)); Consumer charInitializer = (base) -> initBytes(base, chars, (addr, pos) -> addr.setAtIndex(BB_CHAR, pos, (char)(long)pos)); Consumer shortInitializer = (base) -> initBytes(base, shorts, (addr, pos) -> addr.setAtIndex(BB_SHORT, pos, (short)(long)pos)); Consumer intInitializer = (base) -> initBytes(base, ints, (addr, pos) -> addr.setAtIndex(BB_INT, pos, (int)(long)pos)); Consumer floatInitializer = (base) -> initBytes(base, floats, (addr, pos) -> addr.setAtIndex(BB_FLOAT, pos, (float)(long)pos)); Consumer longInitializer = (base) -> initBytes(base, longs, (addr, pos) -> addr.setAtIndex(BB_LONG, pos, (long)pos)); Consumer doubleInitializer = (base) -> initBytes(base, doubles, (addr, pos) -> addr.setAtIndex(BB_DOUBLE, pos, (double)(long)pos)); Consumer byteChecker = (base) -> checkBytes(base, bytes, Function.identity(), (addr, pos) -> addr.get(JAVA_BYTE, pos), ByteBuffer::get); Consumer charChecker = (base) -> checkBytes(base, chars, ByteBuffer::asCharBuffer, (addr, pos) -> addr.getAtIndex(BB_CHAR, pos), CharBuffer::get); Consumer shortChecker = (base) -> checkBytes(base, shorts, ByteBuffer::asShortBuffer, (addr, pos) -> addr.getAtIndex(BB_SHORT, pos), ShortBuffer::get); Consumer intChecker = (base) -> checkBytes(base, ints, ByteBuffer::asIntBuffer, (addr, pos) -> addr.getAtIndex(BB_INT, pos), IntBuffer::get); Consumer floatChecker = (base) -> checkBytes(base, floats, ByteBuffer::asFloatBuffer, (addr, pos) -> addr.getAtIndex(BB_FLOAT, pos), FloatBuffer::get); Consumer longChecker = (base) -> checkBytes(base, longs, ByteBuffer::asLongBuffer, (addr, pos) -> addr.getAtIndex(BB_LONG, pos), LongBuffer::get); Consumer doubleChecker = (base) -> checkBytes(base, doubles, ByteBuffer::asDoubleBuffer, (addr, pos) -> addr.getAtIndex(BB_DOUBLE, pos), DoubleBuffer::get); return new Object[][]{ {byteChecker, byteInitializer, bytes}, {charChecker, charInitializer, chars}, {shortChecker, shortInitializer, shorts}, {intChecker, intInitializer, ints}, {floatChecker, floatInitializer, floats}, {longChecker, longInitializer, longs}, {doubleChecker, doubleInitializer, doubles} }; } static Object defaultValue(Class c) { if (c.isPrimitive()) { if (c == char.class) { return (char)0; } else if (c == boolean.class) { return false; } else if (c == byte.class) { return (byte)0; } else if (c == short.class) { return (short)0; } else if (c == int.class) { return 0; } else if (c == long.class) { return 0L; } else if (c == float.class) { return 0f; } else if (c == double.class) { return 0d; } else { throw new IllegalStateException(); } } else if (c.isArray()) { if (c == char[].class) { return new char[1]; } else if (c == boolean[].class) { return new boolean[1]; } else if (c == byte[].class) { return new byte[1]; } else if (c == short[].class) { return new short[1]; } else if (c == int[].class) { return new int[1]; } else if (c == long[].class) { return new long[1]; } else if (c == float[].class) { return new float[1]; } else if (c == double[].class) { return new double[1]; } else { throw new IllegalStateException(); } } else if (c == String.class) { return "asdf"; } else if (c == ByteBuffer.class) { return ByteBuffer.wrap(new byte[1]); } else if (c == CharBuffer.class) { return CharBuffer.wrap(new char[1]); } else if (c == ShortBuffer.class) { return ShortBuffer.wrap(new short[1]); } else if (c == IntBuffer.class) { return IntBuffer.wrap(new int[1]); } else if (c == FloatBuffer.class) { return FloatBuffer.wrap(new float[1]); } else if (c == LongBuffer.class) { return LongBuffer.wrap(new long[1]); } else if (c == DoubleBuffer.class) { return DoubleBuffer.wrap(new double[1]); } else { return null; } } @DataProvider(name = "bufferSources") public static Object[][] bufferSources() { Predicate heapTest = segment -> !segment.isNative() && !segment.isMapped(); Predicate nativeTest = segment -> segment.isNative() && !segment.isMapped(); Predicate mappedTest = segment -> segment.isNative() && segment.isMapped(); try (FileChannel channel = FileChannel.open(tempPath, StandardOpenOption.READ, StandardOpenOption.WRITE)) { return new Object[][]{ { ByteBuffer.wrap(new byte[256]), heapTest }, { ByteBuffer.allocate(256), heapTest }, { ByteBuffer.allocateDirect(256), nativeTest }, { channel.map(FileChannel.MapMode.READ_WRITE, 0L, 256), mappedTest }, { ByteBuffer.wrap(new byte[256]).asReadOnlyBuffer(), heapTest }, { ByteBuffer.allocate(256).asReadOnlyBuffer(), heapTest }, { ByteBuffer.allocateDirect(256).asReadOnlyBuffer(), nativeTest }, { channel.map(FileChannel.MapMode.READ_WRITE, 0L, 256).asReadOnlyBuffer(), mappedTest } }; } catch (IOException ex) { throw new ExceptionInInitializerError(ex); } } enum MappedSegmentOp { LOAD(MemorySegment::load), UNLOAD(MemorySegment::unload), IS_LOADED(MemorySegment::isLoaded), FORCE(MemorySegment::force), BUFFER_LOAD(m -> ((MappedByteBuffer)m.asByteBuffer()).load()), BUFFER_IS_LOADED(m -> ((MappedByteBuffer)m.asByteBuffer()).isLoaded()), BUFFER_FORCE(m -> ((MappedByteBuffer)m.asByteBuffer()).force()); private final Consumer segmentOp; MappedSegmentOp(Consumer segmentOp) { this.segmentOp = segmentOp; } void apply(MemorySegment segment) { segmentOp.accept(segment); } } @DataProvider(name = "mappedOps") public static Object[][] mappedOps() { return Stream.of(MappedSegmentOp.values()) .map(op -> new Object[] { op }) .toArray(Object[][]::new); } @DataProvider(name = "bufferFactories") public static Object[][] bufferFactories() { List> l = List.of( () -> ByteBuffer.allocate(10), () -> CharBuffer.allocate(10), () -> ShortBuffer.allocate(10), () -> IntBuffer.allocate(10), () -> FloatBuffer.allocate(10), () -> LongBuffer.allocate(10), () -> DoubleBuffer.allocate(10), () -> ByteBuffer.allocateDirect(10), () -> ByteBuffer.allocateDirect(10).asCharBuffer(), () -> ByteBuffer.allocateDirect(10).asShortBuffer(), () -> ByteBuffer.allocateDirect(10).asIntBuffer(), () -> ByteBuffer.allocateDirect(10).asFloatBuffer(), () -> ByteBuffer.allocateDirect(10).asLongBuffer(), () -> ByteBuffer.allocateDirect(10).asDoubleBuffer() ); return l.stream().map(s -> new Object[] { s }).toArray(Object[][]::new); } @DataProvider(name = "fromArrays") public static Object[][] fromArrays() { int len = 16; return Stream.of( MemorySegment.ofArray(new byte[len]), MemorySegment.ofArray(new short[len]), MemorySegment.ofArray(new char[len]), MemorySegment.ofArray(new int[len]), MemorySegment.ofArray(new long[len]), MemorySegment.ofArray(new float[len]), MemorySegment.ofArray(new double[len]) ) .map(s -> new Object[] { s }) .toArray(Object[][]::new); } }