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

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import org.eclipse.jdt.core.IMethod;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jface.viewers.TreeNode;
import org.ntlab.onlineAccessor.JDIInstanceMethodCaller;
import com.sun.jdi.ClassNotLoadedException;
import com.sun.jdi.ClassType;
import com.sun.jdi.Field;
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.ReferenceType;
import com.sun.jdi.StringReference;
import com.sun.jdi.ThreadReference;
import com.sun.jdi.Value;
import com.sun.jdi.VirtualMachine;

public class Variables {
	private VariableData rootThisObjData;
	private List<VariableData> argsData = new ArrayList<>();
	private List<VariableData> allObjData = new ArrayList<>();
	private static final String TRACER = "org.ntlab.traceAnalysisPlatform.tracer.trace";

	public TreeNode[] getVariablesTreeNodes() {
		TreeNode[] roots = new TreeNode[allObjData.size()];
		if (allObjData.isEmpty()) {
			return roots;
		}
		for (int i = 0; i < allObjData.size(); i++) {
			VariableData rootVariableData = allObjData.get(i);
			createVariablesTreeNode(null, roots, i, rootVariableData);
		}
		return roots;
	}
	
	private void createVariablesTreeNode(TreeNode parentNode, TreeNode[] addingNodes, int index, VariableData addingVariableData) {
		TreeNode newNode = new TreeNode(addingVariableData);
		newNode.setParent(parentNode);
		addingNodes[index] = newNode;
		TreeNode[] childNodes = new TreeNode[addingVariableData.getChildren().size()];
		addingNodes[index].setChildren(childNodes);
		for (int i = 0; i < addingVariableData.getChildren().size(); i++) {
			VariableData child = addingVariableData.getChildren().get(i);
			createVariablesTreeNode(newNode, childNodes, i, child);
		}
	}
	
	public List<VariableData> getAllObjectDataByMethodExecution(JDIInstanceMethodCaller methodExecution) {
		if (methodExecution == null) return new ArrayList<>();
		try {
			VirtualMachine vm = methodExecution.getVm();
			ThreadReference thread = methodExecution.getThread();
			ObjectReference method = methodExecution.getReceiver();
			
			JDIInstanceMethodCaller mc = new JDIInstanceMethodCaller(vm, thread, method);
			ObjectReference statements =  (ObjectReference)mc.callInstanceMethod("getStatements");
			mc.changeReceiver(statements);
			int lastOrder = ((IntegerValue)mc.callInstanceMethod("size")).value() - 1;
			mc.changeReceiver(method);
			ObjectReference tp = (ObjectReference)mc.callInstanceMethod("getTracePoint", vm.mirrorOf(lastOrder));
//			methodExecution.getTracePoint(methodExecution.getStatements().size() - 1);			
			
			getAllObjectData(vm, thread, method, tp);
		} catch (InvalidTypeException | ClassNotLoadedException
				| InvocationException | IncompatibleThreadStateException e) {
			e.printStackTrace();
		}
		return allObjData;
	}
	
	public List<VariableData> getAllObjectDataByAlias(JDIInstanceMethodCaller alias) {
		if (alias == null) return new ArrayList<>();
		try {
			VirtualMachine vm = alias.getVm();
			ThreadReference thread = alias.getThread();
			ObjectReference methodExecution = (ObjectReference)alias.callInstanceMethod("getMethodExecution");
			ObjectReference tp = (ObjectReference)alias.callInstanceMethod("getOccurrencePoint");
			getAllObjectData(vm, thread, methodExecution, tp);
		} catch (InvalidTypeException | ClassNotLoadedException | InvocationException
				| IncompatibleThreadStateException e) {
			e.printStackTrace();
		}
		return allObjData;
	}
	
	private void getAllObjectData(VirtualMachine vm, ThreadReference thread, Value methodExecution, Value tp) {
		try {
			resetData();

			// thisのIDとTypeを取得して表示
			getRootThisState(vm, thread, (ObjectReference)methodExecution);
			
			// thisの持つフィールドのIDとTypeを表示 (呼び出し先で再帰)
			getFieldsState(vm, thread, rootThisObjData, tp, "");

			// 引数のIDとType 及びその引数が持つフィールドのIDとTypeを表示 (呼び出し先で再帰)		
			getArgsState(vm, thread, methodExecution, tp, "");
			
			allObjData.add(rootThisObjData);
			for (VariableData argData : argsData) {
				allObjData.add(argData);
			}
		} catch (InvalidTypeException | ClassNotLoadedException
				| InvocationException | IncompatibleThreadStateException e) {
			e.printStackTrace();
		}		
		printAllObjData(); // 確認用	
	}
	
	private void getRootThisState(VirtualMachine vm, ThreadReference thread, ObjectReference methodExecution)
			throws InvalidTypeException, ClassNotLoadedException, InvocationException, IncompatibleThreadStateException {
		JDIInstanceMethodCaller mc = new JDIInstanceMethodCaller(vm, thread, methodExecution);
		String thisObjId = ((StringReference)mc.callInstanceMethod("getThisObjId")).value();
		String thisClassName = ((StringReference)mc.callInstanceMethod("getThisClassName")).value();
		rootThisObjData = new VariableData("this", thisClassName, thisObjId);
	}
	
