/******************************************************************************* * 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.Arrays; import java.util.HashMap; import java.util.HashSet; import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.ClassWriter; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; import org.objectweb.asm.commons.LocalVariablesSorter; import rubah.Rubah; import rubah.bytecode.RubahProxy; import rubah.framework.Clazz; import rubah.framework.Method; import rubah.framework.Type; import rubah.runtime.Version; import rubah.runtime.VersionManager; import rubah.update.UpdateClass; public class ProxyGenerator implements Opcodes { private static final String PROXY_SUFFIX = "__PROXY"; public static final String PROXIED_OBJ_NAME = "proxied"; public static final Type PROXIED_OBJ_TYPE = Type.getType(Object.class); public static final String BASE_OBJ_NAME = "base"; public static final Type BASE_OBJ_TYPE = Type.getType(Object.class); public static final String OFFSET_NAME = "offset"; public static final Type OFFSET_TYPE = Type.INT_TYPE; public static final String SCALE_NAME = "scale"; public static final Type SCALE_TYPE = Type.INT_TYPE; private static final int CLASS_ACCESS = ACC_PUBLIC | ACC_FINAL; private static final int METHOD_ACCESS = ACC_PUBLIC | ACC_SYNTHETIC | ACC_FINAL; private static final Type RUBAH_TYPE = Type.getType(Rubah.class); private static final Type RUBAH_PROXY_TYPE = Type.getType(RubahProxy.class); private static final Type OBJECT_TYPE = Type.getType(Object.class); protected Clazz thisClass; protected Version version; public ProxyGenerator(Clazz c, Version v) { this.thisClass = c; this.version = v; } public static String generateProxyName(String className) { return className + PROXY_SUFFIX; } public static String getOriginalName(String className) { return className.substring(0, className.length() - PROXY_SUFFIX.length()); } public static boolean isProxyName(String className) { return className.endsWith(PROXY_SUFFIX); } public byte[] generateProxy() { return this.generateProxy(generateProxyName(this.thisClass.getFqn())); } // private Map<Pair<Clazz, Method>, Integer> computeInheritedOverloads(Map<Pair<Clazz, Method>, Integer> map) { // // LinkedList<Clazz> inheritancePath = new LinkedList<Clazz>(); // // for (Clazz c = this.thisClass ; c != null ; c = c.getParent()) // inheritancePath.addFirst(c); // // Map<Pair<Clazz, Method>, Integer> ret = new HashMap<Pair<Clazz,Method>, Integer>(); // HashSet<Pair<Method, Integer>> foundOverloads = new HashSet<Pair<Method,Integer>>(); // // for (Clazz c : inheritancePath) { // for (Method m : c.getMethods()) { // Integer overload = map.get(new Pair<Clazz, Method>(c, m)); // if (overload != null) // foundOverloads.add(new Pair<Method, Integer>(m, overload)); // } // // for (Pair<Method, Integer> overload : foundOverloads) // ret.put(new Pair<Clazz, Method>(c, overload.getValue0()), overload.getValue1()); // } // // return ret; // } public byte[] generateProxy(String name) { final String proxyName = name.replace('.', '/'); ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_MAXS); HashMap<Object, String> namesMap = new HashMap<Object, String>(); HashMap<String, Object> objectsMap = new HashMap<String, Object>(); // Map<Pair<Clazz, Method>, Integer> overloads = this.computeInheritedOverloads(this.version.getOverloads()); ClassVisitor visitor = writer; visitor = new ReplaceUniqueByOriginalNames(namesMap, objectsMap, this.version.getNamespace(), visitor); visitor = new UpdatableClassRenamer(this.version, objectsMap, visitor); visitor = this.getMethodsTransformer(proxyName, this.thisClass, visitor); // The placement of this transformer is important // because convert methods require special treatment // as they operate in between versions if (this.version.getPrevious() != null) { UpdateClass updateClass = VersionManager.getInstance().getUpdateClass(this.version); visitor = new ProcessUpdateClass( objectsMap, this.version, updateClass, visitor); visitor = new ClassVisitor(ASM5, visitor) { @Override public void visitEnd() { Type t = thisClass.getASMType(); String updatableName = version.getUpdatableName(t.getClassName()); if (updatableName != null) { Type t1 = Type.getObjectType(updatableName.replace('.', '/')); String prevName = version.getPrevious().getUpdatableName(t.getClassName()); MethodVisitor mv = this.cv.visitMethod( ACC_PUBLIC, "convert", Type.getMethodDescriptor(Type.VOID_TYPE, OBJECT_TYPE, OBJECT_TYPE), null, null); if (prevName != null) { Type t0 = Type.getObjectType(version.getPrevious().getUpdatableName(t.getClassName()).replace('.', '/')); mv.visitVarInsn(ALOAD, 1); mv.visitTypeInsn(CHECKCAST, t0.getInternalName()); mv.visitVarInsn(ALOAD, 2); mv.visitTypeInsn(CHECKCAST, t1.getInternalName()); mv.visitMethodInsn( INVOKESTATIC, proxyName, ProcessUpdateClass.METHOD_NAME_STATIC, Type.getMethodDescriptor(Type.VOID_TYPE, t0, t1), false); mv.visitInsn(RETURN); } else { // Class was introduced this version mv.visitTypeInsn(NEW, "java/lang/Error"); mv.visitInsn(DUP); mv.visitLdcInsn("Should not execute, converting a class that did not exist in the previous version"); mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Error", "<init>", "(Ljava/lang/String;)V", false); mv.visitInsn(ATHROW); } mv.visitMaxs(0, 0); mv.visitEnd(); } this.cv.visitEnd(); } }; } // visitor = new RenameOverloads(objectsMap, overloads, this.version.getNamespace(), visitor); // visitor = new TypeEraser(objectsMap, this.version, visitor); visitor = new AddGettersAndSetters(objectsMap, this.version.getNamespace(), visitor).setGenerateInheritedFields(true); visitor = new ReplaceOriginalNamesByUnique(namesMap, objectsMap, this.version.getNamespace(), visitor); visitor = new OverrideInheritedMethods(this.thisClass, visitor); visitor.visit( V1_5, CLASS_ACCESS, this.thisClass.getASMType().getInternalName(), null, null, null); visitor.visitEnd(); return writer.toByteArray(); } protected ClassVisitor getMethodsTransformer(String proxyName, Clazz thisClass2, ClassVisitor visitor) { return new TransformProxyMethods(proxyName, this.thisClass, visitor); } protected static class TransformProxyMethods extends ClassVisitor { protected String proxyName; protected Clazz owner; public TransformProxyMethods(String proxyName, Clazz owner, ClassVisitor cv) { super(ASM5, cv); this.proxyName = proxyName; this.owner = owner; } @Override public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { this.cv.visit( V1_5, CLASS_ACCESS, proxyName, null, owner.getASMType().getInternalName(), new String[]{ RUBAH_PROXY_TYPE.getInternalName() }); } @Override public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { if (name.equals(ProcessUpdateClass.METHOD_NAME) || name.equals(ProcessUpdateClass.METHOD_NAME_STATIC)) return this.cv.visitMethod(access, name, desc, signature, exceptions); MethodVisitor ret = new MethodVisitor(ASM5) { }; if (Modifier.isStatic(access)) return ret; LocalVariablesSorter mv = new LocalVariablesSorter( access, desc, this.cv.visitMethod(METHOD_ACCESS, name, desc, signature, exceptions)); String ownerName = this.owner.getASMType().getInternalName(); int newObjectVar = mv.newLocal(OBJECT_TYPE.getASMType()); mv.visitCode(); mv.visitVarInsn(ALOAD, 0); mv.visitMethodInsn( INVOKESTATIC, RUBAH_TYPE.getInternalName(), "getConverted", Type.getMethodDescriptor(OBJECT_TYPE, OBJECT_TYPE), false); mv.visitTypeInsn(CHECKCAST, ownerName); int arg = 1; for (Type t : Type.getArgumentTypes(desc)) { mv.visitVarInsn(t.getOpcode(ILOAD), arg); arg += t.getSize(); } mv.visitMethodInsn( INVOKEVIRTUAL, ownerName, name, desc, false); mv.visitInsn(Type.getReturnType(desc).getOpcode(IRETURN)); mv.visitMaxs(0, 0); mv.visitEnd(); return ret; } // @Override // public void visitEnd() { // // Add extra proxy fields // this.cv.visitField( // FIELD_ACCESS, // BASE_OBJ_NAME, // BASE_OBJ_TYPE.getDescriptor(), // null, // null); // this.cv.visitField( // FIELD_ACCESS, // OFFSET_NAME, // OFFSET_TYPE.getDescriptor(), // null, // null); // this.cv.visitField( // FIELD_ACCESS, // PROXIED_OBJ_NAME, // PROXIED_OBJ_TYPE.getDescriptor(), // null, // null); // this.cv.visitField( // FIELD_ACCESS, // SCALE_NAME, // SCALE_TYPE.getDescriptor(), // null, // null); // // super.visitEnd(); // } } private static class OverrideInheritedMethods extends ClassVisitor { private static HashSet<String> BLACKLIST = new HashSet<String>( Arrays.asList(new String[]{ "finalize", "clone", "<init>" })); private static int DISALLOWED_MASK = Modifier.FINAL | Modifier.PRIVATE | Modifier.STATIC; private Clazz thisClass; public OverrideInheritedMethods(Clazz thisClass, ClassVisitor cv) { super(ASM5, cv); this.thisClass = thisClass; } @Override public void visitEnd() { HashSet<Method> overrides = new HashSet<Method>(); Clazz parent = this.thisClass; while (parent != null) { for (Method m : parent.getMethods()) { if (!BLACKLIST.contains(m.getName()) && ((m.getAccess() & DISALLOWED_MASK) == 0) && !overrides.contains(m)) { this.override(m, parent); overrides.add(m); } } parent = parent.getParent(); } super.visitEnd(); } private void override(Method m, Clazz owner) { MethodVisitor mv = this.visitMethod( m.getAccess(), m.getName(), m.getASMDesc(), null, null); mv.visitVarInsn(ALOAD, 0); int var = 1; for (Clazz arg : m.getArgTypes()) { mv.visitVarInsn(arg.getASMType().getOpcode(ILOAD), var); var += arg.getASMType().getSize(); } mv.visitMethodInsn( INVOKESPECIAL, owner.getASMType().getInternalName(), m.getName(), m.getASMDesc(), false); mv.visitInsn(m.getRetType().getASMType().getOpcode(IRETURN)); mv.visitMaxs(0, 0); mv.visitEnd(); } } }