1 /*
  2  *  Copyright (c) 2019, 2020, Oracle and/or its affiliates. All rights reserved.
  3  *  DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  4  *
  5  *  This code is free software; you can redistribute it and/or modify it
  6  *  under the terms of the GNU General Public License version 2 only, as
  7  *  published by the Free Software Foundation.
  8  *
  9  *  This code is distributed in the hope that it will be useful, but WITHOUT
 10  *  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 11  *  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 12  *  version 2 for more details (a copy is included in the LICENSE file that
 13  *  accompanied this code).
 14  *
 15  *  You should have received a copy of the GNU General Public License version
 16  *  2 along with this work; if not, write to the Free Software Foundation,
 17  *  Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 18  *
 19  *  Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 20  *  or visit www.oracle.com if you need additional information or have any
 21  *  questions.
 22  */
 23 
 24 /*
 25  * @test
 26  * @run testng TestSegments
 27  */
 28 
 29 import jdk.incubator.foreign.MemoryAddress;
 30 import jdk.incubator.foreign.MemoryLayout;
 31 import jdk.incubator.foreign.MemoryLayouts;
 32 import jdk.incubator.foreign.MemorySegment;
 33 import org.testng.annotations.DataProvider;
 34 import org.testng.annotations.Test;
 35 import java.lang.invoke.VarHandle;
 36 import java.lang.reflect.Method;
 37 import java.lang.reflect.Modifier;
 38 import java.nio.ByteBuffer;
 39 import java.nio.ByteOrder;
 40 import java.util.ArrayList;
 41 import java.util.List;
 42 import java.util.Spliterator;
 43 import java.util.concurrent.atomic.AtomicBoolean;
 44 import java.util.concurrent.atomic.AtomicReference;
 45 import java.util.function.LongFunction;
 46 import java.util.function.Supplier;
 47 import java.util.stream.Stream;
 48 import static jdk.incubator.foreign.MemorySegment.WRITE;
 49 import static org.testng.Assert.*;
 50 
 51 public class TestSegments {
 52 
 53     @Test(dataProvider = "badSizeAndAlignments", expectedExceptions = IllegalArgumentException.class)
 54     public void testBadAllocateAlign(long size, long align) {
 55         MemorySegment.allocateNative(size, align);
 56     }
 57 
 58     @Test(dataProvider = "badLayouts", expectedExceptions = UnsupportedOperationException.class)
 59     public void testBadAllocateLayout(MemoryLayout layout) {
 60         MemorySegment.allocateNative(layout);
 61     }
 62 
 63     @Test(expectedExceptions = { OutOfMemoryError.class,
 64                                  IllegalArgumentException.class })
 65     public void testAllocateTooBig() {
 66         MemorySegment.allocateNative(Long.MAX_VALUE);
 67     }
 68 
 69     @Test(dataProvider = "segmentOperations")
 70     public void testOpOutsideConfinement(SegmentMember member) throws Throwable {
 71         try (MemorySegment segment = MemorySegment.allocateNative(4)) {
 72             AtomicBoolean failed = new AtomicBoolean(false);
 73             Thread t = new Thread(() -> {
 74                 try {
 75                     Object o = member.method.invoke(segment, member.params);
 76                     if (member.method.getName().equals("acquire")) {
 77                         ((MemorySegment)o).close();
 78                     }
 79                 } catch (ReflectiveOperationException ex) {
 80                     throw new IllegalStateException(ex);
 81                 }
 82             });
 83             t.setUncaughtExceptionHandler((thread, ex) -> failed.set(true));
 84             t.start();
 85             t.join();
 86             assertEquals(failed.get(), member.isConfined());
 87         }
 88     }
 89 
 90     @Test
 91     public void testNativeSegmentIsZeroed() {
 92         VarHandle byteHandle = MemoryLayout.ofSequence(MemoryLayouts.JAVA_BYTE)
 93                 .varHandle(byte.class, MemoryLayout.PathElement.sequenceElement());
 94         try (MemorySegment segment = MemorySegment.allocateNative(1000)) {
 95             for (long i = 0 ; i < segment.byteSize() ; i++) {
 96                 assertEquals(0, (byte)byteHandle.get(segment.baseAddress(), i));
 97             }
 98         }
 99     }
100 
101     @Test
102     public void testNothingSegmentAccess() {
103         VarHandle longHandle = MemoryLayouts.JAVA_LONG.varHandle(long.class);
104         long[] values = { 0L, Integer.MAX_VALUE - 1, (long) Integer.MAX_VALUE + 1 };
105         for (long value : values) {
106             MemoryAddress addr = MemoryAddress.ofLong(value);
107             try {
108                 longHandle.get(addr);
109             } catch (UnsupportedOperationException ex) {
110                 assertTrue(ex.getMessage().contains("Required access mode"));
111             }
112         }
113     }
114 
115     @Test(expectedExceptions = UnsupportedOperationException.class)
116     public void testNothingSegmentOffset() {
117         MemoryAddress addr = MemoryAddress.ofLong(42);
118         assertNull(addr.segment());
119         addr.segmentOffset();
120     }
121 
122     @Test
123     public void testSlices() {
124         VarHandle byteHandle = MemoryLayout.ofSequence(MemoryLayouts.JAVA_BYTE)
125                 .varHandle(byte.class, MemoryLayout.PathElement.sequenceElement());
126         try (MemorySegment segment = MemorySegment.allocateNative(10)) {
127             //init
128             for (byte i = 0 ; i < segment.byteSize() ; i++) {
129                 byteHandle.set(segment.baseAddress(), (long)i, i);
130             }
131             long start = 0;
132             MemoryAddress base = segment.baseAddress();
133             MemoryAddress last = base.addOffset(10);
134             while (!base.equals(last)) {
135                 MemorySegment slice = segment.asSlice(base.segmentOffset(), 10 - start);
136                 for (long i = start ; i < 10 ; i++) {
137                     assertEquals(
138                             byteHandle.get(segment.baseAddress(), i),
139                             byteHandle.get(slice.baseAddress(), i - start)
140                     );
141                 }
142                 base = base.addOffset(1);
143                 start++;
144             }
145         }
146     }
147 
148     @Test(dataProvider = "accessModes")
149     public void testAccessModes(int accessModes) {
150         int[] arr = new int[1];
151         for (AccessActions action : AccessActions.values()) {
152             MemorySegment segment = MemorySegment.ofArray(arr);
153             MemorySegment restrictedSegment = segment.withAccessModes(accessModes);
154             assertEquals(restrictedSegment.accessModes(), accessModes);
155             boolean shouldFail = !restrictedSegment.hasAccessModes(action.accessMode);
156             try {
157                 action.run(restrictedSegment);
158                 assertFalse(shouldFail);
159             } catch (UnsupportedOperationException ex) {
160                 assertTrue(shouldFail);
161             }
162         }
163     }
164 
165     @DataProvider(name = "segmentFactories")
166     public Object[][] segmentFactories() {
167         List<Supplier<MemorySegment>> l = List.of(
168                 () -> MemorySegment.ofArray(new byte[] { 0x00, 0x01, 0x02, 0x03 }),
169                 () -> MemorySegment.ofArray(new char[] {'a', 'b', 'c', 'd' }),
170                 () -> MemorySegment.ofArray(new double[] { 1d, 2d, 3d, 4d} ),
171                 () -> MemorySegment.ofArray(new float[] { 1.0f, 2.0f, 3.0f, 4.0f }),
172                 () -> MemorySegment.ofArray(new int[] { 1, 2, 3, 4 }),
173                 () -> MemorySegment.ofArray(new long[] { 1l, 2l, 3l, 4l } ),
174                 () -> MemorySegment.ofArray(new short[] { 1, 2, 3, 4 } ),
175                 () -> MemorySegment.allocateNative(4),
176                 () -> MemorySegment.allocateNative(4, 8),
177                 () -> MemorySegment.allocateNative(MemoryLayout.ofValueBits(32, ByteOrder.nativeOrder()))
178         );
179         return l.stream().map(s -> new Object[] { s }).toArray(Object[][]::new);
180     }
181 
182     @Test(dataProvider = "segmentFactories")
183     public void testFill(Supplier<MemorySegment> memorySegmentSupplier) {
184         VarHandle byteHandle = MemoryLayout.ofSequence(MemoryLayouts.JAVA_BYTE)
185                 .varHandle(byte.class, MemoryLayout.PathElement.sequenceElement());
186 
187         for (byte value : new byte[] {(byte) 0xFF, (byte) 0x00, (byte) 0x45}) {
188             try (MemorySegment segment = memorySegmentSupplier.get()) {
189                 segment.fill(value);
190                 for (long l = 0; l < segment.byteSize(); l++) {
191                     assertEquals((byte) byteHandle.get(segment.baseAddress(), l), value);
192                 }
193 
194                 // fill a slice
195                 var sliceSegment = segment.asSlice(1, segment.byteSize() - 2).fill((byte) ~value);
196                 for (long l = 0; l < sliceSegment.byteSize(); l++) {
197                     assertEquals((byte) byteHandle.get(sliceSegment.baseAddress(), l), ~value);
198                 }
199                 // assert enclosing slice
200                 assertEquals((byte) byteHandle.get(segment.baseAddress(), 0L), value);
201                 for (long l = 1; l < segment.byteSize() - 2; l++) {
202                     assertEquals((byte) byteHandle.get(segment.baseAddress(), l), (byte) ~value);
203                 }
204                 assertEquals((byte) byteHandle.get(segment.baseAddress(), segment.byteSize() - 1L), value);
205             }
206         }
207     }
208 
209     @Test(dataProvider = "segmentFactories", expectedExceptions = IllegalStateException.class)
210     public void testFillClosed(Supplier<MemorySegment> memorySegmentSupplier) {
211         MemorySegment segment = memorySegmentSupplier.get();
212         segment.close();
213         segment.fill((byte) 0xFF);
214     }
215 
216     @Test(dataProvider = "segmentFactories", expectedExceptions = UnsupportedOperationException.class)
217     public void testFillIllegalAccessMode(Supplier<MemorySegment> memorySegmentSupplier) {
218         try (MemorySegment segment = memorySegmentSupplier.get()) {
219             segment.withAccessModes(segment.accessModes() & ~WRITE).fill((byte) 0xFF);
220         }
221     }
222 
223     @Test(dataProvider = "segmentFactories")
224     public void testFillThread(Supplier<MemorySegment> memorySegmentSupplier) throws Exception {
225         try (MemorySegment segment = memorySegmentSupplier.get()) {
226             AtomicReference<RuntimeException> exception = new AtomicReference<>();
227             Runnable action = () -> {
228                 try {
229                     segment.fill((byte) 0xBA);
230                 } catch (RuntimeException e) {
231                     exception.set(e);
232                 }
233             };
234             Thread thread = new Thread(action);
235             thread.start();
236             thread.join();
237 
238             RuntimeException e = exception.get();
239             if (!(e instanceof IllegalStateException)) {
240                 throw e;
241             }
242         }
243     }
244 
245     @Test
246     public void testFillEmpty() {
247         MemorySegment.ofArray(new byte[] { }).fill((byte) 0xFF);
248         MemorySegment.ofArray(new byte[2]).asSlice(0, 0).fill((byte) 0xFF);
249         MemorySegment.ofByteBuffer(ByteBuffer.allocateDirect(0)).fill((byte) 0xFF);
250     }
251 
252     @Test(expectedExceptions = IllegalArgumentException.class)
253     public void testBadWithAccessModes() {
254         int[] arr = new int[1];
255         MemorySegment segment = MemorySegment.ofArray(arr);
256         segment.withAccessModes((1 << AccessActions.values().length) + 1);
257     }
258 
259     @Test(expectedExceptions = IllegalArgumentException.class)
260     public void testBadHasAccessModes() {
261         int[] arr = new int[1];
262         MemorySegment segment = MemorySegment.ofArray(arr);
263         segment.hasAccessModes((1 << AccessActions.values().length) + 1);
264     }
265 
266     @DataProvider(name = "badSizeAndAlignments")
267     public Object[][] sizesAndAlignments() {
268         return new Object[][] {
269                 { -1, 8 },
270                 { 1, 15 },
271                 { 1, -15 }
272         };
273     }
274 
275     @DataProvider(name = "badLayouts")
276     public Object[][] layouts() {
277         SizedLayoutFactory[] layoutFactories = SizedLayoutFactory.values();
278         Object[][] values = new Object[layoutFactories.length * 2][2];
279         for (int i = 0; i < layoutFactories.length ; i++) {
280             values[i * 2] = new Object[] { MemoryLayout.ofStruct(layoutFactories[i].make(7), MemoryLayout.ofPaddingBits(9)) }; // good size, bad align
281             values[(i * 2) + 1] = new Object[] { layoutFactories[i].make(15).withBitAlignment(16) }; // bad size, good align
282         }
283         return values;
284     }
285 
286     enum SizedLayoutFactory {
287         VALUE_BE(size -> MemoryLayout.ofValueBits(size, ByteOrder.BIG_ENDIAN)),
288         VALUE_LE(size -> MemoryLayout.ofValueBits(size, ByteOrder.LITTLE_ENDIAN)),
289         PADDING(MemoryLayout::ofPaddingBits);
290 
291         private final LongFunction<MemoryLayout> factory;
292 
293         SizedLayoutFactory(LongFunction<MemoryLayout> factory) {
294             this.factory = factory;
295         }
296 
297         MemoryLayout make(long size) {
298             return factory.apply(size);
299         }
300     }
301 
302     @DataProvider(name = "segmentOperations")
303     static Object[][] segmentMembers() {
304         List<SegmentMember> members = new ArrayList<>();
305         for (Method m : MemorySegment.class.getDeclaredMethods()) {
306             //skip statics and method declared in j.l.Object
307             if (m.getDeclaringClass().equals(Object.class) ||
308                     (m.getModifiers() & Modifier.STATIC) != 0) continue;
309             Object[] args = Stream.of(m.getParameterTypes())
310                     .map(TestSegments::defaultValue)
311                     .toArray();
312             members.add(new SegmentMember(m, args));
313         }
314         return members.stream().map(ms -> new Object[] { ms }).toArray(Object[][]::new);
315     }
316 
317     static class SegmentMember {
318         final Method method;
319         final Object[] params;
320 
321         final static List<String> CONFINED_NAMES = List.of(
322                 "close",
323                 "fill",
324                 "toByteArray",
325                 "withOwnerThread"
326         );
327 
328         public SegmentMember(Method method, Object[] params) {
329             this.method = method;
330             this.params = params;
331         }
332 
333         boolean isConfined() {
334             return CONFINED_NAMES.contains(method.getName());
335         }
336 
337         @Override
338         public String toString() {
339             return method.getName();
340         }
341     }
342 
343     static Object defaultValue(Class<?> c) {
344         if (c.isPrimitive()) {
345             if (c == char.class) {
346                 return (char)0;
347             } else if (c == boolean.class) {
348                 return false;
349             } else if (c == byte.class) {
350                 return (byte)0;
351             } else if (c == short.class) {
352                 return (short)0;
353             } else if (c == int.class) {
354                 return 0;
355             } else if (c == long.class) {
356                 return 0L;
357             } else if (c == float.class) {
358                 return 0f;
359             } else if (c == double.class) {
360                 return 0d;
361             } else {
362                 throw new IllegalStateException();
363             }
364         } else {
365             return null;
366         }
367     }
368 
369     @DataProvider(name = "accessModes")
370     public Object[][] accessModes() {
371         int nActions = AccessActions.values().length;
372         Object[][] results = new Object[1 << nActions][];
373         for (int accessModes = 0 ; accessModes < results.length ; accessModes++) {
374             results[accessModes] = new Object[] { accessModes };
375         }
376         return results;
377     }
378 
379     enum AccessActions {
380         ACQUIRE(MemorySegment.ACQUIRE) {
381             @Override
382             void run(MemorySegment segment) {
383                 Spliterator<MemorySegment> spliterator =
384                         MemorySegment.spliterator(segment, MemoryLayout.ofSequence(segment.byteSize(), MemoryLayouts.JAVA_BYTE));
385                 AtomicReference<RuntimeException> exception = new AtomicReference<>();
386                 Runnable action = () -> {
387                     try {
388                         spliterator.tryAdvance(s -> { });
389                     } catch (RuntimeException e) {
390                         exception.set(e);
391                     }
392                 };
393                 Thread thread = new Thread(action);
394                 thread.start();
395                 try {
396                     thread.join();
397                 } catch (InterruptedException ex) {
398                     throw new AssertionError(ex);
399                 }
400                 RuntimeException e = exception.get();
401                 if (e != null) {
402                     throw e;
403                 }
404             }
405         },
406         CLOSE(MemorySegment.CLOSE) {
407             @Override
408             void run(MemorySegment segment) {
409                 segment.close();
410             }
411         },
412         READ(MemorySegment.READ) {
413             @Override
414             void run(MemorySegment segment) {
415                 INT_HANDLE.get(segment.baseAddress());
416             }
417         },
418         WRITE(MemorySegment.WRITE) {
419             @Override
420             void run(MemorySegment segment) {
421                 INT_HANDLE.set(segment.baseAddress(), 42);
422             }
423         },
424         HANDOFF(MemorySegment.HANDOFF) {
425             @Override
426             void run(MemorySegment segment) {
427                 segment.withOwnerThread(new Thread());
428             }
429         };
430 
431         final int accessMode;
432 
433         static VarHandle INT_HANDLE = MemoryLayouts.JAVA_INT.varHandle(int.class);
434 
435         AccessActions(int accessMode) {
436             this.accessMode = accessMode;
437         }
438 
439         abstract void run(MemorySegment segment);
440     }
441 }