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