1 /* 2 * Copyright (c) 2018, 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 value types 28 * @compile -XDallowFlattenabilityModifiers 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 value 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 // remove the following cases when javac and jvm make 98 // static value fields be flattenable 99 test.setValueField("staticPoint", null, null); 100 test.setValueField("staticLine", null, null); 101 } 102 103 @Test 104 public static void testArrayElementSetterAndGetter() throws Throwable { 105 testArray(Point[].class, P); 106 testArray(Line[].class, L); 107 testArray(MutablePath[].class, PATH); 108 } 109 110 static void testArray(Class<?> c, Object o) throws Throwable { 111 MethodHandle setter = MethodHandles.arrayElementSetter(c); 112 MethodHandle getter = MethodHandles.arrayElementGetter(c); 113 MethodHandle ctor = MethodHandles.arrayConstructor(c); 114 int size = 5; 115 Object[] array = (Object[])ctor.invoke(size); 116 for (int i=0; i < size; i++) { 117 setter.invoke(array, i, o); 118 } 119 for (int i=0; i < size; i++) { 120 Object v = (Object)getter.invoke(array, i); 121 assertEquals(v, o); 122 } 123 124 // set an array element to null 125 Class<?> elementType = c.getComponentType(); 126 try { 127 // value array element is flattenable 128 Object v = (Object)setter.invoke(array, 0, null); 129 assertFalse(elementType.isValue(), "should fail to set a value array element to null"); 130 } catch (NullPointerException e) { 131 assertTrue(elementType.isValue(), "should only fail to set a value array element to null"); 132 } 133 } 134 135 private final Class<?> c; 136 private final Object o; 137 private final List<String> names; 138 public MethodHandleTest(String cn, Object o, String... fields) throws Exception { 139 this.c = Class.forName(cn); 140 this.o = o; 141 this.names = List.of(fields); 142 } 143 144 public void run() throws Throwable { 145 for (String name : names) { 146 Field f = c.getDeclaredField(name); 147 unreflectField(f); 148 findGetter(f); 149 varHandle(f); 150 if (c.isValue()) 151 ensureImmutable(f); 152 else 153 ensureNullable(f); 154 } 155 } 156 157 public List<String> names() { 158 return names; 159 } 160 161 void findGetter(Field f) throws Throwable { 162 MethodHandle mh = MethodHandles.lookup().findGetter(c, f.getName(), f.getType()); 163 Object value = mh.invoke(o); 164 } 165 166 void varHandle(Field f) throws Throwable { 167 VarHandle vh = MethodHandles.lookup().findVarHandle(c, f.getName(), f.getType()); 168 Object value = vh.get(o); 169 } 170 171 void unreflectField(Field f) throws Throwable { 172 MethodHandle mh = MethodHandles.lookup().unreflectGetter(f); 173 Object value = mh.invoke(o); 174 } 175 176 /* 177 * Test setting value field to a new value. 178 * The field must be flattenable but may or may not be flattened. 179 */ 180 void setValueField(String name, Object obj, Object value) throws Throwable { 181 Field f = c.getDeclaredField(name); 182 boolean isStatic = Modifier.isStatic(f.getModifiers()); 183 assertTrue(f.getType().isValue()); 184 assertTrue((isStatic && obj == null) || (!isStatic && obj != null)); 185 Object v = f.get(obj); 186 187 // Field::set 188 try { 189 f.set(obj, value); 190 assertEquals(f.get(obj), value); 191 } finally { 192 f.set(obj, v); 193 } 194 195 196 if (isStatic) { 197 setStaticField(f, value); 198 } else { 199 setInstanceField(f, obj, value); 200 } 201 } 202 203 private void setInstanceField(Field f, Object obj, Object value) throws Throwable { 204 Object v = f.get(obj); 205 // MethodHandle::invoke 206 try { 207 MethodHandle mh = MethodHandles.lookup().findSetter(c, f.getName(), f.getType()); 208 mh.invoke(obj, value); 209 assertEquals(f.get(obj), value); 210 } finally { 211 f.set(obj, v); 212 } 213 // VarHandle::set 214 try { 215 VarHandle vh = MethodHandles.lookup().findVarHandle(c, f.getName(), f.getType()); 216 vh.set(obj, value); 217 assertEquals(f.get(obj), value); 218 } finally { 219 f.set(obj, v); 220 } 221 } 222 223 private void setStaticField(Field f, Object value) throws Throwable { 224 Object v = f.get(null); 225 // MethodHandle::invoke 226 try { 227 MethodHandle mh = MethodHandles.lookup().findStaticSetter(c, f.getName(), f.getType()); 228 mh.invoke(f.getType().cast(value)); 229 assertEquals(f.get(null), value); 230 } finally { 231 f.set(null, v); 232 } 233 // VarHandle::set 234 try { 235 VarHandle vh = MethodHandles.lookup().findStaticVarHandle(c, f.getName(), f.getType()); 236 vh.set(f.getType().cast(value)); 237 assertEquals(f.get(null), value); 238 } finally { 239 f.set(null, v); 240 } 241 } 242 243 /* 244 * Test setting the given field to null via reflection, method handle 245 * and var handle. 246 */ 247 void ensureNullable(Field f) throws Throwable { 248 assertFalse(Modifier.isStatic(f.getModifiers())); 249 // flattenable implies non-nullable 250 boolean canBeNull = !isFlattenable(f); 251 // test reflection 252 try { 253 f.set(o, null); 254 assertTrue(canBeNull, f + " cannot be set to null"); 255 } catch (NullPointerException e) { 256 assertFalse(canBeNull, f + " should allow be set to null"); 257 } 258 // test method handle, i.e. putfield bytecode behavior 259 try { 260 MethodHandle mh = MethodHandles.lookup().findSetter(c, f.getName(), f.getType()); 261 mh.invoke(o, null); 262 assertTrue(canBeNull, f + " cannot be set to null"); 263 } catch (NullPointerException e) { 264 assertFalse(canBeNull, f + " should allow be set to null"); 265 } 266 // test var handle 267 try { 268 VarHandle vh = MethodHandles.lookup().findVarHandle(c, f.getName(), f.getType()); 269 vh.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 } 275 276 void ensureImmutable(Field f) throws Throwable { 277 assertFalse(Modifier.isStatic(f.getModifiers())); 278 Object v = f.get(o); 279 // test reflection 280 try { 281 f.set(o, v); 282 throw new RuntimeException(f + " should be immutable"); 283 } catch (IllegalAccessException e) {} 284 285 // test method handle, i.e. putfield bytecode behavior 286 try { 287 MethodHandle mh = MethodHandles.lookup().findSetter(c, f.getName(), f.getType()); 288 mh.invoke(o, v); 289 throw new RuntimeException(f + " should be immutable"); 290 } catch (IllegalAccessException e) { } 291 // test var handle 292 try { 293 VarHandle vh = MethodHandles.lookup().findVarHandle(c, f.getName(), f.getType()); 294 vh.set(o, v); 295 throw new RuntimeException(f + " should be immutable"); 296 } catch (UnsupportedOperationException e) {} 297 } 298 299 boolean isFlattened(Field f) { 300 return (f.getModifiers() & 0x00008000) == 0x00008000; 301 } 302 303 boolean isFlattenable(Field f) { 304 return (f.getModifiers() & 0x00000100) == 0x00000100; 305 } 306 307 }