/* * 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 * @run testng TestLayoutPaths */ import java.lang.foreign.*; import java.lang.foreign.MemoryLayout.PathElement; import org.testng.annotations.*; import java.lang.invoke.MethodHandle; import java.lang.invoke.VarHandle; import java.lang.invoke.VarHandle.AccessMode; import java.nio.ByteOrder; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.function.IntFunction; import java.util.stream.Stream; import static java.lang.foreign.MemoryLayout.PathElement.groupElement; import static java.lang.foreign.MemoryLayout.PathElement.sequenceElement; import static java.lang.foreign.ValueLayout.JAVA_INT; import static java.lang.foreign.ValueLayout.JAVA_SHORT; import static org.testng.Assert.*; public class TestLayoutPaths { @Test(expectedExceptions = IllegalArgumentException.class) public void testBadByteSelectFromSeq() { SequenceLayout seq = MemoryLayout.sequenceLayout(5, JAVA_INT); seq.byteOffset(groupElement("foo")); } @Test(expectedExceptions = IllegalArgumentException.class) public void testBadByteSelectFromStruct() { GroupLayout g = MemoryLayout.structLayout(JAVA_INT); g.byteOffset(sequenceElement()); } @Test(expectedExceptions = IllegalArgumentException.class) public void testBadByteSelectFromValue() { SequenceLayout seq = MemoryLayout.sequenceLayout(5, JAVA_INT); seq.byteOffset(sequenceElement(), sequenceElement()); } @Test(expectedExceptions = IllegalArgumentException.class) public void testUnknownByteStructField() { GroupLayout g = MemoryLayout.structLayout(JAVA_INT); g.byteOffset(groupElement("foo")); } @Test(expectedExceptions = IllegalArgumentException.class) public void testTooBigGroupElementIndex() { GroupLayout g = MemoryLayout.structLayout(JAVA_INT); g.byteOffset(groupElement(1)); } @Test(expectedExceptions = IllegalArgumentException.class) public void testNegativeGroupElementIndex() { GroupLayout g = MemoryLayout.structLayout(JAVA_INT); g.byteOffset(groupElement(-1)); } @Test(expectedExceptions = IllegalArgumentException.class) public void testByteOutOfBoundsSeqIndex() { SequenceLayout seq = MemoryLayout.sequenceLayout(5, JAVA_INT); seq.byteOffset(sequenceElement(6)); } @Test(expectedExceptions = IllegalArgumentException.class) public void testNegativeSeqIndex() { sequenceElement(-2); } @Test(expectedExceptions = IllegalArgumentException.class) public void testByteNegativeSeqIndex() { SequenceLayout seq = MemoryLayout.sequenceLayout(5, JAVA_INT); seq.byteOffset(sequenceElement(-2)); } @Test(expectedExceptions = IllegalArgumentException.class) public void testOutOfBoundsSeqRange() { SequenceLayout seq = MemoryLayout.sequenceLayout(5, JAVA_INT); seq.byteOffset(sequenceElement(6, 2)); } @Test(expectedExceptions = IllegalArgumentException.class) public void testNegativeSeqRange() { sequenceElement(-2, 2); } @Test(expectedExceptions = IllegalArgumentException.class) public void testByteNegativeSeqRange() { SequenceLayout seq = MemoryLayout.sequenceLayout(5, JAVA_INT); seq.byteOffset(sequenceElement(-2, 2)); } @Test(expectedExceptions = IllegalArgumentException.class) public void testIncompleteAccess() { SequenceLayout seq = MemoryLayout.sequenceLayout(5, MemoryLayout.structLayout(JAVA_INT)); seq.varHandle(sequenceElement()); } @Test public void testByteOffsetHandleRange() { SequenceLayout seq = MemoryLayout.sequenceLayout(5, MemoryLayout.structLayout(JAVA_INT)); seq.byteOffsetHandle(sequenceElement(0, 1)); } @Test(expectedExceptions = IllegalArgumentException.class) public void testByteOffsetHandleBadRange() { SequenceLayout seq = MemoryLayout.sequenceLayout(5, MemoryLayout.structLayout(JAVA_INT)); seq.byteOffsetHandle(sequenceElement(5, 1)); // invalid range (starting position is outside the sequence) } @Test public void testBadAlignmentOfRoot() { MemoryLayout struct = MemoryLayout.structLayout( JAVA_INT.withOrder(ByteOrder.LITTLE_ENDIAN), JAVA_SHORT.withOrder(ByteOrder.LITTLE_ENDIAN).withName("x")); assertEquals(struct.byteAlignment(), 4); try (Arena arena = Arena.ofConfined()) { MemorySegment seg = arena.allocate(struct.byteSize() + 2, struct.byteAlignment()).asSlice(2); assertEquals(seg.address() % JAVA_SHORT.byteAlignment(), 0); // should be aligned assertNotEquals(seg.address() % struct.byteAlignment(), 0); // should not be aligned String expectedMessage = "Target offset 0 is incompatible with alignment constraint " + struct.byteAlignment() + " (of [i4s2(x)]) for segment MemorySegment"; VarHandle vhX = struct.varHandle(groupElement("x")); IllegalArgumentException iae = expectThrows(IllegalArgumentException.class, () -> { vhX.set(seg, 0L, (short) 42); }); assertTrue(iae.getMessage().startsWith(expectedMessage)); MethodHandle sliceX = struct.sliceHandle(groupElement("x")); iae = expectThrows(IllegalArgumentException.class, () -> { MemorySegment slice = (MemorySegment) sliceX.invokeExact(seg, 0L); }); assertTrue(iae.getMessage().startsWith(expectedMessage)); } } @Test public void testWrongTypeRoot() { MemoryLayout struct = MemoryLayout.structLayout( JAVA_INT.withOrder(ByteOrder.LITTLE_ENDIAN), JAVA_INT.withOrder(ByteOrder.LITTLE_ENDIAN) ); var expectedMessage = "Bad layout path: attempting to select a sequence element from a non-sequence layout: [i4i4]"; IllegalArgumentException iae = expectThrows(IllegalArgumentException.class, () -> struct.select(PathElement.sequenceElement())); assertEquals(iae.getMessage(), expectedMessage); } @Test public void testWrongTypeEnclosing() { MemoryLayout struct = MemoryLayout.structLayout( MemoryLayout.sequenceLayout(2, MemoryLayout.structLayout( JAVA_INT.withOrder(ByteOrder.LITTLE_ENDIAN).withName("3a"), JAVA_INT.withOrder(ByteOrder.LITTLE_ENDIAN).withName("3b") ).withName("2") ).withName("1") ).withName("0"); var expectedMessage = "Bad layout path: attempting to select a sequence element from a non-sequence layout: " + "[i4(3a)i4(3b)](2), selected from: " + "[2:[i4(3a)i4(3b)](2)](1), selected from: " + "[[2:[i4(3a)i4(3b)](2)](1)](0)"; IllegalArgumentException iae = expectThrows(IllegalArgumentException.class, () -> struct.select(PathElement.groupElement("1"), PathElement.sequenceElement(), PathElement.sequenceElement())); assertEquals(iae.getMessage(), expectedMessage); } @Test public void testBadSequencePathInOffset() { SequenceLayout seq = MemoryLayout.sequenceLayout(10, JAVA_INT); // bad path elements for (PathElement e : List.of( sequenceElement(), sequenceElement(0, 2) )) { try { seq.byteOffset(e); fail(); } catch (IllegalArgumentException ex) { assertTrue(true); } } } @Test public void testBadSequencePathInSelect() { SequenceLayout seq = MemoryLayout.sequenceLayout(10, JAVA_INT); for (PathElement e : List.of( sequenceElement(0), sequenceElement(0, 2) )) { try { seq.select(e); fail(); } catch (IllegalArgumentException ex) { assertTrue(true); } } } @Test(dataProvider = "groupSelectors") public void testStructPaths(IntFunction groupSelector) { long[] offsets = { 0, 1, 3, 7 }; GroupLayout g = MemoryLayout.structLayout( ValueLayout.JAVA_BYTE.withName("0"), ValueLayout.JAVA_CHAR_UNALIGNED.withName("1"), ValueLayout.JAVA_FLOAT_UNALIGNED.withName("2"), ValueLayout.JAVA_LONG_UNALIGNED.withName("3") ); // test select for (int i = 0 ; i < 4 ; i++) { MemoryLayout selected = g.select(groupSelector.apply(i)); assertTrue(selected == g.memberLayouts().get(i)); } // test offset for (int i = 0 ; i < 4 ; i++) { long byteOffset = g.byteOffset(groupSelector.apply(i)); assertEquals(offsets[i], byteOffset); } } @Test(dataProvider = "groupSelectors") public void testUnionPaths(IntFunction groupSelector) { long[] offsets = { 0, 0, 0, 0 }; GroupLayout g = MemoryLayout.unionLayout( ValueLayout.JAVA_BYTE.withName("0"), ValueLayout.JAVA_CHAR.withName("1"), ValueLayout.JAVA_FLOAT.withName("2"), ValueLayout.JAVA_LONG.withName("3") ); // test select for (int i = 0 ; i < 4 ; i++) { MemoryLayout selected = g.select(groupSelector.apply(i)); assertTrue(selected == g.memberLayouts().get(i)); } // test offset for (int i = 0 ; i < 4 ; i++) { long byteOffset = g.byteOffset(groupSelector.apply(i)); assertEquals(offsets[i], byteOffset); } } @DataProvider public static Object[][] groupSelectors() { return new Object[][] { { (IntFunction) PathElement::groupElement }, // by index { (IntFunction) i -> PathElement.groupElement(String.valueOf(i)) } // by name }; } @Test public void testSequencePaths() { long[] offsets = { 0, 1, 2, 3 }; SequenceLayout g = MemoryLayout.sequenceLayout(4, ValueLayout.JAVA_BYTE); // test select MemoryLayout selected = g.select(sequenceElement()); assertTrue(selected == ValueLayout.JAVA_BYTE); // test offset for (int i = 0 ; i < 4 ; i++) { long byteOffset = g.byteOffset(sequenceElement(i)); assertEquals(offsets[i], byteOffset); } } @Test(dataProvider = "testLayouts") public void testOffsetHandle(MemoryLayout layout, PathElement[] pathElements, long[] indexes, long expectedByteOffset) throws Throwable { MethodHandle byteOffsetHandle = layout.byteOffsetHandle(pathElements); byteOffsetHandle = byteOffsetHandle.asSpreader(long[].class, indexes.length); long actualByteOffset = (long) byteOffsetHandle.invokeExact(0L, indexes); assertEquals(actualByteOffset, expectedByteOffset); } @Test(dataProvider = "testLayouts") public void testOffsetHandleOOBIndex(MemoryLayout layout, PathElement[] pathElements, long[] indexes, long expectedByteOffset) throws Throwable { int[] badIndices = { -1, 10 }; MemoryLayout seqLayout = MemoryLayout.sequenceLayout(badIndices[1], layout); for (int badIndex : badIndices) { PathElement[] seqPathElements = new PathElement[pathElements.length + 1]; long[] seqIndexes = new long[indexes.length + 1]; System.arraycopy(pathElements, 0, seqPathElements, 1, pathElements.length); System.arraycopy(indexes, 0, seqIndexes, 1, indexes.length); seqPathElements[0] = PathElement.sequenceElement(); seqIndexes[0] = badIndex; MethodHandle seqByteOffsetHandle = seqLayout.byteOffsetHandle(seqPathElements) .asSpreader(long[].class, seqIndexes.length); assertThrows(IndexOutOfBoundsException.class, () -> seqByteOffsetHandle.invoke(0L, seqIndexes)); } } @Test(dataProvider = "testLayouts", expectedExceptions = ArithmeticException.class) public void testOffsetHandleOverflow(MemoryLayout layout, PathElement[] pathElements, long[] indexes, long expectedByteOffset) throws Throwable { MethodHandle byteOffsetHandle = layout.byteOffsetHandle(pathElements); byteOffsetHandle = byteOffsetHandle.asSpreader(long[].class, indexes.length); byteOffsetHandle.invoke(Long.MAX_VALUE, indexes); } @Test(dataProvider = "testLayouts") public void testVarHandleBadSegment(MemoryLayout layout, PathElement[] pathElements, long[] indexes, long expectedByteOffset) throws Throwable { MemoryLayout seqLayout = MemoryLayout.sequenceLayout(10, layout); PathElement[] seqPathElements = new PathElement[pathElements.length + 1]; long[] seqIndexes = new long[indexes.length + 1]; System.arraycopy(pathElements, 0, seqPathElements, 1, pathElements.length); System.arraycopy(indexes, 0, seqIndexes, 1, indexes.length); seqPathElements[0] = PathElement.sequenceElement(); seqIndexes[0] = 0; MethodHandle getter_handle = seqLayout.varHandle(seqPathElements) .toMethodHandle(AccessMode.GET) .asSpreader(long[].class, seqIndexes.length); MemorySegment segment = Arena.ofAuto().allocate(layout); assertThrows(IndexOutOfBoundsException.class, () -> getter_handle.invoke(segment, 0L, seqIndexes)); } @Test(dataProvider = "testLayouts") public void testSliceHandleBadSegment(MemoryLayout layout, PathElement[] pathElements, long[] indexes, long expectedByteOffset) throws Throwable { MemoryLayout seqLayout = MemoryLayout.sequenceLayout(10, layout); PathElement[] seqPathElements = new PathElement[pathElements.length + 1]; long[] seqIndexes = new long[indexes.length + 1]; System.arraycopy(pathElements, 0, seqPathElements, 1, pathElements.length); System.arraycopy(indexes, 0, seqIndexes, 1, indexes.length); seqPathElements[0] = PathElement.sequenceElement(); seqIndexes[0] = 0; MethodHandle getter_handle = seqLayout.sliceHandle(seqPathElements) .asSpreader(long[].class, seqIndexes.length); MemorySegment segment = Arena.ofAuto().allocate(layout); assertThrows(IndexOutOfBoundsException.class, () -> getter_handle.invoke(segment, 0L, seqIndexes)); } @Test(dataProvider = "testLayouts") public void testArrayElementVarHandleBadSegment(MemoryLayout layout, PathElement[] pathElements, long[] indexes, long expectedByteOffset) throws Throwable { MemoryLayout seqLayout = MemoryLayout.sequenceLayout(10, layout); PathElement[] seqPathElements = new PathElement[pathElements.length + 1]; long[] seqIndexes = new long[indexes.length + 2]; System.arraycopy(pathElements, 0, seqPathElements, 1, pathElements.length); System.arraycopy(indexes, 0, seqIndexes, 2, indexes.length); seqPathElements[0] = PathElement.sequenceElement(); seqIndexes[0] = 0; seqIndexes[1] = 0; MethodHandle getter_handle = seqLayout.arrayElementVarHandle(seqPathElements) .toMethodHandle(AccessMode.GET) .asSpreader(long[].class, seqIndexes.length); MemorySegment segment = Arena.ofAuto().allocate(layout); assertThrows(IndexOutOfBoundsException.class, () -> getter_handle.invoke(segment, 0L, seqIndexes)); } @Test public void testHashCodeCollision() { PathElement sequenceElement = PathElement.sequenceElement(); PathElement dereferenceElement = PathElement.dereferenceElement(); assertNotEquals(sequenceElement.hashCode(), dereferenceElement.hashCode()); } @Test public void testGroupElementIndexToString() { PathElement e = PathElement.groupElement(2); assertEquals(e.toString(), "groupElement(2)"); } @Test public void testGroupElementNameToString() { PathElement e = PathElement.groupElement("x"); assertEquals(e.toString(), "groupElement(\"x\")"); } @Test public void testSequenceElementToString() { PathElement e = PathElement.sequenceElement(); assertEquals(e.toString(), "sequenceElement()"); } @Test public void testSequenceElementIndexToString() { PathElement e = PathElement.sequenceElement(2); assertEquals(e.toString(), "sequenceElement(2)"); } @Test public void testSequenceElementRangeToString() { PathElement e = PathElement.sequenceElement(2, 4); assertEquals(e.toString(), "sequenceElement(2, 4)"); } @Test public void testDerefereceElementToString() { PathElement e = PathElement.dereferenceElement(); assertEquals(e.toString(), "dereferenceElement()"); } @DataProvider public static Object[][] testLayouts() { List testCases = new ArrayList<>(); testCases.add(new Object[] { MemoryLayout.sequenceLayout(10, JAVA_INT), new PathElement[] { sequenceElement() }, new long[] { 4 }, JAVA_INT.byteSize() * 4 }); testCases.add(new Object[] { MemoryLayout.sequenceLayout(10, MemoryLayout.structLayout(JAVA_INT, JAVA_INT.withName("y"))), new PathElement[] { sequenceElement(), groupElement("y") }, new long[] { 4 }, (JAVA_INT.byteSize() * 2) * 4 + JAVA_INT.byteSize() }); testCases.add(new Object[] { MemoryLayout.sequenceLayout(10, MemoryLayout.structLayout(MemoryLayout.paddingLayout(4), JAVA_INT.withName("y"))), new PathElement[] { sequenceElement(), groupElement("y") }, new long[] { 4 }, (JAVA_INT.byteSize() + 4) * 4 + 4 }); testCases.add(new Object[] { MemoryLayout.sequenceLayout(10, JAVA_INT), new PathElement[] { sequenceElement() }, new long[] { 4 }, JAVA_INT.byteSize() * 4 }); testCases.add(new Object[] { MemoryLayout.structLayout( MemoryLayout.sequenceLayout(10, JAVA_INT).withName("data") ), new PathElement[] { groupElement("data"), sequenceElement() }, new long[] { 4 }, JAVA_INT.byteSize() * 4 }); MemoryLayout complexLayout = MemoryLayout.structLayout( MemoryLayout.sequenceLayout(10, MemoryLayout.sequenceLayout(10, MemoryLayout.structLayout( JAVA_INT.withName("x"), JAVA_INT.withName("y") ) ) ).withName("data") ); testCases.add(new Object[] { complexLayout, new PathElement[] { groupElement("data"), sequenceElement(), sequenceElement(), groupElement("x") }, new long[] { 0, 1 }, (JAVA_INT.byteSize() * 2) }); testCases.add(new Object[] { complexLayout, new PathElement[] { groupElement("data"), sequenceElement(), sequenceElement(), groupElement("x") }, new long[] { 1, 0 }, (JAVA_INT.byteSize() * 2) * 10 }); testCases.add(new Object[] { complexLayout, new PathElement[] { groupElement("data"), sequenceElement(), sequenceElement(), groupElement("y") }, new long[] { 0, 1 }, (JAVA_INT.byteSize() * 2) + JAVA_INT.byteSize() }); testCases.add(new Object[] { complexLayout, new PathElement[] { groupElement("data"), sequenceElement(), sequenceElement(), groupElement("y") }, new long[] { 1, 0 }, (JAVA_INT.byteSize() * 2) * 10 + JAVA_INT.byteSize() }); return testCases.toArray(Object[][]::new); } @Test(dataProvider = "testLayouts") public void testSliceHandle(MemoryLayout layout, PathElement[] pathElements, long[] indexes, long expectedByteOffset) throws Throwable { MemoryLayout selected = layout.select(pathElements); MethodHandle sliceHandle = layout.sliceHandle(pathElements); sliceHandle = sliceHandle.asSpreader(long[].class, indexes.length); try (Arena arena = Arena.ofConfined()) { MemorySegment segment = arena.allocate(layout); MemorySegment slice = (MemorySegment) sliceHandle.invokeExact(segment, 0L, indexes); assertEquals(slice.address() - segment.address(), expectedByteOffset); assertEquals(slice.byteSize(), selected.byteSize()); } } }