Skip to content

Commit 0ffb558

Browse files
committed
QL: Support local flow via unification
1 parent 49d5b66 commit 0ffb558

File tree

3 files changed

+233
-0
lines changed

3 files changed

+233
-0
lines changed

ql/ql/src/codeql_ql/dataflow/DataFlow.qll

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,15 @@
11
private import codeql_ql.ast.Ast
22
private import internal.NodesInternal
3+
private import internal.DataFlowNumbering
4+
private import internal.LocalFlow as LocalFlow
35

46
/**
57
* An expression or variable in a formula, including some additional nodes
68
* that are not part of the AST.
9+
*
10+
* Nodes that are locally bound together by equalities are clustered into a "super node",
11+
* which can be accessed using `getSuperNode()`. There is usually no reason to use `Node` directly
12+
* other than to reason about what kind of node is contained in a super node.
713
*/
814
class Node extends TNode {
915
string toString() { none() } // overridden in subclasses
@@ -21,6 +27,12 @@ class Node extends TNode {
2127
* TODO: select clauses
2228
*/
2329
Predicate getEnclosingPredicate() { none() } // overridden in subclasses
30+
31+
/**
32+
* Gets the collection of data-flow nodes locally bound by equalities, represented
33+
* by a "super node".
34+
*/
35+
SuperNode getSuperNode() { result.getANode() = this }
2436
}
2537

2638
/**
@@ -209,3 +221,71 @@ pragma[inline]
209221
Node fieldNode(Predicate pred, FieldDecl fieldDecl) {
210222
result = MkFieldNode(pred, fieldDecl)
211223
}
224+
225+
/**
226+
* A collection of data-flow nodes in the same predicate, locally bound by equalities.
227+
*/
228+
class SuperNode extends LocalFlow::TSuperNode {
229+
private int repr;
230+
231+
SuperNode() { this = LocalFlow::MkSuperNode(repr) }
232+
233+
/** Gets a data-flow node that is part of this super node. */
234+
Node getANode() {
235+
LocalFlow::getRepr(result) = repr
236+
}
237+
238+
/** Gets an AST node from any of the nodes in this super node. */
239+
AstNode asAstNode() {
240+
result = getANode().asAstNode()
241+
}
242+
243+
/**
244+
* Gets a single node from this super node.
245+
*
246+
* The node is arbitrary and the caller should not rely on how the node is chosen.
247+
* The node is currently chosen such that:
248+
* - An `AstNodeNode` is preferred over other nodes.
249+
* - A node occuring earlier is preferred over one occurring later.
250+
*/
251+
Node getArbitraryRepr() {
252+
result = min(Node n | n = getANode() | n order by getInternalId(n))
253+
}
254+
255+
/**
256+
* Gets the predicate containing all nodes that are part of this super node.
257+
*/
258+
Predicate getEnclosingPredicate() {
259+
result = getANode().getEnclosingPredicate()
260+
}
261+
262+
/** Gets a string representation of this super node. */
263+
string toString() {
264+
exists(int c |
265+
c = strictcount(getANode()) and
266+
result = "Super node of " + c + " nodes in " + getEnclosingPredicate().getName()
267+
)
268+
}
269+
270+
/** Gets the location of an arbitrary node in this super node. */
271+
Location getLocation() {
272+
result = getArbitraryRepr().getLocation()
273+
}
274+
275+
/** Gets any member call whose receiver is in the same super node. */
276+
MemberCall getALocalMemberCall() {
277+
superNode(result.getBase()) = this
278+
}
279+
280+
/** Gets any member call whose receiver is in the same super node. */
281+
MemberCall getALocalMemberCall(string name) {
282+
result = this.getALocalMemberCall() and
283+
result.getMemberName() = name
284+
}
285+
}
286+
287+
/** Gets the super node for the given AST node. */
288+
pragma[inline]
289+
SuperNode superNode(AstNode node) {
290+
result = astNode(node).getSuperNode()
291+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
private import codeql_ql.ast.Ast
2+
private import codeql_ql.ast.internal.AstNodeNumbering
3+
private import NodesInternal
4+
5+
/** An arbitrary total ordering of data-flow nodes. */
6+
private predicate internalOrderingKey(TNode node, int tag, int field1, int field2) {
7+
exists(AstNode ast |
8+
node = MkAstNodeNode(ast) and
9+
tag = 0 and
10+
field1 = getPreOrderId(ast) and
11+
field2 = 0
12+
)
13+
or
14+
exists(VarDef var, Formula scope |
15+
node = MkScopedVariable(var, scope) and
16+
tag = 1 and
17+
field1 = getPreOrderId(var) and
18+
field2 = getPreOrderId(scope)
19+
)
20+
or
21+
exists(Predicate pred |
22+
node = MkThisNode(pred) and
23+
tag = 2 and
24+
field1 = getPreOrderId(pred) and
25+
field2 = 0
26+
)
27+
or
28+
exists(Predicate pred |
29+
node = MkResultNode(pred) and
30+
tag = 3 and
31+
field1 = getPreOrderId(pred) and
32+
field2 = 0
33+
)
34+
or
35+
exists(Predicate pred, FieldDecl fieldDecl |
36+
node = MkFieldNode(pred, fieldDecl) and
37+
tag = 4 and
38+
field1 = getPreOrderId(pred) and
39+
field2 = getPreOrderId(fieldDecl)
40+
)
41+
}
42+
43+
/** Gets an integer unique to `node`. */
44+
int getInternalId(TNode node) {
45+
node =
46+
rank[result](TNode n, int tag, int field1, int field2 |
47+
internalOrderingKey(n, tag, field1, field2)
48+
|
49+
n order by tag, field1, field2
50+
)
51+
}
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
/**
2+
* Models local flow edges. Each equivalence class in the local flow relation becomes a super node.
3+
*/
4+
5+
private import codeql_ql.dataflow.DataFlow
6+
private import codeql_ql.ast.Ast
7+
private import codeql_ql.ast.internal.AstNodeNumbering
8+
private import NodesInternal
9+
private import VarScoping
10+
private import DataFlowNumbering
11+
12+
private module Cached {
13+
/**
14+
* Holds if `x` and `y` are bound by an equality (intra-predicate only).
15+
*
16+
* This edge has no orientation, and is used to construct the equivalence relation.
17+
* Each equivalence class becomes a `SuperNode`.
18+
*/
19+
private predicate localEdge(Node x, Node y) {
20+
exists(AstNode a, AstNode b |
21+
x = astNode(a) and
22+
y = astNode(b)
23+
|
24+
// x ~ any(x)
25+
a = b.(Any).getExpr(0)
26+
or
27+
// v ~ any(T v)
28+
a = b.(Any).getArgument(0)
29+
or
30+
// x ~ x as VAR
31+
a = b.(AsExpr).getInnerExpr()
32+
or
33+
// x ~ x.(Type)
34+
a = b.(InlineCast).getBase()
35+
or
36+
// x = y ==> x ~ y
37+
exists(ComparisonFormula compare |
38+
compare.getOperator() = "=" and
39+
a = compare.getLeftOperand() and
40+
b = compare.getRightOperand()
41+
)
42+
)
43+
or
44+
// VarAccess -> ScopedVariable
45+
exists(VarDef var, VarAccess access, VarAccessOrDisjunct scope |
46+
isRefinement(var, access, scope) and
47+
x = astNode(access) and
48+
y = scopedVariable(var, scope)
49+
)
50+
or
51+
// VarAccess -> VarDef (if no refinement exists)
52+
exists(VarDef var, VarAccess access |
53+
isRefinement(var, access, getVarDefScope(var)) and
54+
x = astNode(access) and
55+
y = astNode(var)
56+
)
57+
or
58+
// result ~ enclosing 'result' node
59+
x = resultNode(y.(AstNodeNode).getAstNode().(ResultAccess).getEnclosingPredicate())
60+
or
61+
// this ~ enclosing 'this' node
62+
x = thisNode(y.(AstNodeNode).getAstNode().(ThisAccess).getEnclosingPredicate())
63+
or
64+
// f ~ enclosing field node for 'f'
65+
exists(FieldAccess access |
66+
x = astNode(access) and
67+
y = fieldNode(access.getEnclosingPredicate(), access.getDeclaration())
68+
)
69+
or
70+
// field declaration ~ field node in the charpred
71+
exists(FieldDecl field, Class cls |
72+
cls.getAField() = field and
73+
x = astNode(field.getVarDecl()) and
74+
y = fieldNode(cls.getCharPred(), field)
75+
)
76+
}
77+
78+
/** Like `localEdge` but the parameters are mapped to their internal ID. */
79+
private predicate rawLocalEdge(int x, int y) {
80+
exists(Node a, Node b |
81+
localEdge(a, b) and
82+
x = getInternalId(a) and
83+
y = getInternalId(b)
84+
)
85+
or
86+
// Ensure a representative is generated for singleton components
87+
x = getInternalId(_) and
88+
y = x
89+
}
90+
91+
/** Gets the representative for the equivalence class containing the node with ID `x`. */
92+
private int getRawRepr(int x) = equivalenceRelation(rawLocalEdge/2)(x, result)
93+
94+
/** Gets the ID for the equivalence class containing `node`. */
95+
cached
96+
int getRepr(Node node) { result = getRawRepr(getInternalId(node)) }
97+
98+
cached
99+
newtype TSuperNode = MkSuperNode(int repr) { repr = getRepr(_) }
100+
}
101+
102+
import Cached

0 commit comments

Comments
 (0)