mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-04 18:29:37 +02:00
GT-2848: Refactor DependencyGraph with Deterministic version
This commit is contained in:
parent
dace9682fb
commit
fa558af9c2
11 changed files with 744 additions and 343 deletions
|
@ -37,7 +37,7 @@ import ghidra.program.model.util.AcyclicCallGraphBuilder;
|
||||||
import ghidra.util.Msg;
|
import ghidra.util.Msg;
|
||||||
import ghidra.util.exception.CancelledException;
|
import ghidra.util.exception.CancelledException;
|
||||||
import ghidra.util.exception.InvalidInputException;
|
import ghidra.util.exception.InvalidInputException;
|
||||||
import ghidra.util.graph.DependencyGraph;
|
import ghidra.util.graph.AbstractDependencyGraph;
|
||||||
import ghidra.util.task.TaskMonitor;
|
import ghidra.util.task.TaskMonitor;
|
||||||
|
|
||||||
public class DecompilerParameterIdCmd extends BackgroundCommand {
|
public class DecompilerParameterIdCmd extends BackgroundCommand {
|
||||||
|
@ -74,7 +74,7 @@ public class DecompilerParameterIdCmd extends BackgroundCommand {
|
||||||
monitor.setMessage("Analyzing Call Hierarchy...");
|
monitor.setMessage("Analyzing Call Hierarchy...");
|
||||||
AcyclicCallGraphBuilder builder =
|
AcyclicCallGraphBuilder builder =
|
||||||
new AcyclicCallGraphBuilder(program, entryPoints, true);
|
new AcyclicCallGraphBuilder(program, entryPoints, true);
|
||||||
DependencyGraph<Address> graph = builder.getDependencyGraph(monitor);
|
AbstractDependencyGraph<Address> graph = builder.getDependencyGraph(monitor);
|
||||||
if (graph.isEmpty()) {
|
if (graph.isEmpty()) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,7 +33,7 @@ import ghidra.program.model.symbol.SourceType;
|
||||||
import ghidra.program.model.util.AcyclicCallGraphBuilder;
|
import ghidra.program.model.util.AcyclicCallGraphBuilder;
|
||||||
import ghidra.util.Msg;
|
import ghidra.util.Msg;
|
||||||
import ghidra.util.exception.CancelledException;
|
import ghidra.util.exception.CancelledException;
|
||||||
import ghidra.util.graph.DependencyGraph;
|
import ghidra.util.graph.AbstractDependencyGraph;
|
||||||
import ghidra.util.task.TaskMonitor;
|
import ghidra.util.task.TaskMonitor;
|
||||||
|
|
||||||
public class DecompilerCallConventionAnalyzer extends AbstractAnalyzer {
|
public class DecompilerCallConventionAnalyzer extends AbstractAnalyzer {
|
||||||
|
@ -138,7 +138,7 @@ public class DecompilerCallConventionAnalyzer extends AbstractAnalyzer {
|
||||||
monitor.setMessage("Analyzing Call Hierarchy...");
|
monitor.setMessage("Analyzing Call Hierarchy...");
|
||||||
AcyclicCallGraphBuilder builder =
|
AcyclicCallGraphBuilder builder =
|
||||||
new AcyclicCallGraphBuilder(program, functionEntries, true);
|
new AcyclicCallGraphBuilder(program, functionEntries, true);
|
||||||
DependencyGraph<Address> graph = builder.getDependencyGraph(monitor);
|
AbstractDependencyGraph<Address> graph = builder.getDependencyGraph(monitor);
|
||||||
if (graph.isEmpty()) {
|
if (graph.isEmpty()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,16 +15,16 @@
|
||||||
*/
|
*/
|
||||||
package generic.concurrent;
|
package generic.concurrent;
|
||||||
|
|
||||||
import ghidra.util.graph.DependencyGraph;
|
import ghidra.util.graph.AbstractDependencyGraph;
|
||||||
import ghidra.util.task.TaskMonitor;
|
import ghidra.util.task.TaskMonitor;
|
||||||
|
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
public class ConcurrentGraphQ<I> {
|
public class ConcurrentGraphQ<I> {
|
||||||
private ConcurrentQ<I, Object> queue;
|
private ConcurrentQ<I, Object> queue;
|
||||||
private DependencyGraph<I> graph;
|
private AbstractDependencyGraph<I> graph;
|
||||||
|
|
||||||
public ConcurrentGraphQ(QRunnable<I> runnable, DependencyGraph<I> graph, GThreadPool pool,
|
public ConcurrentGraphQ(QRunnable<I> runnable, AbstractDependencyGraph<I> graph, GThreadPool pool,
|
||||||
TaskMonitor monitor) {
|
TaskMonitor monitor) {
|
||||||
this.graph = graph;
|
this.graph = graph;
|
||||||
// @formatter:off
|
// @formatter:off
|
||||||
|
|
|
@ -0,0 +1,354 @@
|
||||||
|
/* ###
|
||||||
|
* IP: GHIDRA
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package ghidra.util.graph;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class for managing the visiting (processing) of a set of values where some values depend
|
||||||
|
* on other values being process before them. In other words, an acyclic directed graph will
|
||||||
|
* be formed where the vertexes are the values and the edges represent dependencies. Values can
|
||||||
|
* only be removed if they have no dependencies. Since the graph is acyclic, as values are removed
|
||||||
|
* that have no dependencies, other nodes that depend on those nodes will become eligible for
|
||||||
|
* processing and removal. If cycles are introduced, they will eventually cause an IllegalState
|
||||||
|
* exception to occur when removing and processing values. There is also a hasCycles() method
|
||||||
|
* that can be called before processing to find cycle problems up front without wasting time
|
||||||
|
* processing values.
|
||||||
|
*
|
||||||
|
* @param <T> the type of value. Some concrete classes might have restrictions on T.
|
||||||
|
*
|
||||||
|
* @see DependencyGraph
|
||||||
|
* @see DeterministicDependencyGraph
|
||||||
|
*/
|
||||||
|
public abstract class AbstractDependencyGraph<T> {
|
||||||
|
protected Map<T, DependencyNode> nodeMap;
|
||||||
|
protected Set<T> unvisitedIndependentSet;
|
||||||
|
private int visitedButNotDeletedCount = 0;
|
||||||
|
|
||||||
|
public AbstractDependencyGraph() {
|
||||||
|
nodeMap = createNodeMap();
|
||||||
|
unvisitedIndependentSet = createNodeSet();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<T, DependencyNode> getNodeMap() {
|
||||||
|
return nodeMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates the Map of Nodes to {@link DependencyNode}s appropriate for the implementer.
|
||||||
|
* @return a new Map of Nodes to {@link DependencyNode}s.
|
||||||
|
*/
|
||||||
|
protected abstract Map<T, DependencyNode> createNodeMap();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates the Set of Nodes appropriate for the implementer.
|
||||||
|
* @return a new Set of Nodes.
|
||||||
|
*/
|
||||||
|
protected abstract Set<T> createNodeSet();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates the Set of {@link DependencyNode}s appropriate for the implementer.
|
||||||
|
* @return a new Set of {@link DependencyNode}s.
|
||||||
|
*/
|
||||||
|
protected abstract Set<DependencyNode> createDependencyNodeSet();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a copy of this graph.
|
||||||
|
* @return a copy of this graph.
|
||||||
|
*/
|
||||||
|
public abstract AbstractDependencyGraph<T> copy();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copy constructor
|
||||||
|
* Cannot be abstracted here. Each individual implementer must implement their own.
|
||||||
|
* @param other the other DependencyGraph to copy
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds the value to this graph.
|
||||||
|
* @param value the value to add
|
||||||
|
*/
|
||||||
|
public synchronized void addValue(T value) {
|
||||||
|
getOrCreateDependencyNode(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the number of values in this graph.
|
||||||
|
* @return the number of values in this graph.
|
||||||
|
*/
|
||||||
|
public synchronized int size() {
|
||||||
|
return nodeMap.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the graph has no values;
|
||||||
|
* @return true if the graph has no values;
|
||||||
|
*/
|
||||||
|
public synchronized boolean isEmpty() {
|
||||||
|
return nodeMap.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if this graph has the given key.
|
||||||
|
* @param value the value to check if its in this graph
|
||||||
|
* @return true if this graph has the given key.
|
||||||
|
*/
|
||||||
|
public synchronized boolean contains(T value) {
|
||||||
|
return nodeMap.containsKey(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the set of values in this graph.
|
||||||
|
* @return the set of values in this graph.
|
||||||
|
*/
|
||||||
|
public synchronized Set<T> getValues() {
|
||||||
|
return new HashSet<>(nodeMap.keySet());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the set of values in this graph.
|
||||||
|
* @return the set of values in this graph.
|
||||||
|
*/
|
||||||
|
public abstract Set<T> getNodeMapValues();
|
||||||
|
|
||||||
|
private DependencyNode getOrCreateDependencyNode(T value) {
|
||||||
|
DependencyNode dependencyNode = nodeMap.get(value);
|
||||||
|
if (dependencyNode == null) {
|
||||||
|
dependencyNode = new DependencyNode(value);
|
||||||
|
nodeMap.put(value, dependencyNode);
|
||||||
|
unvisitedIndependentSet.add(value);
|
||||||
|
}
|
||||||
|
return dependencyNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a dependency such that value1 depends on value2. Both value1 and value2 will be
|
||||||
|
* added to the graph if they are not already in the graph.
|
||||||
|
* @param value1 the value that depends on value2
|
||||||
|
* @param value2 the value that value1 is depending on
|
||||||
|
*/
|
||||||
|
public synchronized void addDependency(T value1, T value2) {
|
||||||
|
DependencyNode valueNode1 = getOrCreateDependencyNode(value1);
|
||||||
|
DependencyNode valueNode2 = getOrCreateDependencyNode(value2);
|
||||||
|
valueNode2.addNodeThatDependsOnMe(valueNode1);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if there are unvisited values ready (no dependencies) for processing.
|
||||||
|
*
|
||||||
|
* @return true if there are unvisited values ready for processing.
|
||||||
|
*
|
||||||
|
* @exception IllegalStateException is thrown if the graph is not empty and there are no nodes
|
||||||
|
* without dependency which indicates there is a cycle in the graph.
|
||||||
|
*/
|
||||||
|
public synchronized boolean hasUnVisitedIndependentValues() {
|
||||||
|
if (!unvisitedIndependentSet.isEmpty()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
checkCycleState();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes and returns a value that has no dependencies from the graph. If the graph is empty
|
||||||
|
* or all the nodes without dependencies are currently visited, then null will be returned.
|
||||||
|
* NOTE: If the getUnvisitedIndependentValues() method has been called(), this method may
|
||||||
|
* return null until all those "visited" nodes are removed from the graph.
|
||||||
|
* @return return an arbitrary value that has no dependencies and hasn't been visited or null.
|
||||||
|
*/
|
||||||
|
public synchronized T pop() {
|
||||||
|
checkCycleState();
|
||||||
|
if (unvisitedIndependentSet.isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
T value = unvisitedIndependentSet.iterator().next();
|
||||||
|
unvisitedIndependentSet.remove(value);
|
||||||
|
remove(value);
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkCycleState() {
|
||||||
|
if (!isEmpty() && unvisitedIndependentSet.isEmpty() && visitedButNotDeletedCount == 0) {
|
||||||
|
throw new IllegalStateException("Cycle detected!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if this graph has cycles. Normal processing of this graph will eventually reveal
|
||||||
|
* a cycle and throw an exception at the time it is detected. This method allows for a
|
||||||
|
* "fail fast" way to detect cycles.
|
||||||
|
* @return true if cycles exist in the graph.
|
||||||
|
*/
|
||||||
|
public synchronized boolean hasCycles() {
|
||||||
|
try {
|
||||||
|
Set<T> visited = createNodeSet();
|
||||||
|
|
||||||
|
while (!unvisitedIndependentSet.isEmpty()) {
|
||||||
|
Collection<T> values = getUnvisitedIndependentValues();
|
||||||
|
visited.addAll(values);
|
||||||
|
|
||||||
|
for (T k : values) {
|
||||||
|
DependencyNode node = nodeMap.get(k);
|
||||||
|
node.releaseDependencies();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (visited.size() != nodeMap.size()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
reset();
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void reset() {
|
||||||
|
visitedButNotDeletedCount = 0;
|
||||||
|
for (DependencyNode node : nodeMap.values()) {
|
||||||
|
node.numberOfNodesThatIDependOn = 0;
|
||||||
|
}
|
||||||
|
for (DependencyNode node : nodeMap.values()) {
|
||||||
|
if (node.setOfNodesThatDependOnMe != null) {
|
||||||
|
for (DependencyNode child : node.setOfNodesThatDependOnMe) {
|
||||||
|
unvisitedIndependentSet.remove(child.value);
|
||||||
|
child.numberOfNodesThatIDependOn++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
unvisitedIndependentSet = getAllIndependentValues();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a set of all values that have no dependencies. As values are removed from the
|
||||||
|
* graph, dependencies will be removed and additional values will be eligible to be returned
|
||||||
|
* by this method. Once a value has been retrieved using this method, it will be considered
|
||||||
|
* "visited" and future calls to this method will not include those values. To continue
|
||||||
|
* processing the values in the graph, all values return from this method should eventually
|
||||||
|
* be deleted from the graph to "free up" other values. NOTE: values retrieved by this method
|
||||||
|
* will no longer be eligible for return by the pop() method.
|
||||||
|
*
|
||||||
|
* @return the set of values without dependencies that have never been returned by this method
|
||||||
|
* before.
|
||||||
|
*/
|
||||||
|
public synchronized Set<T> getUnvisitedIndependentValues() {
|
||||||
|
checkCycleState();
|
||||||
|
visitedButNotDeletedCount += unvisitedIndependentSet.size();
|
||||||
|
Set<T> returnCollection = unvisitedIndependentSet;
|
||||||
|
unvisitedIndependentSet = createNodeSet();
|
||||||
|
return returnCollection;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the set of all values that have no dependencies regardless of whether or not
|
||||||
|
* they have been "visited" (by the getUnvisitedIndependentValues() method.
|
||||||
|
* @return return the set of all values that have no dependencies.
|
||||||
|
*/
|
||||||
|
public synchronized Set<T> getAllIndependentValues() {
|
||||||
|
Set<T> set = createNodeSet();
|
||||||
|
for (DependencyNode node : nodeMap.values()) {
|
||||||
|
if (node.numberOfNodesThatIDependOn == 0) {
|
||||||
|
set.add(node.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return set;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes the value from the graph. Any dependency from this node to another will be removed,
|
||||||
|
* possible allowing nodes that depend on this node to be eligible for processing.
|
||||||
|
* @param value the value to remove from the graph.
|
||||||
|
*/
|
||||||
|
public synchronized void remove(T value) {
|
||||||
|
DependencyNode node = nodeMap.remove(value);
|
||||||
|
if (node != null) {
|
||||||
|
node.releaseDependencies();
|
||||||
|
if (unvisitedIndependentSet.remove(value)) {
|
||||||
|
visitedButNotDeletedCount--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a set of values that depend on the given value.
|
||||||
|
* @param value the value that other values may depend on.
|
||||||
|
* @return a set of values that depend on the given value.
|
||||||
|
*/
|
||||||
|
public synchronized Set<T> getDependentValues(T value) {
|
||||||
|
Set<T> set = createNodeSet();
|
||||||
|
|
||||||
|
DependencyNode node = nodeMap.get(value);
|
||||||
|
if (node != null && node.setOfNodesThatDependOnMe != null) {
|
||||||
|
for (DependencyNode child : node.setOfNodesThatDependOnMe) {
|
||||||
|
set.add(child.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return set;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected class DependencyNode {
|
||||||
|
private final T value;
|
||||||
|
private Set<DependencyNode> setOfNodesThatDependOnMe;
|
||||||
|
private int numberOfNodesThatIDependOn = 0;
|
||||||
|
|
||||||
|
DependencyNode(T value) {
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public T getValue() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Set<DependencyNode> getSetOfNodesThatDependOnMe() {
|
||||||
|
return setOfNodesThatDependOnMe;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getNumberOfNodesThatIDependOn() {
|
||||||
|
return numberOfNodesThatIDependOn;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void releaseDependencies() {
|
||||||
|
if (setOfNodesThatDependOnMe == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (DependencyNode node : setOfNodesThatDependOnMe) {
|
||||||
|
if (--node.numberOfNodesThatIDependOn == 0) {
|
||||||
|
unvisitedIndependentSet.add(node.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addNodeThatDependsOnMe(DependencyNode node) {
|
||||||
|
if (setOfNodesThatDependOnMe == null) {
|
||||||
|
setOfNodesThatDependOnMe = createDependencyNodeSet();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (setOfNodesThatDependOnMe.add(node)) {
|
||||||
|
// if not already added, increment the dependent node's count so that it knows
|
||||||
|
// how many nodes it depends on.
|
||||||
|
node.numberOfNodesThatIDependOn++;
|
||||||
|
|
||||||
|
unvisitedIndependentSet.remove(node.value); // it has at least one dependency now
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return value == null ? "" : value.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -15,33 +15,23 @@
|
||||||
*/
|
*/
|
||||||
package ghidra.util.graph;
|
package ghidra.util.graph;
|
||||||
|
|
||||||
import java.util.Collection;
|
import java.util.*;
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class for managing the visiting (processing) of a set of values where some values depend
|
* Original Dependency Graph implementation that uses {@link HashMap}s and {@link HashSet}s.
|
||||||
* on other values being process before them. In other words, an acyclic directed graph will
|
* Side affect of these is that data pulled from the graph ({@link #pop()}) is not performed
|
||||||
* be formed where the vertexes are the values and the edges represent dependencies. Values can
|
* in a deterministic order. However, load time for the graph is O(1).
|
||||||
* only be removed if they have no dependencies. Since the graph is acyclic, as values are removed
|
|
||||||
* that have no dependencies, other nodes that depend on those nodes will become eligible for
|
|
||||||
* processing and removal. If cycles are introduced, they will eventually cause an IllegalState
|
|
||||||
* exception to occur when removing and processing values. There is also a hasCycles() method
|
|
||||||
* that can be called before processing to find cycle problems up front without wasting time
|
|
||||||
* processing values.
|
|
||||||
*
|
*
|
||||||
* @param <T> the type of value. This class uses the values as keys in HashSets, so the value
|
* @param <T> the type of value. This class uses the values as keys in HashSets, so the value
|
||||||
* type must be meet the equals() and hashCode() requirements for hashing.
|
* type must be meet the equals() and hashCode() requirements for hashing.
|
||||||
|
*
|
||||||
|
* @see AbstractDependencyGraph
|
||||||
|
* @see DeterministicDependencyGraph
|
||||||
*/
|
*/
|
||||||
public class DependencyGraph<T> {
|
public class DependencyGraph<T> extends AbstractDependencyGraph<T> {
|
||||||
private Map<T, DependencyNode> nodeMap = new HashMap<T, DependencyNode>();
|
|
||||||
private Set<T> unvisitedIndependentSet = new HashSet<T>();
|
|
||||||
private int visitedButNotDeletedCount = 0;
|
|
||||||
|
|
||||||
public DependencyGraph() {
|
public DependencyGraph() {
|
||||||
|
super();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -51,278 +41,39 @@ public class DependencyGraph<T> {
|
||||||
public DependencyGraph(DependencyGraph<T> other) {
|
public DependencyGraph(DependencyGraph<T> other) {
|
||||||
synchronized (other) {
|
synchronized (other) {
|
||||||
for (DependencyNode node : other.nodeMap.values()) {
|
for (DependencyNode node : other.nodeMap.values()) {
|
||||||
addValue(node.value);
|
addValue(node.getValue());
|
||||||
if (node.setOfNodesThatDependOnMe != null) {
|
if (node.getSetOfNodesThatDependOnMe() != null) {
|
||||||
for (DependencyNode child : node.setOfNodesThatDependOnMe) {
|
for (DependencyNode child : node.getSetOfNodesThatDependOnMe()) {
|
||||||
addDependency(child.value, node.value);
|
addDependency(child.getValue(), node.getValue());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
@Override
|
||||||
* Adds the value to this graph.
|
public AbstractDependencyGraph<T> copy() {
|
||||||
* @param value the value to add
|
return new DependencyGraph<>(this);
|
||||||
*/
|
|
||||||
public synchronized void addValue(T value) {
|
|
||||||
getOrCreateDependencyNode(value);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
@Override
|
||||||
* Returns the number of values in this graph.
|
protected Map<T, DependencyNode> createNodeMap() {
|
||||||
* @return the number of values in this graph.
|
return new HashMap<>();
|
||||||
*/
|
|
||||||
public synchronized int size() {
|
|
||||||
return nodeMap.size();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
@Override
|
||||||
* Returns true if the graph has no values;
|
protected Set<T> createNodeSet() {
|
||||||
* @return true if the graph has no values;
|
return new HashSet<>();
|
||||||
*/
|
|
||||||
public synchronized boolean isEmpty() {
|
|
||||||
return nodeMap.isEmpty();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
@Override
|
||||||
* Returns true if this graph has the given key.
|
protected Set<DependencyNode> createDependencyNodeSet() {
|
||||||
* @param value the value to check if its in this graph
|
return new HashSet<>();
|
||||||
* @return true if this graph has the given key.
|
|
||||||
*/
|
|
||||||
public synchronized boolean contains(T value) {
|
|
||||||
return nodeMap.containsKey(value);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
@Override
|
||||||
* Returns the set of values in this graph.
|
public synchronized Set<T> getNodeMapValues() {
|
||||||
* @return the set of values in this graph.
|
return new HashSet<>(nodeMap.keySet());
|
||||||
*/
|
|
||||||
public synchronized Set<T> getValues() {
|
|
||||||
return new HashSet<T>(nodeMap.keySet());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a copy of this graph.
|
|
||||||
* @return a copy of this graph.
|
|
||||||
*/
|
|
||||||
public synchronized DependencyGraph<T> copy() {
|
|
||||||
return new DependencyGraph<T>(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
private DependencyNode getOrCreateDependencyNode(T value) {
|
|
||||||
DependencyNode dependencyNode = nodeMap.get(value);
|
|
||||||
if (dependencyNode == null) {
|
|
||||||
dependencyNode = new DependencyNode(value);
|
|
||||||
nodeMap.put(value, dependencyNode);
|
|
||||||
unvisitedIndependentSet.add(value);
|
|
||||||
}
|
|
||||||
return dependencyNode;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add a dependency such that value1 depends on value2. Both value1 and value2 will be
|
|
||||||
* added to the graph if they are not already in the graph.
|
|
||||||
* @param value1 the value that depends on value2
|
|
||||||
* @param value2 the value that value1 is depending on
|
|
||||||
*/
|
|
||||||
public synchronized void addDependency(T value1, T value2) {
|
|
||||||
DependencyNode valueNode1 = getOrCreateDependencyNode(value1);
|
|
||||||
DependencyNode valueNode2 = getOrCreateDependencyNode(value2);
|
|
||||||
valueNode2.addNodeThatDependsOnMe(valueNode1);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns true if there are unvisited values ready (no dependencies) for processing.
|
|
||||||
*
|
|
||||||
* @return true if there are unvisited values ready for processing.
|
|
||||||
*
|
|
||||||
* @exception IllegalStateException is thrown if the graph is not empty and there are no nodes
|
|
||||||
* without dependency which indicates there is a cycle in the graph.
|
|
||||||
*/
|
|
||||||
public synchronized boolean hasUnVisitedIndependentValues() {
|
|
||||||
if (!unvisitedIndependentSet.isEmpty()) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
checkCycleState();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Removes and returns a value that has no dependencies from the graph. If the graph is empty
|
|
||||||
* or all the nodes without dependencies are currently visited, then null will be returned.
|
|
||||||
* NOTE: If the getUnvisitedIndependentValues() method has been called(), this method may
|
|
||||||
* return null until all those "visited" nodes are removed from the graph.
|
|
||||||
* @return return an arbitrary value that has no dependencies and hasn't been visited or null.
|
|
||||||
*/
|
|
||||||
public synchronized T pop() {
|
|
||||||
checkCycleState();
|
|
||||||
if (unvisitedIndependentSet.isEmpty()) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
T value = unvisitedIndependentSet.iterator().next();
|
|
||||||
unvisitedIndependentSet.remove(value);
|
|
||||||
remove(value);
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void checkCycleState() {
|
|
||||||
if (!isEmpty() && unvisitedIndependentSet.isEmpty() && visitedButNotDeletedCount == 0) {
|
|
||||||
throw new IllegalStateException("Cycle detected!");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if this graph has cycles. Normal processing of this graph will eventually reveal
|
|
||||||
* a cycle and throw an exception at the time it is detected. This method allows for a
|
|
||||||
* "fail fast" way to detect cycles.
|
|
||||||
* @return true if cycles exist in the graph.
|
|
||||||
*/
|
|
||||||
public synchronized boolean hasCycles() {
|
|
||||||
try {
|
|
||||||
Set<T> visited = new HashSet<T>();
|
|
||||||
|
|
||||||
while (!unvisitedIndependentSet.isEmpty()) {
|
|
||||||
Collection<T> values = getUnvisitedIndependentValues();
|
|
||||||
visited.addAll(values);
|
|
||||||
|
|
||||||
for (T k : values) {
|
|
||||||
DependencyNode node = nodeMap.get(k);
|
|
||||||
node.releaseDependencies();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (visited.size() != nodeMap.size()) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
finally {
|
|
||||||
reset();
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void reset() {
|
|
||||||
visitedButNotDeletedCount = 0;
|
|
||||||
for (DependencyNode node : nodeMap.values()) {
|
|
||||||
node.numberOfNodesThatIDependOn = 0;
|
|
||||||
}
|
|
||||||
for (DependencyNode node : nodeMap.values()) {
|
|
||||||
if (node.setOfNodesThatDependOnMe != null) {
|
|
||||||
for (DependencyNode child : node.setOfNodesThatDependOnMe) {
|
|
||||||
unvisitedIndependentSet.remove(child.value);
|
|
||||||
child.numberOfNodesThatIDependOn++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
unvisitedIndependentSet = getAllIndependentValues();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a set of all values that have no dependencies. As values are removed from the
|
|
||||||
* graph, dependencies will be removed and additional values will be eligible to be returned
|
|
||||||
* by this method. Once a value has been retrieved using this method, it will be considered
|
|
||||||
* "visited" and future calls to this method will not include those values. To continue
|
|
||||||
* processing the values in the graph, all values return from this method should eventually
|
|
||||||
* be deleted from the graph to "free up" other values. NOTE: values retrieved by this method
|
|
||||||
* will no longer be eligible for return by the pop() method.
|
|
||||||
*
|
|
||||||
* @return the set of values without dependencies that have never been returned by this method
|
|
||||||
* before.
|
|
||||||
*/
|
|
||||||
public synchronized Set<T> getUnvisitedIndependentValues() {
|
|
||||||
checkCycleState();
|
|
||||||
visitedButNotDeletedCount += unvisitedIndependentSet.size();
|
|
||||||
Set<T> returnCollection = unvisitedIndependentSet;
|
|
||||||
unvisitedIndependentSet = new HashSet<T>();
|
|
||||||
return returnCollection;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the set of all values that have no dependencies regardless of whether or not
|
|
||||||
* they have been "visited" (by the getUnvisitedIndependentValues() method.
|
|
||||||
* @return return the set of all values that have no dependencies.
|
|
||||||
*/
|
|
||||||
public synchronized Set<T> getAllIndependentValues() {
|
|
||||||
Set<T> set = new HashSet<T>();
|
|
||||||
for (DependencyNode node : nodeMap.values()) {
|
|
||||||
if (node.numberOfNodesThatIDependOn == 0) {
|
|
||||||
set.add(node.value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return set;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Removes the value from the graph. Any dependency from this node to another will be removed,
|
|
||||||
* possible allowing nodes that depend on this node to be eligible for processing.
|
|
||||||
* @param value the value to remove from the graph.
|
|
||||||
*/
|
|
||||||
public synchronized void remove(T value) {
|
|
||||||
DependencyNode node = nodeMap.remove(value);
|
|
||||||
if (node != null) {
|
|
||||||
node.releaseDependencies();
|
|
||||||
if (unvisitedIndependentSet.remove(value)) {
|
|
||||||
visitedButNotDeletedCount--;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a set of values that depend on the given value.
|
|
||||||
* @param value the value that other values may depend on.
|
|
||||||
* @return a set of values that depend on the given value.
|
|
||||||
*/
|
|
||||||
public synchronized Set<T> getDependentValues(T value) {
|
|
||||||
Set<T> set = new HashSet<T>();
|
|
||||||
|
|
||||||
DependencyNode node = nodeMap.get(value);
|
|
||||||
if (node != null && node.setOfNodesThatDependOnMe != null) {
|
|
||||||
for (DependencyNode child : node.setOfNodesThatDependOnMe) {
|
|
||||||
set.add(child.value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return set;
|
|
||||||
}
|
|
||||||
|
|
||||||
private class DependencyNode {
|
|
||||||
private final T value;
|
|
||||||
private Set<DependencyNode> setOfNodesThatDependOnMe;
|
|
||||||
private int numberOfNodesThatIDependOn = 0;
|
|
||||||
|
|
||||||
DependencyNode(T value) {
|
|
||||||
this.value = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void releaseDependencies() {
|
|
||||||
if (setOfNodesThatDependOnMe == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
for (DependencyNode node : setOfNodesThatDependOnMe) {
|
|
||||||
if (--node.numberOfNodesThatIDependOn == 0) {
|
|
||||||
unvisitedIndependentSet.add(node.value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void addNodeThatDependsOnMe(DependencyNode node) {
|
|
||||||
if (setOfNodesThatDependOnMe == null) {
|
|
||||||
setOfNodesThatDependOnMe = new HashSet<DependencyGraph<T>.DependencyNode>();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (setOfNodesThatDependOnMe.add(node)) {
|
|
||||||
// if not already added, increment the dependent node's count so that it knows
|
|
||||||
// how many nodes it depends on.
|
|
||||||
node.numberOfNodesThatIDependOn++;
|
|
||||||
|
|
||||||
unvisitedIndependentSet.remove(node.value); // it has at least one dependency now
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return value == null ? "" : value.toString();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,84 @@
|
||||||
|
/* ###
|
||||||
|
* IP: GHIDRA
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package ghidra.util.graph;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
import org.apache.commons.collections4.set.ListOrderedSet;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dependency Graph that uses {@link TreeMap}s and {@link ListOrderedSet}s to provide
|
||||||
|
* determinism in pulling ({@link #pop()}) from the graph. This class seems to consume more
|
||||||
|
* memory than {@link DependencyGraph}, and if memory is not an issue, it also seems to be
|
||||||
|
* slightly faster as well.
|
||||||
|
* <P>
|
||||||
|
* This class was implemented to provide determinism while doing
|
||||||
|
* developmental debugging.
|
||||||
|
*
|
||||||
|
* @param <T> the type of value.
|
||||||
|
*
|
||||||
|
* @see AbstractDependencyGraph
|
||||||
|
* @see DependencyGraph
|
||||||
|
*/
|
||||||
|
public class DeterministicDependencyGraph<T> extends AbstractDependencyGraph<T> {
|
||||||
|
|
||||||
|
public DeterministicDependencyGraph() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copy constructor
|
||||||
|
* @param other the other DependencyGraph to copy
|
||||||
|
*/
|
||||||
|
public DeterministicDependencyGraph(DeterministicDependencyGraph<T> other) {
|
||||||
|
synchronized (other) {
|
||||||
|
for (DependencyNode node : other.nodeMap.values()) {
|
||||||
|
addValue(node.getValue());
|
||||||
|
if (node.getSetOfNodesThatDependOnMe() != null) {
|
||||||
|
for (DependencyNode child : node.getSetOfNodesThatDependOnMe()) {
|
||||||
|
addDependency(child.getValue(), node.getValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AbstractDependencyGraph<T> copy() {
|
||||||
|
return new DeterministicDependencyGraph<>(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Map<T, DependencyNode> createNodeMap() {
|
||||||
|
return new TreeMap<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Set<T> createNodeSet() {
|
||||||
|
return new ListOrderedSet<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Set<DependencyNode> createDependencyNodeSet() {
|
||||||
|
return new ListOrderedSet<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized Set<T> getNodeMapValues() {
|
||||||
|
return ListOrderedSet.listOrderedSet(nodeMap.keySet());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -23,6 +23,7 @@ import org.junit.Assert;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import generic.test.AbstractGenericTest;
|
import generic.test.AbstractGenericTest;
|
||||||
|
import ghidra.util.graph.AbstractDependencyGraph;
|
||||||
import ghidra.util.graph.DependencyGraph;
|
import ghidra.util.graph.DependencyGraph;
|
||||||
import ghidra.util.task.TaskMonitor;
|
import ghidra.util.task.TaskMonitor;
|
||||||
|
|
||||||
|
@ -32,11 +33,11 @@ public class ConcurrentGraphQTest extends AbstractGenericTest {
|
||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void test() throws InterruptedException, Exception {
|
public void test() throws InterruptedException, Exception {
|
||||||
final ArrayList<String> completionOrder = new ArrayList<String>();
|
final ArrayList<String> completionOrder = new ArrayList<>();
|
||||||
|
|
||||||
DependencyGraph<String> graph = new DependencyGraph<String>();
|
AbstractDependencyGraph<String> graph = new DependencyGraph<>();
|
||||||
graph.addDependency("@0", "A8");
|
graph.addDependency("@0", "A8");
|
||||||
graph.addDependency("@1", "A1");
|
graph.addDependency("@1", "A1");
|
||||||
graph.addDependency("@2", "A7");
|
graph.addDependency("@2", "A7");
|
||||||
|
@ -71,10 +72,10 @@ public class ConcurrentGraphQTest extends AbstractGenericTest {
|
||||||
|
|
||||||
assertTrue(!graph.hasCycles());
|
assertTrue(!graph.hasCycles());
|
||||||
|
|
||||||
DependencyGraph<String> savedGraph = graph.copy();
|
AbstractDependencyGraph<String> savedGraph = graph.copy();
|
||||||
|
|
||||||
GThreadPool pool = GThreadPool.getPrivateThreadPool("ConcurrentGraphQ Test");
|
GThreadPool pool = GThreadPool.getPrivateThreadPool("ConcurrentGraphQ Test");
|
||||||
QRunnable<String> runnable = new QRunnable<String>() {
|
QRunnable<String> runnable = new QRunnable<>() {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void run(String item, TaskMonitor monitor) throws Exception {
|
public void run(String item, TaskMonitor monitor) throws Exception {
|
||||||
|
@ -87,7 +88,7 @@ public class ConcurrentGraphQTest extends AbstractGenericTest {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
ConcurrentGraphQ<String> queue = new ConcurrentGraphQ<String>(runnable, graph, pool, null);
|
ConcurrentGraphQ<String> queue = new ConcurrentGraphQ<>(runnable, graph, pool, null);
|
||||||
queue.execute();
|
queue.execute();
|
||||||
checkOrderSatisfiesDependencies(savedGraph, completionOrder);
|
checkOrderSatisfiesDependencies(savedGraph, completionOrder);
|
||||||
}
|
}
|
||||||
|
@ -101,7 +102,7 @@ public class ConcurrentGraphQTest extends AbstractGenericTest {
|
||||||
* @param visitedOrder the actual execution order to be tested
|
* @param visitedOrder the actual execution order to be tested
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
public void checkOrderSatisfiesDependencies(DependencyGraph<String> dependencyGraph,
|
public void checkOrderSatisfiesDependencies(AbstractDependencyGraph<String> dependencyGraph,
|
||||||
List<String> visitedOrder) {
|
List<String> visitedOrder) {
|
||||||
|
|
||||||
if (visitedOrder.size() > dependencyGraph.size()) {
|
if (visitedOrder.size() > dependencyGraph.size()) {
|
||||||
|
@ -111,12 +112,12 @@ public class ConcurrentGraphQTest extends AbstractGenericTest {
|
||||||
Assert.fail("Not all items in the graph were visited");
|
Assert.fail("Not all items in the graph were visited");
|
||||||
}
|
}
|
||||||
|
|
||||||
HashSet<String> items = new HashSet<String>(visitedOrder);
|
HashSet<String> items = new HashSet<>(visitedOrder);
|
||||||
if (items.size() != visitedOrder.size()) {
|
if (items.size() != visitedOrder.size()) {
|
||||||
Assert.fail("duplicate item(s) in linearOrder\n");
|
Assert.fail("duplicate item(s) in linearOrder\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
HashMap<String, Integer> visitedOrderMap = new HashMap<String, Integer>();
|
HashMap<String, Integer> visitedOrderMap = new HashMap<>();
|
||||||
for (int i = 0; i < visitedOrder.size(); i++) {
|
for (int i = 0; i < visitedOrder.size(); i++) {
|
||||||
visitedOrderMap.put(visitedOrder.get(i), i);
|
visitedOrderMap.put(visitedOrder.get(i), i);
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,150 @@
|
||||||
|
/* ###
|
||||||
|
* IP: GHIDRA
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package ghidra.util.datastruct;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
import ghidra.util.graph.DependencyGraph;
|
||||||
|
import ghidra.util.graph.DeterministicDependencyGraph;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performs computation of dependency graph
|
||||||
|
* Does not test the accuracy/functionality of Dependency Graphs.
|
||||||
|
*/
|
||||||
|
public class DependencyGraphPerformanceTest {
|
||||||
|
|
||||||
|
// Specifying the graph edge density ratio: number of edges over the
|
||||||
|
// number of possible edges (|E|/P(|V|,2).
|
||||||
|
// For sparse graphs, where |E|=O(n), can do |E|/|V|.
|
||||||
|
// TODO: What is good for testing.
|
||||||
|
private static final long GRAPH_SEED = 12345L;
|
||||||
|
private static final double GRAPH_EDGE_DENSITY = 0.20;
|
||||||
|
private static final int NUM_DEPENDENCIES = 30000;
|
||||||
|
private static final int GRAPH_SIZE = (int) (NUM_DEPENDENCIES / GRAPH_EDGE_DENSITY);
|
||||||
|
private static final boolean GRAPH_ALLOW_CYCLES = false;
|
||||||
|
|
||||||
|
private final List<DependencyRelation> testRelationships;
|
||||||
|
|
||||||
|
public DependencyGraphPerformanceTest() {
|
||||||
|
testRelationships = constructRandomRelationships(GRAPH_SEED, NUM_DEPENDENCIES, GRAPH_SIZE,
|
||||||
|
GRAPH_ALLOW_CYCLES);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Not intended for nightly or continuous testing. Comment in when needed during development.
|
||||||
|
// @Test
|
||||||
|
public void testLargeDependencyGraph() {
|
||||||
|
Timer timer = new Timer("DependencyGraph");
|
||||||
|
timer.mark();
|
||||||
|
DependencyGraph<String> graph = new DependencyGraph<>();
|
||||||
|
for (DependencyRelation relation : testRelationships) {
|
||||||
|
graph.addDependency(relation.dependent, relation.dependee);
|
||||||
|
}
|
||||||
|
timer.mark();
|
||||||
|
while (!graph.isEmpty()) {
|
||||||
|
graph.pop();
|
||||||
|
}
|
||||||
|
timer.mark();
|
||||||
|
System.out.println(timer);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Not intended for nightly or continuous testing. Comment in when needed during development.
|
||||||
|
// @Test
|
||||||
|
public void testLargeDeterministicDependencyGraph() {
|
||||||
|
Timer timer = new Timer("DeterministicDependencyGraph");
|
||||||
|
timer.mark();
|
||||||
|
DeterministicDependencyGraph<String> graph = new DeterministicDependencyGraph<>();
|
||||||
|
for (DependencyRelation relation : testRelationships) {
|
||||||
|
graph.addDependency(relation.dependent, relation.dependee);
|
||||||
|
}
|
||||||
|
timer.mark();
|
||||||
|
while (!graph.isEmpty()) {
|
||||||
|
graph.pop();
|
||||||
|
}
|
||||||
|
timer.mark();
|
||||||
|
System.out.println(timer);
|
||||||
|
}
|
||||||
|
|
||||||
|
private class Timer {
|
||||||
|
private String testName;
|
||||||
|
private List<Long> times = new ArrayList<>();
|
||||||
|
|
||||||
|
public Timer(String testName) {
|
||||||
|
this.testName = testName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void mark() {
|
||||||
|
times.add(System.currentTimeMillis());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
String report = testName + " (milliseconds)\n";
|
||||||
|
if (!times.isEmpty()) {
|
||||||
|
long prev = times.get(0);
|
||||||
|
long total = times.get(times.size() - 1) - prev;
|
||||||
|
for (int i = 1; i < times.size(); i++) {
|
||||||
|
long current = times.get(i);
|
||||||
|
long diff = current - prev;
|
||||||
|
report += String.format(" %03d: %d\n", i, diff);
|
||||||
|
prev = current;
|
||||||
|
}
|
||||||
|
report += String.format("total: %d\n", total);
|
||||||
|
}
|
||||||
|
return report;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class DependencyRelation {
|
||||||
|
public String dependent;
|
||||||
|
public String dependee;
|
||||||
|
|
||||||
|
public DependencyRelation(int dependentId, int dependeeId) {
|
||||||
|
dependent = "V" + dependentId;
|
||||||
|
dependee = "V" + dependeeId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<DependencyRelation> constructRandomRelationships(long seed, int numDependencies,
|
||||||
|
int graphSize, boolean allowCycles) {
|
||||||
|
List<DependencyRelation> relationships = new ArrayList<>();
|
||||||
|
Random generator = new Random(seed);
|
||||||
|
|
||||||
|
// Not taking a dependent beyond 90% of graph size; similar dependee only in latter
|
||||||
|
// 90%.
|
||||||
|
double factor = 0.90;
|
||||||
|
int limit = (int) (factor * graphSize);
|
||||||
|
int dependeeOffset = graphSize - limit;
|
||||||
|
assert limit != graphSize;
|
||||||
|
|
||||||
|
// TODO: currently ignoring allowCycles parameter.
|
||||||
|
// TODO: Do no cycles for now... ask if would need both types for performance testing.
|
||||||
|
// To disallow cycles, we simply are preventing the dependee number from being less than
|
||||||
|
// the dependent number. This weights the graph in an interesting way: dependents with
|
||||||
|
// smaller numbers will tend to have more dependees.
|
||||||
|
for (int i = 0; i < numDependencies; i++) {
|
||||||
|
int dependentId = generator.nextInt(limit);
|
||||||
|
int dependeeId;
|
||||||
|
do {
|
||||||
|
dependeeId = generator.nextInt(limit) + dependeeOffset;
|
||||||
|
}
|
||||||
|
while (dependeeId <= dependentId);
|
||||||
|
DependencyRelation relation = new DependencyRelation(dependentId, dependeeId);
|
||||||
|
relationships.add(relation);
|
||||||
|
}
|
||||||
|
return relationships;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -22,14 +22,95 @@ import java.util.*;
|
||||||
import org.junit.Assert;
|
import org.junit.Assert;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import ghidra.util.graph.DependencyGraph;
|
import ghidra.util.graph.*;
|
||||||
|
|
||||||
public class DependencyGraphTest {
|
public class DependencyGraphTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testSimpleCase() {
|
public void testSimpleCaseDependencyGraph() {
|
||||||
DependencyGraph<Integer> graph = new DependencyGraph<Integer>();
|
AbstractDependencyGraph<Integer> graph = new DependencyGraph<>();
|
||||||
|
runSimpleCase(graph);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSimpleCaseDeterministicDependencyGraph() {
|
||||||
|
AbstractDependencyGraph<Integer> graph = new DeterministicDependencyGraph<>();
|
||||||
|
runSimpleCase(graph);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testMultipleDependencyCaseDependencyGraph() {
|
||||||
|
AbstractDependencyGraph<Integer> graph = new DependencyGraph<>();
|
||||||
|
runMultipleDependencyCase(graph);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testMultipleDependencyCaseDeterministicDependencyGraph() {
|
||||||
|
AbstractDependencyGraph<Integer> graph = new DeterministicDependencyGraph<>();
|
||||||
|
runMultipleDependencyCase(graph);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPopDependencyGraph() {
|
||||||
|
AbstractDependencyGraph<Integer> graph = new DependencyGraph<>();
|
||||||
|
runPop(graph);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPopDeterministicDependencyGraph() {
|
||||||
|
AbstractDependencyGraph<Integer> graph = new DeterministicDependencyGraph<>();
|
||||||
|
runPop(graph);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPopWithCycleDependencyGraph() {
|
||||||
|
AbstractDependencyGraph<Integer> graph = new DependencyGraph<>();
|
||||||
|
runPopWithCycle(graph);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPopWithCycleDeterministicDependencyGraph() {
|
||||||
|
AbstractDependencyGraph<Integer> graph = new DeterministicDependencyGraph<>();
|
||||||
|
runPopWithCycle(graph);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCycleDetectionDependencyGraph() {
|
||||||
|
AbstractDependencyGraph<Integer> graph = new DependencyGraph<>();
|
||||||
|
runCycleDetection(graph);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCycleDetectionDeterministicDependencyGraph() {
|
||||||
|
AbstractDependencyGraph<Integer> graph = new DeterministicDependencyGraph<>();
|
||||||
|
runCycleDetection(graph);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCycleDetectionDoesNotCorruptGraphDependencyGraph() {
|
||||||
|
AbstractDependencyGraph<Integer> graph = new DependencyGraph<>();
|
||||||
|
runCycleDetectionDoesNotCorruptGraph(graph);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCycleDetectionDoesNotCorruptGraphDeterministicDependencyGraph() {
|
||||||
|
AbstractDependencyGraph<Integer> graph = new DeterministicDependencyGraph<>();
|
||||||
|
runCycleDetectionDoesNotCorruptGraph(graph);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testRandomProcessingOfDependenciesSimulationDependencyGraph() {
|
||||||
|
AbstractDependencyGraph<String> graph = new DependencyGraph<>();
|
||||||
|
runRandomProcessingOfDependenciesSimulation(graph);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testRandomProcessingOfDependenciesSimulationDeterministicDependencyGraph() {
|
||||||
|
AbstractDependencyGraph<String> graph = new DeterministicDependencyGraph<>();
|
||||||
|
runRandomProcessingOfDependenciesSimulation(graph);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void runSimpleCase(AbstractDependencyGraph<Integer> graph) {
|
||||||
graph.addDependency(1, 2);
|
graph.addDependency(1, 2);
|
||||||
graph.addDependency(2, 3);
|
graph.addDependency(2, 3);
|
||||||
graph.addDependency(3, 4);
|
graph.addDependency(3, 4);
|
||||||
|
@ -56,13 +137,9 @@ public class DependencyGraphTest {
|
||||||
graph.remove(1);
|
graph.remove(1);
|
||||||
set = graph.getUnvisitedIndependentValues();
|
set = graph.getUnvisitedIndependentValues();
|
||||||
assertTrue(set.isEmpty());
|
assertTrue(set.isEmpty());
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
private void runMultipleDependencyCase(AbstractDependencyGraph<Integer> graph) {
|
||||||
public void testMultipleDependencyCase() {
|
|
||||||
DependencyGraph<Integer> graph = new DependencyGraph<Integer>();
|
|
||||||
|
|
||||||
graph.addDependency(1, 2);
|
graph.addDependency(1, 2);
|
||||||
graph.addDependency(2, 3);
|
graph.addDependency(2, 3);
|
||||||
graph.addDependency(1, 3);
|
graph.addDependency(1, 3);
|
||||||
|
@ -84,13 +161,9 @@ public class DependencyGraphTest {
|
||||||
graph.remove(1);
|
graph.remove(1);
|
||||||
set = graph.getUnvisitedIndependentValues();
|
set = graph.getUnvisitedIndependentValues();
|
||||||
assertTrue(set.isEmpty());
|
assertTrue(set.isEmpty());
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
private void runPop(AbstractDependencyGraph<Integer> graph) {
|
||||||
public void testPop() {
|
|
||||||
DependencyGraph<Integer> graph = new DependencyGraph<Integer>();
|
|
||||||
|
|
||||||
graph.addDependency(1, 2);
|
graph.addDependency(1, 2);
|
||||||
graph.addDependency(2, 3);
|
graph.addDependency(2, 3);
|
||||||
graph.addDependency(3, 4);
|
graph.addDependency(3, 4);
|
||||||
|
@ -103,10 +176,7 @@ public class DependencyGraphTest {
|
||||||
assertNull(graph.pop());
|
assertNull(graph.pop());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
private void runPopWithCycle(AbstractDependencyGraph<Integer> graph) {
|
||||||
public void testPopWithCycle() {
|
|
||||||
DependencyGraph<Integer> graph = new DependencyGraph<Integer>();
|
|
||||||
|
|
||||||
graph.addDependency(1, 2);
|
graph.addDependency(1, 2);
|
||||||
graph.addDependency(2, 3);
|
graph.addDependency(2, 3);
|
||||||
graph.addDependency(3, 4);
|
graph.addDependency(3, 4);
|
||||||
|
@ -121,13 +191,9 @@ public class DependencyGraphTest {
|
||||||
catch (IllegalStateException e) {
|
catch (IllegalStateException e) {
|
||||||
// expected
|
// expected
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
private void runCycleDetection(AbstractDependencyGraph<Integer> graph) {
|
||||||
public void testCycleDetection() {
|
|
||||||
DependencyGraph<Integer> graph = new DependencyGraph<Integer>();
|
|
||||||
|
|
||||||
graph.addDependency(1, 2);
|
graph.addDependency(1, 2);
|
||||||
graph.addDependency(2, 3);
|
graph.addDependency(2, 3);
|
||||||
graph.addDependency(3, 4);
|
graph.addDependency(3, 4);
|
||||||
|
@ -137,13 +203,9 @@ public class DependencyGraphTest {
|
||||||
graph.addDependency(4, 1);
|
graph.addDependency(4, 1);
|
||||||
|
|
||||||
assertTrue(graph.hasCycles());
|
assertTrue(graph.hasCycles());
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
private void runCycleDetectionDoesNotCorruptGraph(AbstractDependencyGraph<Integer> graph) {
|
||||||
public void testCycleDetectionDoesNotCorruptGraph() {
|
|
||||||
DependencyGraph<Integer> graph = new DependencyGraph<Integer>();
|
|
||||||
|
|
||||||
graph.addDependency(1, 2);
|
graph.addDependency(1, 2);
|
||||||
graph.addDependency(2, 3);
|
graph.addDependency(2, 3);
|
||||||
graph.addDependency(3, 4);
|
graph.addDependency(3, 4);
|
||||||
|
@ -172,14 +234,12 @@ public class DependencyGraphTest {
|
||||||
graph.remove(1);
|
graph.remove(1);
|
||||||
set = graph.getUnvisitedIndependentValues();
|
set = graph.getUnvisitedIndependentValues();
|
||||||
assertTrue(set.isEmpty());
|
assertTrue(set.isEmpty());
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
private void runRandomProcessingOfDependenciesSimulation(
|
||||||
public void testRandomProcessingOfDependenciesSimulation() {
|
AbstractDependencyGraph<String> graph) {
|
||||||
final ArrayList<String> completionOrder = new ArrayList<String>();
|
final ArrayList<String> completionOrder = new ArrayList<>();
|
||||||
|
|
||||||
DependencyGraph<String> graph = new DependencyGraph<String>();
|
|
||||||
graph.addDependency("@0", "A8");
|
graph.addDependency("@0", "A8");
|
||||||
graph.addDependency("@1", "A1");
|
graph.addDependency("@1", "A1");
|
||||||
graph.addDependency("@2", "A7");
|
graph.addDependency("@2", "A7");
|
||||||
|
@ -214,7 +274,7 @@ public class DependencyGraphTest {
|
||||||
|
|
||||||
assertTrue(!graph.hasCycles());
|
assertTrue(!graph.hasCycles());
|
||||||
|
|
||||||
DependencyGraph<String> savedGraph = graph.copy();
|
AbstractDependencyGraph<String> savedGraph = graph.copy();
|
||||||
|
|
||||||
while (!graph.isEmpty()) {
|
while (!graph.isEmpty()) {
|
||||||
completionOrder.add(graph.pop());
|
completionOrder.add(graph.pop());
|
||||||
|
@ -231,7 +291,7 @@ public class DependencyGraphTest {
|
||||||
* @param visitedOrder the actual execution order to be tested
|
* @param visitedOrder the actual execution order to be tested
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
public void checkOrderSatisfiesDependencies(DependencyGraph<String> dependencyGraph,
|
private void checkOrderSatisfiesDependencies(AbstractDependencyGraph<String> dependencyGraph,
|
||||||
List<String> visitedOrder) {
|
List<String> visitedOrder) {
|
||||||
|
|
||||||
if (visitedOrder.size() > dependencyGraph.size()) {
|
if (visitedOrder.size() > dependencyGraph.size()) {
|
||||||
|
@ -241,12 +301,12 @@ public class DependencyGraphTest {
|
||||||
Assert.fail("Not all items in the graph were visited");
|
Assert.fail("Not all items in the graph were visited");
|
||||||
}
|
}
|
||||||
|
|
||||||
HashSet<String> items = new HashSet<String>(visitedOrder);
|
HashSet<String> items = new HashSet<>(visitedOrder);
|
||||||
if (items.size() != visitedOrder.size()) {
|
if (items.size() != visitedOrder.size()) {
|
||||||
Assert.fail("duplicate item(s) in linearOrder\n");
|
Assert.fail("duplicate item(s) in linearOrder\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
HashMap<String, Integer> visitedOrderMap = new HashMap<String, Integer>();
|
HashMap<String, Integer> visitedOrderMap = new HashMap<>();
|
||||||
for (int i = 0; i < visitedOrder.size(); i++) {
|
for (int i = 0; i < visitedOrder.size(); i++) {
|
||||||
visitedOrderMap.put(visitedOrder.get(i), i);
|
visitedOrderMap.put(visitedOrder.get(i), i);
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,6 +22,7 @@ import ghidra.program.model.listing.*;
|
||||||
import ghidra.program.model.symbol.Reference;
|
import ghidra.program.model.symbol.Reference;
|
||||||
import ghidra.program.model.symbol.ReferenceManager;
|
import ghidra.program.model.symbol.ReferenceManager;
|
||||||
import ghidra.util.exception.CancelledException;
|
import ghidra.util.exception.CancelledException;
|
||||||
|
import ghidra.util.graph.AbstractDependencyGraph;
|
||||||
import ghidra.util.graph.DependencyGraph;
|
import ghidra.util.graph.DependencyGraph;
|
||||||
import ghidra.util.task.TaskMonitor;
|
import ghidra.util.task.TaskMonitor;
|
||||||
|
|
||||||
|
@ -84,10 +85,10 @@ public class AcyclicCallGraphBuilder {
|
||||||
* @return the DependencyGraph for the acyclic call graph represented by this object.
|
* @return the DependencyGraph for the acyclic call graph represented by this object.
|
||||||
* @throws CancelledException if the monitor was cancelled.
|
* @throws CancelledException if the monitor was cancelled.
|
||||||
*/
|
*/
|
||||||
public DependencyGraph<Address> getDependencyGraph(TaskMonitor monitor)
|
public AbstractDependencyGraph<Address> getDependencyGraph(TaskMonitor monitor)
|
||||||
throws CancelledException {
|
throws CancelledException {
|
||||||
|
|
||||||
DependencyGraph<Address> graph = new DependencyGraph<>();
|
AbstractDependencyGraph<Address> graph = new DependencyGraph<>();
|
||||||
Deque<Address> startPoints = findStartPoints();
|
Deque<Address> startPoints = findStartPoints();
|
||||||
Set<Address> unprocessed = new TreeSet<>(functionSet); // reliable processing order
|
Set<Address> unprocessed = new TreeSet<>(functionSet); // reliable processing order
|
||||||
|
|
||||||
|
@ -158,7 +159,7 @@ public class AcyclicCallGraphBuilder {
|
||||||
children.toArray(node.children);
|
children.toArray(node.children);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void processForward(DependencyGraph<Address> graph, Set<Address> unprocessed,
|
private void processForward(AbstractDependencyGraph<Address> graph, Set<Address> unprocessed,
|
||||||
Address startFunction, TaskMonitor monitor) throws CancelledException {
|
Address startFunction, TaskMonitor monitor) throws CancelledException {
|
||||||
VisitStack stack = new VisitStack(startFunction);
|
VisitStack stack = new VisitStack(startFunction);
|
||||||
StackNode curnode = stack.peek();
|
StackNode curnode = stack.peek();
|
||||||
|
|
|
@ -27,7 +27,7 @@ import ghidra.program.model.*;
|
||||||
import ghidra.program.model.address.*;
|
import ghidra.program.model.address.*;
|
||||||
import ghidra.program.model.listing.*;
|
import ghidra.program.model.listing.*;
|
||||||
import ghidra.program.model.symbol.ReferenceManager;
|
import ghidra.program.model.symbol.ReferenceManager;
|
||||||
import ghidra.util.graph.DependencyGraph;
|
import ghidra.util.graph.AbstractDependencyGraph;
|
||||||
|
|
||||||
public class AcyclicCallGraphBuilderTest extends AbstractGenericTest {
|
public class AcyclicCallGraphBuilderTest extends AbstractGenericTest {
|
||||||
|
|
||||||
|
@ -102,7 +102,7 @@ public class AcyclicCallGraphBuilderTest extends AbstractGenericTest {
|
||||||
node(3, 4);
|
node(3, 4);
|
||||||
|
|
||||||
AcyclicCallGraphBuilder builder = new AcyclicCallGraphBuilder(program, functions, false);
|
AcyclicCallGraphBuilder builder = new AcyclicCallGraphBuilder(program, functions, false);
|
||||||
DependencyGraph<Address> graph = builder.getDependencyGraph(DUMMY_MONITOR);
|
AbstractDependencyGraph<Address> graph = builder.getDependencyGraph(DUMMY_MONITOR);
|
||||||
|
|
||||||
Assert.assertEquals(4, graph.size());
|
Assert.assertEquals(4, graph.size());
|
||||||
|
|
||||||
|
@ -119,7 +119,7 @@ public class AcyclicCallGraphBuilderTest extends AbstractGenericTest {
|
||||||
node(2,3);
|
node(2,3);
|
||||||
|
|
||||||
AcyclicCallGraphBuilder builder = new AcyclicCallGraphBuilder(program,functions,false);
|
AcyclicCallGraphBuilder builder = new AcyclicCallGraphBuilder(program,functions,false);
|
||||||
DependencyGraph<Address> graph = builder.getDependencyGraph(DUMMY_MONITOR);
|
AbstractDependencyGraph<Address> graph = builder.getDependencyGraph(DUMMY_MONITOR);
|
||||||
|
|
||||||
Assert.assertEquals(3, graph.size());
|
Assert.assertEquals(3, graph.size());
|
||||||
|
|
||||||
|
@ -135,7 +135,7 @@ public class AcyclicCallGraphBuilderTest extends AbstractGenericTest {
|
||||||
node(3, 1);
|
node(3, 1);
|
||||||
|
|
||||||
AcyclicCallGraphBuilder builder = new AcyclicCallGraphBuilder(program, functions, false);
|
AcyclicCallGraphBuilder builder = new AcyclicCallGraphBuilder(program, functions, false);
|
||||||
DependencyGraph<Address> graph = builder.getDependencyGraph(DUMMY_MONITOR);
|
AbstractDependencyGraph<Address> graph = builder.getDependencyGraph(DUMMY_MONITOR);
|
||||||
|
|
||||||
Assert.assertEquals(3, graph.size());
|
Assert.assertEquals(3, graph.size());
|
||||||
|
|
||||||
|
@ -152,7 +152,7 @@ public class AcyclicCallGraphBuilderTest extends AbstractGenericTest {
|
||||||
node(2, 2);
|
node(2, 2);
|
||||||
|
|
||||||
AcyclicCallGraphBuilder builder = new AcyclicCallGraphBuilder(program, functions, false);
|
AcyclicCallGraphBuilder builder = new AcyclicCallGraphBuilder(program, functions, false);
|
||||||
DependencyGraph<Address> graph = builder.getDependencyGraph(DUMMY_MONITOR);
|
AbstractDependencyGraph<Address> graph = builder.getDependencyGraph(DUMMY_MONITOR);
|
||||||
|
|
||||||
Assert.assertEquals(3, graph.size());
|
Assert.assertEquals(3, graph.size());
|
||||||
|
|
||||||
|
@ -168,7 +168,7 @@ public class AcyclicCallGraphBuilderTest extends AbstractGenericTest {
|
||||||
node(3, 1);
|
node(3, 1);
|
||||||
|
|
||||||
AcyclicCallGraphBuilder builder = new AcyclicCallGraphBuilder(program, functions, false);
|
AcyclicCallGraphBuilder builder = new AcyclicCallGraphBuilder(program, functions, false);
|
||||||
DependencyGraph<Address> graph = builder.getDependencyGraph(DUMMY_MONITOR);
|
AbstractDependencyGraph<Address> graph = builder.getDependencyGraph(DUMMY_MONITOR);
|
||||||
|
|
||||||
Assert.assertEquals(3, graph.size());
|
Assert.assertEquals(3, graph.size());
|
||||||
|
|
||||||
|
@ -197,7 +197,7 @@ public class AcyclicCallGraphBuilderTest extends AbstractGenericTest {
|
||||||
thunkNode(17, 18, false);
|
thunkNode(17, 18, false);
|
||||||
|
|
||||||
AcyclicCallGraphBuilder builder = new AcyclicCallGraphBuilder(program, functions, false);
|
AcyclicCallGraphBuilder builder = new AcyclicCallGraphBuilder(program, functions, false);
|
||||||
DependencyGraph<Address> graph = builder.getDependencyGraph(DUMMY_MONITOR);
|
AbstractDependencyGraph<Address> graph = builder.getDependencyGraph(DUMMY_MONITOR);
|
||||||
|
|
||||||
Assert.assertEquals(18, graph.size());
|
Assert.assertEquals(18, graph.size());
|
||||||
|
|
||||||
|
@ -241,7 +241,7 @@ public class AcyclicCallGraphBuilderTest extends AbstractGenericTest {
|
||||||
thunkNode(17, 18, false);
|
thunkNode(17, 18, false);
|
||||||
|
|
||||||
AcyclicCallGraphBuilder builder = new AcyclicCallGraphBuilder(program, functions, true);
|
AcyclicCallGraphBuilder builder = new AcyclicCallGraphBuilder(program, functions, true);
|
||||||
DependencyGraph<Address> graph = builder.getDependencyGraph(DUMMY_MONITOR);
|
AbstractDependencyGraph<Address> graph = builder.getDependencyGraph(DUMMY_MONITOR);
|
||||||
|
|
||||||
Assert.assertEquals(8, graph.size());
|
Assert.assertEquals(8, graph.size());
|
||||||
|
|
||||||
|
@ -265,7 +265,7 @@ public class AcyclicCallGraphBuilderTest extends AbstractGenericTest {
|
||||||
|
|
||||||
thunkNode(5, 3, true); // Thunk node hits recursion from different point
|
thunkNode(5, 3, true); // Thunk node hits recursion from different point
|
||||||
AcyclicCallGraphBuilder builder = new AcyclicCallGraphBuilder(program, functions, true);
|
AcyclicCallGraphBuilder builder = new AcyclicCallGraphBuilder(program, functions, true);
|
||||||
DependencyGraph<Address> graph = builder.getDependencyGraph(DUMMY_MONITOR);
|
AbstractDependencyGraph<Address> graph = builder.getDependencyGraph(DUMMY_MONITOR);
|
||||||
|
|
||||||
Assert.assertEquals(4, graph.size());
|
Assert.assertEquals(4, graph.size());
|
||||||
assertDependents(graph, 2, 1);
|
assertDependents(graph, 2, 1);
|
||||||
|
@ -274,7 +274,7 @@ public class AcyclicCallGraphBuilderTest extends AbstractGenericTest {
|
||||||
Assert.assertFalse(graph.hasCycles());
|
Assert.assertFalse(graph.hasCycles());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void assertDependents(DependencyGraph<Address> graph, int fromID, int... toIDs) {
|
private void assertDependents(AbstractDependencyGraph<Address> graph, int fromID, int... toIDs) {
|
||||||
Set<Address> expectedSet = new HashSet<Address>();
|
Set<Address> expectedSet = new HashSet<Address>();
|
||||||
for (int toAddr : toIDs) {
|
for (int toAddr : toIDs) {
|
||||||
expectedSet.add(functionAddress(toAddr));
|
expectedSet.add(functionAddress(toAddr));
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue