Newer
Older
org.ntlab.reverseDebugger / org.ntlab.reverseDebugger / src / org / ntlab / reverseDebugger / CallStackModels.java
package org.ntlab.reverseDebugger;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.eclipse.jface.viewers.TreeNode;
import org.ntlab.onlineAccessor.JDIInstanceMethodCaller;
import org.ntlab.onlineAccessor.JDIStaticMethodCaller;
import com.sun.jdi.BooleanValue;
import com.sun.jdi.ClassNotLoadedException;
import com.sun.jdi.IncompatibleThreadStateException;
import com.sun.jdi.IntegerValue;
import com.sun.jdi.InvalidTypeException;
import com.sun.jdi.InvocationException;
import com.sun.jdi.ObjectReference;
import com.sun.jdi.StringReference;
import com.sun.jdi.ThreadReference;
import com.sun.jdi.Value;
import com.sun.jdi.VirtualMachine;

@SuppressWarnings("restriction")
public class CallStackModels {
	private String debuggingThreadId = "";
	private List<CallStackModel> debuggingThreadCallStacks = new ArrayList<>();
	private Map<String, List<CallStackModel>> allCallStacks = new HashMap<>();
	private static final String TRACE = "org.ntlab.traceAnalysisPlatform.tracer.trace";

	public List<CallStackModel> getDebuggingThreadCallStacks() {
		return debuggingThreadCallStacks;
	}

	public TreeNode[] getAllCallStacksTree() {
		TreeNode[] roots = new TreeNode[allCallStacks.size()];
		int rootIndex = 0;
		for (String threadId : allCallStacks.keySet()) {
			TreeNode node = getCallStackModelsTree(threadId);
			roots[rootIndex++] = node;
		}
		return roots;
	}

	public TreeNode getCallStackModelsTree(String threadId) {
		List<CallStackModel> callStackModelsInThread = allCallStacks.get(threadId);
		if (callStackModelsInThread.isEmpty()) return null;
		TreeNode root = new TreeNode(threadId);
		TreeNode parentNode = root;
		TreeNode[] childrenNode = new TreeNode[callStackModelsInThread.size()];
		parentNode.setChildren(childrenNode);
		for (int i = 0; i < callStackModelsInThread.size(); i++) {
			TreeNode childNode = new TreeNode(callStackModelsInThread.get(i));
			childNode.setParent(parentNode);
			childrenNode[i] = childNode;
		}
		return root;
	}
	
	public TreeNode[] getDebugingThreadCallStacksTree() {
		TreeNode[] roots = new TreeNode[1];
		roots[0] = getCallStackModelsTree(debuggingThreadId);
		return roots;
	}
	
	public void reset() {
		debuggingThreadId = "";
		debuggingThreadCallStacks.clear();
		allCallStacks.clear();
	}
	
	public void updateByDebuggerStopMethodExecution(JDIInstanceMethodCaller me) {
		if (me == null) return;
		try {
			VirtualMachine vm = me.getVm();
			ThreadReference thread = me.getThread();
			int lineNo = thread.frame(0).location().lineNumber();
			updateInAllThreads(vm, thread, me.getReceiver(), lineNo);
		} catch (InvalidTypeException | ClassNotLoadedException 
				| InvocationException | IncompatibleThreadStateException e) {
			e.printStackTrace();
		}
	}

	public void updateByAlias(JDIInstanceMethodCaller alias) {
		if (alias == null) return;
		VirtualMachine vm = alias.getVm();
		ThreadReference thread = alias.getThread();
		try {
			ObjectReference me = (ObjectReference)alias.callInstanceMethod("getMethodExecution");
			int lineNo = ((IntegerValue)alias.callInstanceMethod("getLineNo")).value();
			updateInAllThreads(vm, thread, me, lineNo);
		} catch (InvalidTypeException | ClassNotLoadedException 
				| InvocationException | IncompatibleThreadStateException e) {
			e.printStackTrace();
		}
	}
	
