net.orfjackal.retrolambda.lambdas.BackportLambdaClass.java Source code

Java tutorial

Introduction

Here is the source code for net.orfjackal.retrolambda.lambdas.BackportLambdaClass.java

Source

// Copyright  2013-2017 Esko Luontola and other Retrolambda contributors
// This software is released under the Apache License 2.0.
// The license text is at http://www.apache.org/licenses/LICENSE-2.0

package net.orfjackal.retrolambda.lambdas;

import org.objectweb.asm.*;
import org.objectweb.asm.tree.*;

import static org.objectweb.asm.Opcodes.*;

public class BackportLambdaClass extends ClassVisitor {

    private static final String SINGLETON_FIELD_NAME = "instance";
    private static final String JAVA_LANG_OBJECT = "java/lang/Object";

    private String lambdaClass;
    private Type constructor;
    private Handle implMethod;
    private Handle accessMethod;
    private LambdaFactoryMethod factoryMethod;

    public BackportLambdaClass(ClassVisitor next) {
        super(ASM5, next);
    }

    @Override
    public void visit(int version, int access, String name, String signature, String superName,
            String[] interfaces) {
        lambdaClass = name;
        LambdaReifier.setLambdaClass(lambdaClass);
        implMethod = LambdaReifier.getLambdaImplMethod();
        accessMethod = LambdaReifier.getLambdaAccessMethod();
        factoryMethod = LambdaReifier.getLambdaFactoryMethod();

        if (superName.equals(LambdaNaming.MAGIC_LAMBDA_IMPL)) {
            superName = JAVA_LANG_OBJECT;
        }
        super.visit(version, access, name, signature, superName, interfaces);
    }

    @Override
    public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
        if (name.equals("<init>")) {
            constructor = Type.getMethodType(desc);
        }
        if (LambdaNaming.isSerializationHook(access, name, desc)) {
            return null; // remove serialization hooks; we serialize lambda instances as-is
        }
        if (LambdaNaming.isPlatformFactoryMethod(access, name, desc, factoryMethod.getDesc())) {
            return null; // remove the JVM's factory method which will not be unused
        }
        MethodVisitor next = super.visitMethod(access, name, desc, signature, exceptions);
        next = new RemoveLambdaFormHiddenAnnotation(next);
        next = new RemoveMagicLambdaConstructorCall(next);
        next = new CallPrivateImplMethodsViaAccessMethods(access, name, desc, signature, exceptions, next);
        return next;
    }

    @Override
    public void visitEnd() {
        if (isStateless()) {
            makeSingleton();
        }
        generateFactoryMethod();
        super.visitEnd();
    }

    private void makeSingleton() {
        FieldVisitor fv = super.visitField(ACC_PRIVATE | ACC_STATIC | ACC_FINAL, SINGLETON_FIELD_NAME,
                singletonFieldDesc(), null, null);
        fv.visitEnd();

        MethodVisitor mv = super.visitMethod(ACC_STATIC, "<clinit>", "()V", null, null);
        mv.visitCode();
        mv.visitTypeInsn(NEW, lambdaClass);
        mv.visitInsn(DUP);
        mv.visitMethodInsn(INVOKESPECIAL, lambdaClass, "<init>", "()V", false);
        mv.visitFieldInsn(PUTSTATIC, lambdaClass, SINGLETON_FIELD_NAME, singletonFieldDesc());
        mv.visitInsn(RETURN);
        mv.visitMaxs(-1, -1); // rely on ClassWriter.COMPUTE_MAXS
        mv.visitEnd();
    }

    private void generateFactoryMethod() {
        MethodVisitor mv = cv.visitMethod(ACC_PUBLIC | ACC_STATIC, factoryMethod.getName(), factoryMethod.getDesc(),
                null, null);
        mv.visitCode();

        if (isStateless()) {
            mv.visitFieldInsn(GETSTATIC, lambdaClass, SINGLETON_FIELD_NAME, singletonFieldDesc());
            mv.visitInsn(ARETURN);

        } else {
            mv.visitTypeInsn(NEW, lambdaClass);
            mv.visitInsn(DUP);
            int varIndex = 0;
            for (Type type : constructor.getArgumentTypes()) {
                mv.visitVarInsn(type.getOpcode(ILOAD), varIndex);
                varIndex += type.getSize();
            }
            mv.visitMethodInsn(INVOKESPECIAL, lambdaClass, "<init>", constructor.getDescriptor(), false);
            mv.visitInsn(ARETURN);
        }

        mv.visitMaxs(-1, -1); // rely on ClassWriter.COMPUTE_MAXS
        mv.visitEnd();
    }

    private String singletonFieldDesc() {
        return "L" + lambdaClass + ";";
    }

    private boolean isStateless() {
        return constructor.getArgumentTypes().length == 0;
    }

    private static class RemoveMagicLambdaConstructorCall extends MethodVisitor {

        public RemoveMagicLambdaConstructorCall(MethodVisitor next) {
            super(ASM5, next);
        }

        @Override
        public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) {
            if (opcode == INVOKESPECIAL && owner.equals(LambdaNaming.MAGIC_LAMBDA_IMPL) && name.equals("<init>")
                    && desc.equals("()V")) {
                owner = JAVA_LANG_OBJECT;
            }
            super.visitMethodInsn(opcode, owner, name, desc, itf);
        }
    }

    private class CallPrivateImplMethodsViaAccessMethods extends MethodNode {
        private final MethodVisitor next;

        public CallPrivateImplMethodsViaAccessMethods(int access, String name, String desc, String signature,
                String[] exceptions, MethodVisitor next) {
            super(ASM5, access, name, desc, signature, exceptions);
            this.next = next;
        }

        @Override
        public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) {
            // Java 8's lambda classes get away with calling private virtual methods
            // by using invokespecial because the JVM relaxes the bytecode validation
            // of the lambda classes it generates. We must however call them through
            // a non-private access method which we have generated.
            if (owner.equals(implMethod.getOwner()) && name.equals(implMethod.getName())
                    && desc.equals(implMethod.getDesc())) {

                if (implMethod.getTag() == H_NEWINVOKESPECIAL && accessMethod.getTag() == H_INVOKESTATIC) {
                    // The impl is a private constructor which is called through an access method.
                    // The current method already did NEW an instance, but we won't use it because
                    // the access method will also instantiate it. The JVM would be OK with a non-empty
                    // stack on ARETURN, but it causes a VerifyError on Android, so here we remove the
                    // unused instance from the stack.
                    boolean found = false;
                    for (int i = instructions.size() - 1; i >= 1; i--) {
                        AbstractInsnNode maybeNew = instructions.get(i - 1);
                        AbstractInsnNode maybeDup = instructions.get(i);
                        if (maybeNew.getOpcode() == NEW && maybeDup.getOpcode() == DUP) {
                            instructions.remove(maybeNew);
                            instructions.remove(maybeDup);
                            found = true;
                            break;
                        }
                    }
                    if (!found) {
                        throw new IllegalStateException(
                                "Expected to find NEW, DUP instructions preceding NEWINVOKESPECIAL. Please file this as a bug.");
                    }
                }

                super.visitMethodInsn(Handles.getOpcode(accessMethod), accessMethod.getOwner(),
                        accessMethod.getName(), accessMethod.getDesc(), accessMethod.getTag() == H_INVOKEINTERFACE);
            } else {
                super.visitMethodInsn(opcode, owner, name, desc, itf);
            }
        }

        @Override
        public void visitEnd() {
            accept(next);
        }
    }
}