Home | History | Annotate | Download | only in desugar
      1 // Copyright 2018 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 import static java.util.stream.Stream.concat;
     20 
     21 import com.google.auto.value.AutoValue;
     22 import com.google.common.base.Splitter;
     23 import com.google.common.collect.ImmutableList;
     24 import com.google.common.collect.ImmutableMap;
     25 import com.google.common.collect.ImmutableSet;
     26 import com.google.common.collect.LinkedHashMultimap;
     27 import com.google.common.collect.Multimap;
     28 import com.google.devtools.build.android.desugar.io.BitFlags;
     29 import com.google.devtools.build.android.desugar.io.CoreLibraryRewriter;
     30 import com.google.errorprone.annotations.Immutable;
     31 import java.lang.reflect.Method;
     32 import java.util.Collection;
     33 import java.util.HashMap;
     34 import java.util.Iterator;
     35 import java.util.LinkedHashMap;
     36 import java.util.LinkedHashSet;
     37 import java.util.List;
     38 import java.util.Objects;
     39 import java.util.Set;
     40 import javax.annotation.Nullable;
     41 import org.objectweb.asm.ClassVisitor;
     42 import org.objectweb.asm.Label;
     43 import org.objectweb.asm.MethodVisitor;
     44 import org.objectweb.asm.Opcodes;
     45 import org.objectweb.asm.Type;
     46 
     47 /**
     48  * Helper that keeps track of which core library classes and methods we want to rewrite.
     49  */
     50 class CoreLibrarySupport {
     51 
     52   private static final Object[] EMPTY_FRAME = new Object[0];
     53   private static final String[] EMPTY_LIST = new String[0];
     54 
     55   private final CoreLibraryRewriter rewriter;
     56   private final ClassLoader targetLoader;
     57   /** Internal name prefixes that we want to move to a custom package. */
     58   private final ImmutableSet<String> renamedPrefixes;
     59   private final ImmutableSet<String> excludeFromEmulation;
     60   /** Internal names of interfaces whose default and static interface methods we'll emulate. */
     61   private final ImmutableSet<Class<?>> emulatedInterfaces;
     62   /** Map from {@code owner#name} core library members to their new owners. */
     63   private final ImmutableMap<String, String> memberMoves;
     64 
     65   /** For the collection of definitions of emulated default methods (deterministic iteration). */
     66   private final Multimap<String, EmulatedMethod> emulatedDefaultMethods =
     67       LinkedHashMultimap.create();
     68 
     69   public CoreLibrarySupport(
     70       CoreLibraryRewriter rewriter,
     71       ClassLoader targetLoader,
     72       List<String> renamedPrefixes,
     73       List<String> emulatedInterfaces,
     74       List<String> memberMoves,
     75       List<String> excludeFromEmulation) {
     76     this.rewriter = rewriter;
     77     this.targetLoader = targetLoader;
     78     checkArgument(
     79         renamedPrefixes.stream().allMatch(prefix -> prefix.startsWith("java/")), renamedPrefixes);
     80     this.renamedPrefixes = ImmutableSet.copyOf(renamedPrefixes);
     81     this.excludeFromEmulation = ImmutableSet.copyOf(excludeFromEmulation);
     82 
     83     ImmutableSet.Builder<Class<?>> classBuilder = ImmutableSet.builder();
     84     for (String itf : emulatedInterfaces) {
     85       checkArgument(itf.startsWith("java/util/"), itf);
     86       Class<?> clazz = loadFromInternal(rewriter.getPrefix() + itf);
     87       checkArgument(clazz.isInterface(), itf);
     88       classBuilder.add(clazz);
     89     }
     90     this.emulatedInterfaces = classBuilder.build();
     91 
     92     // We can call isRenamed and rename below b/c we initialized the necessary fields above
     93     // Use LinkedHashMap to tolerate identical duplicates
     94     LinkedHashMap<String, String> movesBuilder = new LinkedHashMap<>();
     95     Splitter splitter = Splitter.on("->").trimResults().omitEmptyStrings();
     96     for (String move : memberMoves) {
     97       List<String> pair = splitter.splitToList(move);
     98       checkArgument(pair.size() == 2, "Doesn't split as expected: %s", move);
     99       checkArgument(pair.get(0).startsWith("java/"), "Unexpected member: %s", move);
    100       int sep = pair.get(0).indexOf('#');
    101       checkArgument(sep > 0 && sep == pair.get(0).lastIndexOf('#'), "invalid member: %s", move);
    102       checkArgument(!isRenamedCoreLibrary(pair.get(0).substring(0, sep)),
    103           "Original renamed, no need to move it: %s", move);
    104       checkArgument(isRenamedCoreLibrary(pair.get(1)), "Target not renamed: %s", move);
    105       checkArgument(!this.excludeFromEmulation.contains(pair.get(0)),
    106           "Retargeted invocation %s shouldn't overlap with excluded", move);
    107 
    108       String value = renameCoreLibrary(pair.get(1));
    109       String existing = movesBuilder.put(pair.get(0), value);
    110       checkArgument(existing == null || existing.equals(value),
    111           "Two move destinations %s and %s configured for %s", existing, value, pair.get(0));
    112     }
    113     this.memberMoves = ImmutableMap.copyOf(movesBuilder);
    114   }
    115 
    116   public boolean isRenamedCoreLibrary(String internalName) {
    117     String unprefixedName = rewriter.unprefix(internalName);
    118     if (!unprefixedName.startsWith("java/") || renamedPrefixes.isEmpty()) {
    119       return false; // shortcut
    120     }
    121     // Rename any classes desugar might generate under java/ (for emulated interfaces) as well as
    122     // configured prefixes
    123     return looksGenerated(unprefixedName)
    124         || renamedPrefixes.stream().anyMatch(prefix -> unprefixedName.startsWith(prefix));
    125   }
    126 
    127   public String renameCoreLibrary(String internalName) {
    128     internalName = rewriter.unprefix(internalName);
    129     return (internalName.startsWith("java/"))
    130         ? "j$/" + internalName.substring(/* cut away "java/" prefix */ 5)
    131         : internalName;
    132   }
    133 
    134   @Nullable
    135   public String getMoveTarget(String owner, String name) {
    136     return memberMoves.get(rewriter.unprefix(owner) + '#' + name);
    137   }
    138 
    139   /**
    140    * Returns {@code true} for java.* classes or interfaces that are subtypes of emulated interfaces.
    141    * Note that implies that this method always returns {@code false} for user-written classes.
    142    */
    143   public boolean isEmulatedCoreClassOrInterface(String internalName) {
    144     return getEmulatedCoreClassOrInterface(internalName) != null;
    145   }
    146 
    147   /** Includes the given method definition in any applicable core interface emulation logic. */
    148   public void registerIfEmulatedCoreInterface(
    149       int access,
    150       String owner,
    151       String name,
    152       String desc,
    153       String[] exceptions) {
    154     Class<?> emulated = getEmulatedCoreClassOrInterface(owner);
    155     if (emulated == null) {
    156       return;
    157     }
    158     checkArgument(emulated.isInterface(), "Shouldn't be called for a class: %s.%s", owner, name);
    159     checkArgument(
    160         BitFlags.noneSet(
    161             access,
    162             Opcodes.ACC_ABSTRACT | Opcodes.ACC_NATIVE | Opcodes.ACC_STATIC | Opcodes.ACC_BRIDGE),
    163         "Should only be called for default methods: %s.%s", owner, name);
    164     emulatedDefaultMethods.put(
    165         name + ":" + desc, EmulatedMethod.create(access, emulated, name, desc, exceptions));
    166   }
    167 
    168   /**
    169    * If the given invocation needs to go through a companion class of an emulated or renamed
    170    * core interface, this methods returns that interface.  This is a helper method for
    171    * {@link CoreLibraryInvocationRewriter}.
    172    *
    173    * <p>This method can only return non-{@code null} if {@code owner} is a core library type.
    174    * It usually returns an emulated interface, unless the given invocation is a super-call to a
    175    * core class's implementation of an emulated method that's being moved (other implementations
    176    * of emulated methods in core classes are ignored). In that case the class is returned and the
    177    * caller can use {@link #getMoveTarget} to find out where to redirect the invokespecial to.
    178    */
    179   // TODO(kmb): Rethink this API and consider combining it with getMoveTarget().
    180   @Nullable
    181   public Class<?> getCoreInterfaceRewritingTarget(
    182       int opcode, String owner, String name, String desc, boolean itf) {
    183     if (looksGenerated(owner)) {
    184       // Regular desugaring handles generated classes, no emulation is needed
    185       return null;
    186     }
    187     if (!itf && opcode == Opcodes.INVOKESTATIC) {
    188       // Ignore static invocations on classes--they never need rewriting (unless moved but that's
    189       // handled separately).
    190       return null;
    191     }
    192     if ("<init>".equals(name)) {
    193       return null; // Constructors aren't rewritten
    194     }
    195 
    196     Class<?> clazz;
    197     if (isRenamedCoreLibrary(owner)) {
    198       // For renamed invocation targets we just need to do what InterfaceDesugaring does, that is,
    199       // only worry about invokestatic and invokespecial interface invocations; nothing to do for
    200       // classes and invokeinterface.  InterfaceDesugaring ignores bootclasspath interfaces,
    201       // so we have to do its work here for renamed interfaces.
    202       if (itf
    203           && (opcode == Opcodes.INVOKESTATIC || opcode == Opcodes.INVOKESPECIAL)) {
    204         clazz = loadFromInternal(owner);
    205       } else {
    206         return null;
    207       }
    208     } else {
    209       // If not renamed, see if the owner needs emulation.
    210       clazz = getEmulatedCoreClassOrInterface(owner);
    211       if (clazz == null) {
    212         return null;
    213       }
    214     }
    215     checkArgument(itf == clazz.isInterface(), "%s expected to be interface: %s", owner, itf);
    216 
    217     if (opcode == Opcodes.INVOKESTATIC) {
    218       // Static interface invocation always goes to the given owner
    219       checkState(itf); // we should've bailed out above.
    220       return clazz;
    221     }
    222 
    223     // See if the invoked method is a default method, which will need rewriting.  For invokespecial
    224     // we can only get here if its a default method, and invokestatic we handled above.
    225     Method callee = findInterfaceMethod(clazz, name, desc);
    226     if (callee != null && callee.isDefault()) {
    227       if (isExcluded(callee)) {
    228         return null;
    229       }
    230 
    231       if (!itf && opcode == Opcodes.INVOKESPECIAL) {
    232         // See if the invoked implementation is moved; note we ignore all other overrides in classes
    233         Class<?> impl = clazz; // we know clazz is not an interface because !itf
    234         while (impl != null) {
    235           String implName = impl.getName().replace('.', '/');
    236           if (getMoveTarget(implName, name) != null) {
    237             return impl;
    238           }
    239           impl = impl.getSuperclass();
    240         }
    241       }
    242 
    243       Class<?> result = callee.getDeclaringClass();
    244       if (isRenamedCoreLibrary(result.getName().replace('.', '/'))
    245           || emulatedInterfaces.stream().anyMatch(emulated -> emulated.isAssignableFrom(result))) {
    246         return result;
    247       }
    248       // We get here if the declaring class is a supertype of an emulated interface.  In that case
    249       // use the emulated interface instead (since we don't desugar the supertype).  Fail in case
    250       // there are multiple possibilities.
    251       Iterator<Class<?>> roots =
    252           emulatedInterfaces
    253               .stream()
    254               .filter(
    255                   emulated -> emulated.isAssignableFrom(clazz) && result.isAssignableFrom(emulated))
    256               .iterator();
    257       checkState(roots.hasNext()); // must exist
    258       Class<?> substitute = roots.next();
    259       checkState(!roots.hasNext(), "Ambiguous emulation substitute: %s", callee);
    260       return substitute;
    261     } else {
    262       checkArgument(!itf || opcode != Opcodes.INVOKESPECIAL,
    263           "Couldn't resolve interface super call %s.super.%s : %s", owner, name, desc);
    264     }
    265     return null;
    266   }
    267 
    268   /**
    269    * Returns the given class if it's a core library class or interface with emulated default
    270    * methods.  This is equivalent to calling {@link #isEmulatedCoreClassOrInterface} and then
    271    * just loading the class (using the target class loader).
    272    */
    273   public Class<?> getEmulatedCoreClassOrInterface(String internalName) {
    274     if (looksGenerated(internalName)) {
    275       // Regular desugaring handles generated classes, no emulation is needed
    276       return null;
    277     }
    278     {
    279       String unprefixedOwner = rewriter.unprefix(internalName);
    280       if (!unprefixedOwner.startsWith("java/util/") || isRenamedCoreLibrary(unprefixedOwner)) {
    281         return null;
    282       }
    283     }
    284 
    285     Class<?> clazz = loadFromInternal(internalName);
    286     if (emulatedInterfaces.stream().anyMatch(itf -> itf.isAssignableFrom(clazz))) {
    287       return clazz;
    288     }
    289     return null;
    290   }
    291 
    292   public void makeDispatchHelpers(GeneratedClassStore store) {
    293     HashMap<Class<?>, ClassVisitor> dispatchHelpers = new HashMap<>();
    294     for (Collection<EmulatedMethod> group : emulatedDefaultMethods.asMap().values()) {
    295       checkState(!group.isEmpty());
    296       Class<?> root = group
    297           .stream()
    298           .map(EmulatedMethod::owner)
    299           .max(DefaultMethodClassFixer.SubtypeComparator.INSTANCE)
    300           .get();
    301       checkState(group.stream().map(m -> m.owner()).allMatch(o -> root.isAssignableFrom(o)),
    302           "Not a single unique method: %s", group);
    303       String methodName = group.stream().findAny().get().name();
    304 
    305       ImmutableList<Class<?>> customOverrides = findCustomOverrides(root, methodName);
    306 
    307       for (EmulatedMethod methodDefinition : group) {
    308         Class<?> owner = methodDefinition.owner();
    309         ClassVisitor dispatchHelper = dispatchHelpers.computeIfAbsent(owner, clazz -> {
    310           String className = clazz.getName().replace('.', '/') + "$$Dispatch";
    311           ClassVisitor result = store.add(className);
    312           result.visit(
    313               Opcodes.V1_7,
    314               // Must be public so dispatch methods can be called from anywhere
    315               Opcodes.ACC_SYNTHETIC | Opcodes.ACC_PUBLIC,
    316               className,
    317               /*signature=*/ null,
    318               "java/lang/Object",
    319               EMPTY_LIST);
    320           return result;
    321         });
    322 
    323         // Types to check for before calling methodDefinition's companion, sub- before super-types
    324         ImmutableList<Class<?>> typechecks =
    325             concat(group.stream().map(EmulatedMethod::owner), customOverrides.stream())
    326                 .filter(o -> o != owner && owner.isAssignableFrom(o))
    327                 .distinct() // should already be but just in case
    328                 .sorted(DefaultMethodClassFixer.SubtypeComparator.INSTANCE)
    329                 .collect(ImmutableList.toImmutableList());
    330         makeDispatchHelperMethod(dispatchHelper, methodDefinition, typechecks);
    331       }
    332     }
    333   }
    334 
    335   private ImmutableList<Class<?>> findCustomOverrides(Class<?> root, String methodName) {
    336     ImmutableList.Builder<Class<?>> customOverrides = ImmutableList.builder();
    337     for (ImmutableMap.Entry<String, String> move : memberMoves.entrySet()) {
    338       // move.getKey is a string <owner>#<name> which we validated in the constructor.
    339       // We need to take the string apart here to compare owner and name separately.
    340       if (!methodName.equals(move.getKey().substring(move.getKey().indexOf('#') + 1))) {
    341         continue;
    342       }
    343       Class<?> target =
    344           loadFromInternal(
    345               rewriter.getPrefix() + move.getKey().substring(0, move.getKey().indexOf('#')));
    346       if (!root.isAssignableFrom(target)) {
    347         continue;
    348       }
    349       checkState(!target.isInterface(), "can't move emulated interface method: %s", move);
    350       customOverrides.add(target);
    351     }
    352     return customOverrides.build();
    353   }
    354 
    355   private void makeDispatchHelperMethod(
    356       ClassVisitor helper, EmulatedMethod method, ImmutableList<Class<?>> typechecks) {
    357     checkArgument(method.owner().isInterface());
    358     String owner = method.owner().getName().replace('.', '/');
    359     Type methodType = Type.getMethodType(method.descriptor());
    360     String companionDesc =
    361         InterfaceDesugaring.companionDefaultMethodDescriptor(owner, method.descriptor());
    362     MethodVisitor dispatchMethod =
    363         helper.visitMethod(
    364             method.access() | Opcodes.ACC_STATIC,
    365             method.name(),
    366             companionDesc,
    367             /*signature=*/ null,  // signature is invalid due to extra "receiver" argument
    368             method.exceptions().toArray(EMPTY_LIST));
    369 
    370 
    371     dispatchMethod.visitCode();
    372     {
    373       // See if the receiver might come with its own implementation of the method, and call it.
    374       // We do this by testing for the interface type created by EmulatedInterfaceRewriter
    375       Label fallthrough = new Label();
    376       String emulationInterface = renameCoreLibrary(owner);
    377       dispatchMethod.visitVarInsn(Opcodes.ALOAD, 0);  // load "receiver"
    378       dispatchMethod.visitTypeInsn(Opcodes.INSTANCEOF, emulationInterface);
    379       dispatchMethod.visitJumpInsn(Opcodes.IFEQ, fallthrough);
    380       dispatchMethod.visitVarInsn(Opcodes.ALOAD, 0);  // load "receiver"
    381       dispatchMethod.visitTypeInsn(Opcodes.CHECKCAST, emulationInterface);
    382 
    383       visitLoadArgs(dispatchMethod, methodType, 1 /* receiver already loaded above */);
    384       dispatchMethod.visitMethodInsn(
    385           Opcodes.INVOKEINTERFACE,
    386           emulationInterface,
    387           method.name(),
    388           method.descriptor(),
    389           /*itf=*/ true);
    390       dispatchMethod.visitInsn(methodType.getReturnType().getOpcode(Opcodes.IRETURN));
    391 
    392       dispatchMethod.visitLabel(fallthrough);
    393       // Trivial frame for the branch target: same empty stack as before
    394       dispatchMethod.visitFrame(Opcodes.F_SAME, 0, EMPTY_FRAME, 0, EMPTY_FRAME);
    395     }
    396 
    397     // Next, check for subtypes with specialized implementations and call them
    398     for (Class<?> tested : typechecks) {
    399       Label fallthrough = new Label();
    400       String testedName = tested.getName().replace('.', '/');
    401       // In case of a class this must be a member move; for interfaces use the companion.
    402       String target =
    403           tested.isInterface()
    404               ? InterfaceDesugaring.getCompanionClassName(testedName)
    405               : checkNotNull(memberMoves.get(rewriter.unprefix(testedName) + '#' + method.name()));
    406       dispatchMethod.visitVarInsn(Opcodes.ALOAD, 0);  // load "receiver"
    407       dispatchMethod.visitTypeInsn(Opcodes.INSTANCEOF, testedName);
    408       dispatchMethod.visitJumpInsn(Opcodes.IFEQ, fallthrough);
    409       dispatchMethod.visitVarInsn(Opcodes.ALOAD, 0);  // load "receiver"
    410       dispatchMethod.visitTypeInsn(Opcodes.CHECKCAST, testedName);  // make verifier happy
    411 
    412       visitLoadArgs(dispatchMethod, methodType, 1 /* receiver already loaded above */);
    413       dispatchMethod.visitMethodInsn(
    414           Opcodes.INVOKESTATIC,
    415           target,
    416           method.name(),
    417           InterfaceDesugaring.companionDefaultMethodDescriptor(testedName, method.descriptor()),
    418           /*itf=*/ false);
    419       dispatchMethod.visitInsn(methodType.getReturnType().getOpcode(Opcodes.IRETURN));
    420 
    421       dispatchMethod.visitLabel(fallthrough);
    422       // Trivial frame for the branch target: same empty stack as before
    423       dispatchMethod.visitFrame(Opcodes.F_SAME, 0, EMPTY_FRAME, 0, EMPTY_FRAME);
    424     }
    425 
    426     // Call static type's default implementation in companion class
    427     dispatchMethod.visitVarInsn(Opcodes.ALOAD, 0);  // load "receiver"
    428     visitLoadArgs(dispatchMethod, methodType, 1 /* receiver already loaded above */);
    429     dispatchMethod.visitMethodInsn(
    430         Opcodes.INVOKESTATIC,
    431         InterfaceDesugaring.getCompanionClassName(owner),
    432         method.name(),
    433         companionDesc,
    434         /*itf=*/ false);
    435     dispatchMethod.visitInsn(methodType.getReturnType().getOpcode(Opcodes.IRETURN));
    436 
    437     dispatchMethod.visitMaxs(0, 0);
    438     dispatchMethod.visitEnd();
    439   }
    440 
    441   private boolean isExcluded(Method method) {
    442     String unprefixedOwner =
    443         rewriter.unprefix(method.getDeclaringClass().getName().replace('.', '/'));
    444     return excludeFromEmulation.contains(unprefixedOwner + "#" + method.getName());
    445   }
    446 
    447   private Class<?> loadFromInternal(String internalName) {
    448     try {
    449       return targetLoader.loadClass(internalName.replace('/', '.'));
    450     } catch (ClassNotFoundException e) {
    451       throw (NoClassDefFoundError) new NoClassDefFoundError().initCause(e);
    452     }
    453   }
    454 
    455   private static Method findInterfaceMethod(Class<?> clazz, String name, String desc) {
    456     return collectImplementedInterfaces(clazz, new LinkedHashSet<>())
    457         .stream()
    458         // search more subtypes before supertypes
    459         .sorted(DefaultMethodClassFixer.SubtypeComparator.INSTANCE)
    460         .map(itf -> findMethod(itf, name, desc))
    461         .filter(Objects::nonNull)
    462         .findFirst()
    463         .orElse((Method) null);
    464   }
    465 
    466   private static Method findMethod(Class<?> clazz, String name, String desc) {
    467     for (Method m : clazz.getMethods()) {
    468       if (m.getName().equals(name) && Type.getMethodDescriptor(m).equals(desc)) {
    469         return m;
    470       }
    471     }
    472     return null;
    473   }
    474 
    475   private static Set<Class<?>> collectImplementedInterfaces(Class<?> clazz, Set<Class<?>> dest) {
    476     if (clazz.isInterface()) {
    477       if (!dest.add(clazz)) {
    478         return dest;
    479       }
    480     } else if (clazz.getSuperclass() != null) {
    481       collectImplementedInterfaces(clazz.getSuperclass(), dest);
    482     }
    483 
    484     for (Class<?> itf : clazz.getInterfaces()) {
    485       collectImplementedInterfaces(itf, dest);
    486     }
    487     return dest;
    488   }
    489 
    490   /**
    491    * Emits instructions to load a method's parameters as arguments of a method call assumed to have
    492    * compatible descriptor, starting at the given local variable slot.
    493    */
    494   private static void visitLoadArgs(MethodVisitor dispatchMethod, Type neededType, int slot) {
    495     for (Type arg : neededType.getArgumentTypes()) {
    496       dispatchMethod.visitVarInsn(arg.getOpcode(Opcodes.ILOAD), slot);
    497       slot += arg.getSize();
    498     }
    499   }
    500 
    501   /** Checks whether the given class is (likely) generated by desugar itself. */
    502   private static boolean looksGenerated(String owner) {
    503     return owner.contains("$$Lambda$") || owner.endsWith("$$CC") || owner.endsWith("$$Dispatch");
    504   }
    505 
    506   @AutoValue
    507   @Immutable
    508   abstract static class EmulatedMethod {
    509     public static EmulatedMethod create(
    510         int access, Class<?> owner, String name, String desc, @Nullable String[] exceptions) {
    511       return new AutoValue_CoreLibrarySupport_EmulatedMethod(access, owner, name, desc,
    512           exceptions != null ? ImmutableList.copyOf(exceptions) : ImmutableList.of());
    513     }
    514 
    515     abstract int access();
    516     abstract Class<?> owner();
    517     abstract String name();
    518     abstract String descriptor();
    519     abstract ImmutableList<String> exceptions();
    520   }
    521 }
    522