1 /*
  2  * Copyright (c) 2024, 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 /*
 26  * @test
 27  * @enablePreview
 28  * @run junit/othervm NullRestrictedArraysTest
 29  * @run junit/othervm -XX:FlatArrayElementMaxSize=0 NullRestrictedArraysTest
 30  */
 31 
 32 import java.lang.invoke.MethodHandles;
 33 import java.lang.invoke.VarHandle;
 34 import java.lang.reflect.Array;
 35 import java.lang.reflect.Field;
 36 import java.util.Arrays;
 37 import java.util.stream.Stream;
 38 
 39 import jdk.internal.value.CheckedType;
 40 import jdk.internal.value.NullRestrictedCheckedType;
 41 import jdk.internal.value.ValueClass;
 42 import jdk.internal.vm.annotation.ImplicitlyConstructible;
 43 import jdk.internal.vm.annotation.NullRestricted;
 44 
 45 import org.junit.jupiter.api.Test;
 46 import org.junit.jupiter.params.ParameterizedTest;
 47 import org.junit.jupiter.params.provider.Arguments;
 48 import org.junit.jupiter.params.provider.MethodSource;
 49 
 50 import static org.junit.jupiter.api.Assertions.*;
 51 
 52 public class NullRestrictedArraysTest {
 53     interface I {
 54         int getValue();
 55     }
 56     @ImplicitlyConstructible
 57     static value class Value implements I {
 58         int v;
 59         Value() {
 60             this(0);
 61         }
 62         Value(int v) {
 63             this.v = v;
 64         }
 65         public int getValue() {
 66             return v;
 67         }
 68     }
 69 
 70     static class T {
 71         String s;
 72         Value obj;  // can be null
 73         @NullRestricted
 74         Value value;
 75     }
 76 
 77     static Stream<Arguments> checkedTypes() throws ReflectiveOperationException {
 78         return Stream.of(
 79                 Arguments.of(T.class.getDeclaredField("s"), String.class, false),
 80                 Arguments.of(T.class.getDeclaredField("obj"), Value.class, false),
 81                 Arguments.of(T.class.getDeclaredField("value"), Value.class, true)
 82         );
 83     }
 84 
 85     /*
 86      * Test creating null-restricted arrays with CheckedType
 87      */
 88     @ParameterizedTest
 89     @MethodSource("checkedTypes")
 90     public void testCheckedTypeArrays(Field field, Class<?> type, boolean nullRestricted) throws ReflectiveOperationException {
 91         CheckedType checkedType = ValueClass.checkedType(field);
 92         assertTrue(field.getType() == type);
 93         assertTrue(checkedType.boundingClass() == type);
 94         Object[] array = ValueClass.newArrayInstance(checkedType, 4);
 95         assertTrue(ValueClass.isNullRestrictedArray(array) == nullRestricted);
 96         assertTrue(checkedType instanceof NullRestrictedCheckedType == nullRestricted);
 97         for (int i=0; i < array.length; i++) {
 98             array[i] = type.newInstance();
 99         }
100         if (nullRestricted) {
101             // NPE thrown if elements in a null-restricted array set to null
102             assertThrows(NullPointerException.class, () -> array[0] = null);
103         } else {
104             array[0] = null;
105         }
106     }
107 
108     /*
109      * Test Arrays::copyOf and Arrays::copyOfRange to create null-restricted arrays.
110      */
111     @Test
112     public void testArraysCopyOf() {
113         int len = 4;
114         Object[] array = (Object[]) Array.newInstance(Value.class, len);
115         Object[] nullRestrictedArray = ValueClass.newNullRestrictedArray(Value.class, len);
116         for (int i=0; i < len; i++) {
117             array[i] = new Value(i);
118             nullRestrictedArray[i] = new Value(i);
119         }
120         testCopyOf(array, nullRestrictedArray);
121         testCopyOfRange(array, nullRestrictedArray, 1, len+2);
122     };
123 
124     private void testCopyOf(Object[] array, Object[] nullRestrictedArray) {
125         Object[] newArray1 = Arrays.copyOf(array, array.length);
126         Object[] newArray2 = Arrays.copyOf(nullRestrictedArray, nullRestrictedArray.length);
127 
128         assertFalse(ValueClass.isNullRestrictedArray(newArray1));
129         assertTrue(ValueClass.isNullRestrictedArray(newArray2));
130 
131         // elements in a normal array can be null
132         for (int i=0; i < array.length; i++) {
133             newArray1[i] = null;
134         }
135         // NPE thrown if elements in a null-restricted array set to null
136         assertThrows(NullPointerException.class, () -> newArray2[0] = null);
137     }
138 
139     private void testCopyOfRange(Object[] array, Object[] nullRestrictedArray, int from, int to) {
140         Object[] newArray1 = Arrays.copyOfRange(array, from, to);
141         Object[] newArray2 = Arrays.copyOfRange(nullRestrictedArray, from, to);
142         System.out.println("newArray2 " + newArray2.length + " " + Arrays.toString(newArray2));
143         // elements in a normal array can be null
144         for (int i=0; i < newArray1.length; i++) {
145             newArray1[i] = null;
146         }
147         // NPE thrown if elements in a null-restricted array set to null
148         assertThrows(NullPointerException.class, () -> newArray2[0] = null);
149 
150         // check the new array padded with null if normal array and
151         // zero instance if null-restricted array
152         for (int i=0; i < newArray1.length; i++) {
153             if (from+1 >= array.length) {
154                 // padded with null
155                 assertTrue(newArray1[i] == null);
156             }
157         }
158         Class<?> componentType = nullRestrictedArray.getClass().getComponentType();
159         for (int i=0; i < newArray2.length; i++) {
160             if (from+1 >= nullRestrictedArray.length) {
161                 // padded with zero instance
162                 assertTrue(newArray2[i] == ValueClass.zeroInstance(componentType));
163             }
164         }
165     }
166 
167     @Test
168     public void testVarHandle() {
169         int len = 4;
170         Object[] array = (Object[]) Array.newInstance(Value.class, len);
171         Object[] nullRestrictedArray = ValueClass.newNullRestrictedArray(Value.class, len);
172 
173         // Test var handles
174         testVarHandleArray(array, Value[].class);
175         testVarHandleArray(array, I[].class);
176         testVarHandleNullRestrictedArray(nullRestrictedArray, Value[].class);
177         testVarHandleNullRestrictedArray(nullRestrictedArray, I[].class);
178     }
179 
180     private void testVarHandleArray(Object[] array, Class<?> arrayClass) {
181         for (int i=0; i < array.length; i++) {
182             array[i] = new Value(i);
183         }
184 
185         VarHandle vh = MethodHandles.arrayElementVarHandle(arrayClass);
186         Value value = new Value(0);
187         Value value1 =  new Value(1);
188 
189         assertTrue(vh.get(array, 0) == value);
190         assertTrue(vh.getVolatile(array, 0) == value);
191         assertTrue(vh.getOpaque(array, 0) == value);
192         assertTrue(vh.getAcquire(array, 0) == value);
193         vh.set(array, 0, null);
194         vh.setVolatile(array, 0, null);
195         vh.setOpaque(array, 0, null);
196         vh.setRelease(array, 0, null);
197 
198         vh.compareAndSet(array, 1, value1, null);             vh.set(array, 1, value1);
199         vh.compareAndExchange(array, 1, value1, null);        vh.set(array, 1, value1);
200         vh.compareAndExchangeAcquire(array, 1, value1, null); vh.set(array, 1, value1);
201         vh.compareAndExchangeRelease(array, 1, value1, null); vh.set(array, 1, value1);
202         vh.weakCompareAndSet(array, 1, value1, null);         vh.set(array, 1, value1);
203         vh.weakCompareAndSetAcquire(array, 1, value1, null);  vh.set(array, 1, value1);
204         vh.weakCompareAndSetPlain(array, 1, value1, null);    vh.set(array, 1, value1);
205         vh.weakCompareAndSetRelease(array, 1, value1, null);  vh.set(array, 1, value1);
206     }
207 
208     private void testVarHandleNullRestrictedArray(Object[] array, Class<?> arrayClass) {
209         for (int i=0; i < array.length; i++) {
210             array[i] = new Value(i);
211         }
212 
213         VarHandle vh = MethodHandles.arrayElementVarHandle(arrayClass);
214         Value value = new Value(0);
215         Value value1 =  new Value(1);
216         assertTrue(vh.get(array, 0) == value);
217         assertTrue(vh.getVolatile(array, 0) == value);
218         assertTrue(vh.getOpaque(array, 0) == value);
219         assertTrue(vh.getAcquire(array, 0) == value);
220         assertThrows(NullPointerException.class, () -> vh.set(array, 0, null));
221         assertThrows(NullPointerException.class, () -> vh.setVolatile(array, 0, null));
222         assertThrows(NullPointerException.class, () -> vh.setOpaque(array, 0, null));
223         assertThrows(NullPointerException.class, () -> vh.setRelease(array, 0, null));
224 
225         assertThrows(NullPointerException.class, () -> vh.compareAndSet(array, 1, value1, null));
226         assertThrows(NullPointerException.class, () -> vh.compareAndExchange(array, 1, value1, null));
227         assertThrows(NullPointerException.class, () -> vh.compareAndExchangeAcquire(array, 1, value1, null));
228         assertThrows(NullPointerException.class, () -> vh.compareAndExchangeRelease(array, 1, value1, null));
229         assertThrows(NullPointerException.class, () -> vh.weakCompareAndSet(array, 1, value1, null));
230         assertThrows(NullPointerException.class, () -> vh.weakCompareAndSetAcquire(array, 1, value1, null));
231         assertThrows(NullPointerException.class, () -> vh.weakCompareAndSetPlain(array, 1, value1, null));
232         assertThrows(NullPointerException.class, () -> vh.weakCompareAndSetRelease(array, 1, value1, null));
233     }
234 
235 }