| // Copyright 2017 The Bazel Authors. All rights reserved. |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| package com.google.devtools.build.android.desugar; |
| |
| import static com.google.common.base.Preconditions.checkArgument; |
| import static com.google.common.base.Preconditions.checkNotNull; |
| import static com.google.common.base.Preconditions.checkState; |
| |
| import com.google.common.collect.ImmutableList; |
| import com.google.devtools.build.android.desugar.io.BitFlags; |
| import java.util.Comparator; |
| import java.util.HashSet; |
| import java.util.Iterator; |
| import java.util.Set; |
| import java.util.TreeSet; |
| import javax.annotation.Nullable; |
| import org.objectweb.asm.ClassReader; |
| import org.objectweb.asm.ClassVisitor; |
| import org.objectweb.asm.MethodVisitor; |
| import org.objectweb.asm.Opcodes; |
| import org.objectweb.asm.Type; |
| import org.objectweb.asm.tree.AbstractInsnNode; |
| import org.objectweb.asm.tree.InsnList; |
| import org.objectweb.asm.tree.InsnNode; |
| import org.objectweb.asm.tree.MethodInsnNode; |
| import org.objectweb.asm.tree.MethodNode; |
| |
| /** |
| * Fixer of classes that extend interfaces with default methods to declare any missing methods |
| * explicitly and call the corresponding companion method generated by {@link InterfaceDesugaring}. |
| */ |
| public class DefaultMethodClassFixer extends ClassVisitor { |
| |
| private final ClassReaderFactory classpath; |
| private final ClassReaderFactory bootclasspath; |
| private final ClassLoader targetLoader; |
| private final DependencyCollector depsCollector; |
| @Nullable private final CoreLibrarySupport coreLibrarySupport; |
| private final HashSet<String> instanceMethods = new HashSet<>(); |
| |
| private boolean isInterface; |
| private String internalName; |
| private ImmutableList<String> directInterfaces; |
| private String superName; |
| /** This method node caches <clinit>, and flushes out in {@code visitEnd()}; */ |
| private MethodNode clInitMethodNode; |
| |
| public DefaultMethodClassFixer( |
| ClassVisitor dest, |
| ClassReaderFactory classpath, |
| DependencyCollector depsCollector, |
| @Nullable CoreLibrarySupport coreLibrarySupport, |
| ClassReaderFactory bootclasspath, |
| ClassLoader targetLoader) { |
| super(Opcodes.ASM6, dest); |
| this.classpath = classpath; |
| this.coreLibrarySupport = coreLibrarySupport; |
| this.bootclasspath = bootclasspath; |
| this.targetLoader = targetLoader; |
| this.depsCollector = depsCollector; |
| } |
| |
| @Override |
| public void visit( |
| int version, |
| int access, |
| String name, |
| String signature, |
| String superName, |
| String[] interfaces) { |
| checkState(this.directInterfaces == null); |
| isInterface = BitFlags.isSet(access, Opcodes.ACC_INTERFACE); |
| internalName = name; |
| checkArgument( |
| superName != null || "java/lang/Object".equals(name), // ASM promises this |
| "Type without superclass: %s", |
| name); |
| this.directInterfaces = ImmutableList.copyOf(interfaces); |
| this.superName = superName; |
| super.visit(version, access, name, signature, superName, interfaces); |
| } |
| |
| @Override |
| public void visitEnd() { |
| if (!isInterface |
| && (mayNeedInterfaceStubsForEmulatedSuperclass() |
| || defaultMethodsDefined(directInterfaces))) { |
| // Inherited methods take precedence over default methods, so visit all superclasses and |
| // figure out what methods they declare before stubbing in any missing default methods. |
| recordInheritedMethods(); |
| stubMissingDefaultAndBridgeMethods(); |
| // Check whether there are interfaces with default methods and <clinit>. If yes, the following |
| // method call will return a list of interface fields to access in the <clinit> to trigger |
| // the initialization of these interfaces. |
| ImmutableList<String> companionsToTriggerInterfaceClinit = |
| collectOrderedCompanionsToTriggerInterfaceClinit(directInterfaces); |
| if (!companionsToTriggerInterfaceClinit.isEmpty()) { |
| if (clInitMethodNode == null) { |
| clInitMethodNode = new MethodNode(Opcodes.ACC_STATIC, "<clinit>", "()V", null, null); |
| } |
| desugarClinitToTriggerInterfaceInitializers(companionsToTriggerInterfaceClinit); |
| } |
| } |
| if (clInitMethodNode != null && super.cv != null) { // Write <clinit> to the chained visitor. |
| clInitMethodNode.accept(super.cv); |
| } |
| super.visitEnd(); |
| } |
| |
| private boolean isClinitAlreadyDesugared( |
| ImmutableList<String> companionsToAccessToTriggerInterfaceClinit) { |
| InsnList instructions = clInitMethodNode.instructions; |
| if (instructions.size() <= companionsToAccessToTriggerInterfaceClinit.size()) { |
| // The <clinit> must end with RETURN, so if the instruction count is less than or equal to |
| // the companion class count, this <clinit> has not been desugared. |
| return false; |
| } |
| Iterator<AbstractInsnNode> iterator = instructions.iterator(); |
| for (String companion : companionsToAccessToTriggerInterfaceClinit) { |
| if (!iterator.hasNext()) { |
| return false; |
| } |
| AbstractInsnNode first = iterator.next(); |
| if (!(first instanceof MethodInsnNode)) { |
| return false; |
| } |
| MethodInsnNode methodInsnNode = (MethodInsnNode) first; |
| if (methodInsnNode.getOpcode() != Opcodes.INVOKESTATIC |
| || !methodInsnNode.owner.equals(companion) |
| || !methodInsnNode.name.equals( |
| InterfaceDesugaring.COMPANION_METHOD_TO_TRIGGER_INTERFACE_CLINIT_NAME)) { |
| return false; |
| } |
| checkState( |
| methodInsnNode.desc.equals( |
| InterfaceDesugaring.COMPANION_METHOD_TO_TRIGGER_INTERFACE_CLINIT_DESC), |
| "Inconsistent method desc: %s vs %s", |
| methodInsnNode.desc, |
| InterfaceDesugaring.COMPANION_METHOD_TO_TRIGGER_INTERFACE_CLINIT_DESC); |
| |
| if (!iterator.hasNext()) { |
| return false; |
| } |
| AbstractInsnNode second = iterator.next(); |
| if (second.getOpcode() != Opcodes.POP) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| private void desugarClinitToTriggerInterfaceInitializers( |
| ImmutableList<String> companionsToTriggerInterfaceClinit) { |
| if (isClinitAlreadyDesugared(companionsToTriggerInterfaceClinit)) { |
| return; |
| } |
| InsnList desugarInsts = new InsnList(); |
| for (String companionClass : companionsToTriggerInterfaceClinit) { |
| desugarInsts.add( |
| new MethodInsnNode( |
| Opcodes.INVOKESTATIC, |
| companionClass, |
| InterfaceDesugaring.COMPANION_METHOD_TO_TRIGGER_INTERFACE_CLINIT_NAME, |
| InterfaceDesugaring.COMPANION_METHOD_TO_TRIGGER_INTERFACE_CLINIT_DESC, |
| false)); |
| } |
| if (clInitMethodNode.instructions.size() == 0) { |
| clInitMethodNode.instructions.insert(new InsnNode(Opcodes.RETURN)); |
| } |
| clInitMethodNode.instructions.insertBefore( |
| clInitMethodNode.instructions.getFirst(), desugarInsts); |
| } |
| |
| @Override |
| public MethodVisitor visitMethod( |
| int access, String name, String desc, String signature, String[] exceptions) { |
| // Keep track of instance methods implemented in this class for later. |
| if (!isInterface) { |
| recordIfInstanceMethod(access, name, desc); |
| } |
| if ("<clinit>".equals(name)) { |
| checkState(clInitMethodNode == null, "This class fixer has been used. "); |
| clInitMethodNode = new MethodNode(access, name, desc, signature, exceptions); |
| return clInitMethodNode; |
| } |
| return super.visitMethod(access, name, desc, signature, exceptions); |
| } |
| |
| private boolean mayNeedInterfaceStubsForEmulatedSuperclass() { |
| return coreLibrarySupport != null |
| && !coreLibrarySupport.isEmulatedCoreClassOrInterface(internalName) |
| && coreLibrarySupport.isEmulatedCoreClassOrInterface(superName); |
| } |
| |
| private void stubMissingDefaultAndBridgeMethods() { |
| TreeSet<Class<?>> allInterfaces = new TreeSet<>(SubtypeComparator.INSTANCE); |
| for (String direct : directInterfaces) { |
| // Loading ensures all transitively implemented interfaces can be loaded, which is necessary |
| // to produce correct default method stubs in all cases. We could do without classloading but |
| // it's convenient to rely on Class.isAssignableFrom to compute subtype relationships, and |
| // we'd still have to insist that all transitively implemented interfaces can be loaded. |
| // We don't load the visited class, however, in case it's a generated lambda class. |
| Class<?> itf = loadFromInternal(direct); |
| collectInterfaces(itf, allInterfaces); |
| } |
| |
| Class<?> superclass = loadFromInternal(superName); |
| boolean mayNeedStubsForSuperclass = mayNeedInterfaceStubsForEmulatedSuperclass(); |
| if (mayNeedStubsForSuperclass) { |
| // Collect interfaces inherited from emulated superclasses as well, to handle things like |
| // extending AbstractList without explicitly implementing List. |
| for (Class<?> clazz = superclass; clazz != null; clazz = clazz.getSuperclass()) { |
| for (Class<?> itf : superclass.getInterfaces()) { |
| collectInterfaces(itf, allInterfaces); |
| } |
| } |
| } |
| for (Class<?> interfaceToVisit : allInterfaces) { |
| // if J extends I, J is allowed to redefine I's default methods. The comparator we used |
| // above makes sure we visit J before I in that case so we can use J's definition. |
| if (!mayNeedStubsForSuperclass && interfaceToVisit.isAssignableFrom(superclass)) { |
| // superclass is also rewritten and already implements this interface, so we _must_ skip it. |
| continue; |
| } |
| stubMissingDefaultAndBridgeMethods( |
| interfaceToVisit.getName().replace('.', '/'), mayNeedStubsForSuperclass); |
| } |
| } |
| |
| private void stubMissingDefaultAndBridgeMethods( |
| String implemented, boolean mayNeedStubsForSuperclass) { |
| ClassReader bytecode; |
| boolean isBootclasspath; |
| if (bootclasspath.isKnown(implemented)) { |
| if (coreLibrarySupport != null |
| && (coreLibrarySupport.isRenamedCoreLibrary(implemented) |
| || coreLibrarySupport.isEmulatedCoreClassOrInterface(implemented))) { |
| bytecode = checkNotNull(bootclasspath.readIfKnown(implemented), implemented); |
| isBootclasspath = true; |
| } else { |
| // Default methods from interfaces on the bootclasspath that we're not renaming or emulating |
| // are assumed available at runtime, so just ignore them. |
| return; |
| } |
| } else { |
| bytecode = |
| checkNotNull( |
| classpath.readIfKnown(implemented), |
| "Couldn't find interface %s implemented by %s", implemented, internalName); |
| isBootclasspath = false; |
| } |
| bytecode.accept( |
| new DefaultMethodStubber(isBootclasspath, mayNeedStubsForSuperclass), |
| ClassReader.SKIP_DEBUG); |
| } |
| |
| private Class<?> loadFromInternal(String internalName) { |
| try { |
| return targetLoader.loadClass(internalName.replace('/', '.')); |
| } catch (ClassNotFoundException e) { |
| throw new IllegalStateException( |
| "Couldn't load " + internalName + ", is the classpath complete?", e); |
| } |
| } |
| |
| private void collectInterfaces(Class<?> itf, Set<Class<?>> dest) { |
| checkArgument(itf.isInterface()); |
| if (!dest.add(itf)) { |
| return; |
| } |
| for (Class<?> implemented : itf.getInterfaces()) { |
| collectInterfaces(implemented, dest); |
| } |
| } |
| |
| private void recordInheritedMethods() { |
| InstanceMethodRecorder recorder = |
| new InstanceMethodRecorder(mayNeedInterfaceStubsForEmulatedSuperclass()); |
| String internalName = superName; |
| while (internalName != null) { |
| ClassReader bytecode = bootclasspath.readIfKnown(internalName); |
| if (bytecode == null) { |
| bytecode = |
| checkNotNull( |
| classpath.readIfKnown(internalName), "Superclass not found: %s", internalName); |
| } |
| bytecode.accept(recorder, ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG); |
| internalName = bytecode.getSuperName(); |
| } |
| } |
| |
| private void recordIfInstanceMethod(int access, String name, String desc) { |
| if (BitFlags.noneSet(access, Opcodes.ACC_STATIC)) { |
| // Record all declared instance methods, including abstract, bridge, and native methods, as |
| // they all take precedence over default methods. |
| instanceMethods.add(name + ":" + desc); |
| } |
| } |
| |
| /** |
| * Starting from the given interfaces, this method scans the interface hierarchy, finds the |
| * interfaces that have default methods and <clinit>, and returns the companion class names of |
| * these interfaces. |
| * |
| * <p>Note that the returned companion classes are ordered in the order of the interface |
| * initialization, which is consistent with the JVM behavior. For example, "class A implements I1, |
| * I2", the returned list would be [I1$$CC, I2$$CC], not [I2$$CC, I1$$CC]. |
| */ |
| private ImmutableList<String> collectOrderedCompanionsToTriggerInterfaceClinit( |
| ImmutableList<String> interfaces) { |
| ImmutableList.Builder<String> companionCollector = ImmutableList.builder(); |
| HashSet<String> visitedInterfaces = new HashSet<>(); |
| for (String anInterface : interfaces) { |
| collectOrderedCompanionsToTriggerInterfaceClinit( |
| anInterface, visitedInterfaces, companionCollector); |
| } |
| return companionCollector.build(); |
| } |
| |
| private void collectOrderedCompanionsToTriggerInterfaceClinit( |
| String anInterface, |
| HashSet<String> visitedInterfaces, |
| ImmutableList.Builder<String> companionCollector) { |
| if (!visitedInterfaces.add(anInterface)) { |
| return; |
| } |
| ClassReader bytecode = classpath.readIfKnown(anInterface); |
| if (bytecode == null || bootclasspath.isKnown(anInterface)) { |
| return; |
| } |
| String[] parentInterfaces = bytecode.getInterfaces(); |
| if (parentInterfaces != null && parentInterfaces.length > 0) { |
| for (String parentInterface : parentInterfaces) { |
| collectOrderedCompanionsToTriggerInterfaceClinit( |
| parentInterface, visitedInterfaces, companionCollector); |
| } |
| } |
| InterfaceInitializationNecessityDetector necessityDetector = |
| new InterfaceInitializationNecessityDetector(bytecode.getClassName()); |
| bytecode.accept(necessityDetector, ClassReader.SKIP_DEBUG); |
| if (necessityDetector.needsToInitialize()) { |
| // If we need to initialize this interface, we initialize its companion class, and its |
| // companion class will initialize the interface then. This desigin decision is made to avoid |
| // access issue, e.g., package-private interfaces. |
| companionCollector.add(InterfaceDesugaring.getCompanionClassName(anInterface)); |
| } |
| } |
| |
| /** |
| * Recursively searches the given interfaces for default methods not implemented by this class |
| * directly. If this method returns true we need to think about stubbing missing default methods. |
| */ |
| private boolean defaultMethodsDefined(ImmutableList<String> interfaces) { |
| for (String implemented : interfaces) { |
| ClassReader bytecode; |
| if (bootclasspath.isKnown(implemented)) { |
| if (coreLibrarySupport != null |
| && coreLibrarySupport.isEmulatedCoreClassOrInterface(implemented)) { |
| return true; // need to stub in emulated interface methods such as Collection.stream() |
| } else if (coreLibrarySupport != null |
| && coreLibrarySupport.isRenamedCoreLibrary(implemented)) { |
| // Check default methods of renamed interfaces |
| bytecode = checkNotNull(bootclasspath.readIfKnown(implemented), implemented); |
| } else { |
| continue; |
| } |
| } else { |
| bytecode = classpath.readIfKnown(implemented); |
| if (bytecode == null) { |
| // Interface isn't on the classpath, which indicates incomplete classpaths. Record missing |
| // dependency so we can check it later. If we don't check then we may get runtime |
| // failures or wrong behavior from default methods that should've been stubbed in. |
| // TODO(kmb): Print a warning so people can start fixing their deps? |
| depsCollector.missingImplementedInterface(internalName, implemented); |
| continue; |
| } |
| } |
| |
| // Class in classpath and bootclasspath is a bad idea but in any event, assume the |
| // bootclasspath will take precedence like in a classloader. |
| // We can skip code attributes as we just need to find default methods to stub. |
| DefaultMethodFinder finder = new DefaultMethodFinder(); |
| bytecode.accept(finder, ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG); |
| if (finder.foundDefaultMethods()) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| /** Returns {@code true} for non-bridge default methods not in {@link #instanceMethods}. */ |
| private boolean shouldStubAsDefaultMethod(int access, String name, String desc) { |
| // Ignore private methods, which technically aren't default methods and can only be called from |
| // other methods defined in the interface. This also ignores lambda body methods, which is fine |
| // as we don't want or need to stub those. Also ignore bridge methods as javac adds them to |
| // concrete classes as needed anyway and we handle them separately for generated lambda classes. |
| // Note that an exception is that, if a bridge method is for a default interface method, javac |
| // will NOT generate the bridge method in the implementing class. So we need extra logic to |
| // handle these bridge methods. |
| return isNonBridgeDefaultMethod(access) && !instanceMethods.contains(name + ":" + desc); |
| } |
| |
| private static boolean isNonBridgeDefaultMethod(int access) { |
| return BitFlags.noneSet( |
| access, |
| Opcodes.ACC_ABSTRACT | Opcodes.ACC_STATIC | Opcodes.ACC_BRIDGE | Opcodes.ACC_PRIVATE); |
| } |
| |
| /** |
| * Check whether an interface method is a bridge method for a default interface method. This type |
| * of bridge methods is special, as they are not put in the implementing classes by javac. |
| */ |
| private boolean shouldStubAsBridgeDefaultMethod(int access, String name, String desc) { |
| return BitFlags.isSet(access, Opcodes.ACC_BRIDGE | Opcodes.ACC_PUBLIC) |
| && BitFlags.noneSet(access, Opcodes.ACC_ABSTRACT | Opcodes.ACC_STATIC) |
| && !instanceMethods.contains(name + ":" + desc); |
| } |
| |
| /** |
| * Visitor for interfaces that produces delegates in the class visited by the outer {@link |
| * DefaultMethodClassFixer} for every default method encountered. |
| */ |
| private class DefaultMethodStubber extends ClassVisitor { |
| |
| private final boolean isBootclasspathInterface; |
| private final boolean mayNeedStubsForSuperclass; |
| |
| private String stubbedInterfaceName; |
| |
| public DefaultMethodStubber( |
| boolean isBootclasspathInterface, boolean mayNeedStubsForSuperclass) { |
| super(Opcodes.ASM6); |
| this.isBootclasspathInterface = isBootclasspathInterface; |
| this.mayNeedStubsForSuperclass = mayNeedStubsForSuperclass; |
| } |
| |
| @Override |
| public void visit( |
| int version, |
| int access, |
| String name, |
| String signature, |
| String superName, |
| String[] interfaces) { |
| checkArgument(BitFlags.isSet(access, Opcodes.ACC_INTERFACE)); |
| checkState(stubbedInterfaceName == null); |
| stubbedInterfaceName = name; |
| } |
| |
| @Override |
| public MethodVisitor visitMethod( |
| int access, String name, String desc, String signature, String[] exceptions) { |
| if (shouldStubAsDefaultMethod(access, name, desc)) { |
| // Remember we stubbed this method in case it's also defined by subsequently visited |
| // interfaces. javac would force the method to be defined explicitly if there any two |
| // definitions conflict, but see stubMissingDefaultMethods() for how we deal with default |
| // methods redefined in interfaces extending another. |
| recordIfInstanceMethod(access, name, desc); |
| if (!isBootclasspathInterface) { |
| // Don't record these dependencies, as we can't check them |
| depsCollector.assumeCompanionClass( |
| internalName, InterfaceDesugaring.getCompanionClassName(stubbedInterfaceName)); |
| } |
| |
| // Add this method to the class we're desugaring and stub in a body to call the default |
| // implementation in the interface's companion class. ijar omits these methods when setting |
| // ACC_SYNTHETIC modifier, so don't. |
| // Signatures can be wrong, e.g., when type variables are introduced, instantiated, or |
| // refined in the class we're processing, so drop them. |
| MethodVisitor stubMethod = |
| DefaultMethodClassFixer.this.visitMethod(access, name, desc, (String) null, exceptions); |
| |
| String receiverName = stubbedInterfaceName; |
| String owner = InterfaceDesugaring.getCompanionClassName(stubbedInterfaceName); |
| if (mayNeedStubsForSuperclass) { |
| // Reflect what CoreLibraryInvocationRewriter would do if it encountered a super-call to a |
| // moved implementation of an emulated method. Equivalent to emitting the invokespecial |
| // super call here and relying on CoreLibraryInvocationRewriter for the rest |
| Class<?> emulatedImplementation = |
| coreLibrarySupport.getCoreInterfaceRewritingTarget( |
| Opcodes.INVOKESPECIAL, superName, name, desc, /*itf=*/ false); |
| if (emulatedImplementation != null && !emulatedImplementation.isInterface()) { |
| receiverName = emulatedImplementation.getName().replace('.', '/'); |
| owner = checkNotNull(coreLibrarySupport.getMoveTarget(receiverName, name)); |
| } |
| } |
| |
| int slot = 0; |
| stubMethod.visitVarInsn(Opcodes.ALOAD, slot++); // load the receiver |
| Type neededType = Type.getMethodType(desc); |
| for (Type arg : neededType.getArgumentTypes()) { |
| stubMethod.visitVarInsn(arg.getOpcode(Opcodes.ILOAD), slot); |
| slot += arg.getSize(); |
| } |
| stubMethod.visitMethodInsn( |
| Opcodes.INVOKESTATIC, |
| owner, |
| name, |
| InterfaceDesugaring.companionDefaultMethodDescriptor(receiverName, desc), |
| /*itf=*/ false); |
| stubMethod.visitInsn(neededType.getReturnType().getOpcode(Opcodes.IRETURN)); |
| |
| stubMethod.visitMaxs(0, 0); // rely on class writer to compute these |
| stubMethod.visitEnd(); |
| return null; |
| } else if (shouldStubAsBridgeDefaultMethod(access, name, desc)) { |
| recordIfInstanceMethod(access, name, desc); |
| // If we're visiting a bootclasspath interface then we most likely don't have the code. |
| // That means we can't just copy the method bodies as we're trying to do below. |
| checkState(!isBootclasspathInterface, |
| "TODO stub core interface %s bridge methods in %s", stubbedInterfaceName, internalName); |
| // For bridges we just copy their bodies instead of going through the companion class. |
| // Meanwhile, we also need to desugar the copied method bodies, so that any calls to |
| // interface methods are correctly handled. |
| return new InterfaceDesugaring.InterfaceInvocationRewriter( |
| DefaultMethodClassFixer.this.visitMethod(access, name, desc, (String) null, exceptions), |
| stubbedInterfaceName, |
| bootclasspath, |
| targetLoader, |
| depsCollector, |
| internalName); |
| } else { |
| return null; // not a default or bridge method or the class already defines this method. |
| } |
| } |
| } |
| |
| /** |
| * Visitor for interfaces that recursively searches interfaces for default method declarations. |
| */ |
| private class DefaultMethodFinder extends ClassVisitor { |
| @SuppressWarnings("hiding") |
| private ImmutableList<String> interfaces; |
| |
| private boolean found; |
| |
| public DefaultMethodFinder() { |
| super(Opcodes.ASM6); |
| } |
| |
| @Override |
| public void visit( |
| int version, |
| int access, |
| String name, |
| String signature, |
| String superName, |
| String[] interfaces) { |
| checkArgument(BitFlags.isSet(access, Opcodes.ACC_INTERFACE)); |
| checkState(this.interfaces == null); |
| this.interfaces = ImmutableList.copyOf(interfaces); |
| } |
| |
| public boolean foundDefaultMethods() { |
| return found; |
| } |
| |
| @Override |
| public void visitEnd() { |
| if (!found) { |
| found = defaultMethodsDefined(this.interfaces); |
| } |
| } |
| |
| @Override |
| public MethodVisitor visitMethod( |
| int access, String name, String desc, String signature, String[] exceptions) { |
| if (!found && shouldStubAsDefaultMethod(access, name, desc)) { |
| // Found a default method we're not ignoring (instanceMethods at this point contains methods |
| // the top-level visited class implements itself). |
| found = true; |
| } |
| return null; // we don't care about the actual code in these methods |
| } |
| } |
| |
| private class InstanceMethodRecorder extends ClassVisitor { |
| |
| private final boolean ignoreEmulatedMethods; |
| |
| private String className; |
| |
| public InstanceMethodRecorder(boolean ignoreEmulatedMethods) { |
| super(Opcodes.ASM6); |
| this.ignoreEmulatedMethods = ignoreEmulatedMethods; |
| } |
| |
| @Override |
| public void visit( |
| int version, |
| int access, |
| String name, |
| String signature, |
| String superName, |
| String[] interfaces) { |
| checkArgument(BitFlags.noneSet(access, Opcodes.ACC_INTERFACE)); |
| className = name; // updated every time we start visiting another superclass |
| super.visit(version, access, name, signature, superName, interfaces); |
| } |
| |
| @Override |
| public MethodVisitor visitMethod( |
| int access, String name, String desc, String signature, String[] exceptions) { |
| if (ignoreEmulatedMethods |
| && BitFlags.noneSet(access, Opcodes.ACC_STATIC) // short-circuit |
| && coreLibrarySupport.getCoreInterfaceRewritingTarget( |
| Opcodes.INVOKEVIRTUAL, className, name, desc, /*itf=*/ false) |
| != null) { |
| // *don't* record emulated core library method implementations in immediate subclasses of |
| // emulated core library clasess so that they can be stubbed (since the inherited |
| // implementation may be missing at runtime). |
| return null; |
| } |
| recordIfInstanceMethod(access, name, desc); |
| return null; |
| } |
| } |
| |
| /** |
| * Detector to determine whether an interface needs to be initialized when it is loaded. |
| * |
| * <p>If the interface has a default method, and its <clinit> initializes any of its fields, then |
| * this interface needs to be initialized. |
| */ |
| private static class InterfaceInitializationNecessityDetector extends ClassVisitor { |
| |
| private final String internalName; |
| private boolean hasFieldInitializedInClinit; |
| private boolean hasDefaultMethods; |
| |
| public InterfaceInitializationNecessityDetector(String internalName) { |
| super(Opcodes.ASM6); |
| this.internalName = internalName; |
| } |
| |
| public boolean needsToInitialize() { |
| return hasDefaultMethods && hasFieldInitializedInClinit; |
| } |
| |
| @Override |
| public void visit( |
| int version, |
| int access, |
| String name, |
| String signature, |
| String superName, |
| String[] interfaces) { |
| super.visit(version, access, name, signature, superName, interfaces); |
| checkState( |
| internalName.equals(name), |
| "Inconsistent internal names: expected=%s, real=%s", |
| internalName, |
| name); |
| checkArgument( |
| BitFlags.isSet(access, Opcodes.ACC_INTERFACE), |
| "This class visitor is only used for interfaces."); |
| } |
| |
| @Override |
| public MethodVisitor visitMethod( |
| int access, String name, String desc, String signature, String[] exceptions) { |
| if (!hasDefaultMethods) { |
| hasDefaultMethods = isNonBridgeDefaultMethod(access); |
| } |
| if ("<clinit>".equals(name)) { |
| return new MethodVisitor(Opcodes.ASM6) { |
| @Override |
| public void visitFieldInsn(int opcode, String owner, String name, String desc) { |
| if (opcode == Opcodes.PUTSTATIC && internalName.equals(owner)) { |
| hasFieldInitializedInClinit = true; |
| } |
| } |
| }; |
| } |
| return null; // Do not care about the code. |
| } |
| } |
| |
| /** Comparator for classes and interfaces that compares by whether subtyping relationship. */ |
| enum SubtypeComparator implements Comparator<Class<?>> { |
| /** Orders subtypes before supertypes and breaks ties lexicographically. */ |
| INSTANCE; |
| |
| @Override |
| public int compare(Class<?> o1, Class<?> o2) { |
| if (o1 == o2) { |
| return 0; |
| } |
| // order subtypes before supertypes |
| if (o1.isAssignableFrom(o2)) { // o1 is supertype of o2 |
| return 1; // we want o1 to come after o2 |
| } |
| if (o2.isAssignableFrom(o1)) { // o2 is supertype of o1 |
| return -1; // we want o2 to come after o1 |
| } |
| // o1 and o2 aren't comparable so arbitrarily impose lexicographical ordering |
| return o1.getName().compareTo(o2.getName()); |
| } |
| } |
| } |