Skip to content

Commit 248810c

Browse files
Merge pull request #8 from UniVE-SSV/lucaneg/noops
NoOps + Refactoring of CFG adjacency matrix
2 parents 9118c7f + 0bccd7e commit 248810c

File tree

11 files changed

+1375
-42
lines changed

11 files changed

+1375
-42
lines changed
Lines changed: 322 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,322 @@
1+
package it.unive.lisa.cfg;
2+
3+
import java.util.Collection;
4+
import java.util.HashSet;
5+
import java.util.Iterator;
6+
import java.util.Map;
7+
import java.util.Map.Entry;
8+
import java.util.Set;
9+
import java.util.concurrent.ConcurrentHashMap;
10+
import java.util.stream.Collectors;
11+
import java.util.stream.Stream;
12+
13+
import org.apache.commons.lang3.StringUtils;
14+
import org.apache.commons.lang3.tuple.Pair;
15+
16+
import it.unive.lisa.cfg.edge.Edge;
17+
import it.unive.lisa.cfg.edge.FalseEdge;
18+
import it.unive.lisa.cfg.edge.SequentialEdge;
19+
import it.unive.lisa.cfg.edge.TrueEdge;
20+
import it.unive.lisa.cfg.statement.NoOp;
21+
import it.unive.lisa.cfg.statement.Statement;
22+
import it.unive.lisa.util.collections.ExternalSet;
23+
import it.unive.lisa.util.collections.ExternalSetCache;
24+
25+
/**
26+
* An adjacency matrix for a graph that has {@link Statement}s as nodes and
27+
* {@link Edge}s as edges. It is represented as a map between a statement and a
28+
* {@link Pair} of set of edges, where the {@link Pair#getLeft()} yields the set
29+
* of ingoing edges and {@link Pair#getRight()} yields the set of outgoing
30+
* edges. Set of edges are represented as {@link ExternalSet}s derived from a
31+
* matrix-unique {@link ExternalSetCache}.
32+
*
33+
* @author <a href="mailto:[email protected]">Luca Negrini</a>
34+
*/
35+
public class AdjacencyMatrix implements Iterable<Map.Entry<Statement, Pair<ExternalSet<Edge>, ExternalSet<Edge>>>> {
36+
37+
/**
38+
* The factory where edges are stored.
39+
*/
40+
private ExternalSetCache<Edge> edgeFactory;
41+
42+
/**
43+
* The matrix. The left set in the mapped value is the set of ingoing edges,
44+
* while the right one is the set of outgoing edges.
45+
*/
46+
private Map<Statement, Pair<ExternalSet<Edge>, ExternalSet<Edge>>> matrix;
47+
48+
/**
49+
* Builds a new matrix.
50+
*/
51+
public AdjacencyMatrix() {
52+
edgeFactory = new ExternalSetCache<>();
53+
matrix = new ConcurrentHashMap<>();
54+
}
55+
56+
/**
57+
* Copies the given matrix by keeping the same edge {@link ExternalSetCache},
58+
* shallow-copying the {@link Statement}s and deep-copying the values.
59+
*
60+
* @param other the matrix to copy
61+
*/
62+
public AdjacencyMatrix(AdjacencyMatrix other) {
63+
edgeFactory = other.edgeFactory;
64+
matrix = new ConcurrentHashMap<>();
65+
for (Map.Entry<Statement, Pair<ExternalSet<Edge>, ExternalSet<Edge>>> entry : other.matrix.entrySet())
66+
matrix.put(entry.getKey(), Pair.of(entry.getValue().getLeft().copy(), entry.getValue().getRight().copy()));
67+
}
68+
69+
/**
70+
* Adds the given node to the set of nodes.
71+
*
72+
* @param node the node to add
73+
*/
74+
public void addNode(Statement node) {
75+
matrix.put(node, Pair.of(edgeFactory.mkEmptySet(), edgeFactory.mkEmptySet()));
76+
}
77+
78+
/**
79+
* Yields the collection of nodes of this matrix.
80+
*
81+
* @return the collection of nodes
82+
*/
83+
public final Collection<Statement> getNodes() {
84+
return matrix.keySet();
85+
}
86+
87+
/**
88+
* Adds an edge to this matrix
89+
*
90+
* @param e the edge to add
91+
* @throws UnsupportedOperationException if the source or the destination of the
92+
* given edge are not part of this matrix
93+
*/
94+
public void addEdge(Edge e) {
95+
if (!matrix.containsKey(e.getSource()))
96+
throw new UnsupportedOperationException("The source node is not in the graph");
97+
98+
if (!matrix.containsKey(e.getDestination()))
99+
throw new UnsupportedOperationException("The destination node is not in the graph");
100+
101+
matrix.get(e.getSource()).getRight().add(e);
102+
matrix.get(e.getDestination()).getLeft().add(e);
103+
}
104+
105+
/**
106+
* Yields the edge connecting the two given statements, if any. Yields
107+
* {@code null} if such edge does not exist, or if one of the two statements is
108+
* not inside this matrix.
109+
*
110+
* @param source the source statement
111+
* @param destination the destination statement
112+
* @return the edge connecting {@code source} to {@code destination}, or
113+
* {@code null}
114+
*/
115+
public final Edge getEdgeConnecting(Statement source, Statement destination) {
116+
if (!matrix.containsKey(source))
117+
return null;
118+
119+
for (Edge e : matrix.get(source).getRight())
120+
if (e.getDestination().equals(destination))
121+
return e;
122+
123+
return null;
124+
}
125+
126+
/**
127+
* Yields the set of edges of this matrix.
128+
*
129+
* @return the collection of edges
130+
*/
131+
public final Collection<Edge> getEdges() {
132+
return matrix.values().stream()
133+
.flatMap(c -> Stream.concat(c.getLeft().collect().stream(), c.getRight().collect().stream()))
134+
.collect(Collectors.toSet());
135+
}
136+
137+
/**
138+
* Yields the collection of the nodes that are followers of the given one, that
139+
* is, all nodes such that there exist an edge in this matrix going from the
140+
* given node to such node. Yields {@code null} if the node is not in this
141+
* matrix.
142+
*
143+
* @param node the node
144+
* @return the collection of followers
145+
*/
146+
public final Collection<Statement> followersOf(Statement node) {
147+
if (!matrix.containsKey(node))
148+
return null;
149+
150+
return matrix.get(node).getRight().collect().stream().map(e -> e.getDestination()).collect(Collectors.toSet());
151+
}
152+
153+
/**
154+
* Yields the collection of the nodes that are predecessors of the given vertex,
155+
* that is, all nodes such that there exist an edge in this matrix going from
156+
* such node to the given one. Yields {@code null} if the node is not in this
157+
* matrix.
158+
*
159+
* @param node the node
160+
* @return the collection of predecessors
161+
*/
162+
public final Collection<Statement> predecessorsOf(Statement node) {
163+
if (!matrix.containsKey(node))
164+
return null;
165+
166+
return matrix.get(node).getLeft().collect().stream().map(e -> e.getSource()).collect(Collectors.toSet());
167+
}
168+
169+
/**
170+
* Simplifies this matrix, removing all {@link NoOp}s and rewriting the edge set
171+
* accordingly. This method will throw an {@link UnsupportedOperationException}
172+
* if one of the {@link NoOp}s has an outgoing edge that is not a
173+
* {@link SequentialEdge}, since such statement is expected to always be
174+
* sequential.
175+
*
176+
* @throws UnsupportedOperationException if there exists at least one
177+
* {@link NoOp} with an outgoing
178+
* non-sequential edge, or if one of the
179+
* ingoing edges to the {@link NoOp} is
180+
* not currently supported.
181+
*/
182+
public synchronized void simplify() {
183+
Set<Statement> noops = matrix.keySet().stream().filter(k -> k instanceof NoOp).collect(Collectors.toSet());
184+
for (Statement noop : noops) {
185+
for (Edge ingoing : matrix.get(noop).getLeft())
186+
for (Edge outgoing : matrix.get(noop).getRight()) {
187+
if (!(outgoing instanceof SequentialEdge))
188+
throw new UnsupportedOperationException(
189+
"Cannot remove no-op with non-sequential outgoing edge");
190+
191+
// replicate the edge from ingoing.source to outgoing.dest
192+
Edge _new = null;
193+
if (ingoing instanceof SequentialEdge)
194+
_new = new SequentialEdge(ingoing.getSource(), outgoing.getDestination());
195+
else if (ingoing instanceof TrueEdge)
196+
_new = new TrueEdge(ingoing.getSource(), outgoing.getDestination());
197+
else if (ingoing instanceof FalseEdge)
198+
_new = new FalseEdge(ingoing.getSource(), outgoing.getDestination());
199+
else
200+
throw new UnsupportedOperationException("Unknown edge type: " + ingoing.getClass().getName());
201+
202+
matrix.get(ingoing.getSource()).getRight().remove(ingoing);
203+
matrix.get(ingoing.getSource()).getRight().add(_new);
204+
matrix.get(outgoing.getDestination()).getLeft().remove(outgoing);
205+
matrix.get(outgoing.getDestination()).getLeft().add(_new);
206+
}
207+
matrix.remove(noop);
208+
}
209+
}
210+
211+
@Override
212+
public Iterator<Entry<Statement, Pair<ExternalSet<Edge>, ExternalSet<Edge>>>> iterator() {
213+
return matrix.entrySet().iterator();
214+
}
215+
216+
@Override
217+
public int hashCode() {
218+
final int prime = 31;
219+
int result = 1;
220+
result = prime * result + ((matrix == null) ? 0 : matrix.hashCode());
221+
return result;
222+
}
223+
224+
@Override
225+
public boolean equals(Object obj) {
226+
if (this == obj)
227+
return true;
228+
if (obj == null)
229+
return false;
230+
if (getClass() != obj.getClass())
231+
return false;
232+
AdjacencyMatrix other = (AdjacencyMatrix) obj;
233+
if (matrix == null) {
234+
if (other.matrix != null)
235+
return false;
236+
} else if (!matrix.equals(other.matrix))
237+
return false;
238+
return true;
239+
}
240+
241+
/**
242+
* Checks if this matrix is effectively equal to the given one, that is, if they
243+
* have the same structure while potentially being different instances.
244+
*
245+
* @param other the other matrix
246+
* @return {@code true} if this matrix and the given one are effectively equals
247+
*/
248+
public boolean isEqualTo(AdjacencyMatrix other) {
249+
if (this == other)
250+
return true;
251+
if (other == null)
252+
return false;
253+
if (matrix == null) {
254+
if (other.matrix != null)
255+
return false;
256+
} else if (!areEqual(matrix, other.matrix))
257+
return false;
258+
return true;
259+
}
260+
261+
private static boolean areEqual(Map<Statement, Pair<ExternalSet<Edge>, ExternalSet<Edge>>> first,
262+
Map<Statement, Pair<ExternalSet<Edge>, ExternalSet<Edge>>> second) {
263+
// the following keeps track of the unmatched statements in second
264+
Collection<Statement> copy = new HashSet<>(second.keySet());
265+
boolean found;
266+
for (Map.Entry<Statement, Pair<ExternalSet<Edge>, ExternalSet<Edge>>> entry : first.entrySet()) {
267+
found = false;
268+
for (Map.Entry<Statement, Pair<ExternalSet<Edge>, ExternalSet<Edge>>> entry2 : second.entrySet())
269+
if (copy.contains(entry2.getKey()) && entry.getKey().isEqualTo(entry2.getKey())
270+
&& areEqual(entry.getValue().getLeft(), entry2.getValue().getLeft())
271+
&& areEqual(entry.getValue().getRight(), entry2.getValue().getRight())) {
272+
copy.remove(entry2.getKey());
273+
found = true;
274+
break;
275+
}
276+
if (!found)
277+
return false;
278+
}
279+
280+
if (!copy.isEmpty())
281+
// we also have to match all of the entrypoints in cfg.entrypoints
282+
return false;
283+
284+
return true;
285+
}
286+
287+
private static boolean areEqual(ExternalSet<Edge> first, ExternalSet<Edge> second) {
288+
// the following keeps track of the unmatched statements in second
289+
Collection<Edge> copy = second.collect();
290+
boolean found;
291+
for (Edge e : first) {
292+
found = false;
293+
for (Edge ee : second)
294+
if (copy.contains(ee) && e.isEqualTo(ee)) {
295+
copy.remove(ee);
296+
found = true;
297+
break;
298+
}
299+
if (!found)
300+
return false;
301+
}
302+
303+
if (!copy.isEmpty())
304+
// we also have to match all of the entrypoints in cfg.entrypoints
305+
return false;
306+
307+
return true;
308+
}
309+
310+
@Override
311+
public String toString() {
312+
StringBuilder res = new StringBuilder();
313+
for (Map.Entry<Statement, Pair<ExternalSet<Edge>, ExternalSet<Edge>>> entry : this) {
314+
res.append("\"").append(entry.getKey()).append("\" -> [\n\tingoing: ");
315+
res.append(StringUtils.join(entry.getValue().getLeft(), ", "));
316+
res.append("\n\toutgoing: ");
317+
res.append(StringUtils.join(entry.getValue().getRight(), ", "));
318+
res.append("\n]\n");
319+
}
320+
return res.toString();
321+
}
322+
}

0 commit comments

Comments
 (0)