Newer
Older
JavassistTest / JavassistTest / src / tracer / Tracer.java
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 = "";		
	}
}