	private void getFieldsState(VirtualMachine vm, ThreadReference thread, VariableData thisObjData, Value tp, String indent)
			throws InvalidTypeException, ClassNotLoadedException, InvocationException, IncompatibleThreadStateException {
		// フィールドのIDとTypeを取得して表示
		List<ReferenceType> classes = vm.classesByName(thisObjData.getClassName());
		ClassType type = (ClassType)classes.get(0);
		JDIInstanceMethodCaller mc = new JDIInstanceMethodCaller(vm, thread, null);
		ObjectReference trace = (ObjectReference)mc.callStaticMethod(TRACER, "TraceJSON", "getInstance");
		for (Field field : type.allFields()) {
			if (field.isStatic()) continue; // staticフィールドは飛ばす (取ろうとしても、IDもTypeも取れない)
			String fieldName = field.declaringType().name() + "." + field.name(); // 完全限定クラス名
			
			// そのフィールドについての最新の更新情報を取得(FieldUpdate)
			mc.changeReceiver(trace);
			ObjectReference fieldUpdate = (ObjectReference)mc.callInstanceMethod("getRecentlyFieldUpdate", vm.mirrorOf(thisObjData.getId()), vm.mirrorOf(fieldName), tp);
			if (fieldUpdate == null) continue;
			
			// フィールドのIDとTypeを取得(String)
			mc.changeReceiver(fieldUpdate);
			String fieldObjId = ((StringReference)mc.callInstanceMethod("getValueObjId")).value();
			String fieldType = ((StringReference)mc.callInstanceMethod("getValueClassName")).value();
			VariableData fieldData = new VariableData(fieldName, fieldType, fieldObjId);
			thisObjData.addChild(fieldData);

			// 取得したフィールドがオブジェクトや配列の場合に、そのオブジェクトや配列が持つフィールドについての処理を行う
			callDeepHierarchyFieldState(vm, thread, fieldData, tp, (indent + "    "));
		}
	}

	/**
	 * 引数で渡したフィールドが参照型の場合や、配列の場合に、<br>
	 * その参照型オブジェクトが持っているフィールドや、配列の各要素のオブジェクトについて、<br>
	 * それぞれに対応したメソッドをさらに呼び出す (このメソッドと対応メソッドとの相互再帰)
	 */
	private void callDeepHierarchyFieldState(VirtualMachine vm, ThreadReference thread, VariableData thisObjData, Value tp, String indent)
			throws InvalidTypeException, ClassNotLoadedException, InvocationException, IncompatibleThreadStateException {
		// フィールドのIDやTypeがない場合や、Type(=ActualType)が"---"の場合は何もしない
		if (thisObjData.getId() == null || thisObjData.getId().isEmpty() 
				|| thisObjData.getClassName() == null || thisObjData.getClassName().isEmpty()) {
			return;
		}
		final String NULL_ACTUAL_TYPE = "---"; // フィールドに対して明示的にnullを入れた場合のActualTypeの取得文字列
		if (thisObjData.getClassName().equals(NULL_ACTUAL_TYPE)) return;

		final String ARRAY_SIGNATURE_HEAD = "["; // 配列のシグネチャの先頭は、配列の次元数だけ [ が連なる
		if (thisObjData.getClassName().startsWith(ARRAY_SIGNATURE_HEAD)) {
			// フィールドのTypeが配列型( [ で始まる )場合 (その配列が持つ各要素についてさらなるデータ取得処理を呼び出す)
			getArrayState(vm, thread, thisObjData, tp, indent);
		} else {
			String[] primitives = {"byte", "short", "int", "long", "float", "double", "char", "boolean"};
			if (!Arrays.asList(primitives).contains(thisObjData.getClassName())) {
				// フィールドのTypeが参照型(=オブジェクト)の場合 (そのオブジェクトが持っているフィールドについてさらなるデータ取得処理を呼び出す)
				getFieldsState(vm, thread, thisObjData, tp, indent);
			}
		}
	}
	
