/* * Copyright (c) 2020, 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 * @bug 8323552 * @run testng TestMismatch */ import java.lang.foreign.Arena; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Random; import java.util.concurrent.atomic.AtomicReference; import java.lang.foreign.MemorySegment; import java.lang.foreign.ValueLayout; import java.util.function.IntFunction; import java.util.stream.Stream; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; import static java.lang.System.out; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertThrows; public class TestMismatch { // stores an increasing sequence of values into the memory of the given segment static MemorySegment initializeSegment(MemorySegment segment) { for (int i = 0 ; i < segment.byteSize() ; i++) { segment.set(ValueLayout.JAVA_BYTE, i, (byte)i); } return segment; } @Test(dataProvider = "slices", expectedExceptions = IndexOutOfBoundsException.class) public void testNegativeSrcFromOffset(MemorySegment s1, MemorySegment s2) { MemorySegment.mismatch(s1, -1, 0, s2, 0, 0); } @Test(dataProvider = "slices", expectedExceptions = IndexOutOfBoundsException.class) public void testNegativeDstFromOffset(MemorySegment s1, MemorySegment s2) { MemorySegment.mismatch(s1, 0, 0, s2, -1, 0); } @Test(dataProvider = "slices", expectedExceptions = IndexOutOfBoundsException.class) public void testNegativeSrcToOffset(MemorySegment s1, MemorySegment s2) { MemorySegment.mismatch(s1, 0, -1, s2, 0, 0); } @Test(dataProvider = "slices", expectedExceptions = IndexOutOfBoundsException.class) public void testNegativeDstToOffset(MemorySegment s1, MemorySegment s2) { MemorySegment.mismatch(s1, 0, 0, s2, 0, -1); } @Test(dataProvider = "slices", expectedExceptions = IndexOutOfBoundsException.class) public void testNegativeSrcLength(MemorySegment s1, MemorySegment s2) { MemorySegment.mismatch(s1, 3, 2, s2, 0, 0); } @Test(dataProvider = "slices", expectedExceptions = IndexOutOfBoundsException.class) public void testNegativeDstLength(MemorySegment s1, MemorySegment s2) { MemorySegment.mismatch(s1, 0, 0, s2, 3, 2); } @Test(dataProvider = "slices") public void testSameValues(MemorySegment ss1, MemorySegment ss2) { out.format("testSameValues s1:%s, s2:%s\n", ss1, ss2); MemorySegment s1 = initializeSegment(ss1); MemorySegment s2 = initializeSegment(ss2); if (s1.byteSize() == s2.byteSize()) { assertEquals(s1.mismatch(s2), -1); // identical assertEquals(s2.mismatch(s1), -1); } else if (s1.byteSize() > s2.byteSize()) { assertEquals(s1.mismatch(s2), s2.byteSize()); // proper prefix assertEquals(s2.mismatch(s1), s2.byteSize()); } else { assert s1.byteSize() < s2.byteSize(); assertEquals(s1.mismatch(s2), s1.byteSize()); // proper prefix assertEquals(s2.mismatch(s1), s1.byteSize()); } } @Test(dataProvider = "slicesStatic") public void testSameValuesStatic(SliceOffsetAndSize ss1, SliceOffsetAndSize ss2) { out.format("testSameValuesStatic s1:%s, s2:%s\n", ss1, ss2); MemorySegment s1 = initializeSegment(ss1.toSlice()); MemorySegment s2 = initializeSegment(ss2.toSlice()); for (long i = ss2.offset ; i < ss2.size ; i++) { long bytes = i - ss2.offset; long expected = (bytes == ss1.size) ? -1 : Long.min(ss1.size, bytes); assertEquals(MemorySegment.mismatch(ss1.segment, ss1.offset, ss1.endOffset(), ss2.segment, ss2.offset, i), expected); } for (long i = ss1.offset ; i < ss1.size ; i++) { long bytes = i - ss1.offset; long expected = (bytes == ss2.size) ? -1 : Long.min(ss2.size, bytes); assertEquals(MemorySegment.mismatch(ss2.segment, ss2.offset, ss2.endOffset(), ss1.segment, ss1.offset, i), expected); } } @Test public void random() { try (var arena = Arena.ofConfined()) { var rnd = new Random(42); for (int size = 1; size < 64; size++) { // Repeat a fair number of rounds for (int i = 0; i < 147; i++) { var src = arena.allocate(size); // The dst segment might be zero to eight bytes longer var dst = arena.allocate(size + rnd.nextInt(8 + 1)); // Fill the src with random data for (int j = 0; j < size; j++) { src.set(ValueLayout.JAVA_BYTE, j, randomByte(rnd)); } // copy the random data from src to dst dst.copyFrom(src); // Fill the rest (if any) of the dst with random data for (long j = src.byteSize(); j < dst.byteSize(); j++) { dst.set(ValueLayout.JAVA_BYTE, j, randomByte(rnd)); } if (rnd.nextBoolean()) { // In this branch, we inject one or more deviating bytes int beginDiff = rnd.nextInt(size); int endDiff = rnd.nextInt(beginDiff, size); for (int d = beginDiff; d <= endDiff; d++) { byte existing = dst.get(ValueLayout.JAVA_BYTE, d); // Make sure we never get back the same value byte mutatedValue; do { mutatedValue = randomByte(rnd); } while (existing == mutatedValue); dst.set(ValueLayout.JAVA_BYTE, d, mutatedValue); } // They are not equal and differs in position beginDiff assertEquals(src.mismatch(dst), beginDiff); assertEquals(dst.mismatch(src), beginDiff); } else { // In this branch, there is no injection if (src.byteSize() == dst.byteSize()) { // The content matches and they are of equal size assertEquals(src.mismatch(dst), -1); assertEquals(dst.mismatch(src), -1); } else { // The content matches but they are of different length // Remember, the size of src is always smaller or equal // to the size of dst. assertEquals(src.mismatch(dst), src.byteSize()); assertEquals(dst.mismatch(src), src.byteSize()); } } } } } } static byte randomByte(Random rnd) { return (byte) rnd.nextInt(Byte.MIN_VALUE, Byte.MAX_VALUE + 1); } @Test(dataProvider = "slices") public void testDifferentValues(MemorySegment s1, MemorySegment s2) { out.format("testDifferentValues s1:%s, s2:%s\n", s1, s2); s1 = initializeSegment(s1); s2 = initializeSegment(s2); for (long i = s2.byteSize() -1 ; i >= 0; i--) { long expectedMismatchOffset = i; s2.set(ValueLayout.JAVA_BYTE, i, (byte) 0xFF); if (s1.byteSize() == s2.byteSize()) { assertEquals(s1.mismatch(s2), expectedMismatchOffset); assertEquals(s2.mismatch(s1), expectedMismatchOffset); } else if (s1.byteSize() > s2.byteSize()) { assertEquals(s1.mismatch(s2), expectedMismatchOffset); assertEquals(s2.mismatch(s1), expectedMismatchOffset); } else { assert s1.byteSize() < s2.byteSize(); var off = Math.min(s1.byteSize(), expectedMismatchOffset); assertEquals(s1.mismatch(s2), off); // proper prefix assertEquals(s2.mismatch(s1), off); } } } @Test(dataProvider = "slicesStatic") public void testDifferentValuesStatic(SliceOffsetAndSize ss1, SliceOffsetAndSize ss2) { out.format("testDifferentValues s1:%s, s2:%s\n", ss1, ss2); for (long i = ss2.size - 1 ; i >= 0; i--) { if (i >= ss1.size) continue; initializeSegment(ss1.toSlice()); initializeSegment(ss2.toSlice()); long expectedMismatchOffset = i; ss2.toSlice().set(ValueLayout.JAVA_BYTE, i, (byte) 0xFF); for (long j = expectedMismatchOffset + 1 ; j < ss2.size ; j++) { assertEquals(MemorySegment.mismatch(ss1.segment, ss1.offset, ss1.endOffset(), ss2.segment, ss2.offset, j + ss2.offset), expectedMismatchOffset); } for (long j = expectedMismatchOffset + 1 ; j < ss1.size ; j++) { assertEquals(MemorySegment.mismatch(ss2.segment, ss2.offset, ss2.endOffset(), ss1.segment, ss1.offset, j + ss1.offset), expectedMismatchOffset); } } } @Test public void testEmpty() { var s1 = MemorySegment.ofArray(new byte[0]); assertEquals(s1.mismatch(s1), -1); try (Arena arena = Arena.ofConfined()) { var nativeSegment = arena.allocate(4, 4);; var s2 = nativeSegment.asSlice(0, 0); assertEquals(s1.mismatch(s2), -1); assertEquals(s2.mismatch(s1), -1); } } @Test public void testLarge() { // skip if not on 64 bits if (ValueLayout.ADDRESS.byteSize() > 32) { try (Arena arena = Arena.ofConfined()) { var s1 = arena.allocate((long) Integer.MAX_VALUE + 10L, 8);; var s2 = arena.allocate((long) Integer.MAX_VALUE + 10L, 8);; assertEquals(s1.mismatch(s1), -1); assertEquals(s1.mismatch(s2), -1); assertEquals(s2.mismatch(s1), -1); testLargeAcrossMaxBoundary(s1, s2); testLargeMismatchAcrossMaxBoundary(s1, s2); } } } private void testLargeAcrossMaxBoundary(MemorySegment s1, MemorySegment s2) { for (long i = s2.byteSize() -1 ; i >= Integer.MAX_VALUE - 10L; i--) { var s3 = s1.asSlice(0, i); var s4 = s2.asSlice(0, i); // instance assertEquals(s3.mismatch(s3), -1); assertEquals(s3.mismatch(s4), -1); assertEquals(s4.mismatch(s3), -1); // static assertEquals(MemorySegment.mismatch(s1, 0, s1.byteSize(), s1, 0, i), -1); assertEquals(MemorySegment.mismatch(s2, 0, s1.byteSize(), s1, 0, i), -1); assertEquals(MemorySegment.mismatch(s1, 0, s1.byteSize(), s2, 0, i), -1); } } private void testLargeMismatchAcrossMaxBoundary(MemorySegment s1, MemorySegment s2) { for (long i = s2.byteSize() -1 ; i >= Integer.MAX_VALUE - 10L; i--) { s2.set(ValueLayout.JAVA_BYTE, i, (byte) 0xFF); long expectedMismatchOffset = i; assertEquals(s1.mismatch(s2), expectedMismatchOffset); assertEquals(s2.mismatch(s1), expectedMismatchOffset); } } static final Class ISE = IllegalStateException.class; static final Class UOE = UnsupportedOperationException.class; @Test public void testClosed() { MemorySegment s1, s2; try (Arena arena = Arena.ofConfined()) { s1 = arena.allocate(4, 1); s2 = arena.allocate(4, 1);; } assertThrows(ISE, () -> s1.mismatch(s1)); assertThrows(ISE, () -> s1.mismatch(s2)); assertThrows(ISE, () -> s2.mismatch(s1)); } @Test public void testThreadAccess() throws Exception { try (Arena arena = Arena.ofConfined()) { var segment = arena.allocate(4, 1);; { AtomicReference exception = new AtomicReference<>(); Runnable action = () -> { try { MemorySegment.ofArray(new byte[4]).mismatch(segment); } catch (RuntimeException e) { exception.set(e); } }; Thread thread = new Thread(action); thread.start(); thread.join(); RuntimeException e = exception.get(); if (!(e instanceof WrongThreadException)) { throw e; } } { AtomicReference exception = new AtomicReference<>(); Runnable action = () -> { try { segment.mismatch(MemorySegment.ofArray(new byte[4])); } catch (RuntimeException e) { exception.set(e); } }; Thread thread = new Thread(action); thread.start(); thread.join(); RuntimeException e = exception.get(); if (!(e instanceof WrongThreadException)) { throw e; } } } } @Test public void testSameSegment() { var segment = MemorySegment.ofArray(new byte[]{ 1,2,3,4, 1,2,3,4, 1,4}); long match = MemorySegment.mismatch( segment, 0L, 4L, segment, 4L, 8L); assertEquals(match, -1); long noMatch = MemorySegment.mismatch( segment, 0L, 4L, segment, 1L, 5L); assertEquals(noMatch, 0); long noMatchEnd = MemorySegment.mismatch( segment, 0L, 2L, segment, 8L, 10L); assertEquals(noMatchEnd, 1); long same = MemorySegment.mismatch( segment, 0L, 8L, segment, 0L, 8L); assertEquals(same, -1); } enum SegmentKind { NATIVE(i -> Arena.ofAuto().allocate(i, 1)), ARRAY(i -> MemorySegment.ofArray(new byte[i])); final IntFunction segmentFactory; SegmentKind(IntFunction segmentFactory) { this.segmentFactory = segmentFactory; } MemorySegment makeSegment(int elems) { return segmentFactory.apply(elems); } } record SliceOffsetAndSize(MemorySegment segment, long offset, long size) { MemorySegment toSlice() { return segment.asSlice(offset, size); } long endOffset() { return offset + size; } }; @DataProvider(name = "slicesStatic") static Object[][] slicesStatic() { int[] sizes = { 16, 8, 1 }; List aSliceOffsetAndSizes = new ArrayList<>(); List bSliceOffsetAndSizes = new ArrayList<>(); for (List slices : List.of(aSliceOffsetAndSizes, bSliceOffsetAndSizes)) { for (SegmentKind kind : SegmentKind.values()) { MemorySegment segment = kind.makeSegment(16); //compute all slices for (int size : sizes) { for (int index = 0 ; index < 16 ; index += size) { slices.add(new SliceOffsetAndSize(segment, index, size)); } } } } assert aSliceOffsetAndSizes.size() == bSliceOffsetAndSizes.size(); Object[][] sliceArray = new Object[aSliceOffsetAndSizes.size() * bSliceOffsetAndSizes.size()][]; for (int i = 0 ; i < aSliceOffsetAndSizes.size() ; i++) { for (int j = 0 ; j < bSliceOffsetAndSizes.size() ; j++) { sliceArray[i * aSliceOffsetAndSizes.size() + j] = new Object[] { aSliceOffsetAndSizes.get(i), bSliceOffsetAndSizes.get(j) }; } } return sliceArray; } @DataProvider(name = "slices") static Object[][] slices() { Object[][] slicesStatic = slicesStatic(); return Stream.of(slicesStatic) .map(arr -> new Object[]{ ((SliceOffsetAndSize) arr[0]).toSlice(), ((SliceOffsetAndSize) arr[1]).toSlice() }).toArray(Object[][]::new); } }