rubah.bytecode.transformers.ProcessUpdateClass.java Source code

Java tutorial

Introduction

Here is the source code for rubah.bytecode.transformers.ProcessUpdateClass.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.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.InsnNode;
import org.objectweb.asm.tree.MethodInsnNode;
import org.objectweb.asm.tree.MethodNode;

import rubah.Rubah;
import rubah.framework.Clazz;
import rubah.framework.Field;
import rubah.framework.Type;
import rubah.runtime.RubahRuntime;
import rubah.runtime.Version;
import rubah.runtime.state.migrator.UnsafeUtils;
import rubah.tools.UpdateClassGenerator;
import rubah.update.UpdateClass;
import rubah.update.change.Change;

public class ProcessUpdateClass extends RubahTransformer {
    public static final int METHOD_ACCESS = ACC_PUBLIC | ACC_STATIC;
    public static final int METHOD_ACCESS_STATIC = ACC_PUBLIC | ACC_STATIC;
    public static final String METHOD_NAME = "convert";
    public static final String METHOD_NAME_STATIC = "convert$static";
    private static final Type OBJECT_TYPE = Type.getType(Object.class);
    private static final Type UNSAFE_UTILS_TYPE = Type.getType(UnsafeUtils.class);
    private static final Type UNSAFE_TYPE = Type.getType(sun.misc.Unsafe.class);
    private static final Type CLASS_OFFSETS_TYPE = Type.getType(UnsafeUtils.ClassOffsets.class);
    private static final Type CLASS_TYPE = Type.getType(Class.class);

    protected Version version;
    private Type oldDummyType, newDummyType;
    private Map<String, MethodNode> methodNodeIndex;

    public ProcessUpdateClass(HashMap<String, Object> objectsMap, Version v1, UpdateClass updateClass,
            ClassVisitor cv) {
        super(objectsMap, v1.getNamespace(), cv);
        this.version = v1;

        if (updateClass == null) {
            this.methodNodeIndex = new HashMap<String, MethodNode>();
            return;
        }

        this.methodNodeIndex = getMethodNodeIndex(updateClass.getBytes());
    }

    public static Map<String, MethodNode> getMethodNodeIndex(byte[] bytecode) {
        Map<String, MethodNode> methodNodeIndex = new HashMap<String, MethodNode>();
        ClassNode classNode = new ClassNode();
        new ClassReader(bytecode).accept(classNode, 0);
        for (Object obj : classNode.methods) {
            MethodNode mn = (MethodNode) obj;
            methodNodeIndex.put(mn.name + mn.desc, mn);
        }

        return methodNodeIndex;
    }

    private static void getUnsafe(MethodVisitor mv) {
        mv.visitMethodInsn(INVOKESTATIC, UNSAFE_UTILS_TYPE.getInternalName(), "getUnsafe",
                Type.getMethodDescriptor(UNSAFE_TYPE), false);
    }

    private static void getUnsafeUtils(MethodVisitor mv) {
        mv.visitMethodInsn(INVOKESTATIC, UNSAFE_UTILS_TYPE.getInternalName(), "getInstance",
                Type.getMethodDescriptor(UNSAFE_UTILS_TYPE), false);
    }

    private static void getClassOffsets(MethodVisitor mv) {
        mv.visitMethodInsn(INVOKEVIRTUAL, UNSAFE_UTILS_TYPE.getInternalName(), "getOffsets",
                Type.getMethodDescriptor(CLASS_OFFSETS_TYPE, CLASS_TYPE), false);
    }

    private static void getStaticBase(MethodVisitor mv) {
        mv.visitMethodInsn(INVOKEVIRTUAL, CLASS_OFFSETS_TYPE.getInternalName(), "getStaticBase",
                Type.getMethodDescriptor(OBJECT_TYPE), false);
    }

    @Override
    public void visit(int version, int access, String name, String signature, String superName,
            String[] interfaces) {
        super.visit(version, access, name, signature, superName, interfaces);

        String oldTypeName = UpdateClassGenerator.V0_PREFFIX + "." + this.thisClass.getFqn();
        this.oldDummyType = Type.getObjectType(oldTypeName.replace('.', '/'));
        String newTypeName = UpdateClassGenerator.V1_PREFFIX + "." + this.thisClass.getFqn();
        this.newDummyType = Type.getObjectType(newTypeName.replace('.', '/'));
    }

    public static void generateConversionMethod(Version v1, Clazz c1, ConvertMethodGenerator[] generators) {
        Clazz c0 = v1.getUpdate().getV0(c1);

        for (ConvertMethodGenerator generator : generators) {
            MethodVisitor mv = generator.getMethodVisitor();

            if (mv == null) {
                continue;
            }

            mv.visitCode();

            if (c1.getParent().getNamespace().equals(c1.getNamespace())) {
                // Convert parent, if updatable
                generator.callSuper(mv, c1.getParent());
            } else {
                // Copy all fields from parent, if non-updatable
                generator.copyAllFields(mv, c1.getParent());
            }

            HashSet<Field> interestingFields = new HashSet<Field>();
            for (Field f : c1.getFields()) {
                Change<Field> change = v1.getUpdate().getChanges(c0).getFieldChanges().get(f);
                if (generator.isFieldInteresting(f, c1) && (change == null || change.getChangeSet().isEmpty())) {
                    interestingFields.add(f);
                    // Copy unchanged fields
                    generator.convertField(mv, f);
                }
            }

            MethodNode mn = generator.getMethodNode();

            if (mn != null) {
                // Add custom conversion code to end of conversion method
                mn.instructions.resetLabels();
                mn.accept(new ProcessUpdateMethod(v1, generator, interestingFields, mv));
                continue;
            }

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

    protected boolean isClassInteresting() {
        if (this.isInterface || !this.thisClass.getNamespace().equals(this.namespace)) {
            return false;
        }

        Clazz c0 = this.version.getUpdate().getV0(this.thisClass);

        if (c0 == null) {// || UpdateManager.getInstance().isFirstVersion(this.version.getUpdatableName(c0.getFqn()))) {
            return false;
        }

        return true;
    }

    @Override
    public void visitEnd() {
        if (this.isClassInteresting()) {
            generateConversionMethod(this.version, this.thisClass, this.getGenerators());
        }

        super.visitEnd();
    }

    protected ConvertMethodGenerator[] getGenerators() {
        return new ConvertMethodGenerator[] {
                new NormalConvertMethodGenerator(this.oldDummyType, this.newDummyType, this.version, this,
                        this.methodNodeIndex),
                new StaticConvertMethodGenerator(this.thisClass, this.oldDummyType, this.newDummyType, this.version,
                        this, this.methodNodeIndex) };
    }

    private interface Callback {
        String getClassName(String className);
    }

    private static Type getUpdatableType(Type t, final Version version) {
        return getRealType(t, new Callback() {
            @Override
            public String getClassName(String className) {
                if (className.startsWith(UpdateClassGenerator.V0_PREFFIX)) {
                    className = className.replaceFirst(UpdateClassGenerator.V0_PREFFIX + "\\.", "");

                    if (version.getPrevious() != null) {
                        className = version.getPrevious().getUpdatableName(className);
                    } else {
                        className = version.getUpdatableName(className);
                    }
                } else if (className.startsWith(UpdateClassGenerator.V1_PREFFIX)) {
                    className = className.replaceFirst(UpdateClassGenerator.V1_PREFFIX + "\\.", "");
                    className = version.getUpdatableName(className);
                }
                return className;
            }
        });
    }

    private static Type getRealType(Type t, Callback c) {

        Type innerT = (t.isArray() ? t.getElementType() : t);

        if (innerT.isPrimitive()) {
            return t;
        }

        String className = c.getClassName(innerT.getClassName());
        if (className == null) {
            return null;
        }

        className = className.replace('.', '/');

        if (t.isArray()) {
            return Type.getObjectType(className).createArrayType(t.getDimensions());
        }

        return Type.getObjectType(className);
    }

    private static final class UnsafeInfo {
        private final String getterName;
        private final String getterSignature;
        private final String setterName;
        private final String setterSignature;

        public UnsafeInfo(String getterName, String getterSignature, String setterName, String setterSignature) {
            this.getterName = getterName;
            this.getterSignature = getterSignature;
            this.setterName = setterName;
            this.setterSignature = setterSignature;
        }
    }

    private static final Map<Type, UnsafeInfo> unsafeInfoMap;

    static {
        unsafeInfoMap = new HashMap<>();

        for (Type t : Type.primitives) {
            String name;

            switch (t.getSort()) {
            case org.objectweb.asm.Type.BOOLEAN:
                name = "Boolean";
                break;
            case org.objectweb.asm.Type.BYTE:
                name = "Byte";
                break;
            case org.objectweb.asm.Type.CHAR:
                name = "Char";
                break;
            case org.objectweb.asm.Type.DOUBLE:
                name = "Double";
                break;
            case org.objectweb.asm.Type.FLOAT:
                name = "Float";
                break;
            case org.objectweb.asm.Type.INT:
                name = "Int";
                break;
            case org.objectweb.asm.Type.LONG:
                name = "Long";
                break;
            case org.objectweb.asm.Type.SHORT:
                name = "Short";
                break;
            default:
                throw new Error("Should not reach here");
            }

            unsafeInfoMap.put(t,
                    new UnsafeInfo("get" + name, Type.getMethodDescriptor(t, OBJECT_TYPE, Type.LONG_TYPE),
                            "put" + name,
                            Type.getMethodDescriptor(Type.VOID_TYPE, OBJECT_TYPE, Type.LONG_TYPE, t)));
        }
    }

    private static void getFieldUsingUnsafe(MethodVisitor mv, Field f) {
        if (f.getType().getASMType().isPrimitive()) {
            UnsafeInfo info = unsafeInfoMap.get(f.getType().getASMType());
            mv.visitMethodInsn(INVOKEVIRTUAL, "sun/misc/Unsafe", info.getterName, info.getterSignature, false);
        } else {
            mv.visitMethodInsn(INVOKEVIRTUAL, "sun/misc/Unsafe", "getObject",
                    "(Ljava/lang/Object;J)Ljava/lang/Object;", false);
        }
    }

    private static void putFieldUsingUnsafe(MethodVisitor mv, Field f) {
        if (f.getType().getASMType().isPrimitive()) {
            UnsafeInfo info = unsafeInfoMap.get(f.getType().getASMType());
            mv.visitMethodInsn(INVOKEVIRTUAL, "sun/misc/Unsafe", info.setterName, info.setterSignature, false);
        } else {
            mv.visitMethodInsn(INVOKEVIRTUAL, "sun/misc/Unsafe", "putObject",
                    "(Ljava/lang/Object;JLjava/lang/Object;)V", false);
        }
    }

    private static long getFieldOffset(Type t, Field f) {
        try {
            Class<?> c = Class.forName(t.getClassName(), false, Rubah.getLoader());

            while (true) {
                try {
                    if (c == null)
                        return -1;

                    if (Modifier.isStatic(f.getAccess()))
                        return UnsafeUtils.getUnsafe().staticFieldOffset(c.getDeclaredField(f.getName()));
                    else
                        return UnsafeUtils.getUnsafe().objectFieldOffset(c.getDeclaredField(f.getName()));
                } catch (NoSuchFieldException e) {
                    // Keep going
                    c = c.getSuperclass();
                }
            }
        } catch (ReflectiveOperationException | SecurityException e) {
            throw new Error(e);
        }
    }

    public static interface ConvertMethodGenerator {
        public boolean isFieldInteresting(Field f, Clazz owner);

        public void callSuper(MethodVisitor mv, Clazz parent);

        public void copyAllFields(MethodVisitor mv, Clazz parent);

        public MethodNode getMethodNode();

        public MethodVisitor getMethodVisitor();

        public void convertField(MethodVisitor mv, Field f);
    }

    public static class NormalConvertMethodGenerator implements ConvertMethodGenerator {
        protected Type t0, t1;
        private Version version;
        private ClassVisitor cv;
        private Map<String, MethodNode> methodNodeIndex;

        public NormalConvertMethodGenerator(Type t0, Type t1, Version version, ClassVisitor cv,
                Map<String, MethodNode> methodNodeIndex) {
            this.t0 = t0;
            this.t1 = t1;
            this.version = version;
            this.cv = cv;
            this.methodNodeIndex = methodNodeIndex;
        }

        @Override
        public boolean isFieldInteresting(Field f, Clazz owner) {
            // Copy values from all non-static fields
            return !Modifier.isStatic(f.getAccess()) && !f.getName().equals(AddForwardField.FIELD_NAME);
        }

        @Override
        public MethodVisitor getMethodVisitor() {
            if (this.version.getPrevious() == null) {
                return null;
            }

            Type t0 = getUpdatableType(this.t0, this.version);

            if (t0 == null) {
                // This class was introduced in this update
                return null;
            }

            return this.cv.visitMethod(METHOD_ACCESS, METHOD_NAME_STATIC,
                    Type.getMethodDescriptor(Type.VOID_TYPE, t0, getUpdatableType(this.t1, this.version)), null,
                    null);
        }

        @Override
        public void convertField(MethodVisitor mv, Field f) {

            Type t0 = getUpdatableType(this.t0, this.version);
            Type t1 = getUpdatableType(this.t1, this.version);

            long offset0 = getFieldOffset(t0, f);
            long offset1 = getFieldOffset(t1, f);

            if (offset0 < 0 || offset1 < 0)
                // Synthetic field, ignore it
                return;

            getUnsafe(mv);
            mv.visitVarInsn(ALOAD, 1);
            mv.visitLdcInsn(offset1);
            getUnsafe(mv);
            mv.visitVarInsn(ALOAD, 0);
            mv.visitLdcInsn(offset0);
            getFieldUsingUnsafe(mv, f);
            putFieldUsingUnsafe(mv, f);

        }

        @Override
        public MethodNode getMethodNode() {
            return this.methodNodeIndex.get(
                    UpdateClassGenerator.METHOD_NAME + Type.getMethodDescriptor(Type.VOID_TYPE, this.t0, this.t1));
        }

        @Override
        public void callSuper(MethodVisitor mv, Clazz parent) {

            String oldTypeName = UpdateClassGenerator.V0_PREFFIX + "." + parent.getFqn();
            Type oldDummyType = getUpdatableType(Type.getObjectType(oldTypeName.replace('.', '/')), this.version);

            if (oldDummyType == null) {
                // Super is a new class introduced in this update, skip super call
                return;
            }

            Type t1 = Type.getObjectType(this.version.getUpdatableName(parent.getFqn()).replace('.', '/'));
            Type t0 = Type
                    .getObjectType(this.version.getPrevious().getUpdatableName(parent.getFqn()).replace('.', '/'));

            //         String newTypeName =
            //               UpdateClassGenerator.V1_PREFFIX + "." + parent.getFqn();
            //         Type newDummyType = getUpdatableType(
            //               Type.getObjectType(newTypeName.replace('.', '/')),
            //               this.version);

            mv.visitVarInsn(ALOAD, 0); // o0
            mv.visitVarInsn(ALOAD, 1); // o1

            String parentMethodName;

            //         if (oldDummyType.equals(newDummyType)) {
            //            parentMethodName = (PureConversionClassLoader.PURE_CONVERSION_PREFFIX + this.version.getNumber()).replace('.', '/');
            //         } else {
            parentMethodName = ProxyGenerator.generateProxyName(this.version.getUpdatableName(parent.getFqn()))
                    .replace('.', '/');
            //         }

            mv.visitMethodInsn(INVOKESTATIC, parentMethodName, METHOD_NAME_STATIC,
                    Type.getMethodDescriptor(Type.VOID_TYPE, t0, t1), false);
        }

        @Override
        public void copyAllFields(MethodVisitor mv, Clazz c) {

            for ( /* Empty */ ; c != null; c = c.getParent()) {
                for (Field f : c.getFields()) {
                    if (this.isFieldInteresting(f, c)) {
                        this.convertField(mv, f);
                    }
                }
            }

        }
    }

    public static class StaticConvertMethodGenerator implements ConvertMethodGenerator {

        private Clazz ownerClass;
        private Type t0, t1;
        private Version version;
        private ClassVisitor cv;
        private Map<String, MethodNode> methodNodeIndex;

        public StaticConvertMethodGenerator(Clazz ownerClass, Type t0, Type t1, Version version, ClassVisitor cv,
                Map<String, MethodNode> methodNodeIndex) {
            this.ownerClass = ownerClass;
            this.t0 = t0;
            this.t1 = t1;
            this.version = version;
            this.cv = cv;
            this.methodNodeIndex = methodNodeIndex;
        }

        @Override
        public boolean isFieldInteresting(Field f, Clazz owner) {
            // Copy values from static fields defined in this class
            // BUG ALERT:
            // Copying values from static fields in the superclasses may overwrite
            // values already converted with old values, resulting in program bugs

            if (f.isConstant())
                return false;

            return Modifier.isStatic(f.getAccess()) && owner.equals(this.ownerClass);
        }

        @Override
        public MethodVisitor getMethodVisitor() {
            if (this.version.getPrevious() == null) {
                return null;
            }

            return this.cv.visitMethod(METHOD_ACCESS, METHOD_NAME_STATIC,
                    Type.getMethodDescriptor(Type.VOID_TYPE, getUpdatableType(this.t0, this.version)), null, null);
        }

        @Override
        public void convertField(MethodVisitor mv, Field f) {
            //         if (!processingConversionMethod)
            //            return;

            Type t0 = getUpdatableType(this.t0, this.version);
            Type t1 = getUpdatableType(this.t1, this.version);

            long offset0 = getFieldOffset(t0, f);
            long offset1 = getFieldOffset(t1, f);

            if (offset0 < 0 || offset1 < 0)
                // Synthetic field, ignore
                return;

            getUnsafe(mv);
            getUnsafeUtils(mv);
            mv.visitLdcInsn(t1.getASMType());
            getClassOffsets(mv);
            getStaticBase(mv);
            mv.visitLdcInsn(offset1);
            getUnsafe(mv);
            getUnsafeUtils(mv);
            mv.visitLdcInsn(t0.getASMType());
            getClassOffsets(mv);
            getStaticBase(mv);
            mv.visitLdcInsn(offset0);
            getFieldUsingUnsafe(mv, f);
            putFieldUsingUnsafe(mv, f);
        }

        @Override
        public MethodNode getMethodNode() {
            MethodNode ret = this.methodNodeIndex.get(
                    UpdateClassGenerator.METHOD_NAME_STATIC + Type.getMethodDescriptor(Type.VOID_TYPE, this.t1));

            if (ret == null) {
                // Default behavior
                ret = new MethodNode(ASM5, ACC_PUBLIC, UpdateClassGenerator.METHOD_NAME_STATIC,
                        Type.getMethodDescriptor(Type.VOID_TYPE, this.t1), null, null);

                ret.instructions.add(new MethodInsnNode(INVOKESTATIC, t1.getInternalName(),
                        UpdateClassGenerator.COPY_METHOD_NAME_STATIC, Type.getMethodDescriptor(Type.VOID_TYPE),
                        false));

                ret.instructions.add(new InsnNode(RETURN));
            }

            return ret;
        }

        @Override
        public void callSuper(MethodVisitor mv, Clazz parent) {
            // Empty, super was already loaded (and converted) when this code runs
        }

        @Override
        public void copyAllFields(MethodVisitor mv, Clazz parent) {
            // Empty, super was already loaded (and converted) when this code runs
        }
    }

    private static class ProcessUpdateMethod extends MethodVisitor {

        private Version version;
        private ConvertMethodGenerator generator;
        private Set<Field> interestingFields;

        public ProcessUpdateMethod(Version version, ConvertMethodGenerator generator, Set<Field> interestingFields,
                MethodVisitor mv) {
            super(ASM5, mv);
            this.version = version;
            this.generator = generator;
            this.interestingFields = interestingFields;
        }

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

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

        @Override
        public void visitInsn(int opcode) {
            if (opcode == ARRAYLENGTH) {
                mv.visitMethodInsn(INVOKESTATIC, "java/lang/reflect/Array", "getLength", "(Ljava/lang/Object;)I",
                        false);
                return;
            }
            super.visitInsn(opcode);
        }

        @Override
        public void visitLocalVariable(String name, String desc, String signature, Label start, Label end,
                int index) {
            if (index == 0)
                // Skip this
                return;
            super.visitLocalVariable(name, desc, signature, start, end, index - 1);
        }

        @Override
        public void visitFieldInsn(int opcode, String owner, String name, String desc) {

            Type ownerType = Type.getObjectType(owner);
            Type realOwnerType = getUpdatableType(ownerType, this.version);

            Version v = this.version;

            if (owner.startsWith(UpdateClassGenerator.V0_PREFFIX)) {
                v = v.getPrevious();
            }

            Type fieldType = Type.getType(desc);
            int access = (opcode == PUTSTATIC || opcode == GETSTATIC) ? Modifier.STATIC : 0;
            Field f = new Field(access, name, v.getNamespace().getClass(fieldType), false);
            long offset = getFieldOffset(realOwnerType, f);

            if (!ownerType.equals(realOwnerType)) {
                Type realFieldType;

                if (fieldType.isPrimitive())
                    realFieldType = fieldType;
                else
                    realFieldType = getUpdatableType(fieldType, this.version);

                switch (opcode) {
                case GETFIELD:
                    getUnsafe(mv);
                    mv.visitInsn(SWAP);
                    mv.visitLdcInsn(offset);
                    getFieldUsingUnsafe(mv, f);
                    if (!realFieldType.isPrimitive())
                        mv.visitTypeInsn(CHECKCAST, realFieldType.getInternalName());
                    return;
                case PUTFIELD:
                    if (f.getType().getASMType().getSize() == 2) {
                        mv.visitLdcInsn(offset);
                        mv.visitMethodInsn(INVOKESTATIC, Type.getType(UnsafeUtils.class).getInternalName(), "write",
                                Type.getMethodDescriptor(Type.VOID_TYPE, OBJECT_TYPE, f.getType().getASMType(),
                                        Type.LONG_TYPE),
                                false);
                        return;
                    }
                    getUnsafe(mv);
                    mv.visitInsn(DUP_X2);
                    mv.visitInsn(POP);
                    mv.visitLdcInsn(offset);
                    mv.visitInsn(DUP2_X1);
                    mv.visitInsn(POP2);
                    putFieldUsingUnsafe(mv, f);
                    return;
                case GETSTATIC:
                    getUnsafe(mv);
                    getUnsafeUtils(mv);
                    mv.visitLdcInsn(realOwnerType.getASMType());
                    getClassOffsets(mv);
                    getStaticBase(mv);
                    mv.visitLdcInsn(offset);
                    getFieldUsingUnsafe(mv, f);
                    if (!realFieldType.isPrimitive())
                        mv.visitTypeInsn(CHECKCAST, realFieldType.getInternalName());
                    return;
                case PUTSTATIC:
                    getUnsafe(mv);
                    mv.visitInsn(SWAP);
                    getUnsafeUtils(mv);
                    mv.visitLdcInsn(realOwnerType.getASMType());
                    getClassOffsets(mv);
                    getStaticBase(mv);
                    mv.visitInsn(SWAP);
                    mv.visitLdcInsn(offset);
                    mv.visitInsn(DUP2_X1);
                    mv.visitInsn(POP2);
                    putFieldUsingUnsafe(mv, f);
                    return;
                default:
                    throw new Error("Unexpected bytecode");
                }
            }

            super.visitFieldInsn(opcode, realOwnerType.getInternalName(), name, fieldType.getDescriptor());
        }

        @Override
        public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) {

            Type ownerType = Type.getObjectType(owner);
            Type realOwnerType = getUpdatableType(ownerType, this.version);
            if (name.equals("convert") && !ownerType.equals(realOwnerType)) {
                // Dummy invocation, pop arguments and skip
                for (int i = 0, n = Type.getArgumentTypes(desc).length; i < n; i++) {
                    super.visitInsn(POP);
                }
                return;
            }

            if (name.equals(UpdateClassGenerator.COPY_METHOD_NAME_STATIC) && opcode == INVOKESTATIC) {
                // Copy all unchanged fields here
                for (Field f : this.interestingFields) {
                    // Copy unchanged field
                    generator.convertField(mv, f);
                }

                return;
            }

            if (opcode == INVOKESTATIC) {
                // Ensure static fields of class were already converted, if class was present in the prev version
                Type prevOwnerType = getUpdatableType(ownerType, version.getPrevious());

                mv.visitLdcInsn(prevOwnerType.getASMType());
                mv.visitMethodInsn(INVOKESTATIC, Type.getType(RubahRuntime.class).getInternalName(),
                        "ensureStaticFieldsMigrated", "(Ljava/lang/Class;)V", false);
            }

            Type retType = Type.getReturnType(desc);
            Type[] argTypes = Type.getArgumentTypes(desc);

            for (int i = 0; i < argTypes.length; i++) {
                argTypes[i] = getUpdatableType(argTypes[i], this.version);
            }

            retType = getUpdatableType(retType, this.version);

            desc = Type.getMethodDescriptor(retType, argTypes);

            super.visitMethodInsn(opcode, realOwnerType.getInternalName(), name, desc, itf);

        }

        @Override
        public void visitCode() {
            // Already in the middle of a method at this point, skip
        }

        @Override
        public void visitTypeInsn(int opcode, String typeName) {
            Type type = Type.getObjectType(typeName);
            Type realType = getUpdatableType(type, this.version);

            super.visitTypeInsn(opcode, realType.getInternalName());
        }

        @Override
        public void visitLdcInsn(Object cst) {

            if (cst instanceof org.objectweb.asm.Type) {
                Type t = new Type((org.objectweb.asm.Type) cst);
                if (t.getSort() == Type.OBJECT)
                    cst = getUpdatableType(t, this.version).getASMType();
            }

            super.visitLdcInsn(cst);
        }
    }
}