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