	private void updateInAllThreads(VirtualMachine vm, ThreadReference thread, ObjectReference me, int topMethodCallLineNo) 
			throws InvalidTypeException, ClassNotLoadedException, InvocationException, IncompatibleThreadStateException {
		JDIInstanceMethodCaller mc = new JDIInstanceMethodCaller(vm, thread, null);		
		mc.changeReceiver(me);
		ObjectReference statements = (ObjectReference)mc.callInstanceMethod("getStatements");
		mc.changeReceiver(statements);
		int lastIndex = ((IntegerValue)mc.callInstanceMethod("size")).value() - 1;
		mc.changeReceiver(me);
		ObjectReference tp = (ObjectReference)mc.callInstanceMethod("getTracePoint", vm.mirrorOf(lastIndex));		
		mc.changeReceiver(tp);
		ObjectReference tpStatement = (ObjectReference)mc.callInstanceMethod("getStatement");
		mc.changeReceiver(tpStatement);
		
		reset();
		debuggingThreadId = ((StringReference)mc.callInstanceMethod("getThreadNo")).value();
		debuggingThreadCallStacks = update(vm, thread, me, debuggingThreadId, topMethodCallLineNo);
		allCallStacks.put(debuggingThreadId, debuggingThreadCallStacks);
		Value visitor = getVisitor(vm, thread, tp);
		updateOtherThreadCallStacks(vm, thread, visitor);
	}

	private List<CallStackModel> update(VirtualMachine vm, ThreadReference thread, ObjectReference me, String threadId, int topMethodCallLineNo)
			throws InvalidTypeException, ClassNotLoadedException, InvocationException, IncompatibleThreadStateException {
		List<CallStackModel> list = new ArrayList<>();
		JDIInstanceMethodCaller mc = new JDIInstanceMethodCaller(vm, thread, null);
		int callLineNo = topMethodCallLineNo;
		ObjectReference childMe = null;
		ObjectReference tmpMe = me;
		while (tmpMe != null) {
			CallStackModel callStackModel = new CallStackModel(vm, thread, tmpMe, threadId, callLineNo);
			list.add(callStackModel);
			childMe = tmpMe;
			tmpMe = (ObjectReference)callStackModel.callInstanceMethod("getParent");
			if (tmpMe != null) {
				mc.changeReceiver(tmpMe);
				ObjectReference callStatement = (ObjectReference)mc.callInstanceMethod("getMethodInvocation", childMe);
				callLineNo = ((IntegerValue)mc.changeReceiver(callStatement).callInstanceMethod("getLineNo")).value();
			}
		}
		return list;
	}

	private void updateOtherThreadCallStacks(VirtualMachine vm, ThreadReference thread, Value visitor) {
		try {
			JDIInstanceMethodCaller mc = new JDIInstanceMethodCaller(vm, thread, null);
			ObjectReference traceJSON = (ObjectReference)mc.callStaticMethod(TRACE, "TraceJSON", "getInstance");			
			mc.changeReceiver(traceJSON);	ObjectReference allThreads = (ObjectReference)mc.callInstanceMethod("getAllThreads");
			mc.changeReceiver(allThreads);	ObjectReference keySet = (ObjectReference)mc.callInstanceMethod("keySet");
			mc.changeReceiver(keySet);		ObjectReference iterator = (ObjectReference)mc.callInstanceMethod("iterator");
			mc.changeReceiver(iterator);	boolean hasNext = ((BooleanValue)mc.callInstanceMethod("hasNext")).value();
			while (hasNext) {
				StringReference threadId = (StringReference)mc.callInstanceMethod("next");
				if (threadId.value().equals(debuggingThreadId)) {
					mc.changeReceiver(iterator);
					hasNext = ((BooleanValue)mc.callInstanceMethod("hasNext")).value();
					continue;
				}
				
				ObjectReference start = getStart(vm, thread, allThreads, threadId);
				mc.changeReceiver(traceJSON);
				mc.callInstanceMethod("getLastStatementInThread", threadId, start, visitor);
				ObjectReference resultTp = (ObjectReference)mc.callStaticMethod("java.lang.reflect", "Array", "get", start, vm.mirrorOf(0));
				updateOtherThreadCallStacks(vm, thread, threadId.value(), resultTp);
	
				mc.changeReceiver(iterator);
				hasNext = ((BooleanValue)mc.callInstanceMethod("hasNext")).value();
			}
		} catch (InvalidTypeException | ClassNotLoadedException
				| InvocationException | IncompatibleThreadStateException e) {
			e.printStackTrace();
		}
	}
	
