package tracer; import java.io.File; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.net.URL; import java.net.URLDecoder; import java.util.Enumeration; import javassist.CannotCompileException; import javassist.ClassPool; import javassist.CodeConverter; import javassist.CodeConverter.ArrayAccessReplacementMethodNames; import javassist.CtBehavior; import javassist.CtClass; import javassist.CtConstructor; import javassist.CtField; import javassist.CtMethod; import javassist.Modifier; import javassist.NotFoundException; import javassist.bytecode.BadBytecode; import javassist.bytecode.analysis.ControlFlow; import javassist.bytecode.analysis.ControlFlow.Block; import javassist.expr.ConstructorCall; import javassist.expr.ExprEditor; import javassist.expr.FieldAccess; import javassist.expr.MethodCall; public class Tracer { public static int lineNo = 1; private static final String LINE_AND_THREAD = "\":Line \" + (tracer.Tracer.lineNo++) + \":ThreadNo \" + Thread.currentThread().getId()"; private static final String LINE = "\":Line \" + (tracer.Tracer.lineNo++) + \":\""; private static final String STANDARD_CLASSES = "java.util.ListIterator|java.util.Iterator|java.util.List|java.util.Vector|java.util.ArrayList|java.util.Stack|java.util.Map|java.util.HashMap|java.util.Set|java.util.HashSet|java.util.Hashtable|java.util.LinkedList|java.lang.Thread"; private static final String CONCRETE_STANDARD_CLASSES = "java.util.Vector|java.util.ArrayList|java.util.Stack |java.util.HashMap|java.util.HashSet|java.util.Hashtable|java.util.LinkedList|java.lang.Thread"; public static void main(String[] args) { String packageName = "worstCase"; // 指定したパッケージ直下の全クラスにインストゥルメンテーションを行う ClassLoader loader = Thread.currentThread().getContextClassLoader(); URL resource = loader.getResource(packageName); File dir; try { dir = new File(URLDecoder.decode(resource.getPath(), "UTF-8")); for (String file : dir.list()) { if (file.endsWith(".class")) { String className = packageName + "." + file.replace(".class", ""); classInstrumentation(className); } } } catch (UnsupportedEncodingException e) { e.printStackTrace(); } } /** * 指定したクラスにインストゥルメンテーションを行う(仮) * * @param className * クラス名 */ private static void classInstrumentation(String className) { ClassPool cp = ClassPool.getDefault(); CtClass cc; try { cc = cp.get(className); for (final CtConstructor c : cc.getConstructors()) { methodInstrumentation(cc, c); } for (final CtMethod m : cc.getDeclaredMethods()) { methodInstrumentation(cc, m); } cc.writeFile("bin"); } catch (NotFoundException | BadBytecode | CannotCompileException | IOException e) { e.printStackTrace(); // } catch (CannotCompileException | IOException e) { // e.printStackTrace(); } } private static void methodInstrumentation(CtClass cc, final CtBehavior m) throws BadBytecode, NotFoundException, CannotCompileException { // ControlFlow cf = new ControlFlow(m); final String declaredClassName = cc.getName(); String methodSignature = m.getSignature(); // メソッド本体内のフィールドアクセスとメソッド呼び出しを置き換える m.instrument(new ExprEditor() { public void edit(FieldAccess f) throws CannotCompileException { if (f.isReader()) { if (!f.getFieldName().contains("$")) { // AspectJでは final local 変数からのゲットは無視されるので、それに合わせて除外する String thisOutput = ""; if ((m.getModifiers() & Modifier.STATIC) == 0 && m instanceof CtMethod) { thisOutput = "tracer.MyPrintStream.print(\"get:\" + this.getClass().getName() + \":\" + System.identityHashCode(this) + \":\"); "; } else { // staticメソッドかコンストラクタの場合 thisOutput = "tracer.MyPrintStream.print(\"get:" + declaredClassName + ":0:\"); "; } f.replace("{$_ = $proceed(); " + thisOutput + "if ($0 != null) {" + // target "tracer.MyPrintStream.print($0.getClass().getName() + \":\" + System.identityHashCode($0) + \":\"); " + "} else {" + "tracer.MyPrintStream.print(\"---:0:\"); " + "} " + "if ($_ != null) {" + // o "tracer.MyPrintStream.println($_.getClass().getName() + \":\" + System.identityHashCode($_) + " + LINE_AND_THREAD + ");" + "} else {" + "tracer.MyPrintStream.println(\"---:0\" + " + LINE_AND_THREAD + ");" + "} " + "}"); } } else { if (!f.getFieldName().contains("$")) { // この条件がないとなぜか落ちる(無名フィールド?へのセットがあって、それを拾って落ちてる?) f.replace("{$proceed($$); " + "if ($0 != null) {" + // target "tracer.MyPrintStream.print(\"set:\" + $0.getClass().getName() + \":\" + System.identityHashCode($0) + \":\"); " + "} else {" + "tracer.MyPrintStream.print(\"set:" + declaredClassName + ":0:\"); " + "} " + "if ($1 != null) {" + // o[0] "tracer.MyPrintStream.println($1.getClass().getName() + \":\" + System.identityHashCode($1) + " + LINE_AND_THREAD + ");" + "} else {" + "tracer.MyPrintStream.println(\"---:0\" + " + LINE_AND_THREAD + ");" + "}" + "}"); } } } public void edit(MethodCall c) throws CannotCompileException { try { if (c.getMethod().getDeclaringClass().getName().matches(STANDARD_CLASSES)) { CtMethod m = c.getMethod(); Outputs out = generateOutputs(m.getDeclaringClass(), m, true); c.replace("{" + out.newOutput + out.methodOutput + out.classOutput + out.argsOutput + " $_ = $proceed($$); " + out.returnOutput + "}"); } } catch (NotFoundException e) { e.printStackTrace(); } } public void edit(ConstructorCall c) throws CannotCompileException { try { if (c.getConstructor().getDeclaringClass().getName().matches(CONCRETE_STANDARD_CLASSES)) { CtConstructor m = c.getConstructor(); Outputs out = generateOutputs(m.getDeclaringClass(), m, true); c.replace("{" + out.newOutput + out.methodOutput + out.classOutput + out.argsOutput + " $_ = $proceed($$); " + out.returnOutput + "}"); } } catch (NotFoundException e) { e.printStackTrace(); } } }); // メソッド用の出力文を生成する Outputs outputs = generateOutputs(cc, m, false); // メソッドの実行前後に出力文を挿入する m.insertBefore("{" + outputs.newOutput + outputs.methodOutput + outputs.classOutput + outputs.argsOutput + "}"); m.insertAfter("{" + outputs.returnOutput + "}"); // CodeConverter conv = new CodeConverter(); // conv.replaceArrayAccess(cc, new // ArrayAccessReplacementMethodNames() { // @Override // public String shortWrite() { // return null; // } // @Override // public String shortRead() { // return null; // } // @Override // public String objectWrite() { // return null; // } // @Override // public String objectRead() { // return null; // } // @Override // public String longWrite() { // return null; // } // @Override // public String longRead() { // return null; // } // @Override // public String intWrite() { // return null; // } // @Override // public String intRead() { // return null; // } // @Override // public String floatWrite() { // return null; // } // @Override // public String floatRead() { // return null; // } // @Override // public String doubleWrite() { // return null; // } // @Override // public String doubleRead() { // return null; // } // @Override // public String charWrite() { // return null; // } // @Override // public String charRead() { // return null; // } // @Override // public String byteOrBooleanWrite() { // return null; // } // @Override // public String byteOrBooleanRead() { // return null; // } // }); // Block[] blocks = cf.basicBlocks(); // int block0 = m.getMethodInfo().getLineNumber(blocks[0].position()); // int block1 = // m.getMethodInfo().getLineNumber(blocks[1].position()); // int block2 = // m.getMethodInfo().getLineNumber(blocks[2].position()); // int block3 = // m.getMethodInfo().getLineNumber(blocks[3].position()); // int block4 = // m.getMethodInfo().getLineNumber(blocks[4].position()); // m.insertAt(block0, "tracer.MyPrintStream.println(\"block0:\" + " + block0 + ");"); // m.insertAt(block1, "tracer.MyPrintStream.println(\"block1:\" + " + // block1 + ");"); // m.insertAt(block2, "tracer.MyPrintStream.println(\"block2:\" + " + // block2 + ");"); // m.insertAt(block3, "tracer.MyPrintStream.println(\"block3:\" + " + // block3 + ");"); // m.insertAt(block4, "tracer.MyPrintStream.println(\"block4:\" + " + // block4 + ");"); // int block0 = // m.getMethodInfo().getLineNumber(blocks[0].position()); // m.insertAt(block0, "tracer.MyPrintStream.println(\"block0\");"); // m = cc.getDeclaredMethod("getC"); // cf = new ControlFlow(m); // blocks = cf.basicBlocks(); // int block1 = // m.getMethodInfo().getLineNumber(blocks[1].position()); // m.insertAt(block1, "tracer.MyPrintStream.println(\"block1\");"); // m = cc.getDeclaredMethod("getC"); // cf = new ControlFlow(m); // blocks = cf.basicBlocks(); // int block2 = // m.getMethodInfo().getLineNumber(blocks[2].position()); // m.insertAt(block2, "tracer.MyPrintStream.println(\"block2\");"); // m.instrument(new ExprEditor() { // public void edit(MethodCall m) throws CannotCompileException // { // if (m.getClassName().equals("Hello") // && m.getMethodName().equals("say")) // m.replace("$0.hi();"); // } // }); } private static Outputs generateOutputs(CtClass cls, CtBehavior m, boolean isCallerSideInstrumentation) throws NotFoundException { Outputs outputs = new Outputs(); String declaredClassName = cls.getName(); String delimiter = "tracer.MyPrintStream.println(\"Args:\" + "; CtClass parameterClasses[] = m.getParameterTypes(); int p = 0; for (CtClass c : parameterClasses) { if (!c.isPrimitive()) { outputs.argsOutput += delimiter + "$args[" + p + "].getClass().getName() + " + "\":\" + System.identityHashCode($" + (p + 1) + ")"; } else { outputs.argsOutput += delimiter + "$args[" + p + "].getClass().getName() + " + "\":\" + $" + (p + 1); } p++; delimiter = " + \":\" + "; } if (p > 0) { outputs.argsOutput += " + " + LINE_AND_THREAD + ");"; } String accessor = ""; if ((m.getModifiers() & Modifier.PUBLIC) != 0) { accessor = "public "; } else if ((m.getModifiers() & Modifier.PRIVATE) != 0) { accessor = "private "; } else if ((m.getModifiers() & Modifier.PROTECTED) != 0) { accessor = "protected "; } String longName = null; if ((m.getModifiers() & Modifier.STATIC) != 0) { // staticメソッドの場合 longName = accessor + "static " + m.getLongName().replace('$', '.'); // AspectJではメソッドシグニチャ内では無名クラスはドットで区切られる outputs.methodOutput = "tracer.MyPrintStream.println(\"Method " + declaredClassName + "," + longName + ":\" + 0 + " + LINE + " + System.nanoTime() + \":ThreadNo \" + Thread.currentThread().getId());"; outputs.classOutput = "tracer.MyPrintStream.println(\"Class " + declaredClassName + ":\" + 0 + " + LINE_AND_THREAD + ");"; } else if (m instanceof CtConstructor) { // コンストラクタの場合 longName = accessor + m.getLongName().replace('$', '.'); // AspectJではメソッドシグニチャ内では無名クラスはドットで区切られる outputs.newOutput = "tracer.MyPrintStream.println(\"New " + declaredClassName + ":\" + 0 + " + LINE_AND_THREAD + ");"; outputs.methodOutput = "tracer.MyPrintStream.println(\"Method " + declaredClassName + "," + longName + ":\" + 0 + " + LINE + " + System.nanoTime() + \":ThreadNo \" + Thread.currentThread().getId());"; outputs.classOutput = "tracer.MyPrintStream.println(\"Class " + declaredClassName + ":\" + 0 + " + LINE_AND_THREAD + ");"; } else { // 通常のメソッドの場合 longName = accessor + ((CtMethod)m).getReturnType().getName() + " " + m.getLongName().replace('$', '.'); // AspectJではメソッドシグニチャ内では無名クラスはドットで区切られる if (!isCallerSideInstrumentation) { // 呼び出し先に埋め込む場合(通常) outputs.methodOutput = "tracer.MyPrintStream.println(\"Method \" + this.getClass().getName() + \"," + longName + ":\" + System.identityHashCode(this) + " + LINE + " + System.nanoTime() + \":ThreadNo \" + Thread.currentThread().getId());"; outputs.classOutput = "tracer.MyPrintStream.println(\"Class \" + this.getClass().getName() + \":\" + System.identityHashCode(this) + " + LINE_AND_THREAD + ");"; } else { // 呼出し元に埋め込む場合(標準クラスの呼出し) outputs.methodOutput = "tracer.MyPrintStream.println(\"Method \" + $0.getClass().getName() + \"," + longName + ":\" + System.identityHashCode($0) + " + LINE + " + System.nanoTime() + \":ThreadNo \" + Thread.currentThread().getId());"; outputs.classOutput = "tracer.MyPrintStream.println(\"Class \" + $0.getClass().getName() + \":\" + System.identityHashCode($0) + " + LINE_AND_THREAD + ");"; } } String shortName = null; if ((m.getModifiers() & Modifier.STATIC) != 0) { // staticメソッドの場合 shortName = cls.getSimpleName().replace('$', '.') + "." + m.getName() + "()"; // AspectJではメソッドシグニチャ内では無名クラスはドットで区切られる String invocationType = null; if (!isCallerSideInstrumentation) { // 呼び出し先に埋め込む場合(通常) invocationType = "execution"; } else { // 呼出し元に埋め込む場合(標準クラスの呼出し) invocationType = "call"; } if (!((CtMethod)m).getReturnType().isPrimitive() || ((CtMethod)m).getReturnType() == CtClass.voidType) { outputs.returnOutput = "if ($_ != null) {" + "tracer.MyPrintStream.print(\"Return " + invocationType + "(" + shortName + "):\" + $_.getClass().getName() + \":\" + System.identityHashCode($_) + \":\");" + "} else {" + "tracer.MyPrintStream.print(\"Return " + invocationType + "(" + shortName + "):void:0:\");" + "} " + "tracer.MyPrintStream.println(\"0\" + " + LINE_AND_THREAD + ");"; } else { outputs.returnOutput = "tracer.MyPrintStream.print(\"Return " + invocationType + "(" + shortName + "):" + ((CtMethod)m).getReturnType().getName() + ":\" + $_ + \":\");" + "tracer.MyPrintStream.println(\"0\" + " + LINE_AND_THREAD + ");"; } } else if (m instanceof CtConstructor) { // コンストラクタの場合 shortName = m.getName().replace('$', '.') + "()"; // AspectJではメソッドシグニチャ内では無名クラスはドットで区切られる if (!isCallerSideInstrumentation) { // 呼び出し先に埋め込む場合(通常) outputs.returnOutput = "tracer.MyPrintStream.print(\"Return initialization(" + shortName + "):" + declaredClassName + ":\" + System.identityHashCode($0) + \":\");" + "tracer.MyPrintStream.println(\"\" + System.identityHashCode($0) + " + LINE_AND_THREAD + ");"; } else { // 呼出し元に埋め込む場合(標準クラスの呼出し) outputs.returnOutput = "tracer.MyPrintStream.print(\"Return call(" + shortName + "):" + declaredClassName + ":\" + System.identityHashCode($_) + \":\");" + "tracer.MyPrintStream.println(\"\" + System.identityHashCode($_) + " + LINE_AND_THREAD + ");"; } } else { // 通常のメソッドの場合 shortName = cls.getSimpleName().replace('$', '.') + "." + m.getName() + "()"; // AspectJではメソッドシグニチャ内では無名クラスはドットで区切られる if (!isCallerSideInstrumentation) { // 呼び出し先に埋め込む場合(通常) if (!((CtMethod)m).getReturnType().isPrimitive() || ((CtMethod)m).getReturnType() == CtClass.voidType) { outputs.returnOutput = "if ($_ != null) {" + "tracer.MyPrintStream.print(\"Return execution(" + shortName + "):\" + $_.getClass().getName() + \":\" + System.identityHashCode($_) + \":\");" + "} else {" + "tracer.MyPrintStream.print(\"Return execution(" + shortName + "):void:0:\");" + "} " + "tracer.MyPrintStream.println(\"\" + System.identityHashCode(this) + " + LINE_AND_THREAD + ");"; } else { outputs.returnOutput = "tracer.MyPrintStream.print(\"Return execution(" + shortName + "):" + ((CtMethod)m).getReturnType().getName() + ":\" + $_ + \":\");" + "tracer.MyPrintStream.println(\"\" + System.identityHashCode(this) + " + LINE_AND_THREAD + ");"; } } else { // 呼出し元に埋め込む場合(標準クラスの呼出し) if (!((CtMethod)m).getReturnType().isPrimitive() || ((CtMethod)m).getReturnType() == CtClass.voidType) { outputs.returnOutput = "if ($_ != null) {" + "tracer.MyPrintStream.print(\"Return call(" + shortName + "):\" + $_.getClass().getName() + \":\" + System.identityHashCode($_) + \":\");" + "} else {" + "tracer.MyPrintStream.print(\"Return call(" + shortName + "):void:0:\");" + "} " + "tracer.MyPrintStream.println(\"\" + System.identityHashCode($0) + " + LINE_AND_THREAD + ");"; } else { outputs.returnOutput = "tracer.MyPrintStream.print(\"Return execution(" + shortName + "):" + ((CtMethod)m).getReturnType().getName() + ":\" + $_ + \":\");" + "tracer.MyPrintStream.println(\"\" + System.identityHashCode($0) + " + LINE_AND_THREAD + ");"; } } } return outputs; } private static class Outputs { String newOutput = ""; String methodOutput = ""; String classOutput = ""; String argsOutput = ""; String returnOutput = ""; } }