1 /* 2 * Copyright (c) 2018, 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 MethodHandle and VarHandle of value classes 28 * @enablePreview 29 * @run junit/othervm MethodHandleTest 30 */ 31 32 import java.lang.invoke.*; 33 import java.lang.reflect.Field; 34 import java.lang.reflect.Modifier; 35 import java.util.List; 36 import java.util.Set; 37 import java.util.stream.Stream; 38 39 import jdk.internal.value.ValueClass; 40 import jdk.internal.vm.annotation.ImplicitlyConstructible; 41 import jdk.internal.vm.annotation.NullRestricted; 42 import org.junit.jupiter.params.ParameterizedTest; 43 import org.junit.jupiter.params.provider.Arguments; 44 import org.junit.jupiter.params.provider.MethodSource; 45 46 import static org.junit.jupiter.api.Assertions.*; 47 48 public class MethodHandleTest { 49 @ImplicitlyConstructible 50 static value class Point { 51 public int x; 52 public int y; 53 Point(int x, int y) { 54 this.x = x; 55 this.y = y; 56 } 57 } 58 59 @ImplicitlyConstructible 60 static value class Line { 61 @NullRestricted 62 Point p1; 63 @NullRestricted 64 Point p2; 65 66 Line(int x1, int y1, int x2, int y2) { 67 this.p1 = new Point(x1, y1); 68 this.p2 = new Point(x2, y2); 69 } 70 } 71 72 static class Ref { 73 @NullRestricted 74 Point p; 75 Line l; 76 List<String> list; 77 ValueOptional vo; 78 79 Ref(Point p, Line l) { 80 this.p = p; 81 this.l = l; 82 } 83 } 84 85 @ImplicitlyConstructible 86 static value class ValueOptional { 87 private Object o; 88 public ValueOptional(Object o) { 89 this.o = o; 90 } 91 } 92 93 private static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup(); 94 95 static final Point P = new Point(1, 2); 96 static final Line L = new Line(1, 2, 3, 4); 97 static final Ref R = new Ref(P, null); 98 99 static Stream<Arguments> fields() { 100 return Stream.of( 101 // value class with int fields 102 Arguments.of("MethodHandleTest$Point", P, Set.of("x", "y")), 103 // value class whose fields are null-restricted and of value class 104 Arguments.of("MethodHandleTest$Line", L, Set.of("p1", "p2")), 105 // identity class whose non-final fields are of value type, 106 Arguments.of("MethodHandleTest$Ref", R, Set.of("p", "l", "list", "vo")) 107 ); 108 } 109 110 /** 111 * Test MethodHandle invocation on the fields of a given class. 112 * MethodHandle produced by Lookup::unreflectGetter, Lookup::findGetter, 113 * Lookup::findVarHandle. 114 */ 115 @ParameterizedTest 116 @MethodSource("fields") 117 public void testFieldGetter(String cn, Object o, Set<String> fields) throws Throwable { 118 Class<?> c = Class.forName(cn); 119 for (String name : fields) { 120 Field f = c.getDeclaredField(name); 121 var mh = LOOKUP.findGetter(c, f.getName(), f.getType()); 122 var v1 = mh.invoke(o); 123 124 var vh = LOOKUP.findVarHandle(c, f.getName(), f.getType()); 125 var v2 = vh.get(o); 126 127 var mh3 = LOOKUP.unreflectGetter(f); 128 var v3 = mh3.invoke(o); 129 130 if (c.isValue()) 131 ensureImmutable(f, o); 132 } 133 } 134 135 static Stream<Arguments> setters() { 136 return Stream.of( 137 Arguments.of(Ref.class, R, "p", true), 138 Arguments.of(Ref.class, R, "l", false), 139 Arguments.of(Ref.class, R, "list", false), 140 Arguments.of(Ref.class, R, "vo", false) 141 ); 142 } 143 @ParameterizedTest 144 @MethodSource("setters") 145 public void testFieldSetter(Class<?> cls, Object o, String name, boolean nullRestricted) throws Throwable { 146 Field f = cls.getDeclaredField(name); 147 var mh = LOOKUP.findSetter(cls, f.getName(), f.getType()); 148 var vh = LOOKUP.findVarHandle(cls, f.getName(), f.getType()); 149 var mh3 = LOOKUP.unreflectSetter(f); 150 151 if (nullRestricted) { 152 assertThrows(NullPointerException.class, () -> mh.invoke(o, null)); 153 assertThrows(NullPointerException.class, () -> vh.set(o, null)); 154 assertThrows(NullPointerException.class, () -> mh3.invoke(o, null)); 155 } else { 156 mh.invoke(o, null); 157 vh.set(o, null); 158 mh3.invoke(o, null); 159 } 160 } 161 162 static Stream<Arguments> arrays() throws Throwable { 163 return Stream.of( 164 Arguments.of(Point[].class, newArray(Point[].class), P, false), 165 Arguments.of(Point[].class, newNullRestrictedArray(Point.class), P, true), 166 Arguments.of(Line[].class, newArray(Line[].class), L, false), 167 Arguments.of(Line[].class, newNullRestrictedArray(Line.class), L, true), 168 Arguments.of(Ref[].class, newArray(Ref[].class), R, false) 169 ); 170 } 171 172 private static final int ARRAY_SIZE = 5; 173 private static Object[] newArray(Class<?> arrayClass) throws Throwable { 174 MethodHandle ctor = MethodHandles.arrayConstructor(arrayClass); 175 return (Object[])ctor.invoke(ARRAY_SIZE); 176 } 177 private static Object[] newNullRestrictedArray(Class<?> componentClass) throws Throwable { 178 return ValueClass.newNullRestrictedArray(componentClass, ARRAY_SIZE); 179 } 180 181 @ParameterizedTest 182 @MethodSource("arrays") 183 public void testArrayElementSetterAndGetter(Class<?> arrayClass, Object[] array, Object element, boolean nullRestricted) throws Throwable { 184 MethodHandle setter = MethodHandles.arrayElementSetter(array.getClass()); 185 MethodHandle getter = MethodHandles.arrayElementGetter(array.getClass()); 186 VarHandle vh = MethodHandles.arrayElementVarHandle(arrayClass); 187 Class<?> componentType = arrayClass.getComponentType(); 188 189 for (int i=0; i < ARRAY_SIZE; i++) { 190 var v = getter.invoke(array, i); 191 if (nullRestricted) { 192 assertTrue(v == ValueClass.zeroInstance(componentType)); 193 } else { 194 assertTrue(v == null); 195 } 196 } 197 for (int i=0; i < ARRAY_SIZE; i++) { 198 setter.invoke(array, i, element); 199 assertTrue(getter.invoke(array, i) == element); 200 } 201 // set an array element to null 202 if (nullRestricted) { 203 assertTrue(vh.get(array, 1) != null); 204 assertThrows(NullPointerException.class, () -> setter.invoke(array, 1, null)); 205 assertThrows(NullPointerException.class, () -> vh.set(array, 1, null)); 206 } else { 207 setter.invoke(array, 1, null); 208 assertNull(getter.invoke(array, 1)); 209 vh.set(array, 1, null); 210 } 211 } 212 213 static void ensureImmutable(Field f, Object o) throws Throwable { 214 Class<?> c = f.getDeclaringClass(); 215 assertTrue(Modifier.isFinal(f.getModifiers())); 216 assertFalse(Modifier.isStatic(f.getModifiers())); 217 assertTrue(f.trySetAccessible()); 218 219 Object v = f.get(o); 220 221 assertThrows(IllegalAccessException.class, () -> LOOKUP.findSetter(c, f.getName(), f.getType())); 222 assertThrows(IllegalAccessException.class, () -> LOOKUP.unreflectSetter(f)); 223 VarHandle vh = LOOKUP.findVarHandle(c, f.getName(), f.getType()); 224 225 // test var handle 226 assertThrows(UnsupportedOperationException.class, () -> vh.set(o, v)); 227 } 228 }