• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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