1/* 2 * Copyright (c) 2015, 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 */ 25package jdk.dynalink.beans.test; 26 27import static jdk.dynalink.StandardNamespace.ELEMENT; 28import static jdk.dynalink.StandardNamespace.METHOD; 29import static jdk.dynalink.StandardNamespace.PROPERTY; 30import static jdk.dynalink.StandardOperation.CALL; 31import static jdk.dynalink.StandardOperation.GET; 32import static jdk.dynalink.StandardOperation.NEW; 33import static jdk.dynalink.StandardOperation.SET; 34 35import java.lang.invoke.CallSite; 36import java.lang.invoke.MethodHandle; 37import java.lang.invoke.MethodHandles; 38import java.lang.invoke.MethodType; 39import java.security.AccessControlException; 40import java.util.ArrayList; 41import java.util.Date; 42import java.util.List; 43import jdk.dynalink.CallSiteDescriptor; 44import jdk.dynalink.DynamicLinker; 45import jdk.dynalink.DynamicLinkerFactory; 46import jdk.dynalink.NamedOperation; 47import jdk.dynalink.NoSuchDynamicMethodException; 48import jdk.dynalink.Operation; 49import jdk.dynalink.beans.BeansLinker; 50import jdk.dynalink.beans.StaticClass; 51import jdk.dynalink.support.SimpleRelinkableCallSite; 52import org.testng.Assert; 53import org.testng.annotations.AfterTest; 54import org.testng.annotations.BeforeTest; 55import org.testng.annotations.DataProvider; 56import org.testng.annotations.Test; 57 58public class BeanLinkerTest { 59 60 private DynamicLinker linker; 61 private static final MethodHandles.Lookup MY_LOOKUP = MethodHandles.lookup(); 62 63 @SuppressWarnings("unused") 64 @DataProvider 65 private static Object[][] flags() { 66 return new Object[][]{ 67 {Boolean.FALSE}, 68 {Boolean.TRUE} 69 }; 70 } 71 72 // helpers to create callsite objects 73 private CallSite createCallSite(final boolean publicLookup, final Operation op, final MethodType mt) { 74 return linker.link(new SimpleRelinkableCallSite(new CallSiteDescriptor( 75 publicLookup ? MethodHandles.publicLookup() : MY_LOOKUP, op, mt))); 76 } 77 78 private CallSite createCallSite(final boolean publicLookup, final Operation op, final Object name, final MethodType mt) { 79 return createCallSite(publicLookup, op.named(name), mt); 80 } 81 82 private CallSite createGetMethodCallSite(final boolean publicLookup, final String name) { 83 return createCallSite(publicLookup, GET_METHOD, name, MethodType.methodType(Object.class, Object.class)); 84 } 85 86 private static final MethodHandle throwArrayIndexOutOfBounds = findThrower("throwArrayIndexOutOfBounds"); 87 private static final MethodHandle throwIndexOutOfBounds = findThrower("throwIndexOutOfBounds"); 88 89 private static final Operation GET_PROPERTY = GET.withNamespace(PROPERTY); 90 private static final Operation GET_ELEMENT = GET.withNamespace(ELEMENT); 91 private static final Operation GET_METHOD = GET.withNamespace(METHOD); 92 private static final Operation SET_ELEMENT = SET.withNamespace(ELEMENT); 93 94 private static final MethodHandle findThrower(final String name) { 95 try { 96 return MethodHandles.lookup().findStatic(BeanLinkerTest.class, name, 97 MethodType.methodType(Object.class, Object.class, Object.class)); 98 } catch (NoSuchMethodException | IllegalAccessException e) { 99 Assert.fail("Unexpected exception", e); 100 return null; 101 } 102 } 103 104 private static Object throwArrayIndexOutOfBounds(final Object receiver, final Object index) { 105 throw new ArrayIndexOutOfBoundsException(String.valueOf(index)); 106 } 107 108 private static Object throwIndexOutOfBounds(final Object receiver, final Object index) { 109 throw new IndexOutOfBoundsException(String.valueOf(index)); 110 } 111 112 @BeforeTest 113 public void initLinker() { 114 final DynamicLinkerFactory factory = new DynamicLinkerFactory(); 115 factory.setFallbackLinkers(new BeansLinker((req, services) -> { 116 // This is a MissingMemberHandlerFactory that creates a missing 117 // member handler for element getters and setters that throw an 118 // ArrayIndexOutOfBoundsException when applied to an array and an 119 // IndexOutOfBoundsException when applied to a list. 120 121 final CallSiteDescriptor desc = req.getCallSiteDescriptor(); 122 final Operation op = desc.getOperation(); 123 final Operation baseOp = NamedOperation.getBaseOperation(op); 124 if (baseOp != GET_ELEMENT && baseOp != SET_ELEMENT) { 125 // We only handle GET_ELEMENT and SET_ELEMENT. 126 return null; 127 } 128 129 final Object receiver = req.getReceiver(); 130 Assert.assertNotNull(receiver); 131 132 final Class<?> clazz = receiver.getClass(); 133 final MethodHandle throwerHandle; 134 if (clazz.isArray()) { 135 throwerHandle = throwArrayIndexOutOfBounds; 136 } else if (List.class.isAssignableFrom(clazz)) { 137 throwerHandle = throwIndexOutOfBounds; 138 } else { 139 Assert.fail("Unexpected receiver type " + clazz.getName()); 140 return null; 141 } 142 143 final Object name = NamedOperation.getName(op); 144 final MethodHandle nameBoundHandle; 145 if (name == null) { 146 nameBoundHandle = throwerHandle; 147 } else { 148 // If the operation is for a fixed index, bind it 149 nameBoundHandle = MethodHandles.insertArguments(throwerHandle, 1, name); 150 } 151 152 final MethodType callSiteType = desc.getMethodType(); 153 final MethodHandle arityMatchedHandle; 154 if (baseOp == SET_ELEMENT) { 155 // Drop "value" parameter for a setter 156 final int handleArity = nameBoundHandle.type().parameterCount(); 157 arityMatchedHandle = MethodHandles.dropArguments(nameBoundHandle, 158 handleArity, callSiteType.parameterType(handleArity)); 159 } else { 160 arityMatchedHandle = nameBoundHandle; 161 } 162 163 return arityMatchedHandle.asType(callSiteType); 164 })); 165 this.linker = factory.createLinker(); 166 } 167 168 @AfterTest 169 public void afterTest() { 170 this.linker = null; 171 } 172 173 @Test(dataProvider = "flags") 174 public void getPropertyTest(final boolean publicLookup) throws Throwable { 175 final MethodType mt = MethodType.methodType(Object.class, Object.class, String.class); 176 final CallSite cs = createCallSite(publicLookup, GET_PROPERTY, mt); 177 Assert.assertEquals(cs.getTarget().invoke(new Object(), "class"), Object.class); 178 Assert.assertEquals(cs.getTarget().invoke(new Date(), "class"), Date.class); 179 } 180 181 @Test(dataProvider = "flags") 182 public void getPropertyNegativeTest(final boolean publicLookup) throws Throwable { 183 final MethodType mt = MethodType.methodType(Object.class, Object.class, String.class); 184 final CallSite cs = createCallSite(publicLookup, GET_PROPERTY, mt); 185 Assert.assertNull(cs.getTarget().invoke(new Object(), "DOES_NOT_EXIST")); 186 } 187 188 @Test(dataProvider = "flags") 189 public void getPropertyTest2(final boolean publicLookup) throws Throwable { 190 final MethodType mt = MethodType.methodType(Object.class, Object.class); 191 final CallSite cs = createCallSite(publicLookup, GET_PROPERTY, "class", mt); 192 Assert.assertEquals(cs.getTarget().invoke(new Object()), Object.class); 193 Assert.assertEquals(cs.getTarget().invoke(new Date()), Date.class); 194 } 195 196 @Test(dataProvider = "flags") 197 public void getPropertyNegativeTest2(final boolean publicLookup) throws Throwable { 198 final MethodType mt = MethodType.methodType(Object.class, Object.class); 199 final CallSite cs = createCallSite(publicLookup, GET_PROPERTY, "DOES_NOT_EXIST", mt); 200 201 try { 202 cs.getTarget().invoke(new Object()); 203 throw new RuntimeException("Expected NoSuchDynamicMethodException"); 204 } catch (final Throwable th) { 205 Assert.assertTrue(th instanceof NoSuchDynamicMethodException); 206 } 207 } 208 209 @Test(dataProvider = "flags") 210 public void getLengthPropertyTest(final boolean publicLookup) throws Throwable { 211 final MethodType mt = MethodType.methodType(int.class, Object.class, String.class); 212 final CallSite cs = createCallSite(publicLookup, GET_PROPERTY, mt); 213 214 Assert.assertEquals((int) cs.getTarget().invoke(new int[10], "length"), 10); 215 Assert.assertEquals((int) cs.getTarget().invoke(new String[33], "length"), 33); 216 } 217 218 @Test(dataProvider = "flags") 219 public void getElementTest(final boolean publicLookup) throws Throwable { 220 final MethodType mt = MethodType.methodType(int.class, Object.class, int.class); 221 final CallSite cs = createCallSite(publicLookup, GET_ELEMENT, mt); 222 223 final int[] arr = {23, 42}; 224 Assert.assertEquals((int) cs.getTarget().invoke(arr, 0), 23); 225 Assert.assertEquals((int) cs.getTarget().invoke(arr, 1), 42); 226 try { 227 final int x = (int) cs.getTarget().invoke(arr, -1); 228 throw new RuntimeException("expected ArrayIndexOutOfBoundsException"); 229 } catch (final ArrayIndexOutOfBoundsException ex) { 230 } 231 232 try { 233 final int x = (int) cs.getTarget().invoke(arr, arr.length); 234 throw new RuntimeException("expected ArrayIndexOutOfBoundsException"); 235 } catch (final ArrayIndexOutOfBoundsException ex) { 236 } 237 238 final List<Integer> list = new ArrayList<>(); 239 list.add(23); 240 list.add(430); 241 list.add(-4354); 242 Assert.assertEquals((int) cs.getTarget().invoke(list, 0), (int) list.get(0)); 243 Assert.assertEquals((int) cs.getTarget().invoke(list, 1), (int) list.get(1)); 244 Assert.assertEquals((int) cs.getTarget().invoke(list, 2), (int) list.get(2)); 245 try { 246 final int x = (int) cs.getTarget().invoke(list, -1); 247 throw new RuntimeException("expected IndexOutOfBoundsException"); 248 } catch (final IndexOutOfBoundsException ex) { 249 } 250 251 try { 252 final int x = (int) cs.getTarget().invoke(list, list.size()); 253 throw new RuntimeException("expected IndexOutOfBoundsException"); 254 } catch (final IndexOutOfBoundsException ex) { 255 } 256 } 257 258 @Test(dataProvider = "flags") 259 public void setElementTest(final boolean publicLookup) throws Throwable { 260 final MethodType mt = MethodType.methodType(void.class, Object.class, int.class, int.class); 261 final CallSite cs = createCallSite(publicLookup, SET_ELEMENT, mt); 262 263 final int[] arr = {23, 42}; 264 cs.getTarget().invoke(arr, 0, 0); 265 Assert.assertEquals(arr[0], 0); 266 cs.getTarget().invoke(arr, 1, -5); 267 Assert.assertEquals(arr[1], -5); 268 269 try { 270 cs.getTarget().invoke(arr, -1, 12); 271 throw new RuntimeException("expected ArrayIndexOutOfBoundsException"); 272 } catch (final ArrayIndexOutOfBoundsException ex) { 273 } 274 275 try { 276 cs.getTarget().invoke(arr, arr.length, 20); 277 throw new RuntimeException("expected ArrayIndexOutOfBoundsException"); 278 } catch (final ArrayIndexOutOfBoundsException ex) { 279 } 280 281 final List<Integer> list = new ArrayList<>(); 282 list.add(23); 283 list.add(430); 284 list.add(-4354); 285 286 cs.getTarget().invoke(list, 0, -list.get(0)); 287 Assert.assertEquals((int) list.get(0), -23); 288 cs.getTarget().invoke(list, 1, -430); 289 cs.getTarget().invoke(list, 2, 4354); 290 try { 291 cs.getTarget().invoke(list, -1, 343); 292 throw new RuntimeException("expected IndexOutOfBoundsException"); 293 } catch (final IndexOutOfBoundsException ex) { 294 } 295 296 try { 297 cs.getTarget().invoke(list, list.size(), 43543); 298 throw new RuntimeException("expected IndexOutOfBoundsException"); 299 } catch (final IndexOutOfBoundsException ex) { 300 } 301 } 302 303 @Test(dataProvider = "flags") 304 public void newObjectTest(final boolean publicLookup) { 305 final MethodType mt = MethodType.methodType(Object.class, Object.class); 306 final CallSite cs = createCallSite(publicLookup, NEW, mt); 307 308 Object obj = null; 309 try { 310 obj = cs.getTarget().invoke(StaticClass.forClass(Date.class)); 311 } catch (final Throwable th) { 312 throw new RuntimeException(th); 313 } 314 315 Assert.assertTrue(obj instanceof Date); 316 } 317 318 @Test(dataProvider = "flags") 319 public void staticPropertyTest(final boolean publicLookup) { 320 final MethodType mt = MethodType.methodType(Object.class, Class.class); 321 final CallSite cs = createCallSite(publicLookup, GET_PROPERTY, "static", mt); 322 323 Object obj = null; 324 try { 325 obj = cs.getTarget().invoke(Object.class); 326 } catch (final Throwable th) { 327 throw new RuntimeException(th); 328 } 329 330 Assert.assertTrue(obj instanceof StaticClass); 331 Assert.assertEquals(((StaticClass) obj).getRepresentedClass(), Object.class); 332 333 try { 334 obj = cs.getTarget().invoke(Date.class); 335 } catch (final Throwable th) { 336 throw new RuntimeException(th); 337 } 338 339 Assert.assertTrue(obj instanceof StaticClass); 340 Assert.assertEquals(((StaticClass) obj).getRepresentedClass(), Date.class); 341 342 try { 343 obj = cs.getTarget().invoke(Object[].class); 344 } catch (final Throwable th) { 345 throw new RuntimeException(th); 346 } 347 348 Assert.assertTrue(obj instanceof StaticClass); 349 Assert.assertEquals(((StaticClass) obj).getRepresentedClass(), Object[].class); 350 } 351 352 @Test(dataProvider = "flags") 353 public void instanceMethodCallTest(final boolean publicLookup) { 354 final CallSite cs = createGetMethodCallSite(publicLookup, "getClass"); 355 final MethodType mt2 = MethodType.methodType(Class.class, Object.class, Object.class); 356 final CallSite cs2 = createCallSite(publicLookup, CALL, mt2); 357 358 Object method = null; 359 try { 360 method = cs.getTarget().invoke(new Date()); 361 } catch (final Throwable th) { 362 throw new RuntimeException(th); 363 } 364 365 Assert.assertNotNull(method); 366 Assert.assertTrue(BeansLinker.isDynamicMethod(method)); 367 Class clz = null; 368 try { 369 clz = (Class) cs2.getTarget().invoke(method, new Date()); 370 } catch (final Throwable th) { 371 throw new RuntimeException(th); 372 } 373 374 Assert.assertEquals(clz, Date.class); 375 } 376 377 @Test(dataProvider = "flags") 378 public void staticMethodCallTest(final boolean publicLookup) { 379 final CallSite cs = createGetMethodCallSite(publicLookup, "getProperty"); 380 final MethodType mt2 = MethodType.methodType(String.class, Object.class, Object.class, String.class); 381 final CallSite cs2 = createCallSite(publicLookup, CALL, mt2); 382 383 Object method = null; 384 try { 385 method = cs.getTarget().invoke(StaticClass.forClass(System.class)); 386 } catch (final Throwable th) { 387 throw new RuntimeException(th); 388 } 389 390 Assert.assertNotNull(method); 391 Assert.assertTrue(BeansLinker.isDynamicMethod(method)); 392 393 String str = null; 394 try { 395 str = (String) cs2.getTarget().invoke(method, null, "os.name"); 396 } catch (final Throwable th) { 397 throw new RuntimeException(th); 398 } 399 Assert.assertEquals(str, System.getProperty("os.name")); 400 } 401 402 // try calling System.getenv and expect security exception 403 @Test(dataProvider = "flags") 404 public void systemGetenvTest(final boolean publicLookup) { 405 final CallSite cs1 = createGetMethodCallSite(publicLookup, "getenv"); 406 final CallSite cs2 = createCallSite(publicLookup, CALL, MethodType.methodType(Object.class, Object.class, Object.class)); 407 408 try { 409 final Object method = cs1.getTarget().invoke(StaticClass.forClass(System.class)); 410 cs2.getTarget().invoke(method, StaticClass.forClass(System.class)); 411 throw new RuntimeException("should not reach here in any case!"); 412 } catch (final Throwable th) { 413 Assert.assertTrue(th instanceof SecurityException); 414 } 415 } 416 417 // try getting a specific sensitive System property and expect security exception 418 @Test(dataProvider = "flags") 419 public void systemGetPropertyTest(final boolean publicLookup) { 420 final CallSite cs1 = createGetMethodCallSite(publicLookup, "getProperty"); 421 final CallSite cs2 = createCallSite(publicLookup, CALL, MethodType.methodType(String.class, Object.class, Object.class, String.class)); 422 423 try { 424 final Object method = cs1.getTarget().invoke(StaticClass.forClass(System.class)); 425 cs2.getTarget().invoke(method, StaticClass.forClass(System.class), "java.home"); 426 throw new RuntimeException("should not reach here in any case!"); 427 } catch (final Throwable th) { 428 Assert.assertTrue(th instanceof SecurityException); 429 } 430 } 431 432 // check a @CallerSensitive API and expect appropriate access check exception 433 @Test(dataProvider = "flags") 434 public void systemLoadLibraryTest(final boolean publicLookup) { 435 final CallSite cs1 = createGetMethodCallSite(publicLookup, "loadLibrary"); 436 final CallSite cs2 = createCallSite(publicLookup, CALL, MethodType.methodType(void.class, Object.class, Object.class, String.class)); 437 438 try { 439 final Object method = cs1.getTarget().invoke(StaticClass.forClass(System.class)); 440 cs2.getTarget().invoke(method, StaticClass.forClass(System.class), "foo"); 441 throw new RuntimeException("should not reach here in any case!"); 442 } catch (final Throwable th) { 443 if (publicLookup) { 444 Assert.assertTrue(th instanceof IllegalAccessError); 445 } else { 446 Assert.assertTrue(th instanceof AccessControlException); 447 } 448 } 449 } 450} 451