	private void updateOtherThreadCallStacks(VirtualMachine vm, ThreadReference thread, String threadId, ObjectReference tp)
			throws InvalidTypeException, ClassNotLoadedException, InvocationException, IncompatibleThreadStateException {
		JDIInstanceMethodCaller mc = new JDIInstanceMethodCaller(vm, thread, null);
		mc.changeReceiver(tp);
		ObjectReference me = (ObjectReference)mc.callInstanceMethod("getMethodExecution");
		mc.changeReceiver(me);				
		ObjectReference statements = (ObjectReference)mc.callInstanceMethod("getStatements");
		mc.changeReceiver(statements);
		int lastIndex = ((IntegerValue)mc.callInstanceMethod("size")).value() - 1;
		ObjectReference lastStatement = (ObjectReference)mc.callInstanceMethod("get", vm.mirrorOf(lastIndex));
		mc.changeReceiver(lastStatement);
		int lineNo = ((IntegerValue)mc.callInstanceMethod("getLineNo")).value();
		allCallStacks.put(threadId, update(vm, thread, me, threadId, lineNo));		
	}
	
	private ObjectReference getVisitor(VirtualMachine vm, ThreadReference thread, ObjectReference tp)
			throws InvalidTypeException, ClassNotLoadedException, InvocationException, IncompatibleThreadStateException {
		JDIInstanceMethodCaller mc = new JDIInstanceMethodCaller(vm, thread, null);
		
//		// visitor (コンストラクタの引数がない場合)	
//		ObjectReference clazz = (ObjectReference)mc.callStaticMethod(
//				"java.lang", "Class", "forName", vm.mirrorOf("org.ntlab.reverseDebugger.CallStackVisitor"));
//		mc.changeReceiver(clazz);
//		ObjectReference visitor = (ObjectReference)mc.callInstanceMethod("newInstance");

		// visitor (コンストラクタに引数がある場合)
		ObjectReference visitorClass = (ObjectReference)mc.callStaticMethod(
				"java.lang", "Class", "forName", vm.mirrorOf("org.ntlab.reverseDebugger.CallStackVisitor"));
		ObjectReference classClass = (ObjectReference)mc.callStaticMethod(
				"java.lang", "Class", "forName", vm.mirrorOf("java.lang.Class"));
		ObjectReference classClassArray = (ObjectReference)mc.callStaticMethod("java.lang.reflect", "Array", "newInstance", classClass, vm.mirrorOf(1)); // 引数が可変長のメソッドをJDIで呼び出す際は、引数1つでも配列にしないとエラーが出る
		mc.changeReceiver(tp);
		ObjectReference tpClass = (ObjectReference)mc.callInstanceMethod("getClass");
		mc.callStaticMethod("java.lang.reflect", "Array", "set", classClassArray, vm.mirrorOf(0), tpClass);
		mc.changeReceiver(visitorClass);
		ObjectReference constructor = (ObjectReference)mc.callInstanceMethod("getConstructor", classClassArray);
		ObjectReference tpClassArray = (ObjectReference)mc.callStaticMethod("java.lang.reflect", "Array", "newInstance", tpClass, vm.mirrorOf(1)); // 引数が可変長のメソッドをJDIで呼び出す際は、引数1つでも配列にしないとエラーが出る
		mc.callStaticMethod("java.lang.reflect", "Array", "set", tpClassArray, vm.mirrorOf(0), tp);
		mc.changeReceiver(constructor);
		ObjectReference visitor = (ObjectReference)mc.callInstanceMethod("newInstance", tpClassArray);
		return visitor;
	}
	
	private ObjectReference getStart(VirtualMachine vm, ThreadReference thread, ObjectReference allThreads, StringReference threadId)
			throws InvalidTypeException, ClassNotLoadedException, InvocationException, IncompatibleThreadStateException {
		JDIInstanceMethodCaller mc = new JDIInstanceMethodCaller(vm, thread, null);
		
		// start
		mc.changeReceiver(allThreads);
		ObjectReference threadInstance = (ObjectReference)mc.callInstanceMethod("get", threadId);
		mc.changeReceiver(threadInstance);
		ObjectReference currentTp = (ObjectReference)mc.callInstanceMethod("getCurrentTracePoint");
		mc.changeReceiver(currentTp);
		ObjectReference currentTpClass = (ObjectReference)mc.callInstanceMethod("getClass");
		ObjectReference start = (ObjectReference)mc.callStaticMethod("java.lang.reflect", "Array", "newInstance", currentTpClass, vm.mirrorOf(1));
		mc.callStaticMethod("java.lang.reflect", "Array", "set", start, vm.mirrorOf(0), currentTp);
		return start;
	}
}