1 /* 2 * Copyright (c) 2018, 2019, 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/VarHandle on inline types 28 * @compile -XDallowWithFieldOperator Point.java Line.java MutablePath.java MixedValues.java 29 * @run testng/othervm -XX:+EnableValhalla MethodHandleTest 30 */ 31 32 import java.lang.invoke.*; 33 import java.lang.reflect.Field; 34 import java.lang.reflect.Modifier; 35 import java.util.*; 36 37 import org.testng.annotations.BeforeTest; 38 import org.testng.annotations.DataProvider; 39 import org.testng.annotations.Test; 40 import static org.testng.Assert.*; 41 42 public class MethodHandleTest { 43 private static final Point P = Point.makePoint(10, 20); 44 private static final Line L = Line.makeLine(10, 20, 30, 40); 45 private static final MutablePath PATH = MutablePath.makePath(10, 20, 30, 40); 46 47 @Test 48 public static void testPointClass() throws Throwable { 49 MethodHandleTest test = new MethodHandleTest("Point", P, "x", "y"); 50 test.run(); 51 } 52 53 @Test 54 public static void testLineClass() throws Throwable { 55 MethodHandleTest test = new MethodHandleTest("Line", L, "p1", "p2"); 56 test.run(); 57 } 58 59 @Test 60 public static void testMutablePath() throws Throwable { 61 MethodHandleTest test = new MethodHandleTest("MutablePath", PATH, "p1", "p2"); 62 test.run(); 63 64 // set the mutable fields 65 MutablePath path = MutablePath.makePath(1, 2, 3, 44); 66 Point p = Point.makePoint(100, 200); 67 test.setValueField("p1", path, p); 68 test.setValueField("p2", path, p); 69 } 70 71 @Test 72 public static void testValueFields() throws Throwable { 73 MutablePath path = MutablePath.makePath(1, 2, 3, 4); 74 // p1 and p2 are a non-final field of inline type in a reference 75 MethodHandleTest test1 = new MethodHandleTest("Point", path.p1, "x", "y"); 76 test1.run(); 77 78 MethodHandleTest test2 = new MethodHandleTest("Point", path.p2, "x", "y"); 79 test2.run(); 80 } 81 82 @Test 83 public static void testMixedValues() throws Throwable { 84 MixedValues mv = new MixedValues(P, L, PATH, "mixed", "types"); 85 MethodHandleTest test = 86 new MethodHandleTest("MixedValues", mv, "p", "l", "mutablePath", "list", "nfp"); 87 test.run(); 88 89 Point p = Point.makePoint(100, 200); 90 Line l = Line.makeLine(100, 200, 300, 400); 91 test.setValueField("p", mv, p); 92 test.setValueField("nfp", mv, p); 93 test.setValueField("l", mv, l); 94 test.setValueField("l", mv, l); 95 test.setValueField("staticPoint", null, p); 96 test.setValueField("staticLine", null, l); 97 // staticLine is a nullable field 98 test.setValueField("staticLine", null, null); 99 } 100 101 @Test 102 public static void testArrayElementSetterAndGetter() throws Throwable { 103 testArray(Point[].class, P); 104 testArray(Line[].class, L); 105 testArray(MutablePath[].class, PATH); 106 } 107 108 static void testArray(Class<?> c, Object o) throws Throwable { 109 MethodHandle setter = MethodHandles.arrayElementSetter(c); 110 MethodHandle getter = MethodHandles.arrayElementGetter(c); 111 MethodHandle ctor = MethodHandles.arrayConstructor(c); 112 int size = 5; 113 Object[] array = (Object[])ctor.invoke(size); 114 for (int i=0; i < size; i++) { 115 setter.invoke(array, i, o); 116 } 117 for (int i=0; i < size; i++) { 118 Object v = (Object)getter.invoke(array, i); 119 assertEquals(v, o); 120 } 121 122 Class<?> elementType = c.getComponentType(); 123 if (elementType.isValue()) { 124 assertTrue(elementType == elementType.asValueType()); 125 } 126 // set an array element to null 127 try { 128 Object v = (Object)setter.invoke(array, 0, null); 129 assertFalse(elementType.isValue(), "should fail to set an inline class array element to null"); 130 } catch (NullPointerException e) { 131 assertTrue(elementType.isValue(), "should only fail to set an inline class array element to null"); 132 } 133 } 134 135 @Test 136 public static void testNullableArray() throws Throwable { 137 Class<?> arrayClass = (new Point?[0]).getClass(); 138 Class<?> elementType = arrayClass.getComponentType(); 139 assertTrue(elementType == Point.class.asBoxType()); 140 141 MethodHandle setter = MethodHandles.arrayElementSetter(arrayClass); 142 MethodHandle getter = MethodHandles.arrayElementGetter(arrayClass); 143 MethodHandle ctor = MethodHandles.arrayConstructor(arrayClass); 144 Object[] array = (Object[]) ctor.invoke(2); 145 setter.invoke(array, 0, P); 146 setter.invoke(array, 1, null); 147 assertEquals((Point)getter.invoke(array, 0), P); 148 assertNull((Object)getter.invoke(array, 1)); 149 } 150 151 private final Class<?> c; 152 private final Object o; 153 private final List<String> names; 154 public MethodHandleTest(String cn, Object o, String... fields) throws Exception { 155 this.c = Class.forName(cn); 156 this.o = o; 157 this.names = List.of(fields); 158 } 159 160 public void run() throws Throwable { 161 for (String name : names) { 162 Field f = c.getDeclaredField(name); 163 unreflectField(f); 164 findGetter(f); 165 varHandle(f); 166 if (c.isValue()) 167 ensureImmutable(f); 168 else 169 ensureNullable(f); 170 } 171 } 172 173 public List<String> names() { 174 return names; 175 } 176 177 void findGetter(Field f) throws Throwable { 178 MethodHandle mh = MethodHandles.lookup().findGetter(c, f.getName(), f.getType()); 179 Object value = mh.invoke(o); 180 } 181 182 void varHandle(Field f) throws Throwable { 183 VarHandle vh = MethodHandles.lookup().findVarHandle(c, f.getName(), f.getType()); 184 Object value = vh.get(o); 185 } 186 187 void unreflectField(Field f) throws Throwable { 188 MethodHandle mh = MethodHandles.lookup().unreflectGetter(f); 189 Object value = mh.invoke(o); 190 } 191 192 /* 193 * Test setting a field of an inline type to a new value. 194 * The field must be flattenable but may or may not be flattened. 195 */ 196 void setValueField(String name, Object obj, Object value) throws Throwable { 197 Field f = c.getDeclaredField(name); 198 boolean isStatic = Modifier.isStatic(f.getModifiers()); 199 assertTrue(f.getType().isValue()); 200 assertTrue((isStatic && obj == null) || (!isStatic && obj != null)); 201 Object v = f.get(obj); 202 203 // Field::set 204 try { 205 f.set(obj, value); 206 assertEquals(f.get(obj), value); 207 } finally { 208 f.set(obj, v); 209 } 210 211 212 if (isStatic) { 213 setStaticField(f, value); 214 } else { 215 setInstanceField(f, obj, value); 216 } 217 } 218 219 private void setInstanceField(Field f, Object obj, Object value) throws Throwable { 220 Object v = f.get(obj); 221 // MethodHandle::invoke 222 try { 223 MethodHandle mh = MethodHandles.lookup().findSetter(c, f.getName(), f.getType()); 224 mh.invoke(obj, value); 225 assertEquals(f.get(obj), value); 226 } finally { 227 f.set(obj, v); 228 } 229 // VarHandle::set 230 try { 231 VarHandle vh = MethodHandles.lookup().findVarHandle(c, f.getName(), f.getType()); 232 vh.set(obj, value); 233 assertEquals(f.get(obj), value); 234 } finally { 235 f.set(obj, v); 236 } 237 } 238 239 private void setStaticField(Field f, Object value) throws Throwable { 240 Object v = f.get(null); 241 // MethodHandle::invoke 242 try { 243 MethodHandle mh = MethodHandles.lookup().findStaticSetter(c, f.getName(), f.getType()); 244 mh.invoke(f.getType().cast(value)); 245 assertEquals(f.get(null), value); 246 } finally { 247 f.set(null, v); 248 } 249 // VarHandle::set 250 try { 251 VarHandle vh = MethodHandles.lookup().findStaticVarHandle(c, f.getName(), f.getType()); 252 vh.set(f.getType().cast(value)); 253 assertEquals(f.get(null), value); 254 } finally { 255 f.set(null, v); 256 } 257 } 258 259 /* 260 * Test setting the given field to null via reflection, method handle 261 * and var handle. 262 */ 263 void ensureNullable(Field f) throws Throwable { 264 assertFalse(Modifier.isStatic(f.getModifiers())); 265 // flattenable implies non-nullable 266 boolean canBeNull = !isFlattenable(f); 267 // test reflection 268 try { 269 f.set(o, null); 270 assertTrue(canBeNull, f + " cannot be set to null"); 271 } catch (NullPointerException e) { 272 assertFalse(canBeNull, f + " should allow be set to null"); 273 } 274 // test method handle, i.e. putfield bytecode behavior 275 try { 276 MethodHandle mh = MethodHandles.lookup().findSetter(c, f.getName(), f.getType()); 277 mh.invoke(o, null); 278 assertTrue(canBeNull, f + " cannot be set to null"); 279 } catch (NullPointerException e) { 280 assertFalse(canBeNull, f + " should allow be set to null"); 281 } 282 // test var handle 283 try { 284 VarHandle vh = MethodHandles.lookup().findVarHandle(c, f.getName(), f.getType()); 285 vh.set(o, null); 286 assertTrue(canBeNull, f + " cannot be set to null"); 287 } catch (NullPointerException e) { 288 assertFalse(canBeNull, f + " should allow be set to null"); 289 } 290 } 291 292 void ensureImmutable(Field f) throws Throwable { 293 assertFalse(Modifier.isStatic(f.getModifiers())); 294 Object v = f.get(o); 295 // test reflection 296 try { 297 f.set(o, v); 298 throw new RuntimeException(f + " should be immutable"); 299 } catch (IllegalAccessException e) {} 300 301 // test method handle, i.e. putfield bytecode behavior 302 try { 303 MethodHandle mh = MethodHandles.lookup().findSetter(c, f.getName(), f.getType()); 304 mh.invoke(o, v); 305 throw new RuntimeException(f + " should be immutable"); 306 } catch (IllegalAccessException e) { } 307 // test var handle 308 try { 309 VarHandle vh = MethodHandles.lookup().findVarHandle(c, f.getName(), f.getType()); 310 vh.set(o, v); 311 throw new RuntimeException(f + " should be immutable"); 312 } catch (UnsupportedOperationException e) {} 313 } 314 315 boolean isFlattened(Field f) { 316 return (f.getModifiers() & 0x00008000) == 0x00008000; 317 } 318 319 boolean isFlattenable(Field f) { 320 return (f.getModifiers() & 0x00000100) == 0x00000100; 321 } 322 323 }