	private void getArrayState(VirtualMachine vm, ThreadReference thread, VariableData arrayData, Value tp, String indent)
			throws InvalidTypeException, ClassNotLoadedException, InvocationException, IncompatibleThreadStateException {
		JDIInstanceMethodCaller mc = new JDIInstanceMethodCaller(vm, thread, null);
		ObjectReference trace = (ObjectReference)mc.callStaticMethod(TRACER, "TraceJSON", "getInstance");
		for (int i = 0;; i++){
			// その配列要素についての最新の更新情報を取得(ArrayUpdate)
			IntegerValue index = vm.mirrorOf(i);
			mc.changeReceiver(trace);
			ObjectReference arrayUpdate = (ObjectReference)mc.callInstanceMethod("getRecentlyArrayUpdate", vm.mirrorOf(arrayData.getId()), index, tp);
			if (arrayUpdate == null) {
				// 配列のサイズが取得できないため、インデックスがサイズ超過のときに確実に抜けられる方法として仮処理
				// ただし、配列要素の途中に未定義があった場合でも、抜けてしまうのが問題点
				break;
			}
			String arrayIndexName = arrayData.getVariableName() + "[" + index.value() + "]";
			
			// 配列要素のIDとTypeを取得(String)
			mc.changeReceiver(arrayUpdate);
			String valueObjId = ((StringReference)mc.callInstanceMethod("getValueObjectId")).value();
			String valueType = ((StringReference)mc.callInstanceMethod("getValueClassName")).value();
			VariableData arrayIndexData = new VariableData(arrayIndexName, valueType, valueObjId); 
			arrayData.addChild(arrayIndexData);

			// 取得した配列要素がオブジェクトや配列の場合に、そのオブジェクトや配列が持つフィールドについての処理を行う
			callDeepHierarchyFieldState(vm, thread, arrayIndexData, tp, (indent + "    "));
		}
	}

	private void getArgsState(VirtualMachine vm, ThreadReference thread, Value methodExecution, Value tp, String indent)
			throws InvalidTypeException, ClassNotLoadedException, InvocationException, IncompatibleThreadStateException {
		// methodExecutionが持つargumentsを取得(ArrayList)し、そのargumentsのサイズも取得(int)
		JDIInstanceMethodCaller mc = new JDIInstanceMethodCaller(vm, thread, (ObjectReference)methodExecution);
		ObjectReference args = (ObjectReference)mc.callInstanceMethod("getArguments");		
		final int ARGUMENTS_NUM = ((IntegerValue)mc.changeReceiver(args).callInstanceMethod("size")).value();
		if (ARGUMENTS_NUM > 0) {
			JavaEditorOperator javaEditorOperator = new JavaEditorOperator();
			IType type = javaEditorOperator.findIType(new JDIInstanceMethodCaller(vm, thread, (ObjectReference)methodExecution));
			mc.changeReceiver((ObjectReference)methodExecution);
			String methodSignature = ((StringReference)mc.callInstanceMethod("getSignature")).value();
			IMethod method = javaEditorOperator.findIMethod(type, methodSignature);			
			String[] argNames = getParameterNames(method); // 引数のIMethodから仮引数名を取得する
			for (int i = 0; i < ARGUMENTS_NUM; i++) {
				String argName = (argNames.length == ARGUMENTS_NUM) ? argNames[i] : "arg" + i; // 少なくとも引数の個数が不一致のときは正しい引数名が取れていない
				mc.changeReceiver(args);
				ObjectReference arg = (ObjectReference)mc.callInstanceMethod("get", vm.mirrorOf(i));				
				mc.changeReceiver(arg);
				String argId = ((StringReference)mc.callInstanceMethod("getId")).value();
				String argType = ((StringReference)mc.callInstanceMethod("getActualType")).value();
				VariableData argData = new VariableData(argName, argType, argId); 
				argsData.add(argData);
				callDeepHierarchyFieldState(vm, thread, argData, tp, (indent + "    "));
			}
		}
	}

	private String[] getParameterNames(IMethod method) {
		String[] argNames = new String[0];
		if (method != null) {
			try {
				argNames = method.getParameterNames();
			} catch (JavaModelException e) {
				e.printStackTrace();
			}
		}
		return argNames;
	}
	
	public void resetData() {
		rootThisObjData = null;
		argsData.clear();
		allObjData.clear();
	}
	
	private void printAllObjData() {
		System.out.println();
		System.out.println("this");
		printObjData(allObjData.get(0), "");
		System.out.println();
		System.out.println("args");
		for (int i = 1; i < allObjData.size(); i++) {
			printObjData(allObjData.get(i), "");
		}
		System.out.println();
	}
	
	private void printObjData(VariableData data, String indent) {
		StringBuilder sb = new StringBuilder();
		sb.append(data.getVariableName() + ": ");
		sb.append(data.getClassName());
		sb.append("(" + "id = " + data.getId() + ")");
		System.out.println(indent + sb);
		for (VariableData child : data.getChildren()) {
			printObjData(child, indent + "	");
		}
	}
}

class VariableData {
	private String variableName;
	private String className;
	private String id;
	private VariableData parent;
	private List<VariableData> children = new ArrayList<>(); 
	
	VariableData(String variableName, String className, String id) {
		this.variableName = variableName;
		this.className = className;
		this.id = id;
	}
	
	String getVariableName() {
		return variableName;
	}
	
	String getClassName() {
		return className;
	}
	
	String getId() {
		return id;
	}
	
	VariableData getParent() {
		return parent;
	}
	
	List<VariableData> getChildren() {
		return children;
	}
	
	void addChild(VariableData child) {
		children.add(child);
		child.parent = this;
	}
	
	@Override
	public String toString() {
		return variableName + ": " + className + "(" + "id = " + id + ")";
	}
}