• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 package org.robolectric.internal.bytecode;
2 
3 import java.lang.invoke.MethodType;
4 import java.lang.reflect.Modifier;
5 import java.util.List;
6 import java.util.ListIterator;
7 import org.objectweb.asm.ClassReader;
8 import org.objectweb.asm.ClassWriter;
9 import org.objectweb.asm.FieldVisitor;
10 import org.objectweb.asm.Label;
11 import org.objectweb.asm.MethodVisitor;
12 import org.objectweb.asm.Opcodes;
13 import org.objectweb.asm.Type;
14 import org.objectweb.asm.commons.ClassRemapper;
15 import org.objectweb.asm.commons.GeneratorAdapter;
16 import org.objectweb.asm.commons.JSRInlinerAdapter;
17 import org.objectweb.asm.commons.Method;
18 import org.objectweb.asm.commons.Remapper;
19 import org.objectweb.asm.tree.AbstractInsnNode;
20 import org.objectweb.asm.tree.ClassNode;
21 import org.objectweb.asm.tree.FieldInsnNode;
22 import org.objectweb.asm.tree.FieldNode;
23 import org.objectweb.asm.tree.InsnList;
24 import org.objectweb.asm.tree.InsnNode;
25 import org.objectweb.asm.tree.MethodInsnNode;
26 import org.objectweb.asm.tree.MethodNode;
27 import org.objectweb.asm.tree.TypeInsnNode;
28 import org.objectweb.asm.tree.VarInsnNode;
29 
30 public abstract class ClassInstrumentor {
31   private static final String ROBO_INIT_METHOD_NAME = "$$robo$init";
32   static final Type OBJECT_TYPE = Type.getType(Object.class);
33   private static final ShadowImpl SHADOW_IMPL = new ShadowImpl();
34   final Decorator decorator;
35 
ClassInstrumentor(Decorator decorator)36   protected ClassInstrumentor(Decorator decorator) {
37     this.decorator = decorator;
38   }
39 
analyzeClass( byte[] origClassBytes, final InstrumentationConfiguration config, ClassNodeProvider classNodeProvider)40   public MutableClass analyzeClass(
41       byte[] origClassBytes,
42       final InstrumentationConfiguration config,
43       ClassNodeProvider classNodeProvider) {
44     ClassNode classNode =
45         new ClassNode(Opcodes.ASM4) {
46           @Override
47           public FieldVisitor visitField(
48               int access, String name, String desc, String signature, Object value) {
49             desc = config.remapParamType(desc);
50             return super.visitField(access & ~Opcodes.ACC_FINAL, name, desc, signature, value);
51           }
52 
53           @Override
54           public MethodVisitor visitMethod(
55               int access, String name, String desc, String signature, String[] exceptions) {
56             MethodVisitor methodVisitor =
57                 super.visitMethod(access, name, config.remapParams(desc), signature, exceptions);
58             return new JSRInlinerAdapter(methodVisitor, access, name, desc, signature, exceptions);
59           }
60         };
61 
62     final ClassReader classReader = new ClassReader(origClassBytes);
63     classReader.accept(classNode, 0);
64     return new MutableClass(classNode, config, classNodeProvider);
65   }
66 
instrumentToBytes(MutableClass mutableClass)67   byte[] instrumentToBytes(MutableClass mutableClass) {
68     instrument(mutableClass);
69 
70     ClassNode classNode = mutableClass.classNode;
71     ClassWriter writer = new InstrumentingClassWriter(mutableClass.classNodeProvider, classNode);
72     Remapper remapper =
73         new Remapper() {
74           @Override
75           public String map(final String internalName) {
76             return mutableClass.config.mappedTypeName(internalName);
77           }
78         };
79     ClassRemapper visitor = new ClassRemapper(writer, remapper);
80     classNode.accept(visitor);
81     return writer.toByteArray();
82   }
83 
instrument(byte[] origBytes, InstrumentationConfiguration config, ClassNodeProvider classNodeProvider)84   public byte[] instrument(byte[] origBytes, InstrumentationConfiguration config,
85       ClassNodeProvider classNodeProvider) {
86     MutableClass mutableClass = analyzeClass(origBytes, config, classNodeProvider);
87     return instrumentToBytes(mutableClass);
88   }
89 
instrument(MutableClass mutableClass)90   public void instrument(MutableClass mutableClass) {
91     try {
92       // no need to do anything to interfaces
93       if (mutableClass.isInterface()) {
94         return;
95       }
96 
97       makeClassPublic(mutableClass.classNode);
98       if ((mutableClass.classNode.access & Opcodes.ACC_FINAL) == Opcodes.ACC_FINAL) {
99         mutableClass.classNode.visitAnnotation(
100             "Lcom/google/errorprone/annotations/DoNotMock;", true)
101             .visit("value", "This class is final. Consider using the real thing, or "
102                 + "adding/enhancing a Robolectric shadow for it.");
103       }
104       mutableClass.classNode.access = mutableClass.classNode.access & ~Opcodes.ACC_FINAL;
105 
106       // Need Java version >=7 to allow invokedynamic
107       mutableClass.classNode.version = Math.max(mutableClass.classNode.version, Opcodes.V1_7);
108 
109       instrumentMethods(mutableClass);
110 
111       // If there is no constructor, adds one
112       addNoArgsConstructor(mutableClass);
113 
114       addDirectCallConstructor(mutableClass);
115 
116       addRoboInitMethod(mutableClass);
117 
118       decorator.decorate(mutableClass);
119 
120       doSpecialHandling(mutableClass);
121     } catch (Exception e) {
122       throw new RuntimeException("failed to instrument " + mutableClass.getName(), e);
123     }
124   }
125 
instrumentMethods(MutableClass mutableClass)126   private void instrumentMethods(MutableClass mutableClass) {
127     for (MethodNode method : mutableClass.getMethods()) {
128       rewriteMethodBody(mutableClass, method);
129 
130       if (method.name.equals("<clinit>")) {
131         method.name = ShadowConstants.STATIC_INITIALIZER_METHOD_NAME;
132         mutableClass.addMethod(generateStaticInitializerNotifierMethod(mutableClass));
133       } else if (method.name.equals("<init>")) {
134         instrumentConstructor(mutableClass, method);
135       } else if (!isSyntheticAccessorMethod(method) && !Modifier.isAbstract(method.access)) {
136         instrumentNormalMethod(mutableClass, method);
137       }
138     }
139   }
140 
addNoArgsConstructor(MutableClass mutableClass)141   private void addNoArgsConstructor(MutableClass mutableClass) {
142     if (!mutableClass.foundMethods.contains("<init>()V")) {
143       MethodNode defaultConstructor = new MethodNode(Opcodes.ACC_PUBLIC, "<init>", "()V", "()V", null);
144       RobolectricGeneratorAdapter generator = new RobolectricGeneratorAdapter(defaultConstructor);
145       generator.loadThis();
146       generator.visitMethodInsn(Opcodes.INVOKESPECIAL, mutableClass.classNode.superName, "<init>", "()V", false);
147       generator.loadThis();
148       generator.invokeVirtual(mutableClass.classType, new Method(ROBO_INIT_METHOD_NAME, "()V"));
149       generator.returnValue();
150       mutableClass.addMethod(defaultConstructor);
151     }
152   }
153 
addDirectCallConstructor(MutableClass mutableClass)154   protected abstract void addDirectCallConstructor(MutableClass mutableClass);
155 
156   /**
157    * Generates code like this:
158    * ```java
159    * protected void $$robo$init() {
160    *   if (__robo_data__ == null) {
161    *     __robo_data__ = RobolectricInternals.initializing(this);
162    *   }
163    * }
164    * ```
165    */
addRoboInitMethod(MutableClass mutableClass)166   private void addRoboInitMethod(MutableClass mutableClass) {
167     MethodNode initMethodNode = new MethodNode(Opcodes.ACC_PROTECTED, ROBO_INIT_METHOD_NAME, "()V", null, null);
168     RobolectricGeneratorAdapter generator = new RobolectricGeneratorAdapter(initMethodNode);
169     Label alreadyInitialized = new Label();
170     generator.loadThis();                                         // this
171     generator.getField(mutableClass.classType, ShadowConstants.CLASS_HANDLER_DATA_FIELD_NAME, OBJECT_TYPE);  // contents of __robo_data__
172     generator.ifNonNull(alreadyInitialized);
173     generator.loadThis();                                         // this
174     generator.loadThis();                                         // this, this
175     writeCallToInitializing(mutableClass, generator);
176     // this, __robo_data__
177     generator.putField(mutableClass.classType, ShadowConstants.CLASS_HANDLER_DATA_FIELD_NAME, OBJECT_TYPE);
178     generator.mark(alreadyInitialized);
179     generator.returnValue();
180     mutableClass.addMethod(initMethodNode);
181   }
182 
writeCallToInitializing(MutableClass mutableClass, RobolectricGeneratorAdapter generator)183   protected abstract void writeCallToInitializing(MutableClass mutableClass, RobolectricGeneratorAdapter generator);
184 
doSpecialHandling(MutableClass mutableClass)185   private void doSpecialHandling(MutableClass mutableClass) {
186     if (mutableClass.getName().equals("android.os.Build$VERSION")) {
187       for (FieldNode fieldNode : mutableClass.getFields()) {
188         fieldNode.access &= ~(Modifier.FINAL);
189       }
190     }
191   }
192 
isSyntheticAccessorMethod(MethodNode method)193   private boolean isSyntheticAccessorMethod(MethodNode method) {
194     return (method.access & Opcodes.ACC_SYNTHETIC) != 0;
195   }
196 
197 
198   /**
199    * Constructors are instrumented as follows:
200    * # Code other than a call to the superclass constructor is moved to a new method named
201    *   `__constructor__` with the same signature.
202    * # The constructor is modified to call {@link ClassHandler#initializing(Object)} (or
203    *   {@link ClassHandler#getShadowCreator(Class)} for `invokedynamic` JVMs).
204    * # The constructor is modified to then call
205    *   {@link ClassHandler#methodInvoked(String, boolean, Class)} (or
206    *   {@link ClassHandler#findShadowMethodHandle(Class, String, MethodType, boolean)} for
207    *   `invokedynamic` JVMs) with the method name `__constructor__` and the same parameter types.
208    *
209    * Note that most code in the constructor will not be executed unless the {@link ClassHandler}
210    * arranges for it to happen.
211    *
212    * Given a constructor like this:
213    * ```java
214    * public ThisClass(String name, int size) {
215    *   super(name, someStaticMethod());
216    *   this.size = size;
217    * }
218    * ```
219    *
220    * ... generates code like this:
221    * ```java
222    * private $$robo$$__constructor__(String name, int size) {
223    *   this.size = size;
224    * }
225    *
226    * private __constructor__(String name, int size) {
227    *   Plan plan = RobolectricInternals.methodInvoked(
228    *       "pkg/ThisClass/__constructor__(Ljava/lang/String;I)V", true, ThisClass.class);
229    *   if (plan != null) {
230    *     try {
231    *       plan.run(this, new Object[] {name, size});
232    *     } catch (Throwable t) {
233    *       throw RobolectricInternals.cleanStackTrace(t);
234    *     }
235    *   } else {
236    *     $$robo$$__constructor__(name, size);
237    *   }
238    * }
239    *
240    * public ThisClass(String name, int size) {
241    *   super(name, someStaticMethod());
242    *   $$robo$init();
243    * }
244    * ```
245    *
246    * @param method the constructor to instrument
247    */
instrumentConstructor(MutableClass mutableClass, MethodNode method)248   private void instrumentConstructor(MutableClass mutableClass, MethodNode method) {
249     makeMethodPrivate(method);
250 
251     if (mutableClass.containsStubs) {
252       // method.instructions just throws a `stub!` exception, replace it with something anodyne...
253       method.instructions.clear();
254 
255       RobolectricGeneratorAdapter generator = new RobolectricGeneratorAdapter(method);
256       generator.loadThis();
257       generator.visitMethodInsn(Opcodes.INVOKESPECIAL, mutableClass.classNode.superName, "<init>", "()V", false);
258       generator.returnValue();
259       generator.endMethod();
260     }
261 
262     InsnList callSuper = extractCallToSuperConstructor(mutableClass, method);
263     method.name = directMethodName(mutableClass, ShadowConstants.CONSTRUCTOR_METHOD_NAME);
264     mutableClass.addMethod(redirectorMethod(mutableClass, method, ShadowConstants.CONSTRUCTOR_METHOD_NAME));
265 
266     String[] exceptions = exceptionArray(method);
267     MethodNode initMethodNode = new MethodNode(method.access, "<init>", method.desc, method.signature, exceptions);
268     makeMethodPublic(initMethodNode);
269     RobolectricGeneratorAdapter generator = new RobolectricGeneratorAdapter(initMethodNode);
270 
271     initMethodNode.instructions = callSuper;
272 
273     generator.loadThis();
274     generator.invokeVirtual(mutableClass.classType, new Method(ROBO_INIT_METHOD_NAME, "()V"));
275     generateClassHandlerCall(mutableClass, method, ShadowConstants.CONSTRUCTOR_METHOD_NAME, generator);
276 
277     generator.endMethod();
278     mutableClass.addMethod(initMethodNode);
279   }
280 
extractCallToSuperConstructor(MutableClass mutableClass, MethodNode ctor)281   private InsnList extractCallToSuperConstructor(MutableClass mutableClass, MethodNode ctor) {
282     InsnList removedInstructions = new InsnList();
283     int startIndex = 0;
284 
285     AbstractInsnNode[] insns = ctor.instructions.toArray();
286     for (int i = 0; i < insns.length; i++) {
287       AbstractInsnNode node = insns[i];
288 
289       switch (node.getOpcode()) {
290         case Opcodes.ALOAD:
291           VarInsnNode vnode = (VarInsnNode) node;
292           if (vnode.var == 0) {
293             startIndex = i;
294           }
295           break;
296 
297         case Opcodes.INVOKESPECIAL:
298           MethodInsnNode mnode = (MethodInsnNode) node;
299           if (mnode.owner.equals(mutableClass.internalClassName) || mnode.owner.equals(mutableClass.classNode.superName)) {
300             assert mnode.name.equals("<init>");
301 
302             // remove all instructions in the range startIndex..i, from aload_0 to invokespecial <init>
303             while (startIndex <= i) {
304               ctor.instructions.remove(insns[startIndex]);
305               removedInstructions.add(insns[startIndex]);
306               startIndex++;
307             }
308             return removedInstructions;
309           }
310           break;
311 
312         case Opcodes.ATHROW:
313           ctor.visitCode();
314           ctor.visitInsn(Opcodes.RETURN);
315           ctor.visitEnd();
316           return removedInstructions;
317 
318         default:
319           // nothing to do
320       }
321     }
322 
323     throw new RuntimeException("huh? " + ctor.name + ctor.desc);
324   }
325 
326   /**
327    * # Rename the method from `methodName` to `$$robo$$methodName`.
328    * # Make it private so we can invoke it directly without subclass overrides taking precedence.
329    * # Remove `final` and `native` modifiers, if present.
330    * # Create a delegator method named `methodName` which delegates to the {@link ClassHandler}.
331    */
instrumentNormalMethod(MutableClass mutableClass, MethodNode method)332   protected void instrumentNormalMethod(MutableClass mutableClass, MethodNode method) {
333     // if not abstract, set a final modifier
334     if ((method.access & Opcodes.ACC_ABSTRACT) == 0) {
335       method.access = method.access | Opcodes.ACC_FINAL;
336     }
337     // if a native method, remove native modifier and force return a default value
338     if ((method.access & Opcodes.ACC_NATIVE) != 0) {
339       method.access = method.access & ~Opcodes.ACC_NATIVE;
340 
341       RobolectricGeneratorAdapter generator = new RobolectricGeneratorAdapter(method);
342       Type returnType = generator.getReturnType();
343       generator.pushDefaultReturnValueToStack(returnType);
344       generator.returnValue();
345     }
346 
347     // todo figure out
348     String originalName = method.name;
349     method.name = directMethodName(mutableClass, originalName);
350 
351     MethodNode delegatorMethodNode = new MethodNode(method.access, originalName, method.desc, method.signature, exceptionArray(method));
352     delegatorMethodNode.visibleAnnotations = method.visibleAnnotations;
353     delegatorMethodNode.access &= ~(Opcodes.ACC_NATIVE | Opcodes.ACC_ABSTRACT | Opcodes.ACC_FINAL);
354 
355     makeMethodPrivate(method);
356 
357     RobolectricGeneratorAdapter generator = new RobolectricGeneratorAdapter(delegatorMethodNode);
358     generateClassHandlerCall(mutableClass, method, originalName, generator);
359     generator.endMethod();
360     mutableClass.addMethod(delegatorMethodNode);
361   }
362 
directMethodName(MutableClass mutableClass, String originalName)363   private String directMethodName(MutableClass mutableClass, String originalName) {
364     return SHADOW_IMPL.directMethodName(mutableClass.getName(), originalName);
365   }
366 
367   //todo rename
redirectorMethod(MutableClass mutableClass, MethodNode method, String newName)368   private MethodNode redirectorMethod(MutableClass mutableClass, MethodNode method, String newName) {
369     MethodNode redirector = new MethodNode(Opcodes.ASM4, newName, method.desc, method.signature, exceptionArray(method));
370     redirector.access = method.access & ~(Opcodes.ACC_NATIVE | Opcodes.ACC_ABSTRACT | Opcodes.ACC_FINAL);
371     makeMethodPrivate(redirector);
372     RobolectricGeneratorAdapter generator = new RobolectricGeneratorAdapter(redirector);
373     generator.invokeMethod(mutableClass.internalClassName, method);
374     generator.returnValue();
375     return redirector;
376   }
377 
exceptionArray(MethodNode method)378   private String[] exceptionArray(MethodNode method) {
379     List<String> exceptions = method.exceptions;
380     return exceptions.toArray(new String[exceptions.size()]);
381   }
382 
383   /**
384    * Filters methods that might need special treatment because of various reasons
385    */
rewriteMethodBody(MutableClass mutableClass, MethodNode callingMethod)386   private void rewriteMethodBody(MutableClass mutableClass, MethodNode callingMethod) {
387     ListIterator<AbstractInsnNode> instructions = callingMethod.instructions.iterator();
388     while (instructions.hasNext()) {
389       AbstractInsnNode node = instructions.next();
390 
391       switch (node.getOpcode()) {
392         case Opcodes.NEW:
393           TypeInsnNode newInsnNode = (TypeInsnNode) node;
394           newInsnNode.desc = mutableClass.config.mappedTypeName(newInsnNode.desc);
395           break;
396 
397         case Opcodes.GETFIELD:
398           /* falls through */
399         case Opcodes.PUTFIELD:
400           /* falls through */
401         case Opcodes.GETSTATIC:
402           /* falls through */
403         case Opcodes.PUTSTATIC:
404           FieldInsnNode fieldInsnNode = (FieldInsnNode) node;
405           fieldInsnNode.desc = mutableClass.config.mappedTypeName(fieldInsnNode.desc); // todo test
406           break;
407 
408         case Opcodes.INVOKESTATIC:
409           /* falls through */
410         case Opcodes.INVOKEINTERFACE:
411           /* falls through */
412         case Opcodes.INVOKESPECIAL:
413           /* falls through */
414         case Opcodes.INVOKEVIRTUAL:
415           MethodInsnNode targetMethod = (MethodInsnNode) node;
416           targetMethod.desc = mutableClass.config.remapParams(targetMethod.desc);
417           if (isGregorianCalendarBooleanConstructor(targetMethod)) {
418             replaceGregorianCalendarBooleanConstructor(instructions, targetMethod);
419           } else if (mutableClass.config.shouldIntercept(targetMethod)) {
420             interceptInvokeVirtualMethod(mutableClass, instructions, targetMethod);
421           }
422           break;
423 
424         case Opcodes.INVOKEDYNAMIC:
425           /* no unusual behavior */
426           break;
427 
428         default:
429           break;
430       }
431     }
432   }
433 
434   /**
435    * Verifies if the @targetMethod is a `<init>(boolean)` constructor for
436    * {@link java.util.GregorianCalendar}.
437    */
isGregorianCalendarBooleanConstructor(MethodInsnNode targetMethod)438   private boolean isGregorianCalendarBooleanConstructor(MethodInsnNode targetMethod) {
439     return targetMethod.owner.equals("java/util/GregorianCalendar") &&
440         targetMethod.name.equals("<init>") &&
441         targetMethod.desc.equals("(Z)V");
442   }
443 
444   /**
445    * Replaces the void `<init>(boolean)` constructor for a call to the
446    * `void <init>(int, int, int)` one.
447    */
replaceGregorianCalendarBooleanConstructor(ListIterator<AbstractInsnNode> instructions, MethodInsnNode targetMethod)448   private void replaceGregorianCalendarBooleanConstructor(ListIterator<AbstractInsnNode> instructions, MethodInsnNode targetMethod) {
449     // Remove the call to GregorianCalendar(boolean)
450     instructions.remove();
451 
452     // Discard the already-pushed parameter for GregorianCalendar(boolean)
453     instructions.add(new InsnNode(Opcodes.POP));
454 
455     // Add parameters values for calling GregorianCalendar(int, int, int)
456     instructions.add(new InsnNode(Opcodes.ICONST_0));
457     instructions.add(new InsnNode(Opcodes.ICONST_0));
458     instructions.add(new InsnNode(Opcodes.ICONST_0));
459 
460     // Call GregorianCalendar(int, int, int)
461     instructions.add(new MethodInsnNode(Opcodes.INVOKESPECIAL, targetMethod.owner, targetMethod.name, "(III)V", targetMethod.itf));
462   }
463 
464   /**
465    * Decides to call through the appropriate method to intercept the method with an INVOKEVIRTUAL
466    * Opcode, depending if the invokedynamic bytecode instruction is available (Java 7+).
467    */
interceptInvokeVirtualMethod( MutableClass mutableClass, ListIterator<AbstractInsnNode> instructions, MethodInsnNode targetMethod)468   protected abstract void interceptInvokeVirtualMethod(
469       MutableClass mutableClass, ListIterator<AbstractInsnNode> instructions,
470       MethodInsnNode targetMethod);
471 
472   /**
473    * Replaces protected and private class modifiers with public.
474    */
makeClassPublic(ClassNode clazz)475   private void makeClassPublic(ClassNode clazz) {
476     clazz.access = (clazz.access | Opcodes.ACC_PUBLIC) & ~(Opcodes.ACC_PROTECTED | Opcodes.ACC_PRIVATE);
477   }
478 
479   /**
480    * Replaces protected and private method modifiers with public.
481    */
makeMethodPublic(MethodNode method)482   protected void makeMethodPublic(MethodNode method) {
483     method.access = (method.access | Opcodes.ACC_PUBLIC) & ~(Opcodes.ACC_PROTECTED | Opcodes.ACC_PRIVATE);
484   }
485 
486   /**
487    * Replaces protected and public class modifiers with private.
488    */
makeMethodPrivate(MethodNode method)489   private void makeMethodPrivate(MethodNode method) {
490     method.access = (method.access | Opcodes.ACC_PRIVATE) & ~(Opcodes.ACC_PUBLIC | Opcodes.ACC_PROTECTED);
491   }
492 
generateStaticInitializerNotifierMethod(MutableClass mutableClass)493   private MethodNode generateStaticInitializerNotifierMethod(MutableClass mutableClass) {
494     MethodNode methodNode = new MethodNode(Opcodes.ACC_STATIC, "<clinit>", "()V", "()V", null);
495     RobolectricGeneratorAdapter generator = new RobolectricGeneratorAdapter(methodNode);
496     generator.push(mutableClass.classType);
497     generator.invokeStatic(Type.getType(RobolectricInternals.class), new Method("classInitializing", "(Ljava/lang/Class;)V"));
498     generator.returnValue();
499     generator.endMethod();
500     return methodNode;
501   }
502 
503   // todo javadocs
generateClassHandlerCall(MutableClass mutableClass, MethodNode originalMethod, String originalMethodName, RobolectricGeneratorAdapter generator)504   protected abstract void generateClassHandlerCall(MutableClass mutableClass,
505       MethodNode originalMethod, String originalMethodName, RobolectricGeneratorAdapter generator);
506 
getTag(MethodNode m)507   int getTag(MethodNode m) {
508     return Modifier.isStatic(m.access) ? Opcodes.H_INVOKESTATIC : Opcodes.H_INVOKESPECIAL;
509   }
510 
511   public interface Decorator {
decorate(MutableClass mutableClass)512     void decorate(MutableClass mutableClass);
513 
decorateMethodPreClassHandler(MutableClass mutableClass, MethodNode originalMethod, String originalMethodName, RobolectricGeneratorAdapter generator)514     void decorateMethodPreClassHandler(MutableClass mutableClass, MethodNode originalMethod,
515         String originalMethodName, RobolectricGeneratorAdapter generator);
516   }
517 
518   /**
519    * Provides try/catch code generation with a {@link org.objectweb.asm.commons.GeneratorAdapter}.
520    */
521   static class TryCatch {
522     private final Label start;
523     private final Label end;
524     private final Label handler;
525     private final GeneratorAdapter generatorAdapter;
526 
TryCatch(GeneratorAdapter generatorAdapter, Type type)527     TryCatch(GeneratorAdapter generatorAdapter, Type type) {
528       this.generatorAdapter = generatorAdapter;
529       this.start = generatorAdapter.mark();
530       this.end = new Label();
531       this.handler = new Label();
532       generatorAdapter.visitTryCatchBlock(start, end, handler, type.getInternalName());
533     }
534 
end()535     void end() {
536       generatorAdapter.mark(end);
537     }
538 
handler()539     void handler() {
540       generatorAdapter.mark(handler);
541     }
542   }
543 }
544