com.google.devtools.build.android.desugar.InterfaceDesugaring.java Source code

Java tutorial

Introduction

Here is the source code for com.google.devtools.build.android.desugar.InterfaceDesugaring.java

Source

// 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();
            }
        }
    }
}