package kr.ac.snu.selab.soot.analyzer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import kr.ac.snu.selab.soot.callgraph.CallGraph;
import kr.ac.snu.selab.soot.callgraph.SimpleCallGraph;
import kr.ac.snu.selab.soot.callgraph.SootCallGraph;
import kr.ac.snu.selab.soot.graph.DefaultHashMapGraph;
import kr.ac.snu.selab.soot.graph.Graph;
import kr.ac.snu.selab.soot.graph.GraphPathCollector;
import kr.ac.snu.selab.soot.graph.MyNode;
import kr.ac.snu.selab.soot.graph.Path;
import soot.Body;
import soot.Hierarchy;
import soot.SootClass;
import soot.SootField;
import soot.SootMethod;
import soot.Type;
import soot.Unit;
import soot.Value;
import soot.jimple.internal.JAssignStmt;
import soot.jimple.internal.JInvokeStmt;
public class Analysis {
protected List<SootClass> classList;
protected CallGraph callGraph;
protected HashMap<String, SootClass> classMap;
private HashMap<String, SootMethod> methodMap;
private HashMap<String, SootField> fieldMap;
protected Hierarchy hierarchy;
private Map<Map<SootClass, String>, SootMethod> methodMapBySubSignature;
public Analysis(List<SootClass> aClassList, Hierarchy aHierarchy,
CallGraph aGraph) {
classList = new ArrayList<SootClass>();
classMap = new HashMap<String, SootClass>();
methodMap = new HashMap<String, SootMethod>();
fieldMap = new HashMap<String, SootField>();
/*
* This is a map that has keys of (class, subsignature of method) pair.
*/
methodMapBySubSignature = new HashMap<Map<SootClass, String>, SootMethod>();
classList = aClassList;
for (SootClass aClass : classList) {
classMap.put(aClass.getName(), aClass);
for (SootField aField : aClass.getFields()) {
fieldMap.put(aField.toString(), aField);
}
for (SootMethod aMethod : aClass.getMethods()) {
methodMap.put(aMethod.toString(), aMethod);
Map<SootClass, String> key = new HashMap<SootClass, String>();
key.put(aClass, aMethod.getSubSignature());
methodMapBySubSignature.put(key, aMethod);
}
}
callGraph = aGraph;
hierarchy = aHierarchy;
}
public Analysis(List<SootClass> aClassList, Hierarchy aHierarchy,
boolean useSimpleCallGraph) {
classList = new ArrayList<SootClass>();
classMap = new HashMap<String, SootClass>();
methodMap = new HashMap<String, SootMethod>();
fieldMap = new HashMap<String, SootField>();
methodMapBySubSignature = new HashMap<Map<SootClass, String>, SootMethod>();
classList = aClassList;
for (SootClass aClass : classList) {
classMap.put(aClass.getName(), aClass);
for (SootField aField : aClass.getFields()) {
fieldMap.put(aField.toString(), aField);
}
for (SootMethod aMethod : aClass.getMethods()) {
methodMap.put(aMethod.toString(), aMethod);
Map<SootClass, String> key = new HashMap<SootClass, String>();
key.put(aClass, aMethod.getSubSignature());
methodMapBySubSignature.put(key, aMethod);
}
}
hierarchy = aHierarchy;
if (useSimpleCallGraph) {
callGraph = new SimpleCallGraph(classList, methodMap, aHierarchy);
} else {
callGraph = new SootCallGraph(classList);
}
}
protected List<Unit> getUnits(SootMethod aMethod) {
List<Unit> unitList = new ArrayList<Unit>();
if (aMethod.hasActiveBody()) {
Body body = aMethod.getActiveBody();
unitList.addAll(body.getUnits());
}
return unitList;
}
protected boolean isAbstractTypeClass(SootClass aClass) {
boolean result = false;
if (aClass.isInterface() || aClass.isAbstract()) {
result = true;
}
return result;
}
public List<SootClass> getAbstractTypeClassList() {
List<SootClass> result = new ArrayList<SootClass>();
for (SootClass aClass : classList) {
if (isAbstractTypeClass(aClass)) {
result.add(aClass);
}
}
return result;
}
private List<SootClass> getSubTypeClassOf(SootClass aType) {
if (aType.isInterface()) {
return hierarchy.getImplementersOf(aType);
} else {
return hierarchy.getSubclassesOf(aType);
}
}
public List<SootMethod> getOverrideMethodsOf(SootMethod aMethod) {
List<SootMethod> result = new ArrayList<SootMethod>();
SootClass receiverType = aMethod.getDeclaringClass();
List<SootClass> subTypeClassList = getSubTypeClassOf(receiverType);
if (!subTypeClassList.isEmpty()) {
for (SootClass aClass : subTypeClassList) {
Map<SootClass, String> key = new HashMap<SootClass, String>();
key.put(aClass, aMethod.getSubSignature());
SootMethod overrideMethod = methodMapBySubSignature.get(key);
if (overrideMethod != null) {
result.add(overrideMethod);
}
}
}
return result;
}
public List<SootClass> getSuperClassListExcludingInterface(SootClass aClass) {
List<SootClass> result = new ArrayList<SootClass>();
for (SootClass candidateClass : classList) {
if (!candidateClass.isInterface()) {
if (isClassOfSubType(aClass, candidateClass)) {
if (!aClass.equals(candidateClass)) {
result.add(candidateClass);
}
}
}
}
return result;
}
// <JInvokeStmt> interfaceinvoke temp$0.<State: void
// changeSpeed(CeilingFan)>(this)
// <UseBox> VB(temp$0)
// <Type> State
// <UseBox> VB(this)
// <Type> CeilingFan
// <UseBox> VB(interfaceinvoke temp$0.<State: void
// changeSpeed(CeilingFan)>(this))
// <Type> void
// <MethodRef> <State: void changeSpeed(CeilingFan)>
public boolean isInvokeStatementOfReceiverType(Unit aUnit, SootClass aType) {
boolean result = false;
if (aUnit instanceof JInvokeStmt) {
JInvokeStmt statement = (JInvokeStmt) aUnit;
SootClass receiver = statement.getInvokeExpr().getMethod()
.getDeclaringClass();
String key = receiver.toString();
SootClass receiverClass = null;
if (classMap.containsKey(key)) {
receiverClass = classMap.get(key);
}
if (receiverClass != null) {
if (receiverClass instanceof SootClass) {
if (receiverClass.equals(aType)) {
result = true;
}
}
}
}
return result;
}
public boolean isAssignmentStatementHasInvocationOfReceiverType(Unit aUnit,
SootClass aType) {
boolean result = false;
if (aUnit instanceof JAssignStmt) {
JAssignStmt statement = (JAssignStmt) aUnit;
if (statement.containsInvokeExpr()) {
SootClass receiver = statement.getInvokeExpr().getMethod()
.getDeclaringClass();
String key = receiver.toString();
SootClass receiverClass = null;
if (classMap.containsKey(key)) {
receiverClass = classMap.get(key);
}
if (receiverClass != null) {
if (receiverClass instanceof SootClass) {
if (receiverClass.equals(aType)) {
result = true;
}
}
}
}
}
return result;
}
public boolean isClassOfSubType(SootClass aClass, SootClass aType) {
boolean result = false;
if (aType.isInterface() && !(aClass.isInterface())) {
List<SootClass> implementerList = hierarchy
.getImplementersOf(aType);
result = implementerList.contains(aClass);
} else if (aType.isInterface() && aClass.isInterface()) {
if (hierarchy.isInterfaceSubinterfaceOf(aClass, aType)
|| aClass.equals(aType)) {
result = true;
}
} else if (!(aType.isInterface()) && !(aClass.isInterface())) {
result = hierarchy.isClassSubclassOfIncluding(aClass, aType);
}
return result;
}
public boolean isClassOfSubTypeExcluding(SootClass aClass, SootClass aType) {
boolean result = false;
if (aType.isInterface() && !(aClass.isInterface())) {
List<SootClass> implementerList = hierarchy
.getImplementersOf(aType);
result = implementerList.contains(aClass);
} else if (aType.isInterface() && aClass.isInterface()) {
if (hierarchy.isInterfaceSubinterfaceOf(aClass, aType)) {
result = true;
}
} else if (!(aType.isInterface()) && !(aClass.isInterface())) {
result = hierarchy.isClassSubclassOf(aClass, aType);
}
return result;
}
// <Creation Statement> temp$0 = new Off
// <DefBox> VB(temp$0)
// <UseBox> VB(new Off)
public boolean isInstanceCreationStatementOfSubType(Unit aUnit,
SootClass aType) {
boolean result = false;
if (aUnit instanceof JAssignStmt) {
String rightSide = aUnit.getUseBoxes().get(0).getValue().toString();
if (rightSide.matches("new .*")) {
String className = rightSide.substring(4);
SootClass createdClass = classMap.get(className);
if (createdClass != null) {
result = isClassOfSubType(createdClass, aType);
}
}
}
return result;
}
// private boolean isFieldOfType(SootField aField, SootClass aType) {
// boolean result = false;
// if (aType.getName().equals(aField.getType().toString())) {
// result = true;
// }
// return result;
// }
public boolean isFieldOfSubType(SootField aField, SootClass aType) {
boolean result = false;
String key = aField.getType().toString();
SootClass fieldClass = null;
if (classMap.containsKey(key)) {
fieldClass = classMap.get(key);
}
if (fieldClass != null) {
if (isClassOfSubType(fieldClass, aType)) {
result = true;
}
}
return result;
}
public String getReadFieldStringByThisStatement(Unit aUnit) {
String result = null;
if (aUnit instanceof JAssignStmt) {
JAssignStmt assignStmt = (JAssignStmt) aUnit;
String rightSideString = assignStmt.getRightOp().toString();
if (rightSideString.startsWith("this.")) {
rightSideString = rightSideString.substring(5);
}
// Only field variable
if (rightSideString.startsWith("<")) {
result = rightSideString;
}
}
return result;
}
public List<String> getReadFieldStringListByThisMethod(SootMethod aMethod) {
List<String> fieldStringList = new ArrayList<String>();
for (Unit aUnit : getUnits(aMethod)) {
String fieldString = getReadFieldStringByThisStatement(aUnit);
if (fieldString != null) {
fieldStringList.add(fieldString);
}
}
return fieldStringList;
}
public String getWrittenFieldStringByThisStatement(Unit aUnit,
SootClass aType) {
String result = null;
if (aUnit instanceof JAssignStmt) {
JAssignStmt assignStmt = (JAssignStmt) aUnit;
Value rightOp = assignStmt.getRightOp();
@SuppressWarnings("unused")
SootClass rightOpType = null;
String rightOpTypeKey = rightOp.getType().toString();
// if (!(rightOpTypeKey.startsWith("null"))) {
if (classMap.containsKey(rightOpTypeKey)) {
rightOpType = classMap.get(rightOpTypeKey);
// if ((rightOpType != null)
// && isClassOfSubType(rightOpType, aType)) {
String leftSideString = assignStmt.getLeftOp().toString();
if (leftSideString.startsWith("this.")) {
leftSideString = leftSideString.substring(5);
}
// Only field variable
if (leftSideString.startsWith("<")) {
result = leftSideString;
}
// }
}
// }
}
return result;
}
public List<String> getWrittenFieldStringListByThisMethod(
SootMethod aMethod, SootClass aType) {
List<String> fieldStringList = new ArrayList<String>();
for (Unit aUnit : getUnits(aMethod)) {
String fieldString = getWrittenFieldStringByThisStatement(aUnit,
aType);
if (fieldString != null) {
fieldStringList.add(fieldString);
}
}
return fieldStringList;
}
// public boolean isInjectorMethodOfField(SootMethod aMethod, SootField
// aField) {
// boolean result = false;
// Set<String> writtenFieldStringSet = new HashSet<String>();
// writtenFieldStringSet.addAll(getWrittenFieldStringListByThisMethod(aMethod));
//
// for (String fieldString : writtenFieldStringSet) {
// if (aField.toString().equals(fieldString)) {
// result = true;
// break;
// }
// }
// return result;
// }
private String getReturnTypeString(SootMethod aMethod) {
Type returnType = aMethod.getReturnType();
return returnType.toString();
}
public boolean doesThisMethodReturnObjectOfSubtype(SootMethod aMethod,
SootClass aType) {
boolean result = false;
String returnTypeString = getReturnTypeString(aMethod);
SootClass returnType = classMap.get(returnTypeString);
if (returnType != null) {
result = isClassOfSubType(returnType, aType);
}
return result;
}
private List<String> getParameterTypeStringList(SootMethod aMethod) {
@SuppressWarnings("unchecked")
List<Type> parameterTypeList = aMethod.getParameterTypes();
List<String> parameterTypeStringList = new ArrayList<String>();
for (Type aParameterType : parameterTypeList) {
parameterTypeStringList.add(aParameterType.toString());
}
return parameterTypeStringList;
}
public boolean doesThisMethodParameterOfSubtype(SootMethod aMethod,
SootClass aType) {
boolean result = false;
List<String> parameterTypeStringList = getParameterTypeStringList(aMethod);
List<SootClass> parameterTypeList = new ArrayList<SootClass>();
for (String aParameterTypeString : parameterTypeStringList) {
SootClass parameterType = null;
if (classMap.containsKey(aParameterTypeString)) {
parameterType = classMap.get(aParameterTypeString);
}
if (parameterType != null) {
parameterTypeList.add(parameterType);
}
}
for (SootClass aParameterType : parameterTypeList) {
if (isClassOfSubType(aParameterType, aType)) {
result = true;
return result;
}
}
return result;
}
public MethodAnalysisResult analyzeMethodOverType(SootMethod aMethod,
SootClass aType, HashMap<String, MyNode> nodeMap) {
MethodAnalysisResult result = new MethodAnalysisResult();
String aNodeKey = aMethod.toString();
if (nodeMap.containsKey(aNodeKey)) {
result.setMethod((MyMethod) nodeMap.get(aNodeKey));
}
result.setAbstractType(aType);
for (Unit aUnit : getUnits(aMethod)) {
if (isInstanceCreationStatementOfSubType(aUnit, aType)) {
result.getMethod().setIsCreator(true);
result.getMethod().addCreateStatement(aUnit);
}
if (isInvokeStatementOfReceiverType(aUnit, aType)) {
result.getMethod().setIsCaller(true);
result.getMethod().addCallStatement(aUnit);
} else if (isAssignmentStatementHasInvocationOfReceiverType(aUnit,
aType)) {
result.getMethod().setIsCaller(true);
result.getMethod().addCallStatement(aUnit);
}
}
HashSet<SootMethod> callerSet = new HashSet<SootMethod>();
for (MyNode aNode : callGraph.sourceNodes(new MyMethod(aMethod))) {
callerSet.add(((MyMethod) aNode).getMethod());
}
HashSet<SootMethod> calleeSet = new HashSet<SootMethod>();
for (MyNode aNode : callGraph.targetNodes(new MyMethod(aMethod))) {
calleeSet.add(((MyMethod) aNode).getMethod());
}
if (doesThisMethodParameterOfSubtype(aMethod, aType)) {
for (SootMethod callerMethod : callerSet) {
MyNode method = nodeMap.get(callerMethod.toString());
if (method != null) {
result.addSourceNode(method);
// result.targetNodes.add(method); // can make cycle
}
}
}
if (doesThisMethodReturnObjectOfSubtype(aMethod, aType)) {
for (SootMethod callerMethod : callerSet) {
MyNode method = nodeMap.get(callerMethod.toString());
if (method != null) {
result.addTargetNode(method);
}
}
}
for (SootMethod calleeMethod : calleeSet) {
if (doesThisMethodParameterOfSubtype(calleeMethod, aType)) {
MyNode method = nodeMap.get(calleeMethod.toString());
if (method != null) {
result.addTargetNode(method);
// result.sourceNodes.add(method); // can make cycle
}
}
// new ConcreteClass() should have return type of ConcreteClass
// But temp0 = new ConcreteClass
// temp0.<init>
// has void return type
// So it needs special care for creator method
if (isClassOfSubType(calleeMethod.getDeclaringClass(), aType)
&& calleeMethod.getName().equals("<init>")) {
MyNode method = nodeMap.get(calleeMethod.toString());
if (method != null) {
result.addSourceNode(method);
}
}
}
for (SootMethod calleeMethod : calleeSet) {
if (doesThisMethodReturnObjectOfSubtype(calleeMethod, aType)) {
MyNode method = nodeMap.get(calleeMethod.toString());
if (method != null) {
result.addSourceNode(method);
}
}
}
List<SootField> superClassFieldListOfAbstractType = new ArrayList<SootField>();
SootClass classOfThisMethod = aMethod.getDeclaringClass();
if (!classOfThisMethod.isInterface()) {
List<SootClass> superClassList = getSuperClassListExcludingInterface(classOfThisMethod);
for (SootClass aClass : superClassList) {
for (SootField aField : aClass.getFields()) {
if (isFieldOfSubType(aField, aType)) {
superClassFieldListOfAbstractType.add(aField);
}
}
}
}
List<String> readFieldStringList = getReadFieldStringListByThisMethod(aMethod);
for (String readFieldString : readFieldStringList) {
if (nodeMap.containsKey(readFieldString)) {
nodeMap.get(readFieldString).setIsStore(true);
result.addSourceNode(nodeMap.get(readFieldString));
} else {
for (SootField aSuperClassField : superClassFieldListOfAbstractType) {
String fieldKey = aSuperClassField.toString();
if (fieldKey.contains(readFieldString.replaceFirst("<.*:",
""))) {
if (nodeMap.containsKey(fieldKey)) {
nodeMap.get(fieldKey).setIsStore(true);
result.addSourceNode(nodeMap.get(fieldKey));
}
}
}
}
}
List<String> writtenFieldStringList = getWrittenFieldStringListByThisMethod(
aMethod, aType);
for (String writtenFieldString : writtenFieldStringList) {
if (nodeMap.containsKey(writtenFieldString)) {
nodeMap.get(writtenFieldString).setIsStore(true);
result.addTargetNode(nodeMap.get(writtenFieldString));
} else {
for (SootField aSuperClassField : superClassFieldListOfAbstractType) {
String fieldKey = aSuperClassField.toString();
if (fieldKey.contains(writtenFieldString.replaceFirst(
"<.*:", ""))) {
if (nodeMap.containsKey(fieldKey)) {
nodeMap.get(fieldKey).setIsStore(true);
result.addTargetNode(nodeMap.get(fieldKey));
}
}
}
}
}
return result;
}
public Graph<MyNode> getGraphFromMethodAnalysisResultList(
List<MethodAnalysisResult> resultList) {
DefaultHashMapGraph<MyNode> graph = new DefaultHashMapGraph<MyNode>();
HashMap<String, HashSet<MyNode>> sourceMap = graph.getSourceMap();
HashMap<String, HashSet<MyNode>> targetMap = graph.getTargetMap();
for (MethodAnalysisResult result : resultList) {
MyNode selfNode = result.getMethod();
for (MyNode sourceNode : result.getSourceNodes()) {
// add mapping information to targetMap
String key = sourceNode.toString();
if (targetMap.containsKey(key)) {
targetMap.get(key).add(selfNode);
} else {
HashSet<MyNode> newSet = new HashSet<MyNode>();
newSet.add(selfNode);
targetMap.put(key, newSet);
}
// add mapping information to sourceMap
key = selfNode.toString();
if (sourceMap.containsKey(key)) {
sourceMap.get(key).add(sourceNode);
} else {
HashSet<MyNode> newSet = new HashSet<MyNode>();
newSet.add(sourceNode);
sourceMap.put(key, newSet);
}
}
for (MyNode targetNode : result.getTargetNodes()) {
// add mapping information to sourceMap
String key = targetNode.toString();
if (sourceMap.containsKey(key)) {
sourceMap.get(key).add(selfNode);
} else {
HashSet<MyNode> newSet = new HashSet<MyNode>();
newSet.add(selfNode);
sourceMap.put(key, newSet);
}
// add mapping information to targetMap
key = selfNode.toString();
if (targetMap.containsKey(key)) {
targetMap.get(key).add(targetNode);
} else {
HashSet<MyNode> newSet = new HashSet<MyNode>();
newSet.add(targetNode);
targetMap.put(key, newSet);
}
}
}
return graph;
}
public AnalysisResult analyzeOverType(SootClass aType) {
AnalysisResult anAnalysisResult = new AnalysisResult();
List<MethodAnalysisResult> methodAnalysisResultList = new ArrayList<MethodAnalysisResult>();
HashMap<String, MyNode> nodeMap = new HashMap<String, MyNode>();
for (SootClass aClass : classList) {
for (SootField aField : aClass.getFields()) {
// We only consider fields of the abstract type.
if (aField.getType().toString().equals(aType.toString())) {
nodeMap.put(aField.toString(), new MyField(aField));
}
}
for (SootMethod aMethod : aClass.getMethods()) {
nodeMap.put(aMethod.toString(), new MyMethod(aMethod));
}
}
anAnalysisResult.setAbstractType(aType);
for (SootClass aClass : classList) {
for (SootMethod aMethod : aClass.getMethods()) {
methodAnalysisResultList.add(analyzeMethodOverType(aMethod,
aType, nodeMap));
}
}
for (MethodAnalysisResult aResult : methodAnalysisResultList) {
MyNode node = aResult.getMethod();
if (node.isCaller()) {
anAnalysisResult.addCaller(node);
}
if (node.isCreator()) {
anAnalysisResult.addCreator(node);
}
}
Graph<MyNode> referenceFlowGraph = getGraphFromMethodAnalysisResultList(methodAnalysisResultList);
// graphXML = graphXML + referenceFlowGraph.toXML();
for (MyNode callerNode : anAnalysisResult.getCallers()) {
GraphPathCollector<MyNode> pathCollector = new GraphPathCollector<MyNode>(
callerNode, referenceFlowGraph) {
@Override
protected boolean isGoal(MyNode aNode) {
boolean result = false;
if (graph.sourceNodes(aNode).isEmpty()
|| hitSet.contains(aNode.toString())) {
result = true;
}
// result = aNode.isCreator(); // &&
// (graph.sourceNodes(aNode)).isEmpty();
return result;
}
};
List<Path<MyNode>> pathList = pathCollector.run();
List<Path<MyNode>> pathIncludeStoreList = new ArrayList<Path<MyNode>>();
for (Path<MyNode> aPath : pathList) {
boolean doesPathIncludeStore = false;
for (MyNode aNode : aPath.getNodeList()) {
if (aNode.isStore()) {
doesPathIncludeStore = true;
break;
}
}
if (doesPathIncludeStore) {
pathIncludeStoreList.add(aPath);
}
}
if (!pathIncludeStoreList.isEmpty()) {
anAnalysisResult.putReferenceFlowPath(callerNode.key(),
pathIncludeStoreList);
}
}
// for (Entry<String, List<MyPath>> anEntry :
// anAnalysisResult.referenceFlowPathMap
// .entrySet()) {
// for (MyPath aPath : anEntry.getValue()) {
// MyNode creator = aPath.last();
// TriggerPathCollector triggerPathCollector = new TriggerPathCollector(
// creator, callGraph, aType, this);
// List<MyPath> triggerPathList = triggerPathCollector.run();
//
// if (!triggerPathList.isEmpty()) {
// anAnalysisResult.creatorTriggerPathMap.put(aPath,
// triggerPathList);
// }
// }
// }
return anAnalysisResult;
}
public List<AnalysisResult> analyzeOverAllAbstractTypes() {
List<AnalysisResult> analysisResultList = new ArrayList<AnalysisResult>();
List<SootClass> abstractTypeList = getAbstractTypeClassList();
// String graphXML = "<GraphList>";
for (SootClass aType : abstractTypeList) {
if (!aType.toString().equals("org.jhotdraw.framework.Figure")) {
analysisResultList.add(analyzeOverType(aType));
}
}
// graphXML = graphXML + "</GraphList>";
// MyUtil.stringToFile(graphXML,
// "/Users/chanwoo/Documents/workspace/StatePatternExample2/output/StatePatternExample2_ReferenceFlowGraph.xml");
return analysisResultList;
}
}