1 /* 2 * Copyright (c) 2021, 2025, 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:-UseFieldFlattening 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.NullRestricted; 40 import jdk.internal.vm.annotation.Strict; 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 static value class EmptyValue { 50 public boolean isEmpty() { 51 return true; 52 } 53 } 54 55 static value class Value { 56 Object o; 57 @NullRestricted @Strict 58 EmptyValue empty; 59 Value() { 60 this.o = null; 61 this.empty = new EmptyValue(); 62 } 63 Value(EmptyValue empty) { 64 this.o = null; 65 this.empty = empty; 66 } 67 } 68 69 static class Mutable { 70 EmptyValue o; 71 @NullRestricted @Strict 72 EmptyValue empty = new EmptyValue(); 73 @NullRestricted @Strict 74 volatile EmptyValue vempty = new EmptyValue(); 75 } 76 77 @Test 78 public void emptyValueClass() { 79 EmptyValue e = new EmptyValue(); 80 Field[] fields = e.getClass().getDeclaredFields(); 81 assertTrue(fields.length == 0); 82 } 83 84 @Test 85 public void testNonNullFieldAssignment() { 86 var npe = assertThrows(NullPointerException.class, () -> new Value(null)); 87 System.err.println(npe); // log the exception message 88 } 89 90 static Stream<Arguments> getterCases() { 91 Value v = new Value(); 92 EmptyValue emptyValue = new EmptyValue(); 93 Mutable m = new Mutable(); 94 95 return Stream.of( 96 Arguments.of(Value.class, "o", Object.class, v, null), 97 Arguments.of(Value.class, "empty", EmptyValue.class, v, emptyValue), 98 Arguments.of(Mutable.class, "o", EmptyValue.class, m, null), 99 Arguments.of(Mutable.class, "empty", EmptyValue.class, m, emptyValue), 100 Arguments.of(Mutable.class, "vempty", EmptyValue.class, m, emptyValue) 101 ); 102 }; 103 104 @ParameterizedTest 105 @MethodSource("getterCases") 106 public void testGetter(Class<?> type, String name, Class<?> ftype, Object obj, Object expected) throws Throwable { 107 var f = type.getDeclaredField(name); 108 assertTrue(f.getType() == ftype); 109 var o1 = f.get(obj); 110 assertTrue(expected == o1); 111 112 var getter = MethodHandles.lookup().findGetter(type, name, ftype); 113 var o2 = getter.invoke(obj); 114 assertTrue(expected == o2); 115 116 var vh = MethodHandles.lookup().findVarHandle(type, name, ftype); 117 var o3 = vh.get(obj); 118 assertTrue(expected == o3); 119 } 120 121 static Stream<Arguments> setterCases() { 122 EmptyValue emptyValue = new EmptyValue(); 123 Mutable m = new Mutable(); 124 return Stream.of( 125 Arguments.of(Mutable.class, "o", EmptyValue.class, m, null), 126 Arguments.of(Mutable.class, "o", EmptyValue.class, m, emptyValue), 127 Arguments.of(Mutable.class, "empty", EmptyValue.class, m, emptyValue), 128 Arguments.of(Mutable.class, "vempty", EmptyValue.class, m, emptyValue) 129 ); 130 }; 131 132 @ParameterizedTest 133 @MethodSource("setterCases") 134 public void testSetter(Class<?> type, String name, Class<?> ftype, Object obj, Object expected) throws Throwable { 135 var f = type.getDeclaredField(name); 136 assertTrue(f.getType() == ftype); 137 f.set(obj, expected); 138 assertTrue(f.get(obj) == expected); 139 140 var setter = MethodHandles.lookup().findSetter(type, name, ftype); 141 setter.invoke(obj, expected); 142 assertTrue(f.get(obj) == expected); 143 } 144 145 @Test 146 public void noWriteAccess() throws ReflectiveOperationException { 147 Value v = new Value(); 148 Field f = v.getClass().getDeclaredField("o"); 149 assertThrows(IllegalAccessException.class, () -> f.set(v, null)); 150 } 151 152 static Stream<Arguments> nullRestrictedFields() { 153 Mutable m = new Mutable(); 154 return Stream.of( 155 Arguments.of(Mutable.class, "o", EmptyValue.class, m, false), 156 Arguments.of(Mutable.class, "empty", EmptyValue.class, m, true), 157 Arguments.of(Mutable.class, "vempty", EmptyValue.class, m, true) 158 ); 159 }; 160 161 @ParameterizedTest 162 @MethodSource("nullRestrictedFields") 163 public void testNullRestrictedField(Class<?> type, String name, Class<?> ftype, Object obj, boolean nullRestricted) throws Throwable { 164 var f = type.getDeclaredField(name); 165 assertTrue(f.getType() == ftype); 166 if (nullRestricted) { 167 assertThrows(NullPointerException.class, () -> f.set(obj, null)); 168 } else { 169 f.set(obj, null); 170 assertTrue(f.get(obj) == null); 171 } 172 173 var mh = MethodHandles.lookup().findSetter(type, name, ftype); 174 if (nullRestricted) { 175 assertThrows(NullPointerException.class, () -> mh.invoke(obj, null)); 176 } else { 177 mh.invoke(obj, null); 178 assertTrue(f.get(obj) == null); 179 } 180 181 var vh = MethodHandles.lookup().findVarHandle(type, name, ftype); 182 if (nullRestricted) { 183 assertThrows(NullPointerException.class, () -> vh.set(obj, null)); 184 } else { 185 vh.set(obj, null); 186 assertTrue(f.get(obj) == null); 187 } 188 } 189 }