org.evosuite.graphs.ccfg.ClassControlFlowGraph Maven / Gradle / Ivy
/**
* Copyright (C) 2010-2018 Gordon Fraser, Andrea Arcuri and EvoSuite
* contributors
*
* This file is part of EvoSuite.
*
* EvoSuite is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published
* by the Free Software Foundation, either version 3.0 of the License, or
* (at your option) any later version.
*
* EvoSuite is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with EvoSuite. If not, see .
*/
package org.evosuite.graphs.ccfg;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.evosuite.graphs.EvoSuiteGraph;
import org.evosuite.graphs.GraphPool;
import org.evosuite.graphs.ccg.ClassCallGraph;
import org.evosuite.graphs.ccg.ClassCallNode;
import org.evosuite.graphs.cfg.BytecodeInstruction;
import org.evosuite.graphs.cfg.ControlFlowEdge;
import org.evosuite.graphs.cfg.RawControlFlowGraph;
import org.evosuite.utils.JdkPureMethodsList;
import org.objectweb.asm.Type;
/**
* This class computes the Class Control Flow Graph (CCFG) of a CUT.
*
* Given the ClassCallGraph the CCFG is generated as follows:
*
* The RawControlFlowGraph (CFG) of each method in the target class is retrieved
* from the GraphPool and imported into this CCFG. BytecodeInstructions are
* imported as CCFGCodeNodes and ControlFlowEdges as CCFGCodeEdges. Additionally
* each CFG is enclosed by a CCFGMethodEntryNode and CCFGMethodExitNode with an
* edge from the entry node to the first instruction in the CFG and an edge from
* each exit instruction in the CFG to the exit node.
*
* After that each method call instruction as defined in
* BytecodeInstruction.isMethodCall() is replaced by two new nodes
* CCFGMethodCallNode and CCFGMethodReturnNode that are labeled with that call
* instruction. Each incoming edge to the previous CCFGCodeNode is redirected to
* the CCFGMethodCallNode and each outgoing edge from the previous node is
* redirected to the CCFGMethodReturnNode. Then two CCFGMethodCallEdges are
* added. One from the CCFGMethodCallNode to the CCFGMethodEntryNode of the
* called method and one from that methods CCFGMethodExitNode to the
* CCFGMethodReturnNode. NOTE: Not every method call is replaced like this. Only
* calls to methods of the class this CCFG is created for that are either static
* methods - as defined by BytecodeInstruction.isStaticMethodCall() - or calls
* to methods on the same object (this) as defined by
* BytecodeInstruction.isMethodCallOnSameObject().
*
* All this is enclosed by a frame consisting of five CCFGFrameNodes of
* different types. This frame has two dedicated ENTRY and EXIT nodes connected
* via a third node LOOP. The LOOP node has an outgoing edge to CALL which in
* turn has an outgoing edge to each CCFGMethodEntryNode of each public method
* in this graph. Analogously the CCFGMethodExitNode of each public method has
* an outgoing edge to the CCFGFrameNode RETURN which in turn has an outgoing
* edge back to LOOP. All these edges are CCFGFrameEdges.
*
* The frame simulates the possible calls to the CUT a test can potentially
* make. After starting (ENTRY->LOOP) a test can make arbitrary calls to public
* methods (LOOP->CALL) that can in turn call other methods of the class
* (CCFGMethodCallEdges). After returning from a public method call
* (RETURN->LOOP) the test can either make more calls to the class (LOOP->CALL)
* or stop (LOOP->EXIT).
*
* The construction of the CCFG is inspired by: Proc. of the Second ACM SIGSOFT
* Symp. on the Foundations of Softw. Eng., December 1994, pages 154-164
* "Performing Data Flow Testing on Classes" Mary Jean Harrold and Gregg
* Rothermel, Section 5. The resulting CCFG should be as described in the paper
* but our construction differs a little (we don't import the CCG and then
* replace method nodes with CFGs but rather import CFGs and connect them
* directly).
*
* @author Andre Mis
*/
public class ClassControlFlowGraph extends EvoSuiteGraph {
public enum FrameNodeType {
ENTRY, EXIT, LOOP, CALL, RETURN
};
private final String className;
private final ClassCallGraph ccg;
private final ClassLoader classLoader;
private Map methodEntries = new HashMap();
private Map methodExits = new HashMap();
public Set publicMethods = new HashSet();
private Map frameNodes = new HashMap();
// cache of already analyzed methods that are known to be pure or impure
// respectively
private Set pureMethods = new HashSet();
private Set impureMethods = new HashSet();
// auxilary set for purity analysis to keep track of methods that are
// currently
// being analyzed across several CCFGs. elements are of the form
// .
private static Set methodsInPurityAnalysis = new HashSet();
/**
* Given the ClassCallGraph of a class this constructor will build up the
* corresponding CCFG using the RCFGs from the GraphPool.
*
* @param ccg
* a {@link org.evosuite.graphs.ccg.ClassCallGraph} object.
*/
public ClassControlFlowGraph(ClassCallGraph ccg) {
super(CCFGEdge.class);
this.className = ccg.getClassName();
this.ccg = ccg;
this.classLoader = ccg.getClassLoader();
nicenDotOutput();
compute();
}
// purity analysis
public boolean isPure(String methodName) {
if (pureMethods.contains(methodName))
return true;
else if (impureMethods.contains(methodName))
return false;
boolean isPure = analyzePurity(methodName);
if (isPure) {
pureMethods.add(methodName);
return true;
} else {
impureMethods.add(methodName);
return false;
}
}
private boolean analyzePurity(String methodName) {
if (!methodEntries.containsKey(methodName)) {
// workaround to deal with abstract methods for now
// default behaviour for unknown things is "pure" for now
return true;
}
CCFGMethodEntryNode entry = getMethodEntryOf(methodName);
Set handled = new HashSet();
// LoggingUtils.getEvoLogger().info(
// "Starting purity analysis of " + methodName);
// add methodName to set of currently analyzed methods
methodsInPurityAnalysis.add(className + "." + methodName);
boolean r = analyzePurity(methodName, entry, handled);
// remove methodName from set of currently analyzed methods
methodsInPurityAnalysis.remove(className + "." + methodName);
return r;
}
private boolean analyzePurity(String analyzedMethod, CCFGNode currentNode,
Set handled) {
if (handled.contains(currentNode)) {
// if we already handled the node we know it is pure otherwise we
// would have returned
return true;
}
handled.add(currentNode);
// the node at which analysis is supposed to continue
// used for skipping intermediate nodes for CCFGMethodCallNodes
CCFGNode nextNode = currentNode;
if (currentNode instanceof CCFGFieldClassCallNode) {
CCFGFieldClassCallNode fieldCall = (CCFGFieldClassCallNode) currentNode;
// TODO for now we will have to ignore classes that we are not able
// to analyze.
// this should only happen for classes in java.*
String toAnalyze = fieldCall.getClassName() + "."
+ fieldCall.getMethodName();
if (GraphPool.getInstance(classLoader).canMakeCCFGForClass(fieldCall.getClassName())) {
if (!methodsInPurityAnalysis.contains(toAnalyze)) {
ClassControlFlowGraph ccfg = GraphPool.getInstance(classLoader).getCCFG(fieldCall
.getClassName());
if (!ccfg.isPure(fieldCall.getMethodName())) {
// if fieldCall is impure this method is also impure
return false;
}
}
}
else{
//The format that ASM for types and the one used in my data file is different: in particular ASM uses the
//Class.getName format for types see http://docs.oracle.com/javase/6/docs/api/java/lang/Class.html#getName(), while the data
//file with the pure methods uses the qualified name.
//For instance, in my file is: java.blabla.ClassExample.method(java.util.List,java.lang.Class[])
//ASM returns java.blabla.ClassExample.method(Ljava.util.List;[Ljava.lang.Class)V.
//The conversion from qualified name to the JVM/ASM format is not so straightforward,
//well it's not a very complicate problem but there some corner cases that I have to check.
//In the mean time this method convert the ASM/JVM format into the normal one, using an utility
//of ASM.
//The file with the method list is in src/resources, it SHOULD be accurate but not perfect, some methods are missing for sure.
if(toAnalyze.startsWith("java.")){
Type[] parameters = org.objectweb.asm.Type.getArgumentTypes(fieldCall.getOnlyParameters());
String newParams = "";
if(parameters.length!=0){
for (Type i : parameters) {
newParams = newParams + "," + i.getClassName();
}
newParams = newParams.substring(1, newParams.length());
}
toAnalyze=fieldCall.getClassName() + "." + fieldCall.getOnlyMethodName()+"("+newParams+")";
return JdkPureMethodsList.instance.checkPurity(toAnalyze);
}
}
// otherwise proceed
} else if (currentNode instanceof CCFGCodeNode) {
CCFGCodeNode codeNode = (CCFGCodeNode) currentNode;
// it this node alters the state of this object this method is
// impure
if (codeNode.getCodeInstruction().isFieldDefinition())
return false;
// otherwise proceed
} else if (currentNode instanceof CCFGMethodExitNode) {
CCFGMethodExitNode methodExit = (CCFGMethodExitNode) currentNode;
// if we encounter the end of the analyzed method and have not
// detected
// impurity yet then the method is pure
if (methodExit.getMethod().equals(analyzedMethod))
return true;
else
throw new IllegalStateException(
"MethodExitNodes from methods other then the currently analyzed one should not be reached");
} else if (currentNode instanceof CCFGMethodCallNode) {
CCFGMethodCallNode callNode = (CCFGMethodCallNode) currentNode;
// avoid loops in analysis
String toAnalyze = className + "." + callNode.getCalledMethod();
if (!methodsInPurityAnalysis.contains(toAnalyze)) {
// if another method of this class is called check that
// method
// it the called method is impure then this method is impure
if (!isPure(callNode.getCalledMethod()))
return false;
}
// otherwise proceed after the method call has taken place
nextNode = callNode.getReturnNode();
} else if (currentNode instanceof CCFGMethodEntryNode) {
// do nothing special
} else
throw new IllegalStateException(
"purity analysis should not reach this kind of CCFGNode: "
+ currentNode.getClass().toString());
Set children = getChildren(nextNode);
for (CCFGNode child : children) {
if (!analyzePurity(analyzedMethod, child, handled))
return false;
}
// no child was impure so this method is pure
return true;
}
// sanity functions
public boolean isPublicMethod(String method) {
if (method == null)
return false;
CCFGMethodEntryNode entry = getMethodEntryOf(method);
return isPublicMethod(entry);
}
public boolean isPublicMethod(CCFGMethodEntryNode node) {
if (node == null)
return false;
return publicMethods.contains(node);
}
// convenience getters
public CCFGMethodEntryNode getMethodEntryNodeForClassCallNode(
ClassCallNode ccgNode) {
CCFGMethodEntryNode r = methodEntries.get(ccgNode.getMethod());
if (r == null)
throw new IllegalStateException(
"expect the CCFG to contain a CCFGMethodEntryNode for each node in the corresponding CCG "
+ ccgNode.getMethod());
return r;
}
private CCFGMethodEntryNode getMethodEntryOf(String method) {
CCFGMethodEntryNode r = methodEntries.get(method);
if (r == null)
throw new IllegalArgumentException("unknown method: " + method);
return r;
}
private RawControlFlowGraph getRCFG(ClassCallNode ccgNode) {
return GraphPool.getInstance(classLoader).getRawCFG(className, ccgNode.getMethod());
}
/**
*
* getMethodExitOf
*
*
* @param methodEntry
* a {@link org.evosuite.graphs.ccfg.CCFGMethodEntryNode} object.
* @return a {@link org.evosuite.graphs.ccfg.CCFGMethodExitNode} object.
*/
public CCFGMethodExitNode getMethodExitOf(CCFGMethodEntryNode methodEntry) {
if (methodEntry == null)
return null;
return methodExits.get(methodEntry.getMethod());
}
/**
*
* getMethodEntryOf
*
*
* @param methodExit
* a {@link org.evosuite.graphs.ccfg.CCFGMethodExitNode} object.
* @return a {@link org.evosuite.graphs.ccfg.CCFGMethodEntryNode} object.
*/
public CCFGMethodEntryNode getMethodEntryOf(CCFGMethodExitNode methodExit) {
if (methodExit == null)
return null;
return methodEntries.get(methodExit.getMethod());
}
// CCFG computation from CCG and CFGs
private void compute() {
importCFGs();
addFrame();
}
private void importCFGs() {
Map> tempMap = new HashMap>();
// replace each class call node with corresponding CFG
for (ClassCallNode ccgNode : ccg.vertexSet()) {
RawControlFlowGraph cfg = getRCFG(ccgNode);
tempMap.put(cfg, importCFG(cfg));
}
connectCFGs(tempMap);
}
private void connectCFGs(
Map> tempMap) {
for (RawControlFlowGraph cfg : tempMap.keySet()) {
List calls = cfg
.determineMethodCallsToOwnClass();
for (BytecodeInstruction call : calls) {
// we do not want to connect every method call to the target
// class, but only those that are called on the same object or
// are static
if (!(call.isCallToStaticMethod() || call
.isMethodCallOnSameObject())) {
// call.printFrameInformation();
continue;
}
connectCFG(cfg, call, tempMap);
}
}
}
private void connectCFG(
RawControlFlowGraph cfg,
BytecodeInstruction call,
Map> tempMap) {
// add MethodCallNode and MethodReturnNode
CCFGMethodReturnNode returnNode = new CCFGMethodReturnNode(call);
CCFGMethodCallNode callNode = new CCFGMethodCallNode(call, returnNode);
addVertex(callNode);
addVertex(returnNode);
// connect with method entry and exit nodes of called method
CCFGNode calleeEntry = methodEntries.get(call.getCalledMethod());
CCFGNode calleeExit = methodExits.get(call.getCalledMethod());
CCFGMethodCallEdge callEdge = new CCFGMethodCallEdge(call, true);
CCFGMethodCallEdge returnEdge = new CCFGMethodCallEdge(call, false);
addEdge(callNode, calleeEntry, callEdge);
addEdge(calleeExit, returnNode, returnEdge);
// redirect edges from the original CodeNode to the new nodes
CCFGNode origCallNode = tempMap.get(cfg).get(call);
if (!redirectEdges(origCallNode, callNode, returnNode)
|| !graph.removeVertex(origCallNode))
throw new IllegalStateException(
"internal error while connecting cfgs during CCFG construction");
}
private Map importCFG(
RawControlFlowGraph cfg) {
Map temp = new HashMap();
importCFGNodes(cfg, temp);
importCFGEdges(cfg, temp);
// enclose with CCFGMethodEntryNode and CCFGMethodExitNode
encloseCFG(cfg, temp);
return temp;
}
/**
* import CFGs nodes. If the node is a method call to a method of a field
* class, a new CCFGFieldClassCallNode is created. Otherwise, a normal
* CCFGCodeNode is created
*
* @param cfg
* @param temp
*/
private void importCFGNodes(RawControlFlowGraph cfg,
Map temp) {
// add BytecodeInstructions as CCFGCodeNodes
for (BytecodeInstruction code : cfg.vertexSet()) {
CCFGCodeNode node;
if (code.isMethodCallOfField()) {
node = new CCFGFieldClassCallNode(code,
code.getCalledMethodsClass(), code.getCalledMethodName(), code.getMethodCallDescriptor());
} else {
node = new CCFGCodeNode(code);
}
addVertex(node);
temp.put(code, node);
}
}
private void importCFGEdges(RawControlFlowGraph cfg,
Map temp) {
// add ControlFlowEdges as CCFGCodeEdges
for (ControlFlowEdge e : cfg.edgeSet()) {
if (e.isExceptionEdge())
continue;
CCFGCodeNode src = temp.get(cfg.getEdgeSource(e));
CCFGCodeNode target = temp.get(cfg.getEdgeTarget(e));
addEdge(src, target, new CCFGCodeEdge(e));
}
}
private void encloseCFG(RawControlFlowGraph cfg,
Map temp) {
addCCFGMethodEntryNode(cfg, temp);
addCCFGMethodExitNode(cfg, temp);
}
private CCFGMethodEntryNode addCCFGMethodEntryNode(RawControlFlowGraph cfg,
Map temp) {
CCFGCodeNode entryInstruction = temp.get(cfg.determineEntryPoint());
CCFGMethodEntryNode entry = new CCFGMethodEntryNode(
cfg.getMethodName(), entryInstruction);
addVertex(entry);
addEdge(entry, entryInstruction);
methodEntries.put(cfg.getMethodName(), entry);
return entry;
}
private CCFGMethodExitNode addCCFGMethodExitNode(RawControlFlowGraph cfg,
Map temp) {
CCFGMethodExitNode exit = new CCFGMethodExitNode(cfg.getMethodName());
addVertex(exit);
for (BytecodeInstruction exitPoint : cfg.determineExitPoints()) {
addEdge(temp.get(exitPoint), exit);
}
methodExits.put(cfg.getMethodName(), exit);
return exit;
}
private void addFrame() {
addFrameNodes();
addFrameEdges();
connectPublicMethodsToFrame();
}
private void addFrameNodes() {
for (FrameNodeType type : FrameNodeType.values()) {
CCFGFrameNode node = new CCFGFrameNode(type);
addVertex(node);
frameNodes.put(type, node);
}
}
private void addFrameEdges() {
addEdge(getFrameNode(FrameNodeType.ENTRY),
getFrameNode(FrameNodeType.LOOP), new CCFGFrameEdge());
addEdge(getFrameNode(FrameNodeType.LOOP),
getFrameNode(FrameNodeType.CALL), new CCFGFrameEdge());
addEdge(getFrameNode(FrameNodeType.LOOP),
getFrameNode(FrameNodeType.EXIT), new CCFGFrameEdge());
addEdge(getFrameNode(FrameNodeType.RETURN),
getFrameNode(FrameNodeType.LOOP), new CCFGFrameEdge());
}
/**
*
* getFrameNode
*
*
* @param type
* a
* {@link org.evosuite.graphs.ccfg.ClassControlFlowGraph.FrameNodeType}
* object.
* @return a {@link org.evosuite.graphs.ccfg.CCFGFrameNode} object.
*/
public CCFGFrameNode getFrameNode(FrameNodeType type) {
return frameNodes.get(type);
}
/**
* Adds a CCFGFrameEdge from the CCFGFrameNode CALL to the
* CCFGMethodEntryNode of each public method and from their
* CCFGMethodExitNode to the CCFGFrameNode RETURN.
*/
private void connectPublicMethodsToFrame() {
for (ClassCallNode ccgNode : ccg.vertexSet()) {
RawControlFlowGraph cfg = getRCFG(ccgNode);
if (cfg.isPublicMethod()) {
addEdge(getFrameNode(FrameNodeType.CALL),
methodEntries.get(ccgNode.getMethod()),
new CCFGFrameEdge());
addEdge(methodExits.get(ccgNode.getMethod()),
getFrameNode(FrameNodeType.RETURN), new CCFGFrameEdge());
publicMethods.add(methodEntries.get(ccgNode.getMethod()));
}
}
}
// toDot utilities
/**
* Makes .dot output pretty by visualizing different types of nodes and
* edges with different forms and colors
*/
private void nicenDotOutput() {
registerVertexAttributeProvider(new CCFGNodeAttributeProvider());
registerEdgeAttributeProvider(new CCFGEdgeAttributeProvider());
}
/** {@inheritDoc} */
@Override
public String getName() {
return "CCFG_" + className;
}
/** {@inheritDoc} */
@Override
protected String dotSubFolder() {
return toFileString(className) + "/";
}
/**
* @return the ccg
*/
public ClassCallGraph getCcg() {
return ccg;
}
}