/* * Minha.pt: middleware testing platform. * Copyright (c) 2011-2014, Universidade do Minho. * * This program 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 2 * of the License, or (at your option) any later version. * * This program 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 this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ package pt.minha.kernel.instrument; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.CharArrayWriter; import java.io.IOException; import java.io.InputStream; import java.io.PrintWriter; import java.io.Writer; import org.objectweb.asm.ClassReader; import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.ClassWriter; import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; import org.objectweb.asm.commons.RemappingClassAdapter; import org.objectweb.asm.util.CheckClassAdapter; import org.objectweb.asm.util.TraceClassVisitor; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import pt.minha.kernel.instrument.ClassConfig.Action; public class InstrumentationLoader extends ClassLoader { private ClassConfig cc; public InstrumentationLoader(ClassConfig cc) { this.cc = cc; } private Class<?> findLoadedOrGlobal(ClassConfig.Action act, String name) throws ClassNotFoundException { if (act.equals(ClassConfig.Action.invalid)) throw new ClassCastException("class marked as invalid"); /* * Moved and faked classes arrive with the prefixed name. If it * gets here without the prefix, then it should be handled as a * global as the reference comes from a fake, load or global. */ if (act.equals(ClassConfig.Action.global) || act.equals(ClassConfig.Action.fake) || act.equals(ClassConfig.Action.moved)) return super.loadClass(name); Class<?> claz = findLoadedClass(name); if (claz!=null) return claz; return null; } public void transform(Translation trans, ClassVisitor cw) throws IOException { ClassVisitor ca = cw; if (trans.getLogger().isInfoEnabled()) { trans.getLogger().info("transforming {}", trans); } // ------ This is the bytecode re-writting pipeline: ------- // (order is: last in, first used) // Handle readObject/writeObject in Serializable classes ca = new SerializableClassVisitor(ca, trans); // Rewrite some special methods ca = new MethodRemapperClassVisitor(ca, trans); // Rewrite MONITORENTER/MONITORLEAVE to methods in fake.j.l.Object ca = new FakeMonitorClassVisitor(ca, trans); // Rewrite synchronized methods to synchronized blocks ca = new SyncToMonitorClassVisitor(ca, trans); // Redirect references to fake.* and moved.* classes ca = new RemappingClassAdapter(ca, new ClassRemapper(cc, trans)); // Prepare for changes done by other stages ca = new JSRInlinerClassVisitor(ca); // Update translation config from annotations (this is the first // stage in the pipeline!) ca = new AnnotatedClassVisitor(ca, trans); // --------------------------------------------------------------------------------------- InputStream is = getResourceAsStream(trans.getFileName()); ClassReader cr = new ClassReader(is); cr.accept(ca, ClassReader.SKIP_FRAMES); } public byte[] load(Translation trans, Action act) throws IOException { if (act.equals(ClassConfig.Action.load)) { InputStream is = getResourceAsStream(trans.getFileName()); ByteArrayOutputStream baos = new ByteArrayOutputStream(); byte[] buf = new byte[1024]; int n = is.read(buf); while(n>0) { baos.write(buf, 0, n); n = is.read(buf); } return baos.toByteArray(); } else { ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES) { protected String getCommonSuperClass(String type1, String type2) { return InstrumentationLoader.this.getCommonSuperClass(type1, type2); } }; transform(trans, cw); byte[] buf = cw.toByteArray(); Logger logger = trans.getLogger(); if (logger.isDebugEnabled()) { try { Writer out = new CharArrayWriter(); out.write('\n'); ClassReader cr = new ClassReader(new ByteArrayInputStream(buf)); cr.accept(new CheckClassAdapter(new TraceClassVisitor(new PrintWriter(out))), ClassReader.EXPAND_FRAMES); logger.trace("transformed bytecode: {}", out); } catch (Exception e) { logger.error("class validation error", e); } } return buf; } } public Class<?> loadClass(String name) throws ClassNotFoundException { String effname = name.replace('.', '/'); ClassConfig.Action act = cc.get(effname); Class<?> claz = findLoadedOrGlobal(act, name); if (claz != null) return claz; /* * Set up translation configuration defaults. This depends on * the class name prefix (i.e. fake or moved), properties file, * and class file annotations. */ Translation trans = new Translation(effname, act); try { byte[] clsData = load(trans, act); if (trans.isGlobal()) // If we discovered this from an annotation... return super.loadClass(name); else { try { String pname = name.substring(0,name.lastIndexOf('.')); if(getPackage(pname)==null) definePackage(pname, null, null, null, null, null, null, null); } catch (IndexOutOfBoundsException|IllegalArgumentException e) { //Ignore exceptions } return defineClass(name, clsData, 0, clsData.length); } } catch (Exception e) { throw new ClassNotFoundException(name, e); } } public Class<?> forName(String name) throws ClassNotFoundException { Translation trans = new Translation("foobar", ClassConfig.Action.translate); ClassRemapper remapAll = new ClassRemapper(cc, trans); return loadClass(remapAll.map(name)); } /*private static void checkAndDumpClass(byte[] buf) { try { ClassReader cr = new ClassReader(new ByteArrayInputStream(buf)); cr.accept(new CheckClassAdapter(new TraceClassVisitor(new PrintWriter(System.out))), ClassReader.EXPAND_FRAMES); } catch(Exception e) { e.printStackTrace(); } }*/ /* * Rest of this file is based on code in ASM Tests by Eugene Kuleshov (v3.3) * Copyright (c) 2002-2005 France Telecom. * Modified and redistributed according to: http://asm.ow2.org/license.html. * * Used as suggested in ASM mailing list thread: * http://mail-archive.ow2.org/asm/2011-08/msg00056.html */ protected String getCommonSuperClass(String type1, String type2) { ClassInfo ci1 = new ClassInfo(type1); ClassInfo ci2 = new ClassInfo(type2); if (ci1.isAssignableFrom(ci2)) return type1; if (ci2.isAssignableFrom(ci1)) return type2; if (ci1.isInterface() || ci2.isInterface()) return "java/lang/Object"; do { // Should never be null, because if ci1 were the Object class // or an interface, it would have been caught above. ci1 = ci1.getSuperclass(); } while (!ci1.isAssignableFrom(ci2)); return ci1.getType().getInternalName(); } class ClassInfo { private Type type; private boolean isInterface; private String superClass; private String[] interfaces; public ClassInfo(String effname) { Class cls = null; String name = effname.replace('/', '.'); ClassConfig.Action act = cc.get(effname); try { cls = findLoadedOrGlobal(act, name); } catch (ClassNotFoundException e) { // failover... } if (cls != null) { this.type = Type.getType(cls); this.isInterface = cls.isInterface(); if (!isInterface && cls != Object.class) this.superClass = cls.getSuperclass().getName() .replace('.', '/'); Class[] ifs = cls.getInterfaces(); this.interfaces = new String[ifs.length]; for (int i = 0; i < ifs.length; i++) { this.interfaces[i] = ifs[i].getName().replace('.', '/'); } return; } // The class isn't loaded. Try to get the class file, and // extract the information from that. this.type = Type.getObjectType(effname); Translation trans = new Translation(effname, act); ClassVisitor ca = new ClassInfoVisitor(); try { transform(trans, ca); } catch(Exception e) { throw new RuntimeException(e); } } Type getType() { return type; } ClassInfo getSuperclass() { if (superClass == null) { return null; } return new ClassInfo(superClass); } /** * Same as {@link Class#getInterfaces()} */ ClassInfo[] getInterfaces() { if (interfaces == null) { return new ClassInfo[0]; } ClassInfo[] result = new ClassInfo[interfaces.length]; for (int i = 0; i < result.length; ++i) { result[i] = new ClassInfo(interfaces[i]); } return result; } /** * Same as {@link Class#isInterface} */ boolean isInterface() { return isInterface; } private boolean implementsInterface(ClassInfo that) { for (ClassInfo c = this; c != null; c = c.getSuperclass()) { ClassInfo[] interfaces = c.getInterfaces(); for (int i = 0; i < interfaces.length; i++) { ClassInfo iface = interfaces[i]; if (iface.type.equals(that.type) || iface.implementsInterface(that)) { return true; } } } return false; } private boolean isSubclassOf(ClassInfo that) { for (ClassInfo ci = this; ci != null; ci = ci.getSuperclass()) { if (ci.getSuperclass() != null && ci.getSuperclass().type.equals(that.type)) { return true; } } return false; } /** * Same as {@link Class#isAssignableFrom(Class)} */ boolean isAssignableFrom(ClassInfo that) { if (this == that || that.isSubclassOf(this) || that.implementsInterface(this) || (that.isInterface() && getType().getDescriptor().equals("Ljava/lang/Object;"))) { return true; } return false; } private class ClassInfoVisitor extends ClassVisitor { public ClassInfoVisitor() { super(Opcodes.ASM5, new ClassWriter(0)); } @Override public void visit(int version, int access, String name, String signature, String supername, String[] interfaces) { super.visit(version, access, name, signature, supername, interfaces); if (name.equals("java/lang/Object")) return; ClassInfo.this.interfaces = interfaces; ClassInfo.this.superClass = supername; ClassInfo.this.isInterface = (access & Opcodes.ACC_INTERFACE) != 0; } } } }