Skip to content

Commit e1f3b91

Browse files
committed
Create DualGraph.qll
1 parent 8bbb0ec commit e1f3b91

File tree

1 file changed

+92
-0
lines changed

1 file changed

+92
-0
lines changed
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
/**
2+
* Provides an efficient mechanism for checking if two nodes have
3+
* a common ancestor in a graph.
4+
*/
5+
6+
private import Location
7+
8+
signature module DualGraphInputSig<LocationSig Location> {
9+
class Node {
10+
string toString();
11+
12+
Location getLocation();
13+
}
14+
15+
predicate edge(Node node1, Node node2);
16+
}
17+
18+
/**
19+
* Creates a "dual graph" in which each node in the given graph has a "forward" and "backward"
20+
* copy.
21+
*
22+
* All original edges are present in both copies, but reversed in the backward copy.
23+
*
24+
* In addition, all nodes have an edge from their backward node to their forward node.
25+
*
26+
* This can be used to check if two nodes have a common ancestor in the graph, by checking
27+
* if a path exists from the reverse node of one node, to the forward node of another.
28+
*/
29+
module MakeDualGraph<LocationSig Location, DualGraphInputSig<Location> Input> {
30+
private import Input
31+
32+
private newtype TDualNode =
33+
TForward(Node n) or
34+
TBackward(Node n)
35+
36+
/** A forward or backward copy of a node from the original graph. */
37+
class DualNode extends TDualNode {
38+
/** Gets the underlying node if this is a forward node. */
39+
Node asForward() { this = TForward(result) }
40+
41+
/** Gets the underlying node if this is a backward node. */
42+
Node asBackward() { this = TBackward(result) }
43+
44+
/** Gets a string representation of this node. */
45+
string toString() {
46+
result = this.asForward().toString()
47+
or
48+
result = "[rev] " + this.asBackward().toString()
49+
}
50+
51+
/** Gets the location of this node. */
52+
Location getLocation() {
53+
result = this.asForward().getLocation()
54+
or
55+
result = this.asBackward().getLocation()
56+
}
57+
}
58+
59+
/** Gets the node representing the backward node wrapping `n`. */
60+
DualNode getBackwardNode(Node n) { result.asBackward() = n }
61+
62+
/** Gets the node representing the forward node wrapping `n`. */
63+
DualNode getForwardNode(Node n) { result.asForward() = n }
64+
65+
/**
66+
* Holds if the dual graph contains the edge `node1 -> node2`. See `MakeDualGraph`.
67+
*/
68+
predicate dualEdge(DualNode node1, DualNode node2) {
69+
edge(node1.asForward(), node2.asForward())
70+
or
71+
edge(node2.asBackward(), node1.asBackward())
72+
or
73+
node1.asBackward() = node2.asForward()
74+
}
75+
76+
/**
77+
* Holds if there is a non-empty path from `node1 -> node2` in the dual graph.
78+
*/
79+
cached
80+
predicate dualPath(DualNode node1, DualNode node2) = fastTC(dualEdge/2)(node1, node2)
81+
82+
/**
83+
* Holds if `node1` and `node2` have a common ancestor in the original graph, that is,
84+
* there exists a node from which both nodes are reachable.
85+
*/
86+
pragma[inline]
87+
predicate hasCommonAncestor(Node node1, Node node2) {
88+
// Note: `fastTC` only checks for non-empty paths, but there is no need to special-case
89+
// `node1 = node2` because the path `Backward(n) -> Forward(n)` is non-empty.
90+
dualPath(getBackwardNode(node1), getForwardNode(node2))
91+
}
92+
}

0 commit comments

Comments
 (0)