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