package org.deuce.transform.asm; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.lang.instrument.ClassFileTransformer; import java.lang.instrument.IllegalClassFormatException; import java.lang.instrument.Instrumentation; import java.security.ProtectionDomain; import java.util.ArrayList; import java.util.List; import java.util.jar.JarEntry; import java.util.jar.JarInputStream; import java.util.jar.JarOutputStream; import java.util.logging.Level; import java.util.logging.Logger; import org.deuce.reflection.UnsafeHolder; import org.deuce.transform.Exclude; /** * A java agent to dynamically instrument transactional supported classes/ * * @author Guy Korland * @since 1.0 */ @Exclude public class Agent implements ClassFileTransformer { private static final Logger logger = Logger.getLogger("org.deuce.agent"); final private static boolean VERBOSE = Boolean.getBoolean("org.deuce.verbose"); final private static boolean GLOBAL_TXN = Boolean.getBoolean("org.deuce.transaction.global"); /* * @see java.lang.instrument.ClassFileTransformer#transform(java.lang.ClassLoader, * java.lang.String, java.lang.Class, java.security.ProtectionDomain, * byte[]) */ public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException { try { // Don't transform classes from the boot classLoader. if (loader != null) return transform(className, classfileBuffer, false).get(0).getBytecode(); } catch(Exception e) { logger.log( Level.SEVERE, "Fail on class transform: " + className, e); } return classfileBuffer; } /** * @param offline <code>true</code> if this is an offline transform. */ private List<ClassByteCode> transform(String className, byte[] classfileBuffer, boolean offline) throws IllegalClassFormatException { ArrayList<ClassByteCode> byteCodes = new ArrayList<ClassByteCode>(); if (className.startsWith("$") || ExcludeIncludeStore.exclude(className)){ byteCodes.add(new ClassByteCode( className, classfileBuffer)); return byteCodes; } if (logger.isLoggable(Level.FINER)) logger.finer("Transforming: Class=" + className); classfileBuffer = addFrames(className, classfileBuffer); if( GLOBAL_TXN){ ByteCodeVisitor cv = new org.deuce.transaction.global.ClassTransformer( className); byte[] bytecode = cv.visit(classfileBuffer); byteCodes.add(new ClassByteCode( className, bytecode)); } else{ ExternalFieldsHolder fieldsHolder = null; if(offline) { fieldsHolder = new ExternalFieldsHolder(className); } ByteCodeVisitor cv = new org.deuce.transform.asm.ClassTransformer( className, fieldsHolder); byte[] bytecode = cv.visit(classfileBuffer); byteCodes.add(new ClassByteCode( className, bytecode)); if(offline) { byteCodes.add(fieldsHolder.getClassByteCode()); } } if( VERBOSE){ try { verbose(byteCodes); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } return byteCodes; } /** * Reads the bytecode and calculate the frames, to support 1.5- code. * * @param className class to manipulate * @param classfileBuffer original byte code * * @return bytecode with frames */ private byte[] addFrames(String className, byte[] classfileBuffer) { try{ FramesCodeVisitor frameCompute = new FramesCodeVisitor( className); return frameCompute.visit( classfileBuffer); // avoid adding frames to Java6 } catch( FramesCodeVisitor.VersionException ex){ return classfileBuffer; } } public static void premain(String agentArgs, Instrumentation inst) throws Exception{ UnsafeHolder.getUnsafe(); logger.fine("Starting Deuce agent"); inst.addTransformer(new Agent()); } /** * Used for offline instrumentation. * @param args input jar & output jar * e.g.: "C:\Java\jdk1.5.0_13\jre\lib\rt.jar" "C:\rt.jar" * @throws Exception */ public static void main(String[] args) throws Exception{ UnsafeHolder.getUnsafe(); logger.fine("Starting Deuce translator"); // TODO check args Agent agent = new Agent(); agent.transformJar(args[0], args[1]); } private void transformJar( String inFileNames, String outFilenames) throws IOException, IllegalClassFormatException { String[] inFileNamesArr = inFileNames.split(";"); String[] outFilenamesArr = outFilenames.split(";"); if(inFileNamesArr.length != outFilenamesArr.length) throw new IllegalArgumentException("Input files list length doesn't match output files list."); for(int i=0 ; i<inFileNamesArr.length ; ++i){ String inFileName = inFileNamesArr[i]; String outFilename = outFilenamesArr[i]; final int size = 4096; byte[] buffer = new byte[size]; ByteArrayOutputStream baos = new ByteArrayOutputStream(size); JarInputStream jarIS = new JarInputStream(new FileInputStream(inFileName)); JarOutputStream jarOS = new JarOutputStream(new FileOutputStream(outFilename), jarIS.getManifest()); logger.info("Start translating source:" + inFileName + " target:" + outFilename); String nextName = ""; try { for (JarEntry nextJarEntry = jarIS.getNextJarEntry(); nextJarEntry != null; nextJarEntry = jarIS.getNextJarEntry()) { baos.reset(); int read; while ((read = jarIS.read(buffer, 0, size)) > 0) { baos.write(buffer, 0, read); } byte[] bytecode = baos.toByteArray(); nextName = nextJarEntry.getName(); if( nextName.endsWith(".class")){ if( logger.isLoggable(Level.FINE)){ logger.fine("Translating " + nextName); } String className = nextName.substring(0, nextName.length() - ".class".length()); List<ClassByteCode> transformBytecodes = transform( className, bytecode, true); for(ClassByteCode byteCode : transformBytecodes){ JarEntry transformedEntry = new JarEntry(byteCode.getClassName() + ".class"); jarOS.putNextEntry( transformedEntry); jarOS.write( byteCode.getBytecode()); } } else{ jarOS.putNextEntry( nextJarEntry); jarOS.write(bytecode); } } } catch(Exception e){ logger.log(Level.SEVERE, "Failed to translate " + nextName, e); } finally { logger.info("Closing source:" + inFileName + " target:" + outFilename); jarIS.close(); jarOS.close(); } } } private void verbose(List<ClassByteCode> byteCodes) throws FileNotFoundException, IOException { File verbose = new File( "verbose"); verbose.mkdir(); for( ClassByteCode byteCode : byteCodes){ String[] packages = byteCode.getClassName().split("/"); File file = verbose; for( int i=0 ; i<packages.length-1 ; ++i){ file = new File( file, packages[i]); file.mkdir(); } file = new File( file, packages[packages.length -1]); FileOutputStream fs = new FileOutputStream( file + ".class"); fs.write(byteCode.getBytecode()); fs.close(); } } }