/* * JBoss, Home of Professional Open Source * Copyright 2011, Red Hat and individual contributors * by the @authors tag. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. * * @authors Andrew Dinn */ package org.jboss.jokre.transformer; import org.objectweb.asm.ClassReader; import org.objectweb.asm.ClassWriter; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.security.ProtectionDomain; import java.util.Iterator; import java.util.List; /** * class which does the actual bytecode transformation to calls to Map.put() with * calls to the NonReturnMap API */ public class JokreTransformer { public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer, List<String> methodNames) { // traceTransform(className, methodNames); ClassReader reader = new ClassReader(classfileBuffer); // TODO -- see if we really need to compute and expand frames ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_MAXS); // ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_MAXS); MapPutCallAdapter adapter = new MapPutCallAdapter(writer, loader, methodNames); try { reader.accept(adapter, 0); // reader.accept(adapter, 0); if (adapter.isTransformed()) { byte[] newBytes = writer.toByteArray(); maybeDumpClass(className, newBytes); return newBytes; } else { System.err.println("JokreTransformer : Failed to transform class " + className); return classfileBuffer; } } catch (Exception e) { System.err.println("JokreTransformer.transform : exception " + e); e.printStackTrace(System.err); return classfileBuffer; } } /** * Modify the supplied Map class implementer so it's API can be optimized by the Jokre optimizer. * This requires instrumenting the normal slow path put(key, value) method so it notifies Jokre * when it is called and then adding two alternative implementations, a fastpath version which * is void and an alternative slowpath version which does not perform Jokre notification. * @param loader * @param className * @param classBeingRedefined * @param protectionDomain * @param classfileBuffer */ public byte[] extendMapImplementorAPI(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) { ClassReader reader = new ClassReader(classfileBuffer); ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_MAXS|ClassWriter.COMPUTE_FRAMES); MapPutImplementorAdapter adapter = new MapPutImplementorAdapter(writer, loader, className); try { reader.accept(adapter, ClassReader.EXPAND_FRAMES); byte[] newBytes = writer.toByteArray(); maybeDumpClass(className, newBytes); return newBytes; } catch (Exception e) { System.err.println("JokreTransformer : Failed to extend MAP API class " + className); System.err.println("JokreTransformer.transform : exception " + e); e.printStackTrace(System.err); return classfileBuffer; } } public static void maybeDumpClass(String fullName, byte[] bytes) { if (dumpGeneratedClasses) { String externalName = fullName.replace('/', '.'); dumpClass(externalName, bytes); } } public void traceTransform(String className, List<String> methodNames) { StringBuilder builder = new StringBuilder(); builder.append("transform : "); builder.append(className); Iterator<String> iterator = methodNames.iterator(); builder.append("["); String prefix = ""; while (iterator.hasNext()) { String methodName = iterator.next(); builder.append(prefix); builder.append(methodName); prefix = ", "; } builder.append("]"); System.out.println(builder.toString()); } /* helper methods to dump class files */ private static void dumpClass(String fullName, byte[] bytes) { dumpClass(fullName, bytes, false); } private static void dumpClass(String fullName, byte[] bytes, boolean intermediate) { // wrap this in a try catch in case the file i/o code generates a runtime exception // this may happen e.g. because of a security restriction try { int dotIdx = fullName.lastIndexOf('.'); String name = (dotIdx < 0 ? fullName : fullName.substring(dotIdx + 1)); String prefix = (dotIdx > 0 ? File.separator + fullName.substring(0, dotIdx) : ""); String dir = dumpGeneratedClassesDir + prefix.replace('.', File.separatorChar); if (!ensureDumpDirectory(dir)) { System.out.println("JokreTransformer : Cannot dump transformed bytes to directory " + dir + File.separator + prefix); return; } String newname; if (intermediate) { int counter = 0; // add _<n> prefix until we come up with a new name newname = dir + File.separator + name + "_" + counter + ".class"; File file = new File(newname); while (file.exists()) { counter++; newname = dir + File.separator + name + "_" + counter + ".class"; file = new File(newname); } } else { newname = dir + File.separator + name + ".class"; } System.out.println("JokreTransformer : Saving transformed bytes to " + newname); try { FileOutputStream fio = new FileOutputStream(newname); fio.write(bytes); fio.close(); } catch (IOException ioe) { System.out.println("Error saving transformed bytes to" + newname); ioe.printStackTrace(System.out); } } catch (Throwable th) { System.out.println("JokreTransformer : Error saving transformed bytes for class " + fullName); th.printStackTrace(System.out); } } private static boolean ensureDumpDirectory(String fileName) { File file = new File(fileName); if (file.exists()) { return (file.isDirectory() && file.canWrite()); } else { return file.mkdirs(); } } /** * switch to control dumping of generated bytecode to .class files */ private static boolean dumpGeneratedClasses = computeDumpGeneratedClasses(); /** * directory in which to dump generated bytecode .class files (defaults to "." */ private static String dumpGeneratedClassesDir = computeDumpGeneratedClassesDir(); public static boolean computeDumpGeneratedClasses() { return System.getProperty(DUMP_GENERATED_CLASSES) != null; } public static String computeDumpGeneratedClassesDir() { String userDir = System.getProperty(DUMP_GENERATED_CLASSES_DIR); if (userDir != null) { File userFile = new File(userDir); if (userFile.exists() && userFile.isDirectory() && userFile.canWrite()) { return userDir; } else { return "."; } } else { return "."; } } /** * system property set (to any value) in order to switch on dumping of generated bytecode to .class files */ public static final String JOKRE_PACKAGE_PREFIX = "org.jboss.jokre."; /** * system property set (to any value) in order to switch on dumping of generated bytecode to .class files */ public static final String DUMP_GENERATED_CLASSES = JOKRE_PACKAGE_PREFIX + "dump.generated.classes"; /** * system property set (to any value) in order to switch on dumping of generated bytecode to .class files */ public static final String DUMP_GENERATED_CLASSES_DIR = JOKRE_PACKAGE_PREFIX + "dump.generated.classes.directory"; }