• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
CoreLibrarySupport( CoreLibraryRewriter rewriter, ClassLoader targetLoader, List<String> renamedPrefixes, List<String> emulatedInterfaces, List<String> memberMoves, List<String> excludeFromEmulation)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 
isRenamedCoreLibrary(String internalName)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 
renameCoreLibrary(String internalName)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
getMoveTarget(String owner, String name)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    */
isEmulatedCoreClassOrInterface(String internalName)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. */
registerIfEmulatedCoreInterface( int access, String owner, String name, String desc, String[] exceptions)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
getCoreInterfaceRewritingTarget( int opcode, String owner, String name, String desc, boolean itf)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    */
getEmulatedCoreClassOrInterface(String internalName)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 
makeDispatchHelpers(GeneratedClassStore store)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 
findCustomOverrides(Class<?> root, String methodName)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 
makeDispatchHelperMethod( ClassVisitor helper, EmulatedMethod method, ImmutableList<Class<?>> typechecks)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 
isExcluded(Method method)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 
loadFromInternal(String internalName)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 
findInterfaceMethod(Class<?> clazz, String name, String desc)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 
findMethod(Class<?> clazz, String name, String desc)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 
collectImplementedInterfaces(Class<?> clazz, Set<Class<?>> dest)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    */
visitLoadArgs(MethodVisitor dispatchMethod, Type neededType, int slot)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. */
looksGenerated(String owner)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 {
create( int access, Class<?> owner, String name, String desc, @Nullable String[] exceptions)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 
access()515     abstract int access();
owner()516     abstract Class<?> owner();
name()517     abstract String name();
descriptor()518     abstract String descriptor();
exceptions()519     abstract ImmutableList<String> exceptions();
520   }
521 }
522