/** * Copyright (C) 2010-2017 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 <http://www.gnu.org/licenses/>. */ package org.evosuite.graphs.cfg; import java.util.ArrayList; import java.util.Comparator; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.PriorityQueue; import java.util.Queue; import java.util.Set; import org.evosuite.coverage.branch.Branch; import org.evosuite.coverage.branch.BranchPool; import org.evosuite.coverage.dataflow.DefUse; import org.evosuite.coverage.dataflow.DefUseFactory; import org.evosuite.coverage.dataflow.Definition; import org.evosuite.coverage.dataflow.Use; import org.evosuite.graphs.GraphPool; import org.evosuite.utils.ReverseComparator; import org.objectweb.asm.tree.LabelNode; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Represents the complete CFG of a method * * Essentially this is a graph containing all BytecodeInstrucions of a method as * nodes. From each such instruction there is an edge to each possible * instruction the control flow can reach immediately after that instruction. * * @author Andre Mis */ public class RawControlFlowGraph extends ControlFlowGraph<BytecodeInstruction> { private static Logger logger = LoggerFactory.getLogger(RawControlFlowGraph.class); private final ClassLoader classLoader; /** * @return the classLoader */ public ClassLoader getClassLoader() { return classLoader; } /** * <p> * Constructor for RawControlFlowGraph. * </p> * * @param className * a {@link java.lang.String} object. * @param methodName * a {@link java.lang.String} object. * @param access * a int. */ public RawControlFlowGraph(ClassLoader classLoader, String className, String methodName, int access) { super(className, methodName, access); this.classLoader = classLoader; logger.info("Creating new RawCFG for "+className+"."+methodName+": "+this.vertexCount()); } // inherited from ControlFlowGraph /** {@inheritDoc} */ @Override public boolean containsInstruction(BytecodeInstruction instruction) { return containsVertex(instruction); } /** {@inheritDoc} */ @Override public BytecodeInstruction getInstruction(int instructionId) { for (BytecodeInstruction v : vertexSet()) { if (v.getInstructionId() == instructionId) { return v; } } return null; } // @Override // public BytecodeInstruction getBranch(int branchId) { // for (BytecodeInstruction v : vertexSet()) { // if (v.isBranch() && v.getControlDependentBranchId() == branchId) { // return v; // } // } // return null; // } /** * <p> * addEdge * </p> * * @param src * a {@link org.evosuite.graphs.cfg.BytecodeInstruction} object. * @param target * a {@link org.evosuite.graphs.cfg.BytecodeInstruction} object. * @param isExceptionEdge * a boolean. * @return a {@link org.evosuite.graphs.cfg.ControlFlowEdge} object. */ protected ControlFlowEdge addEdge(BytecodeInstruction src, BytecodeInstruction target, boolean isExceptionEdge) { logger.debug("Adding edge to RawCFG of "+className+"."+methodName+": "+this.vertexCount()); if (BranchPool.getInstance(classLoader).isKnownAsBranch(src)) if (src.isBranch()) return addBranchEdge(src, target, isExceptionEdge); else if (src.isSwitch()) return addSwitchBranchEdge(src, target, isExceptionEdge); return addUnlabeledEdge(src, target, isExceptionEdge); } private ControlFlowEdge addUnlabeledEdge(BytecodeInstruction src, BytecodeInstruction target, boolean isExceptionEdge) { return internalAddEdge(src, target, new ControlFlowEdge(isExceptionEdge)); } private ControlFlowEdge addBranchEdge(BytecodeInstruction src, BytecodeInstruction target, boolean isExceptionEdge) { boolean isJumping = !isNonJumpingEdge(src, target); ControlDependency cd = new ControlDependency(src.toBranch(), isJumping); ControlFlowEdge e = new ControlFlowEdge(cd, isExceptionEdge); return internalAddEdge(src, target, e); } private ControlFlowEdge addSwitchBranchEdge(BytecodeInstruction src, BytecodeInstruction target, boolean isExceptionEdge) { if (!target.isLabel()) throw new IllegalStateException( "expect control flow edges from switch statements to always target labelNodes"); LabelNode label = (LabelNode) target.getASMNode(); List<Branch> switchCaseBranches = BranchPool.getInstance(classLoader).getBranchForLabel(label); if (switchCaseBranches == null) { logger.debug("not a switch case label: " + label.toString() + " " + target.toString()); return internalAddEdge(src, target, new ControlFlowEdge(isExceptionEdge)); } // throw new IllegalStateException( // "expect BranchPool to contain a Branch for each switch-case-label"+src.toString()+" to "+target.toString()); // TODO there is an inconsistency when it comes to switches with // empty case: blocks. they do not have their own label, so there // can be multiple ControlFlowEdges from the SWITCH instruction to // one LabelNode. // But currently our RawCFG does not permit multiple edges between // two nodes for (Branch switchCaseBranch : switchCaseBranches) { // TODO n^2 Set<ControlFlowEdge> soFar = incomingEdgesOf(target); boolean handled = false; for (ControlFlowEdge old : soFar) if (switchCaseBranch.equals(old.getBranchInstruction())) handled = true; if (handled) continue; /* * previous try to add fake intermediate nodes for each empty case * block to help the CDG - unsuccessful: * if(switchCaseBranches.size()>1) { // // e = new * ControlFlowEdge(isExceptionEdge); // * e.setBranchInstruction(switchCaseBranch); // * e.setBranchExpressionValue(true); // BytecodeInstruction * fakeInstruction = * BytecodeInstructionPool.createFakeInstruction(className * ,methodName); // addVertex(fakeInstruction); // * internalAddEdge(src,fakeInstruction,e); // // e = new * ControlFlowEdge(isExceptionEdge); // * e.setBranchInstruction(switchCaseBranch); // * e.setBranchExpressionValue(true); // // e = * internalAddEdge(fakeInstruction,target,e); // } else { */ ControlDependency cd = new ControlDependency(switchCaseBranch, true); ControlFlowEdge e = new ControlFlowEdge(cd, isExceptionEdge); e = internalAddEdge(src, target, e); } return new ControlFlowEdge(isExceptionEdge); } private ControlFlowEdge internalAddEdge(BytecodeInstruction src, BytecodeInstruction target, ControlFlowEdge e) { if (!super.addEdge(src, target, e)) { // TODO find out why this still happens logger.debug("unable to add edge from " + src.toString() + " to " + target.toString() + " into the rawCFG of " + getMethodName()); e = super.getEdge(src, target); if (e == null) throw new IllegalStateException( "internal graph error - completely unexpected"); } return e; } private boolean isNonJumpingEdge(BytecodeInstruction src, // TODO move to // ControlFlowGraph // and implement // analog method // in ActualCFG BytecodeInstruction dst) { return Math.abs(src.getInstructionId() - dst.getInstructionId()) == 1; } // functionality used to create ActualControlFlowGraph /** * <p> * determineBasicBlockFor * </p> * * @param instruction * a {@link org.evosuite.graphs.cfg.BytecodeInstruction} object. * @return a {@link org.evosuite.graphs.cfg.BasicBlock} object. */ public BasicBlock determineBasicBlockFor(BytecodeInstruction instruction) { if (instruction == null) throw new IllegalArgumentException("null given"); // TODO clean this up logger.debug("creating basic block for " + instruction.toString()); List<BytecodeInstruction> blockNodes = new ArrayList<BytecodeInstruction>(); blockNodes.add(instruction); Set<BytecodeInstruction> handledChildren = new HashSet<BytecodeInstruction>(); Set<BytecodeInstruction> handledParents = new HashSet<BytecodeInstruction>(); Queue<BytecodeInstruction> queue = new LinkedList<BytecodeInstruction>(); queue.add(instruction); while (!queue.isEmpty()) { BytecodeInstruction current = queue.poll(); logger.debug("handling " + current.toString()); // add child to queue if (outDegreeOf(current) == 1) for (BytecodeInstruction child : getChildren(current)) { // this must be only one edge if inDegree was 1 if (blockNodes.contains(child)) continue; if (handledChildren.contains(child)) continue; handledChildren.add(child); if (inDegreeOf(child) < 2) { // insert child right after current // ... always thought ArrayList had insertBefore() and // insertAfter() methods ... well blockNodes.add(blockNodes.indexOf(current) + 1, child); logger.debug(" added child to queue: " + child.toString()); queue.add(child); } } // add parent to queue if (inDegreeOf(current) == 1) for (BytecodeInstruction parent : getParents(current)) { // this must be only one edge if outDegree was 1 if (blockNodes.contains(parent)) continue; if (handledParents.contains(parent)) continue; handledParents.add(parent); if (outDegreeOf(parent) < 2) { // insert parent right before current blockNodes.add(blockNodes.indexOf(current), parent); logger.debug(" added parent to queue: " + parent.toString()); queue.add(parent); } } } BasicBlock r = new BasicBlock(classLoader, className, methodName, blockNodes); logger.debug("created nodeBlock: " + r.toString()); return r; } /** {@inheritDoc} */ @Override public BytecodeInstruction determineEntryPoint() { BytecodeInstruction noParent = super.determineEntryPoint(); if (noParent != null) return noParent; // copied from ControlFlowGraph.determineEntryPoint(): // there was a back loop to the first instruction within this CFG, so no // candidate return getInstructionWithSmallestId(); } /** {@inheritDoc} */ @Override public Set<BytecodeInstruction> determineExitPoints() { Set<BytecodeInstruction> r = super.determineExitPoints(); // if the last instruction loops back to a previous instruction there is // no node without a child, so just take the last byteCode instruction if (r.isEmpty()) r.add(getInstructionWithBiggestId()); return r; } /** * <p> * getInstructionWithSmallestId * </p> * * @return a {@link org.evosuite.graphs.cfg.BytecodeInstruction} object. */ public BytecodeInstruction getInstructionWithSmallestId() { BytecodeInstruction r = null; for (BytecodeInstruction ins : vertexSet()) { if (r == null || r.getInstructionId() > ins.getInstructionId()) r = ins; } return r; } /** * <p> * getInstructionWithBiggestId * </p> * * @return a {@link org.evosuite.graphs.cfg.BytecodeInstruction} object. */ public BytecodeInstruction getInstructionWithBiggestId() { BytecodeInstruction r = null; for (BytecodeInstruction ins : vertexSet()) { if (r == null || r.getInstructionId() < ins.getInstructionId()) r = ins; } return r; } /** * In some cases there can be isolated nodes within a CFG. For example in an * completely empty try-catch-finally. Since these nodes are not reachable * but cause trouble when determining the entry point of a CFG they get * removed. * * @return a int. */ public int removeIsolatedNodes() { Set<BytecodeInstruction> candidates = determineEntryPoints(); int removed = 0; if (candidates.size() > 1) { for (BytecodeInstruction instruction : candidates) { if (outDegreeOf(instruction) == 0) { if (graph.removeVertex(instruction)) { removed++; BytecodeInstructionPool.getInstance(classLoader).forgetInstruction(instruction); } } } } return removed; } // control distance functionality /** * Returns the Set of BytecodeInstructions that can potentially be executed * from entering the method of this CFG until the given BytecodeInstruction * is reached. * * @param v * a {@link org.evosuite.graphs.cfg.BytecodeInstruction} object. * @return a {@link java.util.Set} object. */ public Set<BytecodeInstruction> getPreviousInstructionsInMethod(BytecodeInstruction v) { Set<BytecodeInstruction> visited = new HashSet<BytecodeInstruction>(); PriorityQueue<BytecodeInstruction> queue = new PriorityQueue<BytecodeInstruction>( graph.vertexSet().size(), new BytecodeInstructionIdComparator()); queue.add(v); while (queue.peek() != null) { BytecodeInstruction current = queue.poll(); if (visited.contains(current)) continue; Set<ControlFlowEdge> incomingEdges = graph.incomingEdgesOf(current); for (ControlFlowEdge incomingEdge : incomingEdges) { BytecodeInstruction source = graph.getEdgeSource(incomingEdge); if (source.getInstructionId() >= current.getInstructionId()) continue; queue.add(source); } visited.add(current); } return visited; } /** * Returns the Set of BytecodeInstructions that can potentially be executed * from passing the given BytecodeInstruction until the end of the method of * this CFG is reached. * * @param v * a {@link org.evosuite.graphs.cfg.BytecodeInstruction} object. * @return a {@link java.util.Set} object. */ public Set<BytecodeInstruction> getLaterInstructionsInMethod(BytecodeInstruction v) { Set<BytecodeInstruction> visited = new HashSet<BytecodeInstruction>(); Comparator<BytecodeInstruction> reverseComp = new ReverseComparator<BytecodeInstruction>( new BytecodeInstructionIdComparator()); PriorityQueue<BytecodeInstruction> queue = new PriorityQueue<BytecodeInstruction>( graph.vertexSet().size(), reverseComp); queue.add(v); while (queue.peek() != null) { BytecodeInstruction current = queue.poll(); if (visited.contains(current)) continue; Set<ControlFlowEdge> outgoingEdges = graph.outgoingEdgesOf(current); for (ControlFlowEdge outgoingEdge : outgoingEdges) { BytecodeInstruction target = graph.getEdgeTarget(outgoingEdge); if (target.getInstructionId() < current.getInstructionId()) continue; queue.add(target); } visited.add(current); } return visited; } // functionality for defUse coverage /** * <p> * getUsesForDef * </p> * * @param def * a {@link org.evosuite.coverage.dataflow.Definition} object. * @return a {@link java.util.Set} object. */ public Set<Use> getUsesForDef(Definition def) { if (!graph.containsVertex(def)) throw new IllegalArgumentException("unknown Definition"); return getUsesForDef(def, def, new HashSet<BytecodeInstruction>()); } private Set<Use> getUsesForDef(Definition targetDef, BytecodeInstruction currentInstruction, Set<BytecodeInstruction> handled) { if (!graph.containsVertex(currentInstruction)) throw new IllegalArgumentException("vertex not in graph"); Set<Use> r = new HashSet<Use>(); if (handled.contains(currentInstruction)) return r; handled.add(currentInstruction); Set<ControlFlowEdge> outgoingEdges = graph.outgoingEdgesOf(currentInstruction); for (ControlFlowEdge e : outgoingEdges) { BytecodeInstruction edgeTarget = graph.getEdgeTarget(e); if (targetDef.canBeActiveFor(edgeTarget)) r.add(DefUseFactory.makeUse(edgeTarget)); if (canOverwriteDU(targetDef, edgeTarget)) continue; // don not follow edges going to previous instructions (avoid loops) // if (edgeTarget.getInstructionId() > // currentInstruction.getInstructionId()) r.addAll(getUsesForDef(targetDef, edgeTarget, handled)); } return r; } /** * <p> * hasDefClearPathToMethodExit * </p> * * @param duVertex * a {@link org.evosuite.coverage.dataflow.Definition} object. * @return a boolean. */ public boolean hasDefClearPathToMethodExit(Definition duVertex) { if (!graph.containsVertex(duVertex)) throw new IllegalArgumentException("vertex not in graph"); if (duVertex.isLocalDU()) return false; return hasDefClearPathToMethodExit(duVertex, duVertex, new HashSet<BytecodeInstruction>()); } /** * <p> * hasDefClearPathFromMethodEntry * </p> * * @param duVertex * a {@link org.evosuite.coverage.dataflow.Use} object. * @return a boolean. */ public boolean hasDefClearPathFromMethodEntry(Use duVertex) { if (!graph.containsVertex(duVertex)) throw new IllegalArgumentException("vertex not in graph"); if (duVertex.isLocalDU()) return false; return hasDefClearPathFromMethodEntry(duVertex, duVertex, new HashSet<BytecodeInstruction>()); } private boolean hasDefClearPathToMethodExit(Definition targetDefUse, BytecodeInstruction currentVertex, Set<BytecodeInstruction> handled) { if (!graph.containsVertex(currentVertex)) throw new IllegalArgumentException("vertex not in graph"); if (handled.contains(currentVertex)) return false; handled.add(currentVertex); Set<ControlFlowEdge> outgoingEdges = graph.outgoingEdgesOf(currentVertex); if (outgoingEdges.size() == 0) return true; for (ControlFlowEdge e : outgoingEdges) { BytecodeInstruction edgeTarget = graph.getEdgeTarget(e); if (canOverwriteDU(targetDefUse, edgeTarget)) continue; // if (edgeTarget.getInstructionId() > // currentVertex.getInstructionId() // dont follow backedges (loops) // && hasDefClearPathToMethodExit(targetDefUse, edgeTarget, // handled)) if (hasDefClearPathToMethodExit(targetDefUse, edgeTarget, handled)) return true; } return false; } private boolean hasDefClearPathFromMethodEntry(Use targetDefUse, BytecodeInstruction currentVertex, Set<BytecodeInstruction> handled) { if (!graph.containsVertex(currentVertex)) throw new IllegalArgumentException("vertex not in graph"); if (handled.contains(currentVertex)) return false; handled.add(currentVertex); Set<ControlFlowEdge> incomingEdges = graph.incomingEdgesOf(currentVertex); if (incomingEdges.size() == 0) return true; for (ControlFlowEdge e : incomingEdges) { BytecodeInstruction edgeStart = graph.getEdgeSource(e); // skip edges coming from a def for the same field if (canOverwriteDU(targetDefUse, edgeStart, new HashSet<String>())) continue; // if (edgeTarget.getInstructionId() > // currentVertex.getInstructionId() // dont follow backedges (loops) // && hasDefClearPathToMethodExit(targetDefUse, edgeTarget, // handled)) if (hasDefClearPathFromMethodEntry(targetDefUse, edgeStart, handled)) return true; } return false; } private boolean callsOverwritingMethod(DefUse targetDefUse, BytecodeInstruction edgeTarget, Set<String> handle) { if (canBeOverwritingMethod(targetDefUse, edgeTarget)) { // DONE in this case we should check if there is a deffree path // for this field in the called method if the called method is // also a method from the class of our targetDU // TODO this does not take into account if the method call is // invoked on the same object. we should actually check if // "this" is on top of the stack (ALOAD_0 previous instruction // before call) RawControlFlowGraph calledGraph = edgeTarget.getCalledCFG(); if (calledGraph == null) { logger.debug("expected cfg to exist for: " + edgeTarget.getCalledMethod() + " ... abstract method?"); return false; } if (!calledGraph.hasDefClearPath(targetDefUse, handle)) return true; } return false; } /** * Checks if the given DefUse has a definition-clear path to its methods * exit * * @param targetDU * a {@link org.evosuite.coverage.dataflow.DefUse} object. * @param handle * a {@link java.util.Set} object. * @return a boolean. */ public boolean hasDefClearPath(DefUse targetDU, Set<String> handle) { BytecodeInstruction entry = determineEntryPoint(); return hasDefClearPath(targetDU, entry, handle); } /** * Auxiliary method for hasDefClearPath(DefUse, Set) */ private boolean hasDefClearPath(DefUse targetDU, BytecodeInstruction currentVertex, Set<String> handle) { if (!graph.containsVertex(currentVertex)) throw new IllegalArgumentException("vertex not in graph"); handle.add(methodName); String targetVariable = targetDU.getVariableName(); if (currentVertex.isDefinitionForVariable(targetVariable)) return false; Set<ControlFlowEdge> outgoingEdges = graph.outgoingEdgesOf(currentVertex); if (outgoingEdges.size() == 0) return true; for (ControlFlowEdge e : outgoingEdges) { BytecodeInstruction edgeTarget = graph.getEdgeTarget(e); // handle edgeTarget being another method call! if (canBeOverwritingMethod(targetDU, edgeTarget) && !handle.contains(edgeTarget.getCalledMethod()) && canOverwriteDU(targetDU, edgeTarget, handle)) continue; if (/* * edgeTarget.getInstructionId() > currentVertex * .getInstructionId() // dont follow backedges (loops) && */hasDefClearPath(targetDU, edgeTarget, handle)) return true; } return false; } private boolean canOverwriteDU(Definition targetDefUse, BytecodeInstruction edgeTarget) { return canOverwriteDU(targetDefUse, edgeTarget, new HashSet<String>()); } private boolean canOverwriteDU(DefUse targetDefUse, BytecodeInstruction edgeTarget, Set<String> handle) { // skip edges going into another def for the same field if (targetDefUse.canBecomeActiveDefinition(edgeTarget)) return true; if (callsOverwritingMethod(targetDefUse, edgeTarget, handle)) return true; return false; } private boolean canBeOverwritingMethod(DefUse targetDefUse, BytecodeInstruction edgeTarget) { return targetDefUse.isFieldDU() && edgeTarget.isMethodCallForClass(targetDefUse.getClassName()); } // miscellaneous /** {@inheritDoc} */ @Override public String toString() { StringBuffer sb = new StringBuffer(); for (ControlFlowEdge e : graph.edgeSet()) { sb.append(graph.getEdgeSource(e) + " -> " + graph.getEdgeTarget(e)); sb.append("\n"); } return sb.toString(); } /** {@inheritDoc} */ @Override public String getCFGType() { return "RCFG"; } // CCFG util /** * <p> * determineMethodCalls * </p> * * @return a {@link java.util.List} object. */ public List<BytecodeInstruction> determineMethodCalls() { List<BytecodeInstruction> calls = new ArrayList<BytecodeInstruction>(); for (BytecodeInstruction ins : graph.vertexSet()) { if (ins.isMethodCall()) { calls.add(ins); } } return calls; } /** * <p> * determineMethodCallsToOwnClass * </p> * * @return a {@link java.util.List} object. */ public List<BytecodeInstruction> determineMethodCallsToOwnClass() { List<BytecodeInstruction> calls = new ArrayList<BytecodeInstruction>(); for (BytecodeInstruction ins : determineMethodCalls()) if (ins.isMethodCallForClass(className)) { // somehow ASMs MethodInsnNode.owner and thus // BytecodeInstruction.getCalledMethodsClass() returns the class // in which the method call is defined and not the class of the // called method. this happens if class A extends class B, the // called method is declared in B and the method call is in A // TODO so for now we have this workaround: if A is our CUT then // the GraphPool does not know B. if (GraphPool.getInstance(classLoader).getRawCFG(className, ins.getCalledMethod()) != null) calls.add(ins); // TODO logger.warn or logger.debug // else // System.out.println("false positive call to own classes method: "+ins.toString()); } return calls; } // TODO hack since RawControlFlowGraph is filled by CFGGenerator from the // outside /** {@inheritDoc} */ public boolean addVertex(BytecodeInstruction ins) { return super.addVertex(ins); } }