1 /* 2 * Copyright (c) 2021, 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 * @summary Test reflection and method handle on accessing a field of a null-restricted value class 28 * that may be flattened or non-flattened 29 * @enablePreview 30 * @run junit/othervm NullRestrictedTest 31 * @run junit/othervm -XX:InlineFieldMaxFlatSize=0 NullRestrictedTest 32 */ 33 34 import java.lang.invoke.MethodHandles; 35 import java.lang.reflect.Field; 36 import java.util.stream.Stream; 37 38 import jdk.internal.value.ValueClass; 39 import jdk.internal.vm.annotation.ImplicitlyConstructible; 40 import jdk.internal.vm.annotation.NullRestricted; 41 42 import org.junit.jupiter.api.Test; 43 import org.junit.jupiter.params.ParameterizedTest; 44 import org.junit.jupiter.params.provider.Arguments; 45 import org.junit.jupiter.params.provider.MethodSource; 46 import static org.junit.jupiter.api.Assertions.*; 47 48 public class NullRestrictedTest { 49 @ImplicitlyConstructible 50 static value class EmptyValue { 51 public boolean isEmpty() { 52 return true; 53 } 54 } 55 56 @ImplicitlyConstructible 57 static value class Value { 58 Object o; 59 @NullRestricted 60 EmptyValue empty; 61 Value() { 62 this.o = null; 63 this.empty = new EmptyValue(); 64 } 65 } 66 67 static class Mutable { 68 EmptyValue o; 69 @NullRestricted 70 EmptyValue empty; 71 @NullRestricted 72 volatile EmptyValue vempty; 73 } 74 75 @Test 76 public void emptyValueClass() { 77 EmptyValue e = new EmptyValue(); 78 Field[] fields = e.getClass().getDeclaredFields(); 79 assertTrue(fields.length == 0); 80 } 81 82 @Test 83 public void lazyInitializedDefaultValue() { 84 // VM lazily sets the null-restricted non-flat field to zero default 85 assertTrue(new Value() == ValueClass.zeroInstance(Value.class)); 86 assertTrue(new Value().empty == ValueClass.zeroInstance(EmptyValue.class)); 87 } 88 89 static Stream<Arguments> getterCases() { 90 Value v = new Value(); 91 EmptyValue emptyValue = ValueClass.zeroInstance(EmptyValue.class); 92 Mutable m = new Mutable(); 93 94 return Stream.of( 95 Arguments.of(Value.class, "o", Object.class, v, null), 96 Arguments.of(Value.class, "empty", EmptyValue.class, v, emptyValue), 97 Arguments.of(Mutable.class, "o", EmptyValue.class, m, null), 98 Arguments.of(Mutable.class, "empty", EmptyValue.class, m, emptyValue), 99 Arguments.of(Mutable.class, "vempty", EmptyValue.class, m, emptyValue) 100 ); 101 }; 102 103 @ParameterizedTest 104 @MethodSource("getterCases") 105 public void testGetter(Class<?> type, String name, Class<?> ftype, Object obj, Object expected) throws Throwable { 106 var f = type.getDeclaredField(name); 107 assertTrue(f.getType() == ftype); 108 var o1 = f.get(obj); 109 assertTrue(expected == o1); 110 111 var getter = MethodHandles.lookup().findGetter(type, name, ftype); 112 var o2 = getter.invoke(obj); 113 assertTrue(expected == o2); 114 115 var vh = MethodHandles.lookup().findVarHandle(type, name, ftype); 116 var o3 = vh.get(obj); 117 assertTrue(expected == o3); 118 } 119 120 static Stream<Arguments> setterCases() { 121 EmptyValue emptyValue = ValueClass.zeroInstance(EmptyValue.class); 122 Mutable m = new Mutable(); 123 return Stream.of( 124 Arguments.of(Mutable.class, "o", EmptyValue.class, m, null), 125 Arguments.of(Mutable.class, "o", EmptyValue.class, m, emptyValue), 126 Arguments.of(Mutable.class, "empty", EmptyValue.class, m, emptyValue), 127 Arguments.of(Mutable.class, "vempty", EmptyValue.class, m, emptyValue) 128 ); 129 }; 130 131 @ParameterizedTest 132 @MethodSource("setterCases") 133 public void testSetter(Class<?> type, String name, Class<?> ftype, Object obj, Object expected) throws Throwable { 134 var f = type.getDeclaredField(name); 135 assertTrue(f.getType() == ftype); 136 f.set(obj, expected); 137 assertTrue(f.get(obj) == expected); 138 139 var setter = MethodHandles.lookup().findSetter(type, name, ftype); 140 setter.invoke(obj, expected); 141 assertTrue(f.get(obj) == expected); 142 } 143 144 @Test 145 public void noWriteAccess() throws ReflectiveOperationException { 146 Value v = new Value(); 147 Field f = v.getClass().getDeclaredField("o"); 148 assertThrows(IllegalAccessException.class, () -> f.set(v, null)); 149 } 150 151 static Stream<Arguments> nullRestrictedFields() { 152 Mutable m = new Mutable(); 153 return Stream.of( 154 Arguments.of(Mutable.class, "o", EmptyValue.class, m, false), 155 Arguments.of(Mutable.class, "empty", EmptyValue.class, m, true), 156 Arguments.of(Mutable.class, "vempty", EmptyValue.class, m, true) 157 ); 158 }; 159 160 @ParameterizedTest 161 @MethodSource("nullRestrictedFields") 162 public void testNullRestrictedField(Class<?> type, String name, Class<?> ftype, Object obj, boolean nullRestricted) throws Throwable { 163 var f = type.getDeclaredField(name); 164 assertTrue(f.getType() == ftype); 165 if (nullRestricted) { 166 assertThrows(NullPointerException.class, () -> f.set(obj, null)); 167 } else { 168 f.set(obj, null); 169 assertTrue(f.get(obj) == null); 170 } 171 172 var mh = MethodHandles.lookup().findSetter(type, name, ftype); 173 if (nullRestricted) { 174 assertThrows(NullPointerException.class, () -> mh.invoke(obj, null)); 175 } else { 176 mh.invoke(obj, null); 177 assertTrue(f.get(obj) == null); 178 } 179 180 var vh = MethodHandles.lookup().findVarHandle(type, name, ftype); 181 if (nullRestricted) { 182 assertThrows(NullPointerException.class, () -> vh.set(obj, null)); 183 } else { 184 vh.set(obj, null); 185 assertTrue(f.get(obj) == null); 186 } 187 } 188 }