rubah.bytecode.transformers.AddGettersAndSetters.java Source code

Java tutorial

Introduction

Here is the source code for rubah.bytecode.transformers.AddGettersAndSetters.java

Source

/*******************************************************************************
 *     Copyright 2014,
 *        Luis Pina <luis@luispina.me>,
 *        Michael Hicks <mwh@cs.umd.edu>
 *     
 *     This file is part of Rubah.
 *
 *     Rubah is free software: you can redistribute it and/or modify
 *     it under the terms of the GNU General Public License as published by
 *     the Free Software Foundation, either version 3 of the License, or
 *     (at your option) any later version.
 *
 *     Rubah is distributed in the hope that it will be useful,
 *     but WITHOUT ANY WARRANTY; without even the implied warranty of
 *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *     GNU General Public License for more details.
 *
 *     You should have received a copy of the GNU General Public License
 *     along with Rubah.  If not, see <http://www.gnu.org/licenses/>.
 *******************************************************************************/
package rubah.bytecode.transformers;

import java.lang.reflect.Modifier;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import org.javatuples.Pair;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.FieldVisitor;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;

import rubah.Rubah;
import rubah.framework.Clazz;
import rubah.framework.Field;
import rubah.framework.Method;
import rubah.framework.Namespace;
import rubah.framework.Type;
import rubah.runtime.Version;
import sun.misc.Unsafe;

public class AddGettersAndSetters extends RubahTransformer {
    public static final String GETTER_PREFFIX = "$GET$";
    public static final String SETTER_PREFFIX = "$SET$";
    private static final int ACCESS = ACC_PUBLIC | ACC_SYNTHETIC;
    private static final Type UNSAFE_TYPE = Type.getType(Unsafe.class);
    private static final Type STRING_TYPE = Type.getType(String.class);
    private static final Type CLASS_TYPE = Type.getType(Class.class);
    private static final Type FIELD_TYPE = Type.getType(java.lang.reflect.Field.class);
    private static final Type OBJECT_TYPE = Type.getType(Object.class);
    private static final Map<Integer, Pair<String, Type>> putInvocationMap;

    static {
        putInvocationMap = new HashMap<Integer, Pair<String, Type>>();
        putInvocationMap.put(org.objectweb.asm.Type.BOOLEAN,
                new Pair<String, Type>("putBoolean", Type.BOOLEAN_TYPE));
        putInvocationMap.put(org.objectweb.asm.Type.BYTE, new Pair<String, Type>("putByte", Type.BYTE_TYPE));
        putInvocationMap.put(org.objectweb.asm.Type.CHAR, new Pair<String, Type>("putChar", Type.CHAR_TYPE));
        putInvocationMap.put(org.objectweb.asm.Type.DOUBLE, new Pair<String, Type>("putDouble", Type.DOUBLE_TYPE));
        putInvocationMap.put(org.objectweb.asm.Type.FLOAT, new Pair<String, Type>("putFloat", Type.FLOAT_TYPE));
        putInvocationMap.put(org.objectweb.asm.Type.INT, new Pair<String, Type>("putInt", Type.INT_TYPE));
        putInvocationMap.put(org.objectweb.asm.Type.LONG, new Pair<String, Type>("putLong", Type.LONG_TYPE));
        putInvocationMap.put(org.objectweb.asm.Type.SHORT, new Pair<String, Type>("putShort", Type.SHORT_TYPE));
        putInvocationMap.put(org.objectweb.asm.Type.BOOLEAN,
                new Pair<String, Type>("putBoolean", Type.BOOLEAN_TYPE));
    }

    private HashSet<Field> fields = new HashSet<Field>();
    private HashSet<String> foundGettersSetters = new HashSet<String>();

    private boolean generateInheritedFields = false;

    public static String generateGetterName(Type owner, String fieldName) {
        return generateGetterName(null, owner, fieldName);
    }

    public static String generateGetterName(Version version, Type owner, String fieldName) {
        if (version != null) {
            String name = version.getOriginalName(owner.getClassName());

            if (name != null) {
                owner = Type.getObjectType(name.replace('.', '/'));
                Clazz ownerClass = version.getNamespace().getClass(owner);
                out: while (ownerClass != null) {
                    for (Field f : ownerClass.getFields()) {
                        if (f.getName().equals(fieldName)) {
                            owner = ownerClass.getASMType();
                            break out;
                        }
                    }

                    ownerClass = ownerClass.getParent();
                }
            }
        }

        return GETTER_PREFFIX + owner.getClassName().replaceAll("[^a-zA-Z0-9]", "\\$") + "$" + fieldName;
    }

    public static String generateSetterName(Type owner, String fieldName) {
        return generateSetterName(null, owner, fieldName);
    }

    public static String generateSetterName(Version version, Type owner, String fieldName) {
        if (version != null) {
            String name = version.getOriginalName(owner.getClassName());

            if (name != null) {
                owner = Type.getObjectType(name.replace('.', '/'));
                Clazz ownerClass = version.getNamespace().getClass(owner);
                out: while (ownerClass != null) {
                    for (Field f : ownerClass.getFields()) {
                        if (f.getName().equals(fieldName)) {
                            owner = ownerClass.getASMType();
                            break out;
                        }
                    }

                    ownerClass = ownerClass.getParent();
                }
            }
        }

        return SETTER_PREFFIX + owner.getClassName().replaceAll("[^a-zA-Z0-9]", "\\$") + "$" + fieldName;
    }

    public AddGettersAndSetters(Namespace namespace, ClassVisitor visitor) {
        super(null, namespace, visitor);
    }

    public AddGettersAndSetters(HashMap<String, Object> objectsMap, Namespace namespace, ClassVisitor visitor) {
        super(objectsMap, namespace, visitor);
    }

    @Override
    public void visit(int version, int access, String name, String signature, String superName,
            String[] interfaces) {
        // Ensure that version >= 1.5
        switch (version) {
        case V1_1:
        case V1_2:
        case V1_3:
        case V1_4:
            version = V1_5;
            break;
        }
        super.visit(version, access, name, signature, superName, interfaces);
    }

    @Override
    public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) {
        FieldVisitor ret = super.visitField(access, name, desc, signature, value);

        if (this.isInterface) {
            return ret;
        }

        Field field = null;
        if (this.objectsMap != null) {
            field = (Field) this.objectsMap.get(name);
        }

        if (field == null) {
            field = new Field(access, name, this.namespace.getClass(Type.getType(desc)), value != null);
        }
        this.fields.add(field);

        return ret;
    }

    @Override
    public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {

        Method m = (this.objectsMap != null ? ((Method) this.objectsMap.get(name)) : null);
        String realName = (m != null ? m.getName() : name);

        if (realName.startsWith(GETTER_PREFFIX) || realName.startsWith(SETTER_PREFFIX)) {
            this.foundGettersSetters.add(realName);
        }

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

    @Override
    public void visitEnd() {

        if (this.thisClass.isInterface())
            return;

        this.generateFields(this.thisClass.getFields(), this.thisClass, true);

        Clazz c = this.thisClass.getParent();

        while (c != null) {
            this.generateFields(c.getFields(), this.thisClass, false);
            if (this.generateInheritedFields)
                this.generateFields(c.getFields(), c, false);
            c = c.getParent();
        }

        super.visitEnd();
    }

    private void generateFields(Set<Field> fields, Clazz owner, boolean generatePrivate) {
        for (Field field : fields) {

            if (field.getName().equals(AddForwardField.FIELD_NAME))
                continue;

            if (!generatePrivate && Modifier.isPrivate(field.getAccess()))
                continue;

            boolean isStatic = Modifier.isStatic(field.getAccess());
            boolean isFinal = Modifier.isFinal(field.getAccess());
            Clazz type = field.getType();
            this.generateGetter(isStatic, field.getName(), owner, type);
            if (isFinal)
                this.generateFinalSetter(isStatic, field.getName(), owner, type);
            else
                this.generateSetter(isStatic, field.getName(), owner, type);
        }
    }

    private void generateGetter(boolean isStatic, String name, Clazz owner, Clazz type) {

        String getterName = generateGetterName(owner.getASMType(), name);

        if (this.foundGettersSetters.contains(getterName))
            return;

        MethodVisitor mv = this.visitMethod((isStatic ? (ACCESS | ACC_STATIC) : ACCESS), getterName,
                Type.getMethodDescriptor(type.getASMType()), null, null);
        mv.visitCode();
        if (isStatic) {
            mv.visitFieldInsn(GETSTATIC, owner.getASMType().getInternalName(), name,
                    type.getASMType().getDescriptor());
        } else {
            mv.visitVarInsn(ALOAD, 0);
            mv.visitFieldInsn(GETFIELD, owner.getASMType().getInternalName(), name,
                    type.getASMType().getDescriptor());
        }
        mv.visitInsn(type.getASMType().getOpcode(IRETURN));
        mv.visitMaxs(0, 0);
        mv.visitEnd();
    }

    private void generateSetter(boolean isStatic, String name, Clazz owner, Clazz type) {
        String setterName = generateSetterName(owner.getASMType(), name);

        if (this.foundGettersSetters.contains(setterName))
            return;

        MethodVisitor mv = this.visitMethod((isStatic ? (ACCESS | ACC_STATIC) : ACCESS), setterName,
                Type.getMethodDescriptor(Type.VOID_TYPE, type.getASMType()), null, null);
        mv.visitCode();

        int i = 0;
        if (!isStatic)
            mv.visitVarInsn(ALOAD, i++);

        mv.visitVarInsn(type.getASMType().getOpcode(ILOAD), i);

        mv.visitFieldInsn((isStatic ? PUTSTATIC : PUTFIELD), owner.getASMType().getInternalName(), name,
                type.getASMType().getDescriptor());

        mv.visitInsn(RETURN);
        mv.visitMaxs(0, 0);
        mv.visitEnd();
    }

    /**
     * Use unsafe to ensure ability to write to final fields
     * @param isStatic
     * @param name
     * @param type
     */
    private void generateFinalSetter(boolean isStatic, String name, Clazz owner, Clazz type) {
        String setterName = generateSetterName(owner.getASMType(), name);

        if (this.foundGettersSetters.contains(setterName))
            return;

        int fieldLocal = 1 + type.getASMType().getSize();
        int unsafeLocal = fieldLocal + 1;

        MethodVisitor mv = this.visitMethod((isStatic ? (ACCESS | ACC_STATIC) : ACCESS), setterName,
                Type.getMethodDescriptor(Type.VOID_TYPE, type.getASMType()), null, null);
        mv.visitCode();
        Label l0 = new Label();
        Label l1 = new Label();
        Label l2 = new Label();
        mv.visitTryCatchBlock(l0, l1, l2, "java/lang/SecurityException");
        Label l3 = new Label();
        mv.visitTryCatchBlock(l0, l1, l3, "java/lang/NoSuchFieldException");
        mv.visitLabel(l0);
        mv.visitLdcInsn(owner.getASMType().getASMType());
        mv.visitLdcInsn(name);
        mv.visitMethodInsn(INVOKEVIRTUAL, CLASS_TYPE.getInternalName(), "getDeclaredField",
                Type.getMethodDescriptor(FIELD_TYPE, STRING_TYPE), false);
        mv.visitVarInsn(ASTORE, fieldLocal);
        mv.visitMethodInsn(INVOKESTATIC, Type.getType(Rubah.class).getInternalName(), "getUnsafe",
                Type.getMethodDescriptor(UNSAFE_TYPE), false);
        mv.visitVarInsn(ASTORE, unsafeLocal);
        mv.visitVarInsn(ALOAD, unsafeLocal);

        if (isStatic) {
            mv.visitVarInsn(ALOAD, unsafeLocal);
            mv.visitVarInsn(ALOAD, fieldLocal);
            mv.visitMethodInsn(INVOKEVIRTUAL, UNSAFE_TYPE.getInternalName(), "staticFieldBase",
                    "(Ljava/lang/reflect/Field;)Ljava/lang/Object;", false);
            mv.visitVarInsn(ALOAD, unsafeLocal);
            mv.visitVarInsn(ALOAD, fieldLocal);
            mv.visitMethodInsn(INVOKEVIRTUAL, UNSAFE_TYPE.getInternalName(), "staticFieldOffset",
                    Type.getMethodDescriptor(Type.LONG_TYPE, FIELD_TYPE), false);
            mv.visitVarInsn(type.getASMType().getOpcode(ILOAD), 0);
        } else {
            mv.visitVarInsn(ALOAD, 0);
            mv.visitVarInsn(ALOAD, unsafeLocal);
            mv.visitVarInsn(ALOAD, fieldLocal);
            mv.visitMethodInsn(INVOKEVIRTUAL, UNSAFE_TYPE.getInternalName(), "objectFieldOffset",
                    Type.getMethodDescriptor(Type.LONG_TYPE, FIELD_TYPE), false);
            mv.visitVarInsn(type.getASMType().getOpcode(ILOAD), 1);
        }

        this.generatePutObjectInvocation(mv, type.getASMType());

        mv.visitLabel(l1);
        Label l6 = new Label();
        mv.visitJumpInsn(GOTO, l6);
        mv.visitLabel(l2);
        mv.visitVarInsn(ASTORE, 2);
        mv.visitTypeInsn(NEW, "java/lang/Error");
        mv.visitInsn(DUP);
        mv.visitVarInsn(ALOAD, 2);
        mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Error", "<init>", "(Ljava/lang/Throwable;)V", false);
        mv.visitInsn(ATHROW);
        mv.visitLabel(l3);
        mv.visitVarInsn(ASTORE, 2);
        mv.visitTypeInsn(NEW, "java/lang/Error");
        mv.visitInsn(DUP);
        mv.visitVarInsn(ALOAD, 2);
        mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Error", "<init>", "(Ljava/lang/Throwable;)V", false);
        mv.visitInsn(ATHROW);
        mv.visitLabel(l6);
        mv.visitInsn(RETURN);
        mv.visitMaxs(0, 0);
        mv.visitEnd();
    }

    private void generatePutObjectInvocation(MethodVisitor mv, Type type) {
        Pair<String, Type> result = putInvocationMap.get(type.getSort());
        if (result == null) {
            result = new Pair<String, Type>("putObject", OBJECT_TYPE);
        }

        mv.visitMethodInsn(INVOKEVIRTUAL, UNSAFE_TYPE.getInternalName(), result.getValue0(),
                Type.getMethodDescriptor(Type.VOID_TYPE, OBJECT_TYPE, Type.LONG_TYPE, result.getValue1()), false);
    }

    public AddGettersAndSetters setGenerateInheritedFields(boolean generateInheritedFields) {
        this.generateInheritedFields = generateInheritedFields;
        return this;
    }
}