2018-tuos-adb-ug-02b-datafl.../com.logicalhacking.dasca.da.../src/main/java/com/logicalhacking/dasca/dataflow/util/SuperGraphUtil.java

903 lines
40 KiB
Java

/*
* (C) Copyright 2010-2015 SAP SE.
* 2016 The University of Sheffield.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
*/
package com.logicalhacking.dasca.dataflow.util;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import org.apache.log4j.Logger;
import com.ibm.wala.dataflow.IFDS.ICFGSupergraph;
import com.ibm.wala.ipa.cfg.BasicBlockInContext;
import com.ibm.wala.ssa.IR;
import com.ibm.wala.ssa.SSABinaryOpInstruction;
import com.ibm.wala.ssa.SSAConditionalBranchInstruction;
import com.ibm.wala.ssa.SSAGotoInstruction;
import com.ibm.wala.ssa.SSAInstruction;
import com.ibm.wala.ssa.SSAInvokeInstruction;
import com.ibm.wala.ssa.SymbolTable;
import com.ibm.wala.ssa.analysis.IExplodedBasicBlock;
import com.ibm.wala.types.MethodReference;
import cvc3.Expr;
import cvc3.SatResult;
import cvc3.ValidityChecker;
/**
* static class for SG helper methods
*
*/
public class SuperGraphUtil {
private static final Logger log = AnalysisUtil.getLogger(SuperGraphUtil.class);
/**
* Adds every nodes ID to iDs, iff it is on a path between the entry and exit point. Uses recursive DFS
* @param iDs
* @param sg
* @param sgNodes
* @param sgNodesReverse
* @param currentId
* @param endId
*/
private static void relevantPathSearch(
HashSet<Integer> iDs,
ICFGSupergraph sg,
HashMap<Integer, BasicBlockInContext<IExplodedBasicBlock>> sgNodes,
HashMap<BasicBlockInContext<IExplodedBasicBlock>, Integer> sgNodesReverse,
ArrayList<MethodReference> acceptedMethods,
int currentId,
int endId) {
if(iDs.contains(currentId)) {
return;
}
iDs.add(currentId);
if(currentId == endId) {
return;
}
BasicBlockInContext<IExplodedBasicBlock> bbic = sgNodes.get(currentId);
boolean isInvoke = bbic.getLastInstruction() instanceof SSAInvokeInstruction;
boolean isStringConcat = isInvoke && bbic.getLastInstruction().toString().contains("StringBuilder") && (bbic.getLastInstruction().toString().contains("append") | bbic.getLastInstruction().toString().contains("toString"));
Iterator<BasicBlockInContext<IExplodedBasicBlock>> sucIt = sg.getSuccNodes(bbic);
while(sucIt.hasNext()) {
BasicBlockInContext<IExplodedBasicBlock> nextChild = sucIt.next();
if(isStringConcat) {
if(!sucIt.hasNext()) { // last association of a String concatenation is java intern
continue;
}
}
MethodReference method = nextChild.getMethod().getReference();
if(isInvoke) {
if(!acceptedMethods.contains(method)) {
acceptedMethods.add(method);
}
} else {
if(!acceptedMethods.contains(method)) {
log.debug("supergraph cut at '" + bbic.getNumber() + " -> " + nextChild.getNumber() + " ("
+ nextChild.toString() + ")'");
continue;
}
}
IExplodedBasicBlock del = nextChild.getDelegate();
if(del.isEntryBlock() && del.toString().contains("init")) {
log.debug("supergraph cut at '" + bbic.getNumber() + " -> " + nextChild.getNumber() + " (" + nextChild.toString() + ")'");
continue;
}
relevantPathSearch(iDs, sg, sgNodes, sgNodesReverse, acceptedMethods, sgNodesReverse.get(nextChild), endId);
}
}
/**
* Analyzes the supergraph and saves the relevant part as a dot file to the location &lt;graphs folder&gt;/<entryClass/
* @return
*/
public static int analyzeAndSaveSuperGraph(ICFGSupergraph sg, String entryClass, String entryMethod) {
int weaknessCount = 0;
if (sg == null) {
throw new IllegalArgumentException("sg was null for entry Class \""+entryClass+"\" and entry method \""+entryMethod+"\"");
}
log.info("--- start analyzing " + entryClass + "." + entryMethod + " ---");
ValidityChecker vc = ValidityChecker.create();
vc.push();
Iterator<BasicBlockInContext<IExplodedBasicBlock>> completeIterator = sg.iterator();
int analysisLevel = AnalysisUtil.getPropertyInteger(AnalysisUtil.CONFIG_ANALYSIS_DEPTH);
HashMap<Integer, BasicBlockInContext<IExplodedBasicBlock>> sgNodes = new HashMap<Integer, BasicBlockInContext<IExplodedBasicBlock>>();
HashMap<BasicBlockInContext<IExplodedBasicBlock>, Integer> sgNodesReversed = new HashMap<BasicBlockInContext<IExplodedBasicBlock>, Integer>();
HashMap<SSAInstructionKey, Integer> sgNodesInstId = new HashMap<SSAInstructionKey, Integer>();
ArrayList<MethodReference> acceptedMethods = new ArrayList<MethodReference>();
// Add all blocks into a Map with a unique identifier in both directions and find entry/exit node
int i = 0;
int mainEntryId = 0;
int mainExitId = 0;
while (completeIterator.hasNext()) {
BasicBlockInContext<IExplodedBasicBlock> current = completeIterator.next();
sgNodes.put(i, current);
log.debug("sgNodes: insert node "+i+" ==> "+current);
sgNodesReversed.put(current, i);
Iterator<SSAInstruction> instIt = current.iterator();
while (instIt.hasNext()) {
SSAInstruction inst = instIt.next();
sgNodesInstId.put(new SSAInstructionKey(inst), i);
log.debug(" sgNodesInstId: insert node "+i+" ==> "+inst);
/* add to include other required methods into CFG
if(inst instanceof AstJavaInvokeInstruction){
AstJavaInvokeInstruction in = (AstJavaInvokeInstruction) inst;
MethodReference meth = in.getDeclaredTarget();
if(meth.toString().contains("calledMethod")){ // check for required method
acceptedMethods.add(meth);
}
}
//*/
}
String signature = current.getMethod().getSignature();
// find entry and exit nodes
if(signature.contains(entryClass) && signature.contains(entryMethod) && current.isEntryBlock()) { // FIXME: entry/exit nodes definition via name is too weak
mainEntryId = i;
log.error("Found Entry Block "+i+"("+entryClass+" / "+entryMethod+":");
log.error(" "+signature);
} else if(signature.contains(entryClass) && signature.contains(entryMethod) && current.isExitBlock()) {
mainExitId = i;
log.error("Found Exit Block "+i+"("+entryClass+" / "+entryMethod+":");
log.error(" "+signature);
}
i++;
}
if(mainEntryId == 0 && mainExitId == 0) {
log.error(" "+entryClass + "." + entryMethod +
": empty entry method, ensure invocation in main method " +
"(mainEntryId = " +mainEntryId + " / mainExitId = " + mainExitId + ")");
return -1;
}
HashSet<Integer> relevantIDs = new HashSet<Integer>();
BasicBlockInContext<IExplodedBasicBlock> bbic = sgNodes.get(mainEntryId);
acceptedMethods.add(bbic.getMethod().getReference());
log.error(" "+entryClass + "." + entryMethod +
"start recursive graph building");
relevantPathSearch(relevantIDs, sg, sgNodes, sgNodesReversed, acceptedMethods, mainEntryId, mainExitId);
// remove irrelevant nodes (not on at least one path between entry and exit node)
log.debug("remove irrelevant nodes");
for (int j = 0; j < i; j++) {
BasicBlockInContext<IExplodedBasicBlock> tmp = sgNodes.get(j);
if(!relevantIDs.contains(j)) {
sgNodesReversed.remove(tmp);
sgNodes.remove(j);
log.debug(" removing node: "+j);
}
}
// build separate adjacency list
log.debug(" "+entryClass + "." + entryMethod +
"build seperate adjacency list");
HashMap<Integer, ArrayList<Integer>> adjList = new HashMap<Integer, ArrayList<Integer>>();
HashMap<Integer, ArrayList<Integer>> adjListReverse = new HashMap<Integer, ArrayList<Integer>>();
boolean removeEmptyNodes = AnalysisUtil.getPropertyBoolean(AnalysisUtil.CONFIG_DOT_REMOVE_EMPTY_NODES);
ArrayList<Integer> emptyNodes = new ArrayList<Integer>();
buildAdjacencyLists(sg, sgNodes, sgNodesReversed, adjList, adjListReverse, emptyNodes, relevantIDs);
// <<< print original adjacency list to log
log.debug("adjacency list before removing empty nodes:");
AnalysisUtil.printAdjList(adjList, log);
//
if(removeEmptyNodes) {
removeEmptyNodes(emptyNodes, adjList, adjListReverse, sgNodes, sgNodesReversed);
// <<< print adjacency list to log
log.debug("adjacency after removing empty nodes:");
AnalysisUtil.printAdjList(adjList, log);
//
}
log.debug(" "+entryClass + "." + entryMethod +
"add conditions to graph nodes");
ArrayList<Integer> visited = new ArrayList<Integer>();
ArrayList<Integer> currentConditions = new ArrayList<Integer>();
HashMap<Integer, Integer> currentConditionsEndId = new HashMap<Integer, Integer>();
HashMap<Integer, ArrayList<Integer>> finalConditions = new HashMap<Integer, ArrayList<Integer>>();
HashMap<Integer, Integer> loops = new HashMap<Integer, Integer>();
addConditionsToGraph(sgNodes, adjList, mainEntryId, visited, mainExitId, currentConditions, currentConditionsEndId, finalConditions, loops);
// remove goto statements for later analysis steps
for (int loopId : loops.keySet()) {
int gotoId = loops.get(loopId);
int gotoTargetId = adjList.get(gotoId).get(0); // goto has exact one child
int afterLoopId = 0;
for(int id : adjList.get(loopId)) {
afterLoopId = Math.max(afterLoopId, id);
}
ArrayList<Integer> newList = new ArrayList<Integer>();
newList.add(afterLoopId);
adjList.put(gotoId, newList);
adjListReverse.get(afterLoopId).add(gotoId);
ArrayList<Integer> gotoTargetReverseList = adjListReverse.get(gotoTargetId);
ArrayList<Integer> newGotoTargetReverseList = new ArrayList<Integer>();
for (int child : gotoTargetReverseList) {
if(child != gotoId) {
newGotoTargetReverseList.add(child);
}
}
adjListReverse.put(gotoTargetId, newGotoTargetReverseList);
}
/* <<< print required conditions for each node
for (Integer key : finalConditions.keySet()) {
StringBuffer sb = new StringBuffer();
for(int condition : finalConditions.get(key)){
sb.append(", " + condition);
}sb.append(" ");
log.debug(key + ": " + sb.substring(2));
}
//*/
/* <<< print changed adjacency list to log
log.debug("adjacency list after removing empty nodes:");
AnalysisUtil.printAdjList(adjList, log);
//*/
//* <<< get definition instruction for each SSA value
HashMap<Integer, SSAInstruction> definitions = AnalysisUtil.getDefs(sgNodes);
//*/
//* <<< get source code line number for each instruction
HashMap<SSAInstructionKey, Integer> lineNumbers = AnalysisUtil.getLineNumbers(sgNodes);
//*/
//* <<< get the corresponding instruction for each condition inside the callgraph
ArrayList<SSAInstruction> conditionsList = AnalysisUtil.getConditions(sgNodes);
ArrayList<Integer> conditionsIdList = new ArrayList<Integer>();
for (SSAInstruction cond : conditionsList) {
conditionsIdList.add(sgNodesInstId.get(new SSAInstructionKey(cond)));
}
//*/
//* <<< get the corresponding source code line number for each condition (node id) inside the callgraph
HashMap<Integer, Integer> conditionLineNumber = new HashMap<Integer, Integer>();
for (SSAInstruction instCondition : conditionsList) {
int nodeId = sgNodesInstId.get(new SSAInstructionKey(instCondition));
int lineNumber = lineNumbers.get(new SSAInstructionKey(instCondition));
conditionLineNumber.put(nodeId, lineNumber);
}
//*/
//* <<< build CVC3 expressions from conditional instruction
log.debug("** get CVC3 expressions for each conditional branch instruction");
HashMap<Integer, Expr> expressions = new HashMap<Integer, Expr>();
IR entryIR = sgNodes.values().iterator().next().getNode().getIR();
vc.pop();
vc.push();
for (SSAInstruction instCondition : conditionsList) {
int condId = sgNodesInstId.get(new SSAInstructionKey(instCondition));
if(loops.keySet().contains(condId)) {
Expr expr = SMTChecker.getExprForLoop(vc , instCondition, entryIR);
expressions.put(sgNodesInstId.get(new SSAInstructionKey(instCondition)), expr);
} else {
Expr expr = SMTChecker.getExprForConditionalBranchInstruction(vc , instCondition, entryIR);
expressions.put(sgNodesInstId.get(new SSAInstructionKey(instCondition)), expr);
}
vc.pop();
vc.push();
}
vc.pop();
vc.push();
//*/
ArrayList<SSAInstruction> sqlExecutes = AnalysisUtil.getSQLExecutes(sgNodes);
log.debug("** get source/sink pairs for each SQL instruction");
String sanitizerMethods = AnalysisUtil.getPropertyString(AnalysisUtil.CONFIG_SANITIZER); // good sources are specified as sanitizer
String[] methods = sanitizerMethods.split(",");
HashSet<String> sanitizer = new HashSet<String>();
for (int k = 0; k < methods.length; k++) {
String sanitizerMethod = methods[k].trim();
sanitizer.add(sanitizerMethod);
}
String badSourceMethods = AnalysisUtil.getPropertyString(AnalysisUtil.CONFIG_BAD_SRC);
methods = badSourceMethods.split(",");
HashSet<String> badMethods = new HashSet<String>();
for (int k = 0; k < methods.length; k++) {
String badMethod = methods[k].trim() + "()";
badMethods.add(badMethod);
}
//* <<< get possible vulnerabilities for each SQL execute
HashMap<SSAInstructionKey, ArrayList<SSAInstruction>> sinkSources = new HashMap<SSAInstructionKey, ArrayList<SSAInstruction>>();
for (SSAInstruction ssaInstruction : sqlExecutes) {
boolean isPreparedStmt = ssaInstruction.toString().contains("Prepared");
sinkSources.put(new SSAInstructionKey(ssaInstruction), AnalysisUtil.analyzeStatementExecute(ssaInstruction, definitions, isPreparedStmt, badMethods));
}
boolean containsVulnerability = false;
for (SSAInstruction sink : sqlExecutes) {
if(!sinkSources.containsKey(new SSAInstructionKey(sink))) { // no vulnerability possible
continue;
}
ArrayList<SSAInstruction> badSources = sinkSources.get(new SSAInstructionKey(sink));
if(badSources != null) {
for (SSAInstruction source : badSources) {
boolean isNotMutuallyExclusive = true;
if(analysisLevel >= AnalysisUtil.ANALYSIS_DEPTH_EXCLUSIVE) {
isNotMutuallyExclusive = isNotMutuallyExclusive(sink, source, sgNodesInstId, finalConditions, expressions, vc);
}
if(isNotMutuallyExclusive) {
boolean isNotSanitized = true;
if(analysisLevel >= AnalysisUtil.ANALYSIS_DEPTH_SANITIZING) {
log.debug("Calling isNotSanitized:");
log.debug(" Source (" + sgNodesInstId.get(new SSAInstructionKey(source)) + "): " + source);
log.debug(" Sink (" + sgNodesInstId.get(new SSAInstructionKey(sink)) + "): " + sink);
isNotSanitized = isNotSanitized(sgNodesInstId.get(new SSAInstructionKey(source)), sgNodesInstId.get(new SSAInstructionKey(sink)), adjList, finalConditions, expressions, vc, conditionsIdList, sanitizer, sgNodes);
}
if(isNotSanitized) {
weaknessCount++;
containsVulnerability = true;
log.warn("SQL execute [" + lineNumbers.get(new SSAInstructionKey(sink)) + "] with bad source readLine [" + lineNumbers.get(new SSAInstructionKey(source)) + "] (" + entryClass + "." + entryMethod + ")");
}
}
}
}
}
if(!containsVulnerability) {
log.info("(" + entryClass + "." + entryMethod + ") is save");
}
//*/
String filePath = String.format("%s_SG.dot", entryMethod);
generateDotFile(sgNodes, adjList, entryClass, filePath, finalConditions);
return weaknessCount;
}
/**
* Computes all possible control flows between source and sink and checks, if every single path is sanitized.
* @return true, iff there exists at least one direct unsanitized path between source and sink
*/
private static boolean isNotSanitized(int sourceId,
int sinkId, HashMap<Integer, ArrayList<Integer>> adjList,
HashMap<Integer, ArrayList<Integer>> finalConditions,
HashMap<Integer, Expr> expressions,
ValidityChecker vc,
ArrayList<Integer> conditionsIdList,
HashSet<String> sanitizer,
HashMap<Integer,BasicBlockInContext<IExplodedBasicBlock>> sgNodes) {
log.debug("** get possible control flows from source to sink");
HashMap<Integer, HashSet<ArrayList<Integer>>> paths = new HashMap<Integer, HashSet<ArrayList<Integer>>>();
calculatePaths(sourceId, sinkId, adjList, paths);
HashSet<ArrayList<Integer>> pathList = paths.get(sourceId);
HashSet<ArrayList<Integer>> possibleFlowPaths = new HashSet<ArrayList<Integer>>();
for (ArrayList<Integer> path : pathList) {
boolean isPossibleFlow = isPossibleFlow(path, finalConditions, expressions, vc, conditionsIdList);
StringBuffer sb = new StringBuffer();
for (int i = 0; i < path.size(); i++) {
sb.append(" -> " + path.get(i));
}
sb.append(" ");
if(isPossibleFlow) {
log.debug("FLOW [possible]: " + sb.substring(3)); // cut off the last arrow
possibleFlowPaths.add(path);
} else {
log.debug("FLOW [impossible]: " + sb.substring(3));
}
}
loopPossiblePaths:
for (ArrayList<Integer> possibleFlowPath : possibleFlowPaths) {
boolean containsSanitizer = false;
for (String sanitizerMethod : sanitizer) {
for(int nodeId : possibleFlowPath) {
BasicBlockInContext<IExplodedBasicBlock> node = sgNodes.get(nodeId);
SSAInstruction inst = node.getLastInstruction();
if(inst != null) {
if(inst.toString().contains(sanitizerMethod)) {
containsSanitizer = true;
continue loopPossiblePaths;
}
}
}
}
if(!containsSanitizer) {
return true; // path contains no sanitizer => is direct path between bad source and bad sink
}
}
return false; // every possible path contained at least one sanitizer
}
private static boolean isPossibleFlow(ArrayList<Integer> path,
HashMap<Integer, ArrayList<Integer>> finalConditions,
HashMap<Integer, Expr> expressions,
ValidityChecker vc,
ArrayList<Integer> conditionsIdList) {
vc.pop();
vc.push();
List<Expr> allExpr = new ArrayList<Expr>();
ArrayList<Integer> allConditions = new ArrayList<Integer>();
for (int i=0; i<path.size(); i++) {
int node = path.get(i);
ArrayList<Integer> conditions = finalConditions.get(node);
if(conditionsIdList.contains(node)) {
ArrayList<Integer> childConditions = finalConditions.get(path.get(i+1).intValue());
if(childConditions == null || childConditions.isEmpty() || !(childConditions.contains(node) | childConditions.contains(-node))) { // jump over if-block without else part => else condition needs to be set
allExpr.add(vc.notExpr(expressions.get(node)));
}
}
if(conditions != null) {
for (int condId : conditions) {
if(!allConditions.contains(condId)) {
allConditions.add(condId);
if(condId < 0) {
allExpr.add(vc.notExpr(expressions.get(Math.abs(condId))));
} else {
allExpr.add(expressions.get(condId));
}
}
}
}
}
if(allExpr.isEmpty()) {
return true;
}
Expr completeExpr = vc.andExpr(allExpr);
SatResult satResult = vc.checkUnsat(completeExpr);
boolean satisfiable = satResult.equals(SatResult.SATISFIABLE);
log.info(" isPossibleFlow: checking expression [" + (satisfiable?"SAT":"UNSAT") + "]: " + completeExpr);
return satisfiable;
}
private static void calculatePaths(int currentId, int target,
HashMap<Integer, ArrayList<Integer>> adjList,
HashMap<Integer, HashSet<ArrayList<Integer>>> paths) {
log.debug(" searching for "+currentId+" in adjList.");
ArrayList<Integer> children = adjList.get(currentId);
if (null == children) {
throw new RuntimeException ("No entry found in adjList for "+currentId);
}
HashSet<ArrayList<Integer>> currentPaths = new HashSet<ArrayList<Integer>>();
for(int child : children) {
if(child == target) {
ArrayList<Integer> last = new ArrayList<Integer>();
last.add(child);
currentPaths.add(last);
continue;
}
calculatePaths(child, target, adjList, paths);
HashSet<ArrayList<Integer>> childPaths = paths.get(child);
if(childPaths!= null) {
for (ArrayList<Integer> childPath : childPaths) {
ArrayList<Integer> newPath = new ArrayList<Integer>();
newPath.add(child);
newPath.addAll(childPath);
currentPaths.add(newPath);
}
}
}
paths.put(currentId, currentPaths);
}
/**
* Checks, if sink and source node are mutually excluded because of the respective conditions
* @param sink
* @param source
* @param sgNodesInstId
* @param finalConditions
* @param expressions
* @param vc
* @return
*/
private static boolean isNotMutuallyExclusive(SSAInstruction sink,
SSAInstruction source,
HashMap<SSAInstructionKey, Integer> sgNodesInstId,
HashMap<Integer,ArrayList<Integer>> finalConditions,
HashMap<Integer, Expr> expressions, ValidityChecker vc) {
vc.pop();
vc.push();
int sourceId = sgNodesInstId.get(new SSAInstructionKey(source));
int sinkId = sgNodesInstId.get(new SSAInstructionKey(sink));
ArrayList<Integer> combinedConditions = new ArrayList<Integer>();
if(finalConditions.containsKey(sourceId)) {
combinedConditions.addAll(finalConditions.get(sourceId));
}
if(finalConditions.containsKey(sinkId)) {
combinedConditions.addAll(finalConditions.get(sinkId));
}
Expr expr = getConditionExpression(combinedConditions, expressions, vc);
vc.pop();
vc.push();
if(expr.toString().equalsIgnoreCase("null")) {
log.debug(" isNotMutuallyExclusive: EXPR: "+expr+" is NULL (combinedConditions: "
+ combinedConditions + "/ expressions: " + expressions +")");
return true;
}
SatResult satResult = vc.checkUnsat(expr);
boolean satisfiable = satResult.equals(SatResult.SATISFIABLE);
log.info(" isNotMutuallyExclusive: checking expression [" + (satisfiable?"SAT":"UNSAT") + "]: " + expr);
return satisfiable;
}
private static Expr getConditionExpression(ArrayList<Integer> nodeIds, HashMap<Integer, Expr> expressions, ValidityChecker vc) {
if(nodeIds.isEmpty()) {
return vc.nullExpr();
}
List<Expr> exprList = new ArrayList<Expr>();
for (int i=0; i<nodeIds.size(); i++) {
Integer conditionId = nodeIds.get(i);
Expr tmpExpr = expressions.get(Math.abs(conditionId));
log.debug(" expressions["+conditionId+"] = "+tmpExpr);
if(conditionId<0) {
tmpExpr = vc.notExpr(tmpExpr);
}
exprList.add(tmpExpr);
}
return vc.andExpr(exprList);
}
private static void removeEmptyNodes(ArrayList<Integer> emptyNodes,
HashMap<Integer, ArrayList<Integer>> adjList,
HashMap<Integer, ArrayList<Integer>> adjListReverse, HashMap<Integer,BasicBlockInContext<IExplodedBasicBlock>> sgNodes, HashMap<BasicBlockInContext<IExplodedBasicBlock>,Integer> sgNodesReversed) {
Collections.sort(emptyNodes);
StringBuffer sb = new StringBuffer();
for (int integer : emptyNodes) {
sb.append(", " + integer);
}
sb.append(" ");
log.debug("empty nodes in supergraph:" + sb.substring(1));
// remove empty nodes from supergraph
log.debug("remove empty nodes from supergraph");
for (Integer kill: emptyNodes) {
ArrayList<Integer> children = adjList.get(kill);
if(children == null) {
children = new ArrayList<Integer>();
}
// insert new edges around removed nodes
for(Integer father : adjListReverse.get(kill)) {
ArrayList<Integer> tmpList = adjList.get(father);
if(tmpList==null) {
tmpList = new ArrayList<Integer>();
}
tmpList.remove(kill);
tmpList.addAll(children);
for (Integer childId : children) {
ArrayList<Integer> childFathers = adjListReverse.get(childId);
if(childFathers==null) {
childFathers = new ArrayList<Integer>();
}
childFathers.remove(kill);
childFathers.add(father);
adjListReverse.put(childId, childFathers);
}
adjList.put(father, tmpList);
}
}
for (Integer kill: emptyNodes) { // requires two loops to build the new edges correct
adjList.remove(kill);
sgNodesReversed.remove(sgNodes.get(kill));
sgNodes.remove(kill);
}
}
private static void buildAdjacencyLists(
ICFGSupergraph sg, HashMap<Integer, BasicBlockInContext<IExplodedBasicBlock>> sgNodes,
HashMap<BasicBlockInContext<IExplodedBasicBlock>, Integer> sgNodesReversed,
HashMap<Integer, ArrayList<Integer>> adjList,
HashMap<Integer, ArrayList<Integer>> adjListReverse,
ArrayList<Integer> emptyNodes, HashSet<Integer> relevantIDs) {
for (int key1 : sgNodes.keySet()) {
BasicBlockInContext<IExplodedBasicBlock> val1 = sgNodes.get(key1);
Iterator<BasicBlockInContext<IExplodedBasicBlock>> sucIt = sg.getSuccNodes(val1);
// add 'key1->sucId' to adjList
ArrayList<Integer> list = adjList.get(key1);
if(list == null) {
list = new ArrayList<Integer>();
}
while (sucIt.hasNext()) {
BasicBlockInContext<IExplodedBasicBlock> suc = sucIt.next();
if(!sgNodesReversed.containsKey(suc)) {
continue;
}
int sucId = sgNodesReversed.get(suc);
if(!list.contains(sucId)) {
list.add(sucId);
}
// add 'sucId->key1' to adjListReverse
ArrayList<Integer> listReverse = adjListReverse.get(sucId);
if(listReverse == null) {
listReverse = new ArrayList<Integer>();
}
if(!listReverse.contains(key1)) {
listReverse.add(key1);
}
adjListReverse.put(sucId, listReverse);
// add empty nodes to list
Iterator<SSAInstruction> it = suc.iterator();
if(relevantIDs.contains(sucId)) {
if(!emptyNodes.contains(sucId)) {
if(!it.hasNext() && !suc.isEntryBlock() && !suc.isExitBlock() && !suc.isCatchBlock()) {
emptyNodes.add(sucId);
}
}
}
}
adjList.put(key1, list);
}
}
/**
* Analyzes the given nodes of the supergraph and fills the list finalConditions
* @param sgNodes
* @param adjList
* @param currentId
* @param visited
* @param mainEndId
* @param currentConditions
* @param currentConditionsEndId
* @param finalConditions
* @param loops
* @return
*/
private static boolean addConditionsToGraph( // boolean return is used in recursion to signalize the existence of an else block
HashMap<Integer, BasicBlockInContext<IExplodedBasicBlock>> sgNodes,
HashMap<Integer, ArrayList<Integer>> adjList,
Integer currentId,
ArrayList<Integer> visited,
Integer mainEndId,
ArrayList<Integer> currentConditions,
HashMap<Integer, Integer> currentConditionsEndId,
HashMap<Integer, ArrayList<Integer>> finalConditions,
HashMap<Integer, Integer> loops) {
if(!visited.contains(currentId)) {
visited.add(currentId);
}
if(currentId == mainEndId) {
finalConditions.put(mainEndId, new ArrayList<Integer>());
return false;
}
SSAInstruction currentInstruction = sgNodes.get(currentId).getLastInstruction();
ArrayList<Integer> children = adjList.get(currentId);
Integer ifStart = 0;
Integer ifEnd = 0;
if(!currentConditions.isEmpty()) { // inside an if-block
ifStart = currentConditions.get(currentConditions.size()-1); // last condition is the most inner condition
ifEnd = currentConditionsEndId.get(ifStart);
if(currentId > Math.abs(ifEnd) && sgNodes.get(currentId).isCatchBlock()) { // most inner if block has else AND current node is join
return false;
}
if(currentId > Math.abs(ifEnd) && Math.abs(ifEnd) != mainEndId) { // most inner if block has else AND current node is join
currentConditionsEndId.put(ifStart,currentId);
return true;
}
if(currentId.intValue() == ifEnd.intValue()) { // end of single if block
return false;
}
if(currentId == (-ifEnd)) { // end of else block
ArrayList<Integer> tmpCond = new ArrayList<Integer>(currentConditions);
HashMap<Integer, Integer> tmpCondEnd = new HashMap<Integer, Integer>(currentConditionsEndId);
int lastId = currentConditions.size();
while(lastId > 0 && currentId == -tmpCondEnd.get(tmpCond.get(--lastId))) { // delete all conditions, which end at this node
Integer del = currentConditions.get(lastId);
currentConditions.remove(del);
currentConditionsEndId.remove(del);
}
}
} // END IF inside an if-block
if(currentInstruction instanceof SSAConditionalBranchInstruction) {
Integer childTrue = Math.min(children.get(0), children.get(1)); // WALA always takes the true branch first
Integer childFalse = Math.max(children.get(0), children.get(1));
ArrayList<Integer> conditions = new ArrayList<Integer>();
for(int i =0; i<currentConditions.size(); i++) {
conditions.add(currentConditions.get(i));
}
finalConditions.put(currentId, conditions);
currentConditions.add(currentId);
boolean isLastIfBlock = childTrue.intValue() == mainEndId.intValue(); // => its an if block without else and without any instructions after it
if(isLastIfBlock) {
childTrue = Math.max(children.get(0), children.get(1));
currentConditionsEndId.put(currentId, mainEndId);
} else {
currentConditionsEndId.put(currentId, childFalse);
}
boolean isRealIfElse = addConditionsToGraph(sgNodes, adjList, childTrue, visited, mainEndId, currentConditions, currentConditionsEndId, finalConditions, loops);
currentConditions.remove(currentId);
Integer newEnd = currentConditionsEndId.remove(currentId);
if(isRealIfElse) {
currentConditions.add(-currentId);
currentConditionsEndId.put(-currentId, -newEnd); // last visited is the reached join from if-block
}
if(!isLastIfBlock) {
return addConditionsToGraph(sgNodes, adjList, childFalse, visited, mainEndId, currentConditions, currentConditionsEndId, finalConditions, loops);
}
return isRealIfElse;
} // END IF conditional branch instruction
else if(currentInstruction instanceof SSAGotoInstruction) {
int child = children.get(0); // goto always points to exactly one child node
if(currentId == Math.abs(ifEnd)-1) { // last node of if block
ArrayList<Integer> conditions = new ArrayList<Integer>();
for(int i =0; i<currentConditions.size(); i++) {
conditions.add(currentConditions.get(i));
}
finalConditions.put(currentId, conditions);
if(!(Math.abs(ifEnd) == child)) {
if(child <= Math.abs(ifStart)) { // is loop
loops.put(ifStart, currentId.intValue());
return false;
}
currentConditionsEndId.put(ifStart, child);
return true;
} else {
return false;
}
}
} // END IF goto instruction
else {
ArrayList<Integer> conditions = new ArrayList<Integer>();
for(int i =0; i<currentConditions.size(); i++) {
conditions.add(currentConditions.get(i));
}
finalConditions.put(currentId, conditions);
}
System.out.println(""+currentInstruction);
boolean isRealIfElse = false;
for(int i=0; i<children.size(); i++) {
int child = children.get(i);
isRealIfElse = isRealIfElse | addConditionsToGraph(sgNodes, adjList, child, visited, mainEndId, currentConditions, currentConditionsEndId, finalConditions, loops);
}
return isRealIfElse;
}
private static void generateDotFile(
HashMap<Integer, BasicBlockInContext<IExplodedBasicBlock>> sgNodes,
HashMap<Integer, ArrayList<Integer>> adjList, String entryClass,
String filePath, HashMap<Integer,ArrayList<Integer>> finalConditions) {
FileWriter fstream;
try {
log.debug("start generating dot file");
String path = AnalysisUtil.getPropertyString(AnalysisUtil.CONFIG_DOT_PATH) + File.separator;
path += "java" + File.separator;
File sgDir = new File(path + entryClass);
sgDir.mkdirs();
File sgFile = new File(path + entryClass + File.separator + filePath);
fstream = new FileWriter(sgFile);
BufferedWriter out = new BufferedWriter(fstream);
out.write("digraph SuperGraph {");
out.newLine();
out.write("node [shape=record];");
out.newLine();
// add relevant nodes to dot
log.debug("add nodes to dot file");
for (Integer key : sgNodes.keySet()) {
BasicBlockInContext<IExplodedBasicBlock> val = sgNodes.get(key);
Iterator<SSAInstruction> insIt = val.iterator();
IExplodedBasicBlock del = val.getDelegate();
int key2 = del.getGraphNodeId(); //cfg.getNumber(val);
// print the labels and define the id (key)
out.write(key + " [");
out.write("label = \" <f0>" + key + "-" + key2 + "| <f1>" + AnalysisUtil.sanitize(val.getMethod().getSignature()));
IR ir = val.getNode().getIR();
SymbolTable symTab = ir.getSymbolTable();
int j = 2;
// print the instruction field of the label
while (insIt.hasNext()) {
// insCount++;
SSAInstruction ins = insIt.next();
if(ins instanceof SSAConditionalBranchInstruction) {
ins = (SSAConditionalBranchInstruction) ins;
SSAInstruction cmp = val.getNode().getDU().getDef(ins.getUse(0));
if(cmp instanceof SSABinaryOpInstruction) {
SSABinaryOpInstruction bCmp = (SSABinaryOpInstruction) cmp;
out.write(" | <f" + j++ + "> " + bCmp.toString(symTab));
}
} else if(ins instanceof SSAInvokeInstruction) {
//TODO
}
out.write(" | <f" + j++ + "> " + AnalysisUtil.sanitize(ins.toString(symTab)));
}
if(finalConditions.containsKey(key)) {
StringBuffer sb = new StringBuffer();
for(int condition : finalConditions.get(key)) {
sb.append(", " + condition);
}
sb.append(" ");
out.write(" | <f" + j++ + "> " + sb.substring(2));
}
if(val.isEntryBlock()) {
out.write(" | <f" + j++ + "> [ENTRY]");
} else if(val.isExitBlock()) {
out.write(" | <f" + j++ + "> [EXIT]");
} else if(val.isCatchBlock()) {
out.write(" | <f" + j++ + "> [CATCH]");
}
out.write("\"];");
out.newLine();
}
// add relevant edges to dot
log.debug("add edges to dot file");
for (Integer src : adjList.keySet()) {
for(int dest : adjList.get(src)) {
out.write(src + "->" + dest + ";");
out.newLine();
}
}
// write dot file to file system
out.write("}");
out.close();
log.info("dot file generated (" + sgFile.getAbsolutePath() + ")");
} catch (IOException e) {
e.printStackTrace();
}
}
}