// 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.checkState; import javax.annotation.Nullable; import org.objectweb.asm.AnnotationVisitor; import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; import org.objectweb.asm.TypePath; /** * Visitor that moves methods with bodies from interfaces into a companion class and rewrites * call sites accordingly (which is only needed for static interface methods). Default methods * are kept as abstract methods with all their annotations. * * <p>Any necessary companion classes will be added to the given {@link GeneratedClassStore}. It's * the caller's responsibility to write those out. * * <p>Relies on {@link DefaultMethodClassFixer} to stub in method bodies for moved default methods. * Assumes that lambdas are already desugared. Ignores bridge methods, which are handled specially. */ class InterfaceDesugaring extends ClassVisitor { static final String COMPANION_SUFFIX = "$$CC"; private final ClassReaderFactory bootclasspath; private final GeneratedClassStore store; private String internalName; private int bytecodeVersion; private int accessFlags; @Nullable private ClassVisitor companion; public InterfaceDesugaring(ClassVisitor dest, ClassReaderFactory bootclasspath, GeneratedClassStore store) { super(Opcodes.ASM5, dest); this.bootclasspath = bootclasspath; this.store = store; } @Override public void visit( int version, int access, String name, String signature, String superName, String[] interfaces) { companion = null; internalName = name; bytecodeVersion = version; accessFlags = access; super.visit(version, access, name, signature, superName, interfaces); } @Override public void visitEnd() { if (companion != null) { companion.visitEnd(); } super.visitEnd(); } @Override public MethodVisitor visitMethod( int access, String name, String desc, String signature, String[] exceptions) { MethodVisitor result; if (BitFlags.isSet(accessFlags, Opcodes.ACC_INTERFACE) && BitFlags.noneSet(access, Opcodes.ACC_ABSTRACT | Opcodes.ACC_BRIDGE) && !"<clinit>".equals(name)) { checkArgument(BitFlags.noneSet(access, Opcodes.ACC_NATIVE), "Forbidden per JLS ch 9.4"); boolean isLambdaBody = name.startsWith("lambda$") && BitFlags.isSet(access, Opcodes.ACC_SYNTHETIC); if (isLambdaBody) { access &= ~Opcodes.ACC_PUBLIC; // undo visibility change from LambdaDesugaring // Rename lambda method to reflect the new owner. Not doing so confuses LambdaDesugaring // if it's run over this class again. name += COMPANION_SUFFIX; } if (BitFlags.isSet(access, Opcodes.ACC_STATIC)) { // Completely move static interface methods, which requires rewriting call sites result = companion() .visitMethod(access & ~Opcodes.ACC_PRIVATE, name, desc, signature, exceptions); } else { MethodVisitor abstractDest; if (isLambdaBody) { // Completely move lambda bodies, which requires rewriting call sites access &= ~Opcodes.ACC_PRIVATE; abstractDest = null; } else { // Make default methods abstract but move their implementation into a static method with // corresponding signature. Doesn't require callsite rewriting but implementing classes // may need to implement default methods explicitly. checkArgument(BitFlags.noneSet(access, Opcodes.ACC_PRIVATE), "Unexpected private interface method %s.%s : %s", name, internalName, desc); abstractDest = super.visitMethod( access | Opcodes.ACC_ABSTRACT, name, desc, signature, exceptions); } // TODO(b/37110951): adjust signature with explicit receiver type, which may be generic MethodVisitor codeDest = companion() .visitMethod( access | Opcodes.ACC_STATIC, name, companionDefaultMethodDescriptor(internalName, desc), (String) null, // drop signature, since given one doesn't include the new param exceptions); result = abstractDest != null ? new MultiplexAnnotations(codeDest, abstractDest) : codeDest; } } else { result = super.visitMethod(access, name, desc, signature, exceptions); } return result != null ? new InterfaceInvocationRewriter(result) : null; } /** * Returns the descriptor of a static method for an instance method with the given receiver and * description, simply by pre-pending the given descriptor's parameter list with the given * receiver type. */ static String companionDefaultMethodDescriptor(String interfaceName, String desc) { Type type = Type.getMethodType(desc); Type[] companionArgs = new Type[type.getArgumentTypes().length + 1]; companionArgs[0] = Type.getObjectType(interfaceName); System.arraycopy(type.getArgumentTypes(), 0, companionArgs, 1, type.getArgumentTypes().length); return Type.getMethodDescriptor(type.getReturnType(), companionArgs); } private ClassVisitor companion() { if (companion == null) { checkState(BitFlags.isSet(accessFlags, Opcodes.ACC_INTERFACE)); String companionName = internalName + COMPANION_SUFFIX; companion = store.add(companionName); companion.visit( bytecodeVersion, // Companion class must be public so moved methods can be called from anywhere (accessFlags | Opcodes.ACC_SYNTHETIC | Opcodes.ACC_PUBLIC) & ~Opcodes.ACC_INTERFACE, companionName, (String) null, // signature "java/lang/Object", new String[0]); } return companion; } /** * Rewriter for calls to static interface methods and super calls to default methods, unless * they're part of the bootclasspath, as well as all lambda body methods. Keeps calls to * interface methods declared in the bootclasspath as-is (but note that these would presumably * fail on devices without those methods). */ private class InterfaceInvocationRewriter extends MethodVisitor { public InterfaceInvocationRewriter(MethodVisitor dest) { super(Opcodes.ASM5, dest); } @Override public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) { // Assume that any static interface methods on the classpath are moved if (itf) { if (name.startsWith("lambda$")) { // Redirect lambda invocations to completely remove all lambda methods from interfaces. checkArgument(!owner.endsWith(COMPANION_SUFFIX), "%s shouldn't consider %s an interface", internalName, owner); checkArgument(!bootclasspath.isKnown(owner)); // must be in current input if (opcode == Opcodes.INVOKEINTERFACE) { opcode = Opcodes.INVOKESTATIC; desc = companionDefaultMethodDescriptor(owner, desc); } else { checkArgument(opcode == Opcodes.INVOKESTATIC, "Unexpected opcode %s to invoke %s.%s", opcode, owner, name); } // Reflect that InterfaceDesugaring moves and renames the lambda body method owner += COMPANION_SUFFIX; name += COMPANION_SUFFIX; checkState(name.equals(LambdaDesugaring.uniqueInPackage(owner, name)), "Unexpected lambda body method name %s for %s", name, owner); itf = false; } else if ((opcode == Opcodes.INVOKESTATIC || opcode == Opcodes.INVOKESPECIAL) && !bootclasspath.isKnown(owner)) { checkArgument(!owner.endsWith(COMPANION_SUFFIX), "%s shouldn't consider %s an interface", internalName, owner); if (opcode == Opcodes.INVOKESPECIAL) { // Turn Interface.super.m() into Interface$$CC.m(receiver) opcode = Opcodes.INVOKESTATIC; desc = companionDefaultMethodDescriptor(owner, desc); } owner += COMPANION_SUFFIX; itf = false; } } super.visitMethodInsn(opcode, owner, name, desc, itf); } } /** * Method visitor that behaves like a passthrough but additionally duplicates all annotations * into a second given {@link MethodVisitor}. */ private static class MultiplexAnnotations extends MethodVisitor { private final MethodVisitor annotationOnlyDest; public MultiplexAnnotations(@Nullable MethodVisitor dest, MethodVisitor annotationOnlyDest) { super(Opcodes.ASM5, dest); this.annotationOnlyDest = annotationOnlyDest; } @Override public void visitParameter(String name, int access) { super.visitParameter(name, access); annotationOnlyDest.visitParameter(name, access); } @Override public AnnotationVisitor visitTypeAnnotation( int typeRef, TypePath typePath, String desc, boolean visible) { AnnotationVisitor dest = super.visitTypeAnnotation(typeRef, typePath, desc, visible); AnnotationVisitor annoDest = annotationOnlyDest.visitTypeAnnotation(typeRef, typePath, desc, visible); return new MultiplexAnnotationVisitor(dest, annoDest); } @Override public AnnotationVisitor visitParameterAnnotation(int parameter, String desc, boolean visible) { AnnotationVisitor dest = super.visitParameterAnnotation(parameter, desc, visible); AnnotationVisitor annoDest = annotationOnlyDest.visitParameterAnnotation(parameter, desc, visible); return new MultiplexAnnotationVisitor(dest, annoDest); } } /** * Annotation visitor that recursively passes the visited annotations to any number of given * {@link AnnotationVisitor}s. */ private static class MultiplexAnnotationVisitor extends AnnotationVisitor { private final AnnotationVisitor[] moreDestinations; public MultiplexAnnotationVisitor(@Nullable AnnotationVisitor dest, AnnotationVisitor... moreDestinations) { super(Opcodes.ASM5, dest); this.moreDestinations = moreDestinations; } @Override public void visit(String name, Object value) { super.visit(name, value); for (AnnotationVisitor dest : moreDestinations) { dest.visit(name, value); } } @Override public void visitEnum(String name, String desc, String value) { super.visitEnum(name, desc, value); for (AnnotationVisitor dest : moreDestinations) { dest.visitEnum(name, desc, value); } } @Override public AnnotationVisitor visitAnnotation(String name, String desc) { AnnotationVisitor[] subVisitors = new AnnotationVisitor[moreDestinations.length]; AnnotationVisitor dest = super.visitAnnotation(name, desc); for (int i = 0; i < subVisitors.length; ++i) { subVisitors[i] = moreDestinations[i].visitAnnotation(name, desc); } return new MultiplexAnnotationVisitor(dest, subVisitors); } @Override public AnnotationVisitor visitArray(String name) { AnnotationVisitor[] subVisitors = new AnnotationVisitor[moreDestinations.length]; AnnotationVisitor dest = super.visitArray(name); for (int i = 0; i < subVisitors.length; ++i) { subVisitors[i] = moreDestinations[i].visitArray(name); } return new MultiplexAnnotationVisitor(dest, subVisitors); } @Override public void visitEnd() { super.visitEnd(); for (AnnotationVisitor dest : moreDestinations) { dest.visitEnd(); } } } }