1 /* 2 * Copyright (C) 2019 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.tools.layoutlib.create; 18 19 20 import static org.junit.Assert.assertEquals; 21 import static org.junit.Assert.assertFalse; 22 import static org.junit.Assert.assertNotNull; 23 import static org.junit.Assert.assertNull; 24 import static org.junit.Assert.assertSame; 25 import static org.junit.Assert.assertTrue; 26 import static org.junit.Assert.fail; 27 28 import com.android.tools.layoutlib.create.dataclass.ClassWithNative; 29 import com.android.tools.layoutlib.create.dataclass.OuterClass; 30 import com.android.tools.layoutlib.create.dataclass.OuterClass.InnerClass; 31 import com.android.tools.layoutlib.create.dataclass.OuterClass.StaticInnerClass; 32 33 import org.junit.Before; 34 import org.junit.Test; 35 import org.objectweb.asm.ClassReader; 36 import org.objectweb.asm.ClassVisitor; 37 import org.objectweb.asm.ClassWriter; 38 39 import java.io.IOException; 40 import java.io.PrintWriter; 41 import java.io.StringWriter; 42 import java.lang.annotation.Annotation; 43 import java.lang.reflect.Constructor; 44 import java.lang.reflect.InvocationTargetException; 45 import java.lang.reflect.Method; 46 import java.lang.reflect.Modifier; 47 import java.util.HashMap; 48 import java.util.HashSet; 49 import java.util.Map; 50 import java.util.Map.Entry; 51 import java.util.Set; 52 53 public class DelegateClassAdapterTest { 54 55 private MockLog mLog; 56 57 private static final String NATIVE_CLASS_NAME = ClassWithNative.class.getName(); 58 private static final String OUTER_CLASS_NAME = OuterClass.class.getName(); 59 private static final String INNER_CLASS_NAME = InnerClass.class.getName(); 60 private static final String STATIC_INNER_CLASS_NAME = StaticInnerClass.class.getName(); 61 // use a string to avoid triggering the static init 62 private static final String CLASS_WITH_STATIC_INIT_NAME = 63 "com.android.tools.layoutlib.create.dataclass.ClassWithStaticInit"; 64 private static final String INNER_CLASS_WITH_STATIC_INIT_NAME = 65 "com.android.tools.layoutlib.create.dataclass.ClassWithStaticInit$InnerClass"; 66 private static final String INNER_CLASS_WITH_STATIC_INIT_DELEGATE_NAME = 67 "com.android.tools.layoutlib.create.dataclass.ClassWithStaticInit_InnerClass_Delegate"; 68 69 @Before setUp()70 public void setUp() throws Exception { 71 mLog = new MockLog(); 72 mLog.setVerbose(true); // capture debug error too 73 } 74 75 /** 76 * Tests that a class not being modified still works. 77 */ 78 @Test testNoOp()79 public void testNoOp() throws Throwable { 80 // create an instance of the class that will be modified 81 // (load the class in a distinct class loader so that we can trash its definition later) 82 ClassLoader cl1 = new ClassLoader(this.getClass().getClassLoader()) { }; 83 @SuppressWarnings("unchecked") 84 Class<ClassWithNative> clazz1 = (Class<ClassWithNative>) cl1.loadClass(NATIVE_CLASS_NAME); 85 ClassWithNative instance1 = clazz1.newInstance(); 86 assertEquals(42, instance1.add(20, 22)); 87 try { 88 instance1.callNativeInstance(10, 3.1415, new Object[0] ); 89 fail("Test should have failed to invoke callTheNativeMethod [1]"); 90 } catch (UnsatisfiedLinkError e) { 91 // This is expected to fail since the native method is not implemented. 92 } 93 94 // Now process it but tell the delegate to not modify any method 95 ClassWriter cw = new ClassWriter(0 /*flags*/); 96 97 HashSet<String> delegateMethods = new HashSet<>(); 98 String internalClassName = NATIVE_CLASS_NAME.replace('.', '/'); 99 DelegateClassAdapter cv = new DelegateClassAdapter( 100 mLog, cw, internalClassName, delegateMethods); 101 102 ClassReader cr = new ClassReader(NATIVE_CLASS_NAME); 103 cr.accept(cv, 0 /* flags */); 104 105 // Load the generated class in a different class loader and try it again 106 107 ClassLoader2 cl2 = null; 108 try { 109 cl2 = new ClassLoader2() { 110 @Override 111 public void testModifiedInstance() throws Exception { 112 Class<?> clazz2 = loadClass(NATIVE_CLASS_NAME); 113 Object i2 = clazz2.newInstance(); 114 assertNotNull(i2); 115 assertEquals(42, callAdd(i2, 20, 22)); 116 117 try { 118 callCallNativeInstance(i2, 10, 3.1415, new Object[0]); 119 fail("Test should have failed to invoke callTheNativeMethod [2]"); 120 } catch (InvocationTargetException e) { 121 // This is expected to fail since the native method has NOT been 122 // overridden here. 123 assertEquals(UnsatisfiedLinkError.class, e.getCause().getClass()); 124 } 125 126 // Check that the native method does NOT have the new annotation 127 Method[] m = clazz2.getDeclaredMethods(); 128 Method nativeInstanceMethod = null; 129 for (Method method : m) { 130 if ("native_instance".equals(method.getName())) { 131 nativeInstanceMethod = method; 132 break; 133 } 134 } 135 assertNotNull(nativeInstanceMethod); 136 assertTrue(Modifier.isNative(nativeInstanceMethod.getModifiers())); 137 Annotation[] a = nativeInstanceMethod.getAnnotations(); 138 assertEquals(0, a.length); 139 } 140 }; 141 cl2.add(NATIVE_CLASS_NAME, cw); 142 cl2.testModifiedInstance(); 143 } catch (Throwable t) { 144 throw dumpGeneratedClass(t, cl2); 145 } 146 } 147 148 @Test testConstructorAfterDelegate()149 public void testConstructorAfterDelegate() throws Throwable { 150 ClassWriter cw = new ClassWriter(0 /*flags*/); 151 152 String internalClassName = NATIVE_CLASS_NAME.replace('.', '/'); 153 154 HashSet<String> delegateMethods = new HashSet<>(); 155 delegateMethods.add("<init>"); 156 DelegateClassAdapter cv = new DelegateClassAdapter( 157 mLog, cw, internalClassName, delegateMethods); 158 159 ClassReader cr = new ClassReader(NATIVE_CLASS_NAME); 160 cr.accept(cv, 0 /* flags */); 161 162 ClassLoader2 cl2 = null; 163 try { 164 cl2 = new ClassLoader2() { 165 @Override 166 public void testModifiedInstance() throws Exception { 167 Class<?> clazz2 = loadClass(NATIVE_CLASS_NAME); 168 Object i2 = clazz2.newInstance(); 169 assertNotNull(i2); 170 assertEquals(123, clazz2.getField("mId").getInt(i2)); 171 } 172 }; 173 cl2.add(NATIVE_CLASS_NAME, cw); 174 cl2.testModifiedInstance(); 175 } catch (Throwable t) { 176 throw dumpGeneratedClass(t, cl2); 177 } 178 } 179 180 @Test testInnerConstructorAfterDelegate()181 public void testInnerConstructorAfterDelegate() throws Throwable { 182 ClassWriter cw = new ClassWriter(0 /*flags*/); 183 184 String internalClassName = INNER_CLASS_NAME.replace('.', '/'); 185 186 HashSet<String> delegateMethods = new HashSet<>(); 187 delegateMethods.add("<init>"); 188 DelegateClassAdapter cv = new DelegateClassAdapter( 189 mLog, cw, internalClassName, delegateMethods); 190 191 ClassReader cr = new ClassReader(INNER_CLASS_NAME); 192 cr.accept(cv, 0 /* flags */); 193 194 ClassLoader2 cl2 = null; 195 try { 196 cl2 = new ClassLoader2() { 197 @Override 198 public void testModifiedInstance() throws Exception { 199 Class<?> outerClazz2 = loadClass(OUTER_CLASS_NAME); 200 Object o2 = outerClazz2.newInstance(); 201 202 Class<?> clazz2 = loadClass(INNER_CLASS_NAME); 203 Object i2 = clazz2.getConstructor(outerClazz2).newInstance(o2); 204 assertNotNull(i2); 205 assertEquals(98, clazz2.getField("mInnerId").getInt(i2)); 206 } 207 }; 208 cl2.add(INNER_CLASS_NAME, cw); 209 cl2.testModifiedInstance(); 210 } catch (Throwable t) { 211 throw dumpGeneratedClass(t, cl2); 212 } 213 } 214 215 @Test testStaticInnerConstructorAfterDelegate()216 public void testStaticInnerConstructorAfterDelegate() throws Throwable { 217 ClassWriter cw = new ClassWriter(0 /*flags*/); 218 219 String internalClassName = STATIC_INNER_CLASS_NAME.replace('.', '/'); 220 221 HashSet<String> delegateMethods = new HashSet<>(); 222 delegateMethods.add("<init>"); 223 DelegateClassAdapter cv = new DelegateClassAdapter( 224 mLog, cw, internalClassName, delegateMethods); 225 226 ClassReader cr = new ClassReader(STATIC_INNER_CLASS_NAME); 227 cr.accept(cv, 0 /* flags */); 228 229 ClassLoader2 cl2 = null; 230 try { 231 cl2 = new ClassLoader2() { 232 @Override 233 public void testModifiedInstance() throws Exception { 234 Class<?> clazz2 = loadClass(STATIC_INNER_CLASS_NAME); 235 Object i2 = clazz2.newInstance(); 236 assertNotNull(i2); 237 assertEquals(42, clazz2.getField("mStaticInnerId").getInt(i2)); 238 } 239 }; 240 cl2.add(STATIC_INNER_CLASS_NAME, cw); 241 cl2.testModifiedInstance(); 242 } catch (Throwable t) { 243 throw dumpGeneratedClass(t, cl2); 244 } 245 } 246 247 @Test testDelegateStaticInitializer()248 public void testDelegateStaticInitializer() throws Throwable { 249 ClassWriter cw = new ClassWriter(0 /*flags*/); 250 251 String internalClassName = CLASS_WITH_STATIC_INIT_NAME.replace('.', '/'); 252 253 HashSet<String> delegateMethods = new HashSet<>(); 254 delegateMethods.add("<clinit>"); 255 DelegateClassAdapter cv = new DelegateClassAdapter( 256 mLog, cw, internalClassName, delegateMethods); 257 258 ClassReader cr = new ClassReader(CLASS_WITH_STATIC_INIT_NAME); 259 cr.accept(cv, 0 /* flags */); 260 261 ClassLoader2 cl2 = null; 262 try { 263 cl2 = new ClassLoader2() { 264 @Override 265 public void testModifiedInstance() throws Exception { 266 Class<?> clazz2 = loadClass(CLASS_WITH_STATIC_INIT_NAME); 267 268 assertNull(clazz2.getField("sList").get(null)); 269 270 Class<?> delegateClass = loadClass(CLASS_WITH_STATIC_INIT_NAME + "_Delegate"); 271 assertNotNull( delegateClass.getField("sList").get(null)); 272 } 273 }; 274 cl2.add(CLASS_WITH_STATIC_INIT_NAME, cw); 275 cl2.testModifiedInstance(); 276 } catch (Throwable t) { 277 throw dumpGeneratedClass(t, cl2); 278 } 279 } 280 281 @Test testDelegateInnerClassStaticInitializer()282 public void testDelegateInnerClassStaticInitializer() throws Throwable { 283 ClassWriter cw = new ClassWriter(0 /*flags*/); 284 285 String internalClassName = INNER_CLASS_WITH_STATIC_INIT_NAME.replace('.', '/'); 286 287 HashSet<String> delegateMethods = new HashSet<>(); 288 delegateMethods.add("<clinit>"); 289 DelegateClassAdapter cv = new DelegateClassAdapter( 290 mLog, cw, internalClassName, delegateMethods); 291 292 ClassReader cr = new ClassReader(INNER_CLASS_WITH_STATIC_INIT_NAME); 293 cr.accept(cv, 0 /* flags */); 294 295 ClassLoader2 cl2 = null; 296 try { 297 cl2 = new ClassLoader2() { 298 @Override 299 public void testModifiedInstance() throws Exception { 300 Class<?> clazz2 = loadClass(INNER_CLASS_WITH_STATIC_INIT_NAME); 301 302 assertNull(clazz2.getField("sInnerList").get(null)); 303 304 Class<?> delegateClass = loadClass(INNER_CLASS_WITH_STATIC_INIT_DELEGATE_NAME); 305 assertNotNull( delegateClass.getField("sList").get(null)); 306 } 307 }; 308 cl2.add(INNER_CLASS_WITH_STATIC_INIT_NAME, cw); 309 cl2.testModifiedInstance(); 310 } catch (Throwable t) { 311 throw dumpGeneratedClass(t, cl2); 312 } 313 } 314 315 @Test testDelegateNative()316 public void testDelegateNative() throws Throwable { 317 ClassWriter cw = new ClassWriter(0 /*flags*/); 318 String internalClassName = NATIVE_CLASS_NAME.replace('.', '/'); 319 320 HashSet<String> delegateMethods = new HashSet<>(); 321 delegateMethods.add(DelegateClassAdapter.ALL_NATIVES); 322 DelegateClassAdapter cv = new DelegateClassAdapter( 323 mLog, cw, internalClassName, delegateMethods); 324 325 ClassReader cr = new ClassReader(NATIVE_CLASS_NAME); 326 cr.accept(cv, 0 /* flags */); 327 328 // Load the generated class in a different class loader and try it 329 ClassLoader2 cl2 = null; 330 try { 331 cl2 = new ClassLoader2() { 332 @Override 333 public void testModifiedInstance() throws Exception { 334 Class<?> clazz2 = loadClass(NATIVE_CLASS_NAME); 335 Object i2 = clazz2.newInstance(); 336 assertNotNull(i2); 337 338 // Use reflection to access inner methods 339 assertEquals(42, callAdd(i2, 20, 22)); 340 341 Object[] objResult = new Object[] { null }; 342 int result = callCallNativeInstance(i2, 10, 3.1415, objResult); 343 assertEquals((int)(10 + 3.1415), result); 344 assertSame(i2, objResult[0]); 345 346 // Check that the native method now has the new annotation and is not native 347 Method[] m = clazz2.getDeclaredMethods(); 348 Method nativeInstanceMethod = null; 349 for (Method method : m) { 350 if ("native_instance".equals(method.getName())) { 351 nativeInstanceMethod = method; 352 break; 353 } 354 } 355 assertNotNull(nativeInstanceMethod); 356 assertFalse(Modifier.isNative(nativeInstanceMethod.getModifiers())); 357 Annotation[] a = nativeInstanceMethod.getAnnotations(); 358 assertEquals("LayoutlibDelegate", a[0].annotationType().getSimpleName()); 359 } 360 }; 361 cl2.add(NATIVE_CLASS_NAME, cw); 362 cl2.testModifiedInstance(); 363 } catch (Throwable t) { 364 throw dumpGeneratedClass(t, cl2); 365 } 366 } 367 368 @Test testDelegateInner()369 public void testDelegateInner() throws Throwable { 370 // We'll delegate the "get" method of both the inner and outer class. 371 HashSet<String> delegateMethods = new HashSet<>(); 372 delegateMethods.add("get"); 373 delegateMethods.add("privateMethod"); 374 375 // Generate the delegate for the outer class. 376 ClassWriter cwOuter = new ClassWriter(0 /*flags*/); 377 String outerClassName = OUTER_CLASS_NAME.replace('.', '/'); 378 DelegateClassAdapter cvOuter = new DelegateClassAdapter( 379 mLog, cwOuter, outerClassName, delegateMethods); 380 ClassReader cr = new ClassReader(OUTER_CLASS_NAME); 381 cr.accept(cvOuter, 0 /* flags */); 382 383 // Generate the delegate for the inner class. 384 ClassWriter cwInner = new ClassWriter(0 /*flags*/); 385 String innerClassName = INNER_CLASS_NAME.replace('.', '/'); 386 DelegateClassAdapter cvInner = new DelegateClassAdapter( 387 mLog, cwInner, innerClassName, delegateMethods); 388 cr = new ClassReader(INNER_CLASS_NAME); 389 cr.accept(cvInner, 0 /* flags */); 390 391 // Load the generated classes in a different class loader and try them 392 ClassLoader2 cl2 = null; 393 try { 394 cl2 = new ClassLoader2() { 395 @Override 396 public void testModifiedInstance() throws Exception { 397 398 // Check the outer class 399 Class<?> outerClazz2 = loadClass(OUTER_CLASS_NAME); 400 Object o2 = outerClazz2.newInstance(); 401 assertNotNull(o2); 402 403 // The original Outer.get returns 1+10+20, 404 // but the delegate makes it return 4+10+20 405 assertEquals(4+10+20, callGet(o2, 10, 20)); 406 assertEquals(1+10+20, callGet_Original(o2, 10, 20)); 407 408 // The original Outer has a private method, 409 // so by default we can't access it. 410 boolean gotIllegalAccessException = false; 411 try { 412 callMethod(o2, "privateMethod", false /*makePublic*/); 413 } catch(IllegalAccessException e) { 414 gotIllegalAccessException = true; 415 } 416 assertTrue(gotIllegalAccessException); 417 418 // The private method from original Outer has been 419 // delegated. The delegate generated should have the 420 // same access. 421 gotIllegalAccessException = false; 422 try { 423 assertEquals("outerPrivateMethod", 424 callMethod(o2, "privateMethod_Original", false /*makePublic*/)); 425 } catch (IllegalAccessException e) { 426 gotIllegalAccessException = true; 427 } 428 assertTrue(gotIllegalAccessException); 429 430 // Check the inner class. Since it's not a static inner class, we need 431 // to use the hidden constructor that takes the outer class as first parameter. 432 Class<?> innerClazz2 = loadClass(INNER_CLASS_NAME); 433 Constructor<?> innerCons = innerClazz2.getConstructor(outerClazz2); 434 Object i2 = innerCons.newInstance(o2); 435 assertNotNull(i2); 436 437 // The original Inner.get returns 3+10+20, 438 // but the delegate makes it return 6+10+20 439 assertEquals(6+10+20, callGet(i2, 10, 20)); 440 assertEquals(3+10+20, callGet_Original(i2, 10, 20)); 441 } 442 }; 443 cl2.add(OUTER_CLASS_NAME, cwOuter.toByteArray()); 444 cl2.add(INNER_CLASS_NAME, cwInner.toByteArray()); 445 cl2.testModifiedInstance(); 446 } catch (Throwable t) { 447 throw dumpGeneratedClass(t, cl2); 448 } 449 } 450 451 @Test testDelegateStaticInner()452 public void testDelegateStaticInner() throws Throwable { 453 // We'll delegate the "get" method of both the inner and outer class. 454 HashSet<String> delegateMethods = new HashSet<>(); 455 delegateMethods.add("get"); 456 457 // Generate the delegate for the outer class. 458 ClassWriter cwOuter = new ClassWriter(0 /*flags*/); 459 String outerClassName = OUTER_CLASS_NAME.replace('.', '/'); 460 DelegateClassAdapter cvOuter = new DelegateClassAdapter( 461 mLog, cwOuter, outerClassName, delegateMethods); 462 ClassReader cr = new ClassReader(OUTER_CLASS_NAME); 463 cr.accept(cvOuter, 0 /* flags */); 464 465 // Generate the delegate for the static inner class. 466 ClassWriter cwInner = new ClassWriter(0 /*flags*/); 467 String innerClassName = STATIC_INNER_CLASS_NAME.replace('.', '/'); 468 DelegateClassAdapter cvInner = new DelegateClassAdapter( 469 mLog, cwInner, innerClassName, delegateMethods); 470 cr = new ClassReader(STATIC_INNER_CLASS_NAME); 471 cr.accept(cvInner, 0 /* flags */); 472 473 // Load the generated classes in a different class loader and try them 474 ClassLoader2 cl2 = null; 475 try { 476 cl2 = new ClassLoader2() { 477 @Override 478 public void testModifiedInstance() throws Exception { 479 480 // Check the outer class 481 Class<?> outerClazz2 = loadClass(OUTER_CLASS_NAME); 482 Object o2 = outerClazz2.newInstance(); 483 assertNotNull(o2); 484 485 // Check the inner class. Since it's not a static inner class, we need 486 // to use the hidden constructor that takes the outer class as first parameter. 487 Class<?> innerClazz2 = loadClass(STATIC_INNER_CLASS_NAME); 488 Constructor<?> innerCons = innerClazz2.getConstructor(); 489 Object i2 = innerCons.newInstance(); 490 assertNotNull(i2); 491 492 // The original StaticInner.get returns 100+10+20, 493 // but the delegate makes it return 6+10+20 494 assertEquals(6+10+20, callGet(i2, 10, 20)); 495 assertEquals(100+10+20, callGet_Original(i2, 10, 20)); 496 } 497 }; 498 cl2.add(OUTER_CLASS_NAME, cwOuter.toByteArray()); 499 cl2.add(STATIC_INNER_CLASS_NAME, cwInner.toByteArray()); 500 cl2.testModifiedInstance(); 501 } catch (Throwable t) { 502 throw dumpGeneratedClass(t, cl2); 503 } 504 } 505 506 //------- 507 508 /** 509 * A class loader than can define and instantiate our modified classes. 510 * <p/> 511 * The trick here is that this class loader will test our <em>modified</em> version 512 * of the classes, the one with the delegate calls. 513 * <p/> 514 * Trying to do so in the original class loader generates all sort of link issues because 515 * there are 2 different definitions of the same class name. This class loader will 516 * define and load the class when requested by name and provide helpers to access the 517 * instance methods via reflection. 518 */ 519 private abstract class ClassLoader2 extends ClassLoader { 520 521 private final Map<String, byte[]> mClassDefs = new HashMap<>(); 522 ClassLoader2()523 public ClassLoader2() { 524 super(null); 525 } 526 add(String className, byte[] definition)527 public ClassLoader2 add(String className, byte[] definition) { 528 mClassDefs.put(className, definition); 529 return this; 530 } 531 add(String className, ClassWriter rewrittenClass)532 public ClassLoader2 add(String className, ClassWriter rewrittenClass) { 533 mClassDefs.put(className, rewrittenClass.toByteArray()); 534 return this; 535 } 536 getByteCode()537 private Set<Entry<String, byte[]>> getByteCode() { 538 return mClassDefs.entrySet(); 539 } 540 541 @SuppressWarnings("unused") 542 @Override findClass(String name)543 protected Class<?> findClass(String name) throws ClassNotFoundException { 544 try { 545 return super.findClass(name); 546 } catch (ClassNotFoundException e) { 547 548 byte[] def = mClassDefs.get(name); 549 if (def != null) { 550 // Load the modified ClassWithNative from its bytes representation. 551 return defineClass(name, def, 0, def.length); 552 } 553 554 try { 555 // Load everything else from the original definition into the new class loader. 556 ClassReader cr = new ClassReader(name); 557 ClassWriter cw = new ClassWriter(0); 558 cr.accept(cw, 0); 559 byte[] bytes = cw.toByteArray(); 560 return defineClass(name, bytes, 0, bytes.length); 561 562 } catch (IOException ioe) { 563 throw new RuntimeException(ioe); 564 } 565 } 566 } 567 568 /** 569 * Accesses {@link OuterClass#get} or {@link InnerClass#get}via reflection. 570 */ callGet(Object instance, int a, long b)571 public int callGet(Object instance, int a, long b) throws Exception { 572 Method m = instance.getClass().getMethod("get", 573 int.class, long.class); 574 575 Object result = m.invoke(instance, a, b); 576 return (Integer) result; 577 } 578 579 /** 580 * Accesses the "_Original" methods for {@link OuterClass#get} 581 * or {@link InnerClass#get}via reflection. 582 */ callGet_Original(Object instance, int a, long b)583 public int callGet_Original(Object instance, int a, long b) throws Exception { 584 Method m = instance.getClass().getMethod("get_Original", 585 int.class, long.class); 586 587 Object result = m.invoke(instance, a, b); 588 return (Integer) result; 589 } 590 591 /** 592 * Accesses the any declared method that takes no parameter via reflection. 593 */ 594 @SuppressWarnings("unchecked") callMethod(Object instance, String methodName, boolean makePublic)595 public <T> T callMethod(Object instance, String methodName, boolean makePublic) throws Exception { 596 Method m = instance.getClass().getDeclaredMethod(methodName, (Class<?>[])null); 597 598 boolean wasAccessible = m.isAccessible(); 599 if (makePublic && !wasAccessible) { 600 m.setAccessible(true); 601 } 602 603 Object result = m.invoke(instance, (Object[])null); 604 605 if (makePublic && !wasAccessible) { 606 m.setAccessible(false); 607 } 608 609 return (T) result; 610 } 611 612 /** 613 * Accesses {@link ClassWithNative#add(int, int)} via reflection. 614 */ callAdd(Object instance, int a, int b)615 public int callAdd(Object instance, int a, int b) throws Exception { 616 Method m = instance.getClass().getMethod("add", 617 int.class, int.class); 618 619 Object result = m.invoke(instance, a, b); 620 return (Integer) result; 621 } 622 623 /** 624 * Accesses {@link ClassWithNative#callNativeInstance(int, double, Object[])} 625 * via reflection. 626 */ callCallNativeInstance(Object instance, int a, double d, Object[] o)627 public int callCallNativeInstance(Object instance, int a, double d, Object[] o) 628 throws Exception { 629 Method m = instance.getClass().getMethod("callNativeInstance", 630 int.class, double.class, Object[].class); 631 632 Object result = m.invoke(instance, a, d, o); 633 return (Integer) result; 634 } 635 testModifiedInstance()636 public abstract void testModifiedInstance() throws Exception; 637 } 638 639 /** 640 * For debugging, it's useful to dump the content of the generated classes 641 * along with the exception that was generated. 642 * 643 * However to make it work you need to pull in the org.objectweb.asm.util.TraceClassVisitor 644 * class and associated utilities which are found in the ASM source jar. Since we don't 645 * want that dependency in the source code, we only put it manually for development and 646 * access the TraceClassVisitor via reflection if present. 647 * 648 * @param t The exception thrown by {@link ClassLoader2#testModifiedInstance()} 649 * @param cl2 The {@link ClassLoader2} instance with the generated bytecode. 650 * @return Either original {@code t} or a new wrapper {@link Throwable} 651 */ dumpGeneratedClass(Throwable t, ClassLoader2 cl2)652 private Throwable dumpGeneratedClass(Throwable t, ClassLoader2 cl2) { 653 try { 654 // For debugging, dump the bytecode of the class in case of unexpected error 655 // if we can find the TraceClassVisitor class. 656 Class<?> tcvClass = Class.forName("org.objectweb.asm.util.TraceClassVisitor"); 657 658 StringBuilder sb = new StringBuilder(); 659 sb.append('\n').append(t.getClass().getCanonicalName()); 660 if (t.getMessage() != null) { 661 sb.append(": ").append(t.getMessage()); 662 } 663 664 for (Entry<String, byte[]> entry : cl2.getByteCode()) { 665 String className = entry.getKey(); 666 byte[] bytes = entry.getValue(); 667 668 StringWriter sw = new StringWriter(); 669 PrintWriter pw = new PrintWriter(sw); 670 // next 2 lines do: TraceClassVisitor tcv = new TraceClassVisitor(pw); 671 Constructor<?> cons = tcvClass.getConstructor(pw.getClass()); 672 Object tcv = cons.newInstance(pw); 673 ClassReader cr2 = new ClassReader(bytes); 674 cr2.accept((ClassVisitor) tcv, 0 /* flags */); 675 676 sb.append("\nBytecode dump: <").append(className).append(">:\n") 677 .append(sw.toString()); 678 } 679 680 // Re-throw exception with new message 681 return new RuntimeException(sb.toString(), t); 682 } catch (Throwable ignore) { 683 // In case of problem, just throw the original exception as-is. 684 return t; 685 } 686 } 687 688 } 689