1 /* 2 * Copyright (c) 2021, 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. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package test.java.lang.runtime; 27 28 import java.io.Serializable; 29 import java.lang.Enum.EnumDesc; 30 import java.lang.constant.ClassDesc; 31 import java.lang.invoke.CallSite; 32 import java.lang.invoke.MethodHandle; 33 import java.lang.invoke.MethodHandles; 34 import java.lang.invoke.MethodType; 35 import java.lang.runtime.SwitchBootstraps; 36 import java.util.concurrent.atomic.AtomicBoolean; 37 38 import org.testng.annotations.Test; 39 40 41 import static org.testng.Assert.assertEquals; 42 import static org.testng.Assert.assertFalse; 43 import static org.testng.Assert.assertTrue; 44 import static org.testng.Assert.fail; 45 46 /** 47 * @test 48 * @bug 8318144 49 * @enablePreview 50 * @compile SwitchBootstrapsTest.java 51 * @run testng/othervm SwitchBootstrapsTest 52 */ 53 @Test 54 public class SwitchBootstrapsTest { 55 56 public static final MethodHandle BSM_TYPE_SWITCH; 57 public static final MethodHandle BSM_ENUM_SWITCH; 58 59 static { 60 try { 61 BSM_TYPE_SWITCH = MethodHandles.lookup().findStatic(SwitchBootstraps.class, "typeSwitch", 62 MethodType.methodType(CallSite.class, MethodHandles.Lookup.class, String.class, MethodType.class, Object[].class)); 63 BSM_ENUM_SWITCH = MethodHandles.lookup().findStatic(SwitchBootstraps.class, "enumSwitch", 64 MethodType.methodType(CallSite.class, MethodHandles.Lookup.class, String.class, MethodType.class, Object[].class)); 65 } 66 catch (ReflectiveOperationException e) { 67 throw new AssertionError("Should not happen", e); 68 } 69 } 70 testType(Object target, int start, int result, Object... labels)71 private void testType(Object target, int start, int result, Object... labels) throws Throwable { 72 MethodType switchType = MethodType.methodType(int.class, Object.class, int.class); 73 MethodHandle indy = ((CallSite) BSM_TYPE_SWITCH.invoke(MethodHandles.lookup(), "", switchType, labels)).dynamicInvoker(); 74 assertEquals((int) indy.invoke(target, start), result); 75 assertEquals(-1, (int) indy.invoke(null, start)); 76 } 77 testEnum(Enum<?> target, int start, int result, Object... labels)78 private void testEnum(Enum<?> target, int start, int result, Object... labels) throws Throwable { 79 testEnum(target.getClass(), target, start, result, labels); 80 } 81 testEnum(Class<?> targetClass, Enum<?> target, int start, int result, Object... labels)82 private void testEnum(Class<?> targetClass, Enum<?> target, int start, int result, Object... labels) throws Throwable { 83 MethodType switchType = MethodType.methodType(int.class, targetClass, int.class); 84 MethodHandle indy = ((CallSite) BSM_ENUM_SWITCH.invoke(MethodHandles.lookup(), "", switchType, labels)).dynamicInvoker(); 85 assertEquals((int) indy.invoke(target, start), result); 86 assertEquals(-1, (int) indy.invoke(null, start)); 87 } 88 89 public enum E1 { 90 A, 91 B; 92 } 93 94 public enum E2 { 95 C; 96 } 97 testTypes()98 public void testTypes() throws Throwable { 99 testType("", 0, 0, String.class, Object.class); 100 testType("", 0, 0, Object.class); 101 testType("", 0, 1, Integer.class); 102 testType("", 0, 1, Integer.class, Serializable.class); 103 testType(E1.A, 0, 0, E1.class, Object.class); 104 testType(E2.C, 0, 1, E1.class, Object.class); 105 testType(new Serializable() { }, 0, 1, Comparable.class, Serializable.class); 106 testType("", 0, 0, "", String.class); 107 testType("", 1, 1, "", String.class); 108 testType("a", 0, 1, "", String.class); 109 testType(1, 0, 0, 1, Integer.class); 110 testType(2, 0, 1, 1, Integer.class); 111 testType(Byte.valueOf((byte) 1), 0, 0, 1, Integer.class); 112 testType(Short.valueOf((short) 1), 0, 0, 1, Integer.class); 113 testType(Character.valueOf((char) 1), 0, 0, 1, Integer.class); 114 testType(Integer.valueOf((int) 1), 0, 0, 1, Integer.class); 115 try { 116 testType(1, 0, 1, 1.0, Integer.class); 117 fail("Didn't get the expected exception."); 118 } catch (IllegalArgumentException ex) { 119 //OK 120 } 121 testType("", 0, 0, String.class, String.class, String.class); 122 testType("", 1, 1, String.class, String.class, String.class); 123 testType("", 2, 2, String.class, String.class, String.class); 124 testType("", 0, 0); 125 } 126 testEnums()127 public void testEnums() throws Throwable { 128 testEnum(E1.A, 0, 2, "B", "C", "A", E1.class); 129 testEnum(E1.B, 0, 0, "B", "C", "A", E1.class); 130 testEnum(E1.B, 1, 3, "B", "C", "A", E1.class); 131 try { 132 testEnum(E1.B, 1, 3, "B", "C", "A", E2.class); 133 fail("Didn't get the expected exception."); 134 } catch (IllegalArgumentException ex) { 135 //OK 136 } 137 try { 138 testEnum(E1.B, 1, 3, "B", "C", "A", String.class); 139 fail("Didn't get the expected exception."); 140 } catch (IllegalArgumentException ex) { 141 //OK 142 } 143 testEnum(E1.B, 0, 0, "B", "A"); 144 testEnum(E1.A, 0, 1, "B", "A"); 145 testEnum(E1.A, 0, 0, "A", "A", "B"); 146 testEnum(E1.A, 1, 1, "A", "A", "B"); 147 testEnum(E1.A, 2, 3, "A", "A", "B"); 148 testEnum(E1.A, 0, 0); 149 } 150 testEnumsWithConstants()151 public void testEnumsWithConstants() throws Throwable { 152 enum E { 153 A {}, 154 B {}, 155 C {} 156 } 157 ClassDesc eDesc = E.class.describeConstable().get(); 158 Object[] typeParams = new Object[] { 159 EnumDesc.of(eDesc, "A"), 160 EnumDesc.of(eDesc, "B"), 161 EnumDesc.of(eDesc, "C"), 162 "a", 163 String.class 164 }; 165 testType(E.A, 0, 0, typeParams); 166 testType(E.B, 0, 1, typeParams); 167 testType(E.C, 0, 2, typeParams); 168 testType("a", 0, 3, typeParams); 169 testType("x", 0, 4, typeParams); 170 testType('a', 0, 5, typeParams); 171 testEnum(E.class, E.A, 0, 0, "A", "B", "C"); 172 testEnum(E.class, E.B, 0, 1, "A", "B", "C"); 173 testEnum(E.class, E.C, 0, 2, "A", "B", "C"); 174 } 175 testWrongSwitchTypes()176 public void testWrongSwitchTypes() throws Throwable { 177 MethodType[] switchTypes = new MethodType[] { 178 MethodType.methodType(int.class, Object.class), 179 MethodType.methodType(int.class, double.class, int.class), 180 MethodType.methodType(int.class, Object.class, Integer.class) 181 }; 182 for (MethodType switchType : switchTypes) { 183 try { 184 BSM_TYPE_SWITCH.invoke(MethodHandles.lookup(), "", switchType); 185 fail("Didn't get the expected exception."); 186 } catch (IllegalArgumentException ex) { 187 //OK, expected 188 } 189 } 190 MethodType[] enumSwitchTypes = new MethodType[] { 191 MethodType.methodType(int.class, Enum.class), 192 MethodType.methodType(int.class, Object.class, int.class), 193 MethodType.methodType(int.class, double.class, int.class), 194 MethodType.methodType(int.class, Enum.class, Integer.class) 195 }; 196 for (MethodType enumSwitchType : enumSwitchTypes) { 197 try { 198 BSM_ENUM_SWITCH.invoke(MethodHandles.lookup(), "", enumSwitchType); 199 fail("Didn't get the expected exception."); 200 } catch (IllegalArgumentException ex) { 201 //OK, expected 202 } 203 } 204 } 205 testSwitchLabelTypes()206 public void testSwitchLabelTypes() throws Throwable { 207 enum E {A} 208 try { 209 testType(E.A, 0, -1, E.A); 210 fail("Didn't get the expected exception."); 211 } catch (IllegalArgumentException ex) { 212 //OK, expected 213 } 214 } 215 testSwitchQualifiedEnum()216 public void testSwitchQualifiedEnum() throws Throwable { 217 enum E {A, B, C} 218 Object[] labels = new Object[] { 219 EnumDesc.of(ClassDesc.of(E.class.getName()), "A"), 220 EnumDesc.of(ClassDesc.of(E.class.getName()), "B"), 221 EnumDesc.of(ClassDesc.of(E.class.getName()), "C") 222 }; 223 testType(E.A, 0, 0, labels); 224 testType(E.B, 0, 1, labels); 225 testType(E.C, 0, 2, labels); 226 } 227 testNullLabels()228 public void testNullLabels() throws Throwable { 229 MethodType switchType = MethodType.methodType(int.class, Object.class, int.class); 230 try { 231 BSM_TYPE_SWITCH.invoke(MethodHandles.lookup(), "", switchType, (Object[]) null); 232 fail("Didn't get the expected exception."); 233 } catch (NullPointerException ex) { 234 //OK 235 } 236 try { 237 BSM_TYPE_SWITCH.invoke(MethodHandles.lookup(), "", switchType, 238 new Object[] {1, null, String.class}); 239 fail("Didn't get the expected exception."); 240 } catch (IllegalArgumentException ex) { 241 //OK 242 } 243 MethodType enumSwitchType = MethodType.methodType(int.class, E1.class, int.class); 244 try { 245 BSM_TYPE_SWITCH.invoke(MethodHandles.lookup(), "", enumSwitchType, (Object[]) null); 246 fail("Didn't get the expected exception."); 247 } catch (NullPointerException ex) { 248 //OK 249 } 250 try { 251 BSM_TYPE_SWITCH.invoke(MethodHandles.lookup(), "", enumSwitchType, 252 new Object[] {1, null, String.class}); 253 fail("Didn't get the expected exception."); 254 } catch (IllegalArgumentException ex) { 255 //OK 256 } 257 } 258 259 private static AtomicBoolean enumInitialized = new AtomicBoolean(); testEnumInitialization1()260 public void testEnumInitialization1() throws Throwable { 261 enumInitialized.set(false); 262 263 enum E { 264 A; 265 266 static { 267 enumInitialized.set(true); 268 } 269 } 270 271 MethodType enumSwitchType = MethodType.methodType(int.class, E.class, int.class); 272 273 CallSite invocation = (CallSite) BSM_ENUM_SWITCH.invoke(MethodHandles.lookup(), "", enumSwitchType, new Object[] {"A"}); 274 assertFalse(enumInitialized.get()); 275 assertEquals(invocation.dynamicInvoker().invoke(null, 0), -1); 276 assertFalse(enumInitialized.get()); 277 E e = E.A; 278 assertTrue(enumInitialized.get()); 279 assertEquals(invocation.dynamicInvoker().invoke(e, 0), 0); 280 } 281 testEnumInitialization2()282 public void testEnumInitialization2() throws Throwable { 283 enumInitialized.set(false); 284 285 enum E { 286 A; 287 288 static { 289 enumInitialized.set(true); 290 } 291 } 292 293 MethodType switchType = MethodType.methodType(int.class, Object.class, int.class); 294 Object[] labels = new Object[] { 295 EnumDesc.of(ClassDesc.of(E.class.getName()), "A"), 296 "test" 297 }; 298 CallSite invocation = (CallSite) BSM_TYPE_SWITCH.invoke(MethodHandles.lookup(), "", switchType, labels); 299 assertFalse(enumInitialized.get()); 300 assertEquals(invocation.dynamicInvoker().invoke(null, 0), -1); 301 assertFalse(enumInitialized.get()); 302 assertEquals(invocation.dynamicInvoker().invoke("test", 0), 1); 303 assertFalse(enumInitialized.get()); 304 E e = E.A; 305 assertTrue(enumInitialized.get()); 306 assertEquals(invocation.dynamicInvoker().invoke(e, 0), 0); 307 } 308 testIncorrectEnumLabels()309 public void testIncorrectEnumLabels() throws Throwable { 310 try { 311 testEnum(E1.B, 0, -1, "B", 1); 312 fail("Didn't get the expected exception."); 313 } catch (IllegalArgumentException ex) { 314 //OK 315 } 316 try { 317 testEnum(E1.B, 0, -1, "B", null); 318 fail("Didn't get the expected exception."); 319 } catch (IllegalArgumentException ex) { 320 //OK 321 } 322 } 323 testIncorrectEnumStartIndex()324 public void testIncorrectEnumStartIndex() throws Throwable { 325 try { 326 testEnum(E1.B, -1, -1, "B"); 327 fail("Didn't get the expected exception."); 328 } catch (IndexOutOfBoundsException ex) { 329 //OK 330 } 331 try { 332 testEnum(E1.B, 2, -1, "B"); 333 fail("Didn't get the expected exception."); 334 } catch (IndexOutOfBoundsException ex) { 335 //OK 336 } 337 } 338 testIncorrectTypeStartIndex()339 public void testIncorrectTypeStartIndex() throws Throwable { 340 try { 341 testType("", -1, -1, ""); 342 fail("Didn't get the expected exception."); 343 } catch (IndexOutOfBoundsException ex) { 344 //OK 345 } 346 try { 347 testType("", 2, -1, ""); 348 fail("Didn't get the expected exception."); 349 } catch (IndexOutOfBoundsException ex) { 350 //OK 351 } 352 } 353 354 } 355