1 // Copyright 2017 The Bazel Authors. All rights reserved. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 package com.google.devtools.build.android.desugar; 15 16 import static com.google.common.base.Preconditions.checkArgument; 17 import static com.google.common.base.Preconditions.checkNotNull; 18 import static com.google.common.base.Preconditions.checkState; 19 20 import com.google.devtools.build.android.desugar.io.BitFlags; 21 import com.google.devtools.build.android.desugar.io.FieldInfo; 22 import java.lang.reflect.Method; 23 import javax.annotation.Nullable; 24 import org.objectweb.asm.AnnotationVisitor; 25 import org.objectweb.asm.ClassVisitor; 26 import org.objectweb.asm.FieldVisitor; 27 import org.objectweb.asm.MethodVisitor; 28 import org.objectweb.asm.Opcodes; 29 import org.objectweb.asm.Type; 30 import org.objectweb.asm.TypePath; 31 32 /** 33 * Visitor that moves methods with bodies from interfaces into a companion class and rewrites call 34 * sites accordingly (which is only needed for static interface methods). Default methods are kept 35 * as abstract methods with all their annotations. 36 * 37 * <p>Any necessary companion classes will be added to the given {@link GeneratedClassStore}. It's 38 * the caller's responsibility to write those out. 39 * 40 * <p>Relies on {@link DefaultMethodClassFixer} to stub in method bodies for moved default methods. 41 * Assumes that lambdas are already desugared. Ignores bridge methods, which are handled specially. 42 */ 43 class InterfaceDesugaring extends ClassVisitor { 44 45 static final String COMPANION_METHOD_TO_TRIGGER_INTERFACE_CLINIT_NAME = "$$triggerInterfaceInit"; 46 static final String COMPANION_METHOD_TO_TRIGGER_INTERFACE_CLINIT_DESC = "()V"; 47 48 static final String INTERFACE_STATIC_COMPANION_METHOD_SUFFIX = "$$STATIC$$"; 49 50 private final ClassVsInterface interfaceCache; 51 private final DependencyCollector depsCollector; 52 private final CoreLibrarySupport coreLibrarySupport; 53 private final ClassReaderFactory bootclasspath; 54 private final ClassLoader targetLoader; 55 private final GeneratedClassStore store; 56 private final boolean legacyJaCoCo; 57 58 private String internalName; 59 private int bytecodeVersion; 60 private int accessFlags; 61 private int numberOfDefaultMethods; 62 @Nullable private ClassVisitor companion; 63 @Nullable private FieldInfo interfaceFieldToAccessInCompanionMethodToTriggerInterfaceClinit; 64 InterfaceDesugaring( ClassVisitor dest, ClassVsInterface interfaceCache, DependencyCollector depsCollector, @Nullable CoreLibrarySupport coreLibrarySupport, ClassReaderFactory bootclasspath, ClassLoader targetLoader, GeneratedClassStore store, boolean legacyJaCoCo)65 public InterfaceDesugaring( 66 ClassVisitor dest, 67 ClassVsInterface interfaceCache, 68 DependencyCollector depsCollector, 69 @Nullable CoreLibrarySupport coreLibrarySupport, 70 ClassReaderFactory bootclasspath, 71 ClassLoader targetLoader, 72 GeneratedClassStore store, 73 boolean legacyJaCoCo) { 74 super(Opcodes.ASM6, dest); 75 this.interfaceCache = interfaceCache; 76 this.depsCollector = depsCollector; 77 this.coreLibrarySupport = coreLibrarySupport; 78 this.bootclasspath = bootclasspath; 79 this.targetLoader = targetLoader; 80 this.store = store; 81 this.legacyJaCoCo = legacyJaCoCo; 82 } 83 84 @Override visit( int version, int access, String name, String signature, String superName, String[] interfaces)85 public void visit( 86 int version, 87 int access, 88 String name, 89 String signature, 90 String superName, 91 String[] interfaces) { 92 companion = null; 93 numberOfDefaultMethods = 0; 94 internalName = name; 95 bytecodeVersion = version; 96 accessFlags = access; 97 if (isInterface()) { 98 interfaceCache.addKnownInterfaces(name); 99 // Record interface hierarchy. This helps avoid parsing .class files when double-checking 100 // desugaring results later using collected dependency information. 101 depsCollector.recordExtendedInterfaces(name, interfaces); 102 } else { 103 interfaceCache.addKnownClass(name); 104 } 105 interfaceCache.addKnownClass(superName).addKnownInterfaces(interfaces); 106 super.visit(version, access, name, signature, superName, interfaces); 107 } 108 109 @Override visitEnd()110 public void visitEnd() { 111 if (companion != null) { 112 // Record classes with default methods. This increases precision when double-checking 113 // desugaring results later, without parsing .class files again, compared to just looking 114 // for companion classes in a given desugared Jar which may only contain static methods. 115 depsCollector.recordDefaultMethods(internalName, numberOfDefaultMethods); 116 117 // Emit a method to access the fields of the interfaces that need initialization. 118 emitInterfaceFieldAccessInCompanionMethodToTriggerInterfaceClinit(); 119 companion.visitEnd(); 120 } 121 super.visitEnd(); 122 } 123 emitInterfaceFieldAccessInCompanionMethodToTriggerInterfaceClinit()124 private void emitInterfaceFieldAccessInCompanionMethodToTriggerInterfaceClinit() { 125 if (companion == null 126 || interfaceFieldToAccessInCompanionMethodToTriggerInterfaceClinit == null) { 127 return; 128 } 129 130 // Create a method to access the interface fields 131 MethodVisitor visitor = 132 checkNotNull( 133 companion.visitMethod( 134 Opcodes.ACC_STATIC | Opcodes.ACC_PUBLIC, 135 COMPANION_METHOD_TO_TRIGGER_INTERFACE_CLINIT_NAME, 136 COMPANION_METHOD_TO_TRIGGER_INTERFACE_CLINIT_DESC, 137 null, 138 null), 139 "Cannot get a method visitor to write out %s to the companion class.", 140 COMPANION_METHOD_TO_TRIGGER_INTERFACE_CLINIT_NAME); 141 // Visit the interface field to triger <clinit> of the interface. 142 143 visitor.visitFieldInsn( 144 Opcodes.GETSTATIC, 145 interfaceFieldToAccessInCompanionMethodToTriggerInterfaceClinit.owner(), 146 interfaceFieldToAccessInCompanionMethodToTriggerInterfaceClinit.name(), 147 interfaceFieldToAccessInCompanionMethodToTriggerInterfaceClinit.desc()); 148 Type fieldType = 149 Type.getType(interfaceFieldToAccessInCompanionMethodToTriggerInterfaceClinit.desc()); 150 if (fieldType.getSort() == Type.LONG || fieldType.getSort() == Type.DOUBLE) { 151 visitor.visitInsn(Opcodes.POP2); 152 } else { 153 visitor.visitInsn(Opcodes.POP); 154 } 155 visitor.visitInsn(Opcodes.RETURN); 156 } 157 158 @Override visitField( int access, String name, String desc, String signature, Object value)159 public FieldVisitor visitField( 160 int access, String name, String desc, String signature, Object value) { 161 if (legacyJaCoCo 162 && isInterface() 163 && BitFlags.isSet(access, Opcodes.ACC_FINAL) 164 && "$jacocoData".equals(name)) { 165 // Move $jacocoData field to companion class and remove final modifier. We'll rewrite field 166 // accesses accordingly. Code generated by older JaCoCo versions tried to assign to this 167 // final field in methods, and interface fields have to be private, so we move the field 168 // to a class, which ends up looking pretty similar to what JaCoCo generates for classes. 169 access &= ~Opcodes.ACC_FINAL; 170 return companion().visitField(access, name, desc, signature, value); 171 } else { 172 return super.visitField(access, name, desc, signature, value); 173 } 174 } 175 176 @Override visitMethod( int access, String name, String desc, String signature, String[] exceptions)177 public MethodVisitor visitMethod( 178 int access, String name, String desc, String signature, String[] exceptions) { 179 String codeOwner = internalName; 180 MethodVisitor result; 181 if (isInterface() && isStaticInitializer(name)) { 182 result = 183 new InterfaceFieldWriteCollector( 184 super.visitMethod(access, name, desc, signature, exceptions)); 185 if (result != null && legacyJaCoCo) { 186 result = new MoveJacocoFieldAccess(result); 187 } 188 } else if (isInterface() 189 && BitFlags.noneSet(access, Opcodes.ACC_ABSTRACT | Opcodes.ACC_BRIDGE)) { 190 checkArgument(BitFlags.noneSet(access, Opcodes.ACC_NATIVE), "Forbidden per JLS ch 9.4"); 191 192 boolean isLambdaBody = 193 name.startsWith("lambda$") && BitFlags.isSynthetic(access); 194 if (isLambdaBody) { 195 access &= ~Opcodes.ACC_PUBLIC; // undo visibility change from LambdaDesugaring 196 } 197 name = normalizeInterfaceMethodName(name, isLambdaBody, BitFlags.isStatic(access)); 198 codeOwner = getCompanionClassName(internalName); 199 200 if (BitFlags.isStatic(access)) { 201 // Completely move static interface methods, which requires rewriting call sites 202 result = 203 companion() 204 .visitMethod(access & ~Opcodes.ACC_PRIVATE, name, desc, signature, exceptions); 205 } else { 206 MethodVisitor abstractDest; 207 if (isLambdaBody) { 208 // Completely move lambda bodies, which requires rewriting call sites 209 access &= ~Opcodes.ACC_PRIVATE; 210 abstractDest = null; 211 } else { 212 // Make default methods abstract but move their implementation into a static method with 213 // corresponding signature. Doesn't require callsite rewriting but implementing classes 214 // may need to implement default methods explicitly. 215 checkArgument( 216 BitFlags.noneSet(access, Opcodes.ACC_PRIVATE), 217 "Unexpected private interface method %s.%s : %s", 218 name, 219 internalName, 220 desc); 221 ++numberOfDefaultMethods; 222 if (coreLibrarySupport != null) { 223 coreLibrarySupport.registerIfEmulatedCoreInterface( 224 access, internalName, name, desc, exceptions); 225 } 226 abstractDest = 227 super.visitMethod(access | Opcodes.ACC_ABSTRACT, name, desc, signature, exceptions); 228 } 229 230 // TODO(b/37110951): adjust signature with explicit receiver type, which may be generic 231 MethodVisitor codeDest = 232 companion() 233 .visitMethod( 234 access | Opcodes.ACC_STATIC, 235 name, 236 companionDefaultMethodDescriptor(internalName, desc), 237 (String) null, // drop signature, since given one doesn't include the new param 238 exceptions); 239 240 result = abstractDest != null ? new MultiplexAnnotations(codeDest, abstractDest) : codeDest; 241 } 242 if (result != null && legacyJaCoCo) { 243 result = new MoveJacocoFieldAccess(result); 244 } 245 } else { 246 result = super.visitMethod(access, name, desc, signature, exceptions); 247 } 248 return result != null 249 ? new InterfaceInvocationRewriter( 250 result, 251 isInterface() ? internalName : null, 252 bootclasspath, 253 targetLoader, 254 depsCollector, 255 codeOwner) 256 : null; 257 } 258 259 @Override visitOuterClass(String owner, String name, String desc)260 public void visitOuterClass(String owner, String name, String desc) { 261 // Proguard gets grumpy if an outer method doesn't exist, which can be the result of moving 262 // interface methods to companion classes (b/68260836). In that case (for which we need to 263 // figure out if "owner" is an interface) need to adjust the outer method information. 264 if (name != null && interfaceCache.isOuterInterface(owner, internalName)) { 265 // Just drop outer method info. That's unfortunate, but the only alternative would be to 266 // change the outer method to point to the companion class, which would mean the 267 // reflection methods that use this information would return a companion ($$CC) class name 268 // as well as a possibly-modified method name and signature, so it seems better to return 269 // the correct original interface name and no method information. Doing this also saves 270 // us from doing even more work to figure out whether the method is static and a lambda 271 // method, which we'd need to known to adjust name and descriptor correctly. 272 name = null; 273 desc = null; 274 } // otherwise there's no enclosing method that could've been moved, or owner is a class 275 super.visitOuterClass(owner, name, desc); 276 } 277 isInterface()278 private boolean isInterface() { 279 return BitFlags.isInterface(accessFlags); 280 } 281 isStaticInitializer(String methodName)282 private static boolean isStaticInitializer(String methodName) { 283 return "<clinit>".equals(methodName); 284 } 285 normalizeInterfaceMethodName(String name, boolean isLambda, boolean isStatic)286 static String normalizeInterfaceMethodName(String name, boolean isLambda, boolean isStatic) { 287 if (isLambda) { 288 // Rename lambda method to reflect the new owner. Not doing so confuses LambdaDesugaring 289 // if it's run over this class again. LambdaDesugaring has already renamed the method from 290 // its original name to include the interface name at this point. 291 return name + DependencyCollector.INTERFACE_COMPANION_SUFFIX; 292 } else if (isStatic) { 293 return name + INTERFACE_STATIC_COMPANION_METHOD_SUFFIX; 294 } else { 295 return name; 296 } 297 } 298 getCompanionClassName(String interfaceName)299 static String getCompanionClassName(String interfaceName) { 300 return interfaceName + DependencyCollector.INTERFACE_COMPANION_SUFFIX; 301 } 302 303 /** 304 * Returns the descriptor of a static method for an instance method with the given receiver and 305 * description, simply by pre-pending the given descriptor's parameter list with the given 306 * receiver type. 307 */ companionDefaultMethodDescriptor(String interfaceName, String desc)308 static String companionDefaultMethodDescriptor(String interfaceName, String desc) { 309 Type type = Type.getMethodType(desc); 310 Type[] companionArgs = new Type[type.getArgumentTypes().length + 1]; 311 companionArgs[0] = Type.getObjectType(interfaceName); 312 System.arraycopy(type.getArgumentTypes(), 0, companionArgs, 1, type.getArgumentTypes().length); 313 return Type.getMethodDescriptor(type.getReturnType(), companionArgs); 314 } 315 companion()316 private ClassVisitor companion() { 317 if (companion == null) { 318 checkState(isInterface()); 319 String companionName = getCompanionClassName(internalName); 320 321 companion = store.add(companionName); 322 companion.visit( 323 bytecodeVersion, 324 // Companion class must be public so moved methods can be called from anywhere 325 (accessFlags | Opcodes.ACC_SYNTHETIC | Opcodes.ACC_PUBLIC) & ~Opcodes.ACC_INTERFACE, 326 companionName, 327 (String) null, // signature 328 "java/lang/Object", 329 new String[0]); 330 } 331 return companion; 332 } 333 334 /** 335 * Interface field scanner to get the first field of the current interface that is written in the 336 * initializer. 337 */ 338 private class InterfaceFieldWriteCollector extends MethodVisitor { 339 InterfaceFieldWriteCollector(MethodVisitor mv)340 public InterfaceFieldWriteCollector(MethodVisitor mv) { 341 super(Opcodes.ASM6, mv); 342 } 343 344 @Override visitFieldInsn(int opcode, String owner, String name, String desc)345 public void visitFieldInsn(int opcode, String owner, String name, String desc) { 346 if (interfaceFieldToAccessInCompanionMethodToTriggerInterfaceClinit == null 347 && opcode == Opcodes.PUTSTATIC 348 && owner.equals(internalName)) { 349 // It is possible that an interface initializer can sets fields of other classes. 350 // (b/64290760), so we test whether the owner is the same as the internalName. 351 interfaceFieldToAccessInCompanionMethodToTriggerInterfaceClinit = 352 FieldInfo.create(owner, name, desc); 353 } 354 super.visitFieldInsn(opcode, owner, name, desc); 355 } 356 } 357 358 /** 359 * Rewriter for calls to static interface methods and super calls to default methods, unless 360 * they're part of the bootclasspath, as well as all lambda body methods. Keeps calls to interface 361 * methods declared in the bootclasspath as-is (but note that these would presumably fail on 362 * devices without those methods). 363 */ 364 static class InterfaceInvocationRewriter extends MethodVisitor { 365 366 /** 367 * If we're visiting a method declared in an interface, the internal name of that interface. 368 * That lets us rewrite invocations of other methods within that interface even if the bytecode 369 * fails to indicate them as interface method invocations, as older versions of JaCoCo failed to 370 * do (b/62623509). 371 */ 372 @Nullable private final String interfaceName; 373 374 private final ClassReaderFactory bootclasspath; 375 private final ClassLoader targetLoader; 376 private final DependencyCollector depsCollector; 377 /** Internal name that'll be used to record any dependencies on interface methods. */ 378 private final String declaringClass; 379 InterfaceInvocationRewriter( MethodVisitor dest, @Nullable String knownInterfaceName, ClassReaderFactory bootclasspath, ClassLoader targetLoader, DependencyCollector depsCollector, String declaringClass)380 public InterfaceInvocationRewriter( 381 MethodVisitor dest, 382 @Nullable String knownInterfaceName, 383 ClassReaderFactory bootclasspath, 384 ClassLoader targetLoader, 385 DependencyCollector depsCollector, 386 String declaringClass) { 387 super(Opcodes.ASM6, dest); 388 this.interfaceName = knownInterfaceName; 389 this.bootclasspath = bootclasspath; 390 this.targetLoader = targetLoader; 391 this.depsCollector = depsCollector; 392 this.declaringClass = declaringClass; 393 } 394 395 @Override visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf)396 public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) { 397 // Assume that any static interface methods on the classpath are moved 398 if ((itf || owner.equals(interfaceName)) && !bootclasspath.isKnown(owner)) { 399 boolean isLambda = name.startsWith("lambda$"); 400 name = normalizeInterfaceMethodName(name, isLambda, opcode == Opcodes.INVOKESTATIC); 401 if (isLambda) { 402 // Redirect lambda invocations to completely remove all lambda methods from interfaces. 403 checkArgument(!owner.endsWith(DependencyCollector.INTERFACE_COMPANION_SUFFIX), 404 "shouldn't consider %s an interface", owner); 405 if (opcode == Opcodes.INVOKEINTERFACE) { 406 opcode = Opcodes.INVOKESTATIC; 407 desc = companionDefaultMethodDescriptor(owner, desc); 408 } else { 409 checkArgument( 410 opcode == Opcodes.INVOKESTATIC, 411 "Unexpected opcode %s to invoke %s.%s", 412 opcode, 413 owner, 414 name); 415 } 416 // Reflect that InterfaceDesugaring moves and renames the lambda body method 417 owner += DependencyCollector.INTERFACE_COMPANION_SUFFIX; 418 itf = false; 419 // Record dependency on companion class 420 depsCollector.assumeCompanionClass(declaringClass, owner); 421 422 String expectedLambdaMethodName = LambdaDesugaring.uniqueInPackage(owner, name); 423 checkState( 424 name.equals(expectedLambdaMethodName), 425 "Unexpected lambda body method name for %s: real=%s, expected=%s", 426 owner, 427 name, 428 expectedLambdaMethodName); 429 } else if ((opcode == Opcodes.INVOKESTATIC || opcode == Opcodes.INVOKESPECIAL)) { 430 checkArgument(!owner.endsWith(DependencyCollector.INTERFACE_COMPANION_SUFFIX), 431 "shouldn't consider %s an interface", owner); 432 if (opcode == Opcodes.INVOKESPECIAL) { 433 // Turn Interface.super.m() into DefiningInterface$$CC.m(receiver). Note that owner 434 // always refers to the current type's immediate super-interface, but the default method 435 // may be inherited by that interface, so we have to figure out where the method is 436 // defined and invoke it in the corresponding companion class (b/73355452). Note that 437 // we're always dealing with interfaces here, and all interface methods are public, 438 // so using Class.getMethods should suffice to find inherited methods. Also note this 439 // can only be a default method invocation, no abstract method invocation. 440 owner = 441 findDefaultMethod(owner, name, desc) 442 .getDeclaringClass().getName().replace('.', '/'); 443 opcode = Opcodes.INVOKESTATIC; 444 desc = companionDefaultMethodDescriptor(owner, desc); 445 } 446 owner += DependencyCollector.INTERFACE_COMPANION_SUFFIX; 447 itf = false; 448 // Record dependency on companion class 449 depsCollector.assumeCompanionClass(declaringClass, owner); 450 } 451 } 452 super.visitMethodInsn(opcode, owner, name, desc, itf); 453 } 454 findDefaultMethod(String owner, String name, String desc)455 private Method findDefaultMethod(String owner, String name, String desc) { 456 try { 457 Class<?> clazz = targetLoader.loadClass(owner.replace('/', '.')); 458 // otherwise getting public methods with getMethods() below isn't enough 459 checkArgument(clazz.isInterface(), "Not an interface: %s", owner); 460 for (Method m : clazz.getMethods()) { 461 if (m.getName().equals(name) && Type.getMethodDescriptor(m).equals(desc)) { 462 checkState(m.isDefault(), "Found non-default method: %s", m); 463 return m; 464 } 465 } 466 } catch (ClassNotFoundException e) { 467 throw new IllegalStateException("Couldn't load " + owner, e); 468 } 469 throw new IllegalArgumentException("Method not found: " + owner + "." + name + desc); 470 } 471 } 472 473 /** 474 * Method visitor intended for interface method bodies that rewrites jacoco field accesses to 475 * expect the field in the companion class, to work around problematic bytecode emitted by older 476 * JaCoCo versions (b/62623509). 477 */ 478 private static class MoveJacocoFieldAccess extends MethodVisitor { 479 MoveJacocoFieldAccess(MethodVisitor mv)480 public MoveJacocoFieldAccess(MethodVisitor mv) { 481 super(Opcodes.ASM6, mv); 482 } 483 484 @Override visitFieldInsn(int opcode, String owner, String name, String desc)485 public void visitFieldInsn(int opcode, String owner, String name, String desc) { 486 if ("$jacocoData".equals(name)) { 487 checkState(!owner.endsWith(DependencyCollector.INTERFACE_COMPANION_SUFFIX), 488 "Expected interface: %s", owner); 489 owner = getCompanionClassName(owner); 490 } 491 super.visitFieldInsn(opcode, owner, name, desc); 492 } 493 } 494 495 /** 496 * Method visitor that behaves like a passthrough but additionally duplicates all annotations into 497 * a second given {@link MethodVisitor}. 498 */ 499 private static class MultiplexAnnotations extends MethodVisitor { 500 501 private final MethodVisitor annotationOnlyDest; 502 MultiplexAnnotations(@ullable MethodVisitor dest, MethodVisitor annotationOnlyDest)503 public MultiplexAnnotations(@Nullable MethodVisitor dest, MethodVisitor annotationOnlyDest) { 504 super(Opcodes.ASM6, dest); 505 this.annotationOnlyDest = annotationOnlyDest; 506 } 507 508 @Override visitParameter(String name, int access)509 public void visitParameter(String name, int access) { 510 super.visitParameter(name, access); 511 annotationOnlyDest.visitParameter(name, access); 512 } 513 514 @Override visitAnnotation(String desc, boolean visible)515 public AnnotationVisitor visitAnnotation(String desc, boolean visible) { 516 AnnotationVisitor dest = super.visitAnnotation(desc, visible); 517 AnnotationVisitor annoDest = annotationOnlyDest.visitAnnotation(desc, visible); 518 return new MultiplexAnnotationVisitor(dest, annoDest); 519 } 520 521 @Override visitTypeAnnotation( int typeRef, TypePath typePath, String desc, boolean visible)522 public AnnotationVisitor visitTypeAnnotation( 523 int typeRef, TypePath typePath, String desc, boolean visible) { 524 AnnotationVisitor dest = super.visitTypeAnnotation(typeRef, typePath, desc, visible); 525 AnnotationVisitor annoDest = 526 annotationOnlyDest.visitTypeAnnotation(typeRef, typePath, desc, visible); 527 return new MultiplexAnnotationVisitor(dest, annoDest); 528 } 529 530 @Override visitParameterAnnotation(int parameter, String desc, boolean visible)531 public AnnotationVisitor visitParameterAnnotation(int parameter, String desc, boolean visible) { 532 AnnotationVisitor dest = super.visitParameterAnnotation(parameter, desc, visible); 533 AnnotationVisitor annoDest = 534 annotationOnlyDest.visitParameterAnnotation(parameter, desc, visible); 535 return new MultiplexAnnotationVisitor(dest, annoDest); 536 } 537 } 538 539 /** 540 * Annotation visitor that recursively passes the visited annotations to any number of given 541 * {@link AnnotationVisitor}s. 542 */ 543 private static class MultiplexAnnotationVisitor extends AnnotationVisitor { 544 545 private final AnnotationVisitor[] moreDestinations; 546 MultiplexAnnotationVisitor( @ullable AnnotationVisitor dest, AnnotationVisitor... moreDestinations)547 public MultiplexAnnotationVisitor( 548 @Nullable AnnotationVisitor dest, AnnotationVisitor... moreDestinations) { 549 super(Opcodes.ASM6, dest); 550 this.moreDestinations = moreDestinations; 551 } 552 553 @Override visit(String name, Object value)554 public void visit(String name, Object value) { 555 super.visit(name, value); 556 for (AnnotationVisitor dest : moreDestinations) { 557 dest.visit(name, value); 558 } 559 } 560 561 @Override visitEnum(String name, String desc, String value)562 public void visitEnum(String name, String desc, String value) { 563 super.visitEnum(name, desc, value); 564 for (AnnotationVisitor dest : moreDestinations) { 565 dest.visitEnum(name, desc, value); 566 } 567 } 568 569 @Override visitAnnotation(String name, String desc)570 public AnnotationVisitor visitAnnotation(String name, String desc) { 571 AnnotationVisitor[] subVisitors = new AnnotationVisitor[moreDestinations.length]; 572 AnnotationVisitor dest = super.visitAnnotation(name, desc); 573 for (int i = 0; i < subVisitors.length; ++i) { 574 subVisitors[i] = moreDestinations[i].visitAnnotation(name, desc); 575 } 576 return new MultiplexAnnotationVisitor(dest, subVisitors); 577 } 578 579 @Override visitArray(String name)580 public AnnotationVisitor visitArray(String name) { 581 AnnotationVisitor[] subVisitors = new AnnotationVisitor[moreDestinations.length]; 582 AnnotationVisitor dest = super.visitArray(name); 583 for (int i = 0; i < subVisitors.length; ++i) { 584 subVisitors[i] = moreDestinations[i].visitArray(name); 585 } 586 return new MultiplexAnnotationVisitor(dest, subVisitors); 587 } 588 589 @Override visitEnd()590 public void visitEnd() { 591 super.visitEnd(); 592 for (AnnotationVisitor dest : moreDestinations) { 593 dest.visitEnd(); 594 } 595 } 596 } 597 } 598