/******************************************************************************* * Copyright (c) 2010 Freescale Semiconductor. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Serge Beauchamp (Freescale Semiconductor) - initial API and implementation *******************************************************************************/ package com.freescale.deadlockpreventer; import java.io.ByteArrayInputStream; import java.io.IOException; import java.lang.instrument.ClassFileTransformer; import java.lang.instrument.IllegalClassFormatException; import java.security.ProtectionDomain; import java.util.ArrayList; import javassist.CannotCompileException; import javassist.ClassPool; import javassist.CtBehavior; import javassist.CtClass; import javassist.CtMethod; import javassist.Modifier; import javassist.bytecode.BadBytecode; import javassist.bytecode.Bytecode; import javassist.bytecode.CodeAttribute; import javassist.bytecode.CodeIterator; import javassist.bytecode.ConstPool; import javassist.bytecode.LineNumberAttribute; import javassist.bytecode.MethodInfo; import javassist.bytecode.Opcode; import javassist.bytecode.StackMapTable; public class Transformer implements ClassFileTransformer { private static final String LEAVE_LOCK = "leaveLock"; private static final String ENTER_LOCK = "enterLock"; private static final String LEAVE_LOCK_CUSTOM = "leaveLockCustom"; private static final String ENTER_LOCK_CUSTOM_UNSCOPED = "enterLockCustomUnscoped"; private static final String LEAVE_LOCK_CUSTOM_UNSCOPED = "leaveLockCustomUnscoped"; private static final String ENTER_LOCK_CUSTOM = "enterLockCustom"; private static final String LJAVA_LANG_OBJECT_V = "(Ljava/lang/Object;)V"; private static final String LJAVA_LANG_OBJECT__INT_V = "(Ljava/lang/Object;I)V"; private static final String COM_FREESCALE_DEADLOCK_PREVENTER_ANALYZER = "com/freescale/deadlockpreventer/Analyzer"; static boolean rebuildStackMap = false; public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException { ArrayList<RegisteredLock> registeredMethods = null; for (RegisteredLock registeredLock : registeredLocks) { if (registeredLock.className.equals(className)) { if (registeredMethods == null) registeredMethods = new ArrayList<Transformer.RegisteredLock>(); registeredMethods.add(registeredLock); } } if (registeredMethods == null) { if (className.startsWith("java/")) return classfileBuffer; if (className.startsWith("sun/")) return classfileBuffer; } if (className.startsWith("com/freescale/deadlockpreventer/") && !className.startsWith("com/freescale/deadlockpreventer/tests")) return classfileBuffer; if (className.startsWith("javassist/")) return classfileBuffer; try { Analyzer.instance().disable(); ClassPool globalPool = ClassPool.getDefault(); ClassPool.doPruning = true; CtClass currentClass = null; try { final CtClass cls = globalPool.makeClass(new ByteArrayInputStream( classfileBuffer)); currentClass = cls; final CtMethod methods[] = cls.getDeclaredMethods(); for (int i = 0; i < methods.length; i++) { boolean updateLineNumber = false; MonitorEditor editor = new MonitorEditor(); final CtMethod ctMethod = methods[i]; editor.edit(cls, ctMethod); MethodInfo methodInfo = ctMethod.getMethodInfo(); if (Modifier.isSynchronized(ctMethod.getModifiers())) { CodeAttribute codeAttribute = methodInfo.getCodeAttribute(); if (codeAttribute != null) { try { ctMethod.insertBefore(new CtBehavior.CodeInserter() { public void insert(Bytecode code, int pos) { insertEnterLock(code, pos, Analyzer.LOCK_NORMAL, Modifier.isStatic(ctMethod.getModifiers()), false, cls.getName(), false); } }, false); if (Analyzer.instance().shouldWriteInstrumentedClasses()) cls.debugWriteFile(); ctMethod.insertAfter(new CtBehavior.CodeInserter() { public void insert(Bytecode code, int pos) { insertEnterLock(code, pos, Analyzer.UNLOCK_NORMAL, Modifier.isStatic(ctMethod.getModifiers()), false, cls.getName(), false); } }, true, false); if (Analyzer.instance().shouldWriteInstrumentedClasses()) cls.debugWriteFile(); } catch (CannotCompileException e) { e.printStackTrace(); return classfileBuffer; } updateLineNumber = true; } } if (registeredMethods != null) { for (final RegisteredLock registeredLock : registeredMethods) { if (registeredLock.methodName.equals(ctMethod.getName()) && registeredLock.signature.equals(ctMethod.getSignature())) { updateLineNumber = true; try { ctMethod.insertBefore(new CtBehavior.CodeInserter() { public void insert(Bytecode code, int pos) { insertEnterLock(code, pos, registeredLock.lockMode, Modifier.isStatic(ctMethod.getModifiers()), true, cls.getName(), registeredLock.unscoped); } }, false); } catch (CannotCompileException e) { e.printStackTrace(); return classfileBuffer; } } } } if (updateLineNumber) { CodeAttribute codeAttribute = methodInfo.getCodeAttribute(); if (codeAttribute != null) { LineNumberAttribute ainfo = (LineNumberAttribute)codeAttribute.getAttribute(LineNumberAttribute.tag); if (ainfo != null) ainfo.includeZeroPc(); } if (rebuildStackMap) methodInfo.rebuildStackMapIf6(cls.getClassPool(), cls.getClassFile2()); else codeAttribute.removeAttribute(StackMapTable.tag); } } if (Analyzer.instance().shouldWriteInstrumentedClasses()) cls.debugWriteFile(); if (Analyzer.instance().isDebug()) System.out.println("transform: " + className); return cls.toBytecode(); } catch (IOException e) { e.printStackTrace(); } catch (RuntimeException e) { e.printStackTrace(); } catch (Throwable e) { e.printStackTrace(); } finally { if (currentClass != null) currentClass.detach(); } } finally { Analyzer.instance().enable(); } return classfileBuffer; } private void insertEnterLock(Bytecode b, int pos, int lockMode, boolean isStatic, boolean isCustom, String className, boolean unscoped) { b.setMaxStack(b.getMaxStack() + 1); String name = ENTER_LOCK; String signature = LJAVA_LANG_OBJECT_V; boolean isCounted = false; if (isCustom) { switch(lockMode) { case Analyzer.LOCK_NORMAL: if (unscoped) name = ENTER_LOCK_CUSTOM_UNSCOPED; else name = ENTER_LOCK_CUSTOM; signature = LJAVA_LANG_OBJECT_V; break; case Analyzer.UNLOCK_NORMAL: if (unscoped) name = LEAVE_LOCK_CUSTOM_UNSCOPED; else name = LEAVE_LOCK_CUSTOM; signature = LJAVA_LANG_OBJECT_V; break; case Analyzer.LOCK_COUNTED: name = ENTER_LOCK_CUSTOM; signature = LJAVA_LANG_OBJECT__INT_V; isCounted = true; break; case Analyzer.UNLOCK_COUNTED: name = LEAVE_LOCK_CUSTOM; signature = LJAVA_LANG_OBJECT__INT_V; isCounted = true; break; } } else { name = (lockMode == Analyzer.LOCK_NORMAL ? ENTER_LOCK: LEAVE_LOCK); signature = LJAVA_LANG_OBJECT_V; } if (isStatic) { int pc = b.currentPc(); b.addLdc(b.getConstPool().addStringInfo(className)); b.addInvokestatic("java/lang/Class", "forName", "(Ljava/lang/String;)Ljava/lang/Class;"); int endHandler = b.currentPc(); Bytecode tmp = new Bytecode(b.getConstPool(), 1, 0); tmp.addInvokestatic("javassist/runtime/DotClass", "fail", "(Ljava/lang/ClassNotFoundException;)Ljava/lang/NoClassDefFoundError;"); tmp.addAthrow(); tmp.addGoto(b.getSize() + 4); b.addGoto(tmp.getSize()); int handler = b.currentPc(); b.addInvokestatic("javassist/runtime/DotClass", "fail", "(Ljava/lang/ClassNotFoundException;)Ljava/lang/NoClassDefFoundError;"); b.addAthrow(); if (isCounted) b.addIload(0); b.addInvokestatic(COM_FREESCALE_DEADLOCK_PREVENTER_ANALYZER, name, signature); b.addExceptionHandler(pc, endHandler, handler, "java.lang.ClassNotFoundException"); } else { // 0 is 'this' for class methods b.addAload(0); if (isCounted) b.addIload(1); b.addInvokestatic(COM_FREESCALE_DEADLOCK_PREVENTER_ANALYZER, name, signature); } } public static void lock(Object lock) { System.out.println("lock: " + lock); } public static void unlock(Object lock) { System.out.println("unlock: " + lock); } static class MonitorEditor { public MonitorEditor() { } public void edit(CtClass cls, CtMethod ctMethod) throws CannotCompileException { MethodInfo methodInfo = ctMethod.getMethodInfo(); CodeAttribute codeAttribute = methodInfo.getCodeAttribute(); if (codeAttribute == null) return; CodeIterator iterator = codeAttribute.iterator(); boolean changed = false; while (iterator.hasNext()) { try { int pos = iterator.next(); int c = iterator.byteAt(pos); if (c == Opcode.MONITORENTER) { changed = true; ConstPool cp = methodInfo.getConstPool(); CodeAttribute attr = methodInfo.getCodeAttribute(); attr.setMaxStack(attr.getMaxStack() + 1); // insert invokestatic Bytecode b = new Bytecode(cp, 1, 0); b.addInvokestatic(COM_FREESCALE_DEADLOCK_PREVENTER_ANALYZER, ENTER_LOCK, LJAVA_LANG_OBJECT_V); byte[] code = b.get(); iterator.insert(pos, code); // insert dup b = new Bytecode(cp, 1, 0); b.addDup(); code = b.get(); iterator.insert(pos, code); } if (c == Opcode.MONITOREXIT) { ConstPool cp = methodInfo.getConstPool(); // insert invokestatic Bytecode b = new Bytecode(cp, 1, 0); b.addInvokestatic(COM_FREESCALE_DEADLOCK_PREVENTER_ANALYZER, LEAVE_LOCK, LJAVA_LANG_OBJECT_V); byte[] code = b.get(); iterator.insert(pos, code); b = new Bytecode(cp, 1, 0); b.addDup(); code = b.get(); iterator.insert(pos, code); } } catch (BadBytecode e) { e.printStackTrace(); } } if (changed) { try { if (Analyzer.instance().shouldWriteInstrumentedClasses()) cls.debugWriteFile(); if (rebuildStackMap) methodInfo.rebuildStackMapIf6(cls.getClassPool(), cls.getClassFile2()); } catch (BadBytecode e) { e.printStackTrace(); } } } } static class RegisteredLock { int lockMode; String className; String methodName; String signature; boolean unscoped; public RegisteredLock(int lockMode, String className, String methodName, String signature, boolean unscoped) { this.lockMode = lockMode; this.className = className; this.methodName = methodName; this.signature = signature; this.unscoped = unscoped; } } ArrayList<RegisteredLock> registeredLocks = new ArrayList<RegisteredLock>(); public void register(int lockMode, String className, String methodName, String signature, boolean unscoped) { registeredLocks.add(new RegisteredLock(lockMode, className, methodName, signature, unscoped)); } }