com.android.build.gradle.shrinker.DependencyFinderVisitor.java Source code

Java tutorial

Introduction

Here is the source code for com.android.build.gradle.shrinker.DependencyFinderVisitor.java

Source

/*
 * Copyright (C) 2015 The Android Open Source Project
 *
 * 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.android.build.gradle.shrinker;

import static com.android.build.gradle.shrinker.AbstractShrinker.isSdkPackage;

import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
import com.android.build.gradle.internal.incremental.ByteCodeUtils;
import com.android.build.gradle.shrinker.PostProcessingData.UnresolvedReference;
import com.google.common.base.Objects;
import com.google.common.collect.ImmutableMap;
import java.util.ArrayDeque;
import java.util.Deque;
import org.objectweb.asm.AnnotationVisitor;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.FieldVisitor;
import org.objectweb.asm.Handle;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.signature.SignatureReader;
import org.objectweb.asm.signature.SignatureVisitor;

/**
 * {@link ClassVisitor} that finds all dependencies that should be added to the shrinker graph.
 *
 * <p>Subclasses should implement the {@link #handleDependency(Object, Object, DependencyType)}
 * method.
 */
abstract class DependencyFinderVisitor<T> extends ClassVisitor {
    private final ShrinkerGraph<T> mGraph;
    private String mClassName;
    private boolean mIsAnnotation;
    private T mKlass;

    DependencyFinderVisitor(ShrinkerGraph<T> graph, ClassVisitor cv) {
        super(Opcodes.ASM5, cv);
        mGraph = graph;
    }

    @Override
    public void visit(int version, int access, String name, String signature, String superName,
            String[] interfaces) {
        if (interfaces == null) {
            interfaces = new String[0];
        }

        mKlass = mGraph.getClassReference(name);
        if (superName != null && !isSdkPackage(superName)) {
            // Don't create graph edges for obvious things.
            handleDependency(mKlass, mGraph.getClassReference(superName), DependencyType.REQUIRED_CLASS_STRUCTURE);
        }

        if (interfaces.length > 0) {
            handleInterfaceInheritance(mKlass);

            if (!Objects.equal(superName, "java/lang/Object")) {
                // It's possible the superclass is implementing a method from the interface, we may
                // need to add more edges to the graph to represent this.
                handleMultipleInheritance(mKlass);
            }
        }

        mClassName = name;
        mIsAnnotation = (access & Opcodes.ACC_ANNOTATION) != 0;

        if (signature != null) {
            SignatureReader reader = new SignatureReader(signature);
            SignatureVisitor visitor = new DependencyFinderSignatureVisitor();
            reader.accept(visitor);
        }

        super.visit(version, access, name, signature, superName, interfaces);
    }

    @Override
    public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
        T method = mGraph.getMemberReference(mClassName, name, desc);

        if ((access & Opcodes.ACC_STATIC) == 0 && !name.equals(ByteCodeUtils.CONSTRUCTOR)) {
            handleVirtualMethod(method);
        }

        // Keep class initializers, but also keep all default constructors. This is the ProGuard
        // behavior,
        if (name.equals(ByteCodeUtils.CLASS_INITIALIZER)
                || (name.equals(ByteCodeUtils.CONSTRUCTOR) && desc.equals("()V"))) {
            handleDependency(mKlass, method, DependencyType.REQUIRED_CLASS_STRUCTURE);
        }

        if (mIsAnnotation) {
            // TODO: Strip unused annotation classes members.
            handleDependency(mKlass, method, DependencyType.REQUIRED_CLASS_STRUCTURE);
        }

        if (signature != null) {
            SignatureReader reader = new SignatureReader(signature);
            SignatureVisitor visitor = new DependencyFinderSignatureVisitor();
            reader.accept(visitor);
        }

        return new DependencyFinderMethodVisitor(method, desc,
                super.visitMethod(access, name, desc, signature, exceptions));
    }

    @Override
    public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) {
        T field = mGraph.getMemberReference(mClassName, name, desc);
        Type fieldType = Type.getType(desc);
        handleDeclarationType(field, fieldType);

        if (signature != null) {
            SignatureReader reader = new SignatureReader(signature);
            SignatureVisitor visitor = new DependencyFinderSignatureVisitor();
            reader.acceptType(visitor);
        }

        return super.visitField(access, name, desc, signature, value);
    }

    @Override
    public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
        if (!visible) {
            return super.visitAnnotation(desc, false);
        } else {
            return handleAnnotation(mKlass, desc);
        }
    }

    @NonNull
    private AnnotationVisitor handleAnnotation(T source, String desc) {
        Type type = Type.getType(desc);
        handleDeclarationType(source, type);
        return new DependencyFinderAnnotationVisitor(type.getInternalName(), source,
                super.visitAnnotation(desc, true));
    }

    @Override
    public void visitInnerClass(String name, String outerName, String innerName, int access) {
        if (mClassName.equals(name) && outerName != null) {
            // I'm the inner class, keep the outer class, as ProGuard does.
            handleDependency(mKlass, mGraph.getClassReference(outerName), DependencyType.REQUIRED_CLASS_STRUCTURE);
        }
        super.visitInnerClass(name, outerName, innerName, access);
    }

    private void handleDeclarationType(T member, Type type) {
        String className = getTargetClassName(type);
        if (className != null) {
            T classReference = mGraph.getClassReference(className);
            handleDependency(member, classReference, DependencyType.REQUIRED_CLASS_STRUCTURE);
        }
    }

    /**
     * Extracts a dependency target from a {@link Type} object. Ignores SDK classes.
     *
     * @param type A {@link Type} object, encountered by the visitor. Cannot be of sort METHOD.
     * @return If the {@link Type} is for a class or an object array, the internal name of that
     *     class. Null if the that class is an SDK class.
     * @see AbstractShrinker#isSdkPackage(String)
     */
    @Nullable
    private static String getTargetClassName(Type type) {
        switch (type.getSort()) {
        case Type.VOID:
        case Type.SHORT:
        case Type.INT:
        case Type.LONG:
        case Type.FLOAT:
        case Type.BOOLEAN:
        case Type.BYTE:
        case Type.DOUBLE:
        case Type.CHAR:
            return null;
        case Type.ARRAY:
            return getTargetClassName(type.getElementType());
        case Type.OBJECT:
            String name = type.getInternalName();
            return isSdkPackage(name) ? null : name;
        case Type.METHOD:
            throw new IllegalArgumentException("Can't extract one class from a METHOD Type.");
        default:
            throw new AssertionError("Unknown Type sort " + type.getSort());
        }
    }

    protected abstract void handleDependency(T source, T target, DependencyType type);

    protected abstract void handleMultipleInheritance(T klass);

    protected abstract void handleVirtualMethod(T method);

    protected abstract void handleInterfaceInheritance(T klass);

    protected abstract void handleUnresolvedReference(UnresolvedReference<T> reference);

    private class DependencyFinderMethodVisitor extends MethodVisitor {

        private final T mMethod;

        /*
         * We want to detect calls to AtomicFieldUpdaters to figure out dependencies in this
         * common case that uses reflection. We detect the method call and, if both instructions
         * that precede it are two LDCs. In that case the first one should be a type and the
         * second one should be a field name.
         */
        private final Deque<Object> mLastLdcs;

        DependencyFinderMethodVisitor(T method, String desc, MethodVisitor mv) {
            super(Opcodes.ASM5, mv);
            this.mMethod = method;
            mLastLdcs = new ArrayDeque<>();
            visitType(Type.getMethodType(desc));
        }

        @Override
        public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
            if (!visible) {
                return super.visitAnnotation(desc, false);
            } else {
                return handleAnnotation(mMethod, desc);
            }
        }

        @Override
        public AnnotationVisitor visitAnnotationDefault() {
            return new DependencyFinderAnnotationVisitor(null, mMethod, super.visitAnnotationDefault());
        }

        @Override
        public void visitTypeInsn(int opcode, String type) {
            String className = getTargetClassName(Type.getObjectType(type));
            if (className != null) {
                T classReference = mGraph.getClassReference(className);
                handleDependency(mMethod, classReference, DependencyType.REQUIRED_CODE_REFERENCE);
            }

            mLastLdcs.clear();
            super.visitTypeInsn(opcode, type);
        }

        @Override
        public void visitFieldInsn(int opcode, String owner, String name, String desc) {
            if (!isSdkPackage(owner)) {
                handleDependency(mMethod, mGraph.getClassReference(owner), DependencyType.REQUIRED_CODE_REFERENCE);
                T target = mGraph.getMemberReference(owner, name, desc);
                handleUnresolvedReference(
                        new UnresolvedReference<>(mMethod, target, false, DependencyType.REQUIRED_CODE_REFERENCE));
            }

            mLastLdcs.clear();
            super.visitFieldInsn(opcode, owner, name, desc);
        }

        @Override
        public void visitLdcInsn(Object cst) {
            if (cst instanceof Type) {
                Type type = Type.getObjectType(((Type) cst).getInternalName());
                String className = getTargetClassName(type);
                if (className != null) {
                    T classReference = mGraph.getClassReference(className);
                    handleDependency(mMethod, classReference, DependencyType.REQUIRED_CODE_REFERENCE);
                }
            }

            mLastLdcs.push(cst);
            super.visitLdcInsn(cst);
        }

        @Override
        public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) {
            // This can be the case when calling "clone" on arrays, which is done in Enum classes.
            // Just ignore it, we know arrays not declared in the program, so there's no point in
            // creating the dependency.
            Type type = Type.getObjectType(owner);
            if (type.getSort() != Type.ARRAY && !isSdkPackage(owner)) {
                handleDependency(mMethod, mGraph.getClassReference(owner), DependencyType.REQUIRED_CODE_REFERENCE);

                T target = mGraph.getMemberReference(owner, name, desc);

                if (opcode == Opcodes.INVOKESPECIAL
                        && (name.equals(ByteCodeUtils.CONSTRUCTOR) || owner.equals(mClassName))) {
                    // The "invokenonvirtual" semantics of invokespecial, for calling constructors
                    // and private methods.
                    handleDependency(mMethod, target, DependencyType.REQUIRED_CODE_REFERENCE);
                } else {
                    // In all other cases we have to go through resolution stage (including fields,
                    // static methods etc).
                    handleUnresolvedReference(new UnresolvedReference<>(mMethod, target,
                            opcode == Opcodes.INVOKESPECIAL, DependencyType.REQUIRED_CODE_REFERENCE));
                }
            }

            ReflectionMethod reflectionMethod = ReflectionMethod.findBySignature(new Signature(owner, name, desc));

            if (reflectionMethod != null) {
                Deque<Object> stackCopy = new ArrayDeque<>(mLastLdcs);
                T target = reflectionMethod.getMember(mGraph, stackCopy);
                if (target != null) {
                    if (reflectionMethod == ReflectionMethod.CLASS_FOR_NAME) {
                        // 'target' is a class, create a direct dependency.
                        handleDependency(mMethod, target, DependencyType.REQUIRED_CODE_REFERENCE_REFLECTION);
                    } else {
                        // Resolve the exact dependency.
                        handleUnresolvedReference(new UnresolvedReference<>(mMethod, target, false,
                                DependencyType.REQUIRED_CODE_REFERENCE_REFLECTION));
                    }
                }
            }

            mLastLdcs.clear();
            super.visitMethodInsn(opcode, owner, name, desc, itf);
        }

        @Override
        public void visitMultiANewArrayInsn(String desc, int dims) {
            String className = getTargetClassName(Type.getType(desc));
            if (className != null) {
                handleDependency(mMethod, mGraph.getClassReference(className),
                        DependencyType.REQUIRED_CODE_REFERENCE);
            }

            mLastLdcs.clear();
            super.visitMultiANewArrayInsn(desc, dims);
        }

        @Override
        public void visitTryCatchBlock(Label start, Label end, Label handler, String type) {
            if (type != null && !isSdkPackage(type)) {
                handleDependency(mMethod, mGraph.getClassReference(type), DependencyType.REQUIRED_CODE_REFERENCE);
            }

            mLastLdcs.clear();
            super.visitTryCatchBlock(start, end, handler, type);
        }

        @Override
        public void visitInsn(int opcode) {
            mLastLdcs.clear();
            super.visitInsn(opcode);
        }

        @Override
        public void visitIntInsn(int opcode, int operand) {
            mLastLdcs.clear();
            super.visitIntInsn(opcode, operand);
        }

        @Override
        public void visitVarInsn(int opcode, int var) {
            mLastLdcs.clear();
            super.visitVarInsn(opcode, var);
        }

        @Override
        public void visitInvokeDynamicInsn(String name, String desc, Handle bsm, Object... bsmArgs) {
            visitType(Type.getMethodType(desc));
            visitConstantHandle(bsm);

            for (Object bsmArg : bsmArgs) {
                if (bsmArg instanceof Type) {
                    visitType((Type) bsmArg);
                } else if (bsmArg instanceof Handle) {
                    visitConstantHandle((Handle) bsmArg);
                }
            }

            mLastLdcs.clear();
            super.visitInvokeDynamicInsn(name, desc, bsm, bsmArgs);
        }

        private void visitType(Type type) {
            if (type.getSort() == Type.METHOD) {
                visitType(type.getReturnType());
                for (Type argType : type.getArgumentTypes()) {
                    visitType(argType);
                }
            } else {
                String className = getTargetClassName(type);
                if (className != null) {
                    handleDependency(mMethod, mGraph.getClassReference(className),
                            DependencyType.REQUIRED_CODE_REFERENCE);
                }
            }
        }

        private void visitConstantHandle(Handle bsm) {
            // Make sure all types mentioned in the bytecode are kept.
            visitType(Type.getMethodType(bsm.getDesc()));

            if (!isSdkPackage(bsm.getOwner())) {
                T targetMethod = mGraph.getMemberReference(bsm.getOwner(), bsm.getName(), bsm.getDesc());

                if (bsm.getTag() == Opcodes.H_INVOKESPECIAL) {
                    // This MethodHandle won't look for overrides.
                    handleDependency(mMethod, targetMethod, DependencyType.REQUIRED_CODE_REFERENCE);
                } else {
                    handleUnresolvedReference(new UnresolvedReference<>(mMethod, targetMethod,
                            bsm.getTag() == Opcodes.H_INVOKESPECIAL, DependencyType.REQUIRED_CODE_REFERENCE));
                }
            }
        }

        @Override
        public void visitIincInsn(int var, int increment) {
            mLastLdcs.clear();
            super.visitIincInsn(var, increment);
        }

        @Override
        public void visitJumpInsn(int opcode, Label label) {
            mLastLdcs.clear();
            super.visitJumpInsn(opcode, label);
        }

        @Override
        public void visitLocalVariable(String name, String desc, String signature, Label start, Label end,
                int index) {
            if (signature != null) {
                SignatureReader reader = new SignatureReader(signature);
                SignatureVisitor visitor = new DependencyFinderSignatureVisitor();
                reader.acceptType(visitor);
            }
            super.visitLocalVariable(name, desc, signature, start, end, index);
        }
    }

    private class DependencyFinderAnnotationVisitor extends AnnotationVisitor {
        private final String mAnnotationName;
        private final T mSource;

        DependencyFinderAnnotationVisitor(String annotationName, T source, AnnotationVisitor av) {
            super(Opcodes.ASM5, av);
            mAnnotationName = annotationName;
            mSource = source;
        }

        @Override
        public void visit(String name, Object value) {
            if (value instanceof Type) {
                handleDeclarationType(mSource, (Type) value);
            }
            super.visit(name, value);
        }

        @Override
        public void visitEnum(String name, String desc, String value) {
            String internalName = getTargetClassName(Type.getType(desc));
            if (internalName != null) {
                handleDependency(mSource, mGraph.getClassReference(internalName),
                        DependencyType.REQUIRED_CLASS_STRUCTURE);
                handleDependency(mSource, mGraph.getMemberReference(internalName, value, desc),
                        DependencyType.REQUIRED_CLASS_STRUCTURE);
            }

            super.visitEnum(name, desc, value);
        }

        @Override
        public AnnotationVisitor visitAnnotation(String name, String desc) {
            String internalName = getTargetClassName(Type.getType(desc));
            if (internalName != null) {
                handleDependency(mSource, mGraph.getClassReference(internalName),
                        DependencyType.REQUIRED_CLASS_STRUCTURE);
            }
            return new DependencyFinderAnnotationVisitor(mAnnotationName, mSource,
                    super.visitAnnotation(name, desc));
        }

        @Override
        public AnnotationVisitor visitArray(String name) {
            return new DependencyFinderAnnotationVisitor(mAnnotationName, mSource, super.visitArray(name));
        }
    }

    private class DependencyFinderSignatureVisitor extends SignatureVisitor {

        DependencyFinderSignatureVisitor() {
            super(Opcodes.ASM5);
        }

        @Override
        public void visitClassType(String name) {
            if (!isSdkPackage(name)) {
                mGraph.addTypeFromGenericSignature(mKlass, mGraph.getClassReference(name));
            }
            super.visitClassType(name);
        }

        @Override
        public void visitInnerClassType(String name) {
            if (!isSdkPackage(name)) {
                mGraph.addTypeFromGenericSignature(mKlass, mGraph.getClassReference(name));
            }
            super.visitInnerClassType(name);
        }
    }

    /** A method signature, tuple of owner, name and descriptor. */
    private static class Signature {
        @NonNull
        private final String owner;
        @NonNull
        private final String name;
        @NonNull
        private final String desc;

        Signature(@NonNull String owner, @NonNull String name, @NonNull String desc) {
            this.owner = owner;
            this.name = name;
            this.desc = desc;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) {
                return true;
            }
            if (o == null || getClass() != o.getClass()) {
                return false;
            }
            Signature method = (Signature) o;
            return Objects.equal(owner, method.owner) && Objects.equal(name, method.name)
                    && Objects.equal(desc, method.desc);
        }

        @Override
        public int hashCode() {
            return Objects.hashCode(owner, name, desc);
        }
    }

    /** Represents reflection APIs that we recognize and understand. */
    private enum ReflectionMethod {
        CLASS_FOR_NAME("java/lang/Class", "forName", "(Ljava/lang/String;)Ljava/lang/Class;") {
            @Override
            public <T> T getMember(ShrinkerGraph<T> graph, Deque<Object> stack) {
                if (!(stack.peek() instanceof String)) {
                    return null;
                }

                return graph.getClassReference(ByteCodeUtils.toInternalName((String) stack.pop()));
            }
        },
        ATOMIC_INTEGER_FIELD_UPDATER("java/util/concurrent/atomic/AtomicIntegerFieldUpdater", "newUpdater",
                "(Ljava/lang/Class;Ljava/lang/String;)"
                        + "Ljava/util/concurrent/atomic/AtomicIntegerFieldUpdater;") {
            @Override
            public <T> T getMember(ShrinkerGraph<T> graph, Deque<Object> stack) {
                return primitiveFieldUpdater(graph, stack, "I");
            }
        },
        ATOMIC_LONG_FIELD_UPDATER("java/util/concurrent/atomic/AtomicLongFieldUpdater", "newUpdater",
                "(Ljava/lang/Class;Ljava/lang/String;)" + "Ljava/util/concurrent/atomic/AtomicLongFieldUpdater;") {
            @Override
            public <T> T getMember(ShrinkerGraph<T> graph, Deque<Object> stack) {
                return primitiveFieldUpdater(graph, stack, "J");
            }
        },
        ATOMIC_REFERENCE_FIELD_UPDATER("java/util/concurrent/atomic/AtomicReferenceFieldUpdater", "newUpdater",
                "(Ljava/lang/Class;Ljava/lang/Class;Ljava/lang/String;)"
                        + "Ljava/util/concurrent/atomic/AtomicReferenceFieldUpdater;") {
            @Override
            public <T> T getMember(ShrinkerGraph<T> graph, Deque<Object> stack) {
                if (!(stack.peek() instanceof String)) {
                    return null;
                }
                String fieldName = (String) stack.pop();

                if (!(stack.peek() instanceof Type)) {
                    return null;
                }
                Type fieldType = (Type) stack.pop();

                if (!(stack.peek() instanceof Type)) {
                    return null;
                }
                Type klass = (Type) stack.pop();

                return graph.getMemberReference(klass.getInternalName(), fieldName, fieldType.getDescriptor());
            }
        },;
        private static final ImmutableMap<Signature, ReflectionMethod> BY_SIGNATURE;

        static {
            // Store all known reflection methods, indexed by "full" signature.
            ImmutableMap.Builder<Signature, ReflectionMethod> builder = ImmutableMap.builder();
            for (ReflectionMethod reflectionMethod : ReflectionMethod.values()) {
                builder.put(reflectionMethod.getSignature(), reflectionMethod);
            }
            BY_SIGNATURE = builder.build();
        }

        /**
         * Finds an instance of {@link ReflectionMethod} for the given {@link Signature}.
         *
         * @param signature signature to check
         * @return {@link ReflectionMethod} with the given signature, if supported, null otherwise
         */
        @Nullable
        public static ReflectionMethod findBySignature(Signature signature) {
            return BY_SIGNATURE.get(signature);
        }

        @NonNull
        private Signature mSignature;

        ReflectionMethod(@NonNull String owner, @NonNull String name, @NonNull String desc) {
            mSignature = new Signature(owner, name, desc);
        }

        @NonNull
        public Signature getSignature() {
            return mSignature;
        }

        /**
         * Returns the referenced class or member, that the given reflection method indirectly
         * references.
         *
         * <p>{@link DependencyFinderVisitor} keeps track of consecutive {@code ldc} instructions
         * and passes the loaded values to this method as the {@code stack} parameter.
         *
         * @param graph {@link ShrinkerGraph} in use
         * @param stack read-only copy of the constant values that have been pushed on the stack
         *     just before invoking the method in question
         * @return target of the "indirect" reference, or null if it cannot be determined from the
         *     constant arguments
         */
        @Nullable
        public abstract <T> T getMember(ShrinkerGraph<T> graph, Deque<Object> stack);

        /**
         * Common code for handling {@code AtomicIntegerFieldUpdater} and {@code
         * AtomicLongFieldUpdater}.
         *
         * @see #getMember(ShrinkerGraph, Deque)
         */
        private static <T> T primitiveFieldUpdater(@NonNull ShrinkerGraph<T> graph, @NonNull Deque<Object> stack,
                @NonNull String desc) {
            if (!(stack.peek() instanceof String)) {
                return null;
            }
            String fieldName = (String) stack.pop();

            if (!(stack.peek() instanceof Type)) {
                return null;
            }
            Type type = (Type) stack.pop();

            return graph.getMemberReference(type.getInternalName(), fieldName, desc);
        }
    }
}