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