Skip to content

Commit 2b84540

Browse files
committed
QL: Add scoped variable nodes
1 parent 2d640e7 commit 2b84540

File tree

3 files changed

+144
-0
lines changed

3 files changed

+144
-0
lines changed

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

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,42 @@ Node astNode(AstNode node) {
4545
result = MkAstNodeNode(node)
4646
}
4747

48+
/**
49+
* A data-flow node representing a variable within a specific scope.
50+
*/
51+
class ScopedVariableNode extends Node, MkScopedVariable {
52+
private VarDef var;
53+
private AstNode scope;
54+
55+
ScopedVariableNode() { this = MkScopedVariable(var, scope) }
56+
57+
override string toString() {
58+
result = "Variable '" + var.getName() + "' scoped to " + scope.getLocation().getStartLine() + ":" + scope.getLocation().getStartColumn()
59+
}
60+
61+
override Location getLocation() {
62+
result = scope.getLocation()
63+
}
64+
65+
/** Gets the variable being refined to a specific scope. */
66+
VarDef getVariable() {
67+
result = var
68+
}
69+
70+
/** Gets the scope to which this variable has been refined. */
71+
AstNode getScope() {
72+
result = scope
73+
}
74+
}
75+
76+
/**
77+
* Gets the data-flow node corresponding to `var` restricted to `scope`.
78+
*/
79+
pragma[inline]
80+
Node scopedVariable(VarDef var, AstNode scope) {
81+
result = MkScopedVariable(var, scope)
82+
}
83+
4884
/**
4985
* A data-flow node representing `this` within a class predicate, charpred, or newtype branch.
5086
*/

ql/ql/src/codeql_ql/dataflow/internal/NodesInternal.qll

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,15 @@
11
private import codeql_ql.ast.Ast
2+
private import VarScoping
23

34
newtype TNode =
45
MkAstNodeNode(AstNode node) {
56
node instanceof Expr or
67
node instanceof VarDef
78
} or
9+
MkScopedVariable(VarDef var, AstNode scope) {
10+
isRefinement(var, _, scope) and
11+
not scope = getVarDefScope(var)
12+
} or
813
MkThisNode(Predicate pred) {
914
pred instanceof ClassPredicate or
1015
pred instanceof CharPred or
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
private import codeql_ql.ast.Ast
2+
private import codeql_ql.ast.internal.AstNodeNumbering
3+
4+
/** Gets the disjunction immediately containing another disjunction `inner`. */
5+
private Disjunction getOuterDisjunction(Disjunction inner) { result.getAnOperand() = inner }
6+
7+
/**
8+
* Get the root of a disjunction tree containing `f`, if any.
9+
*/
10+
private Disjunction getRootDisjunction(Disjunction f) {
11+
not exists(getOuterDisjunction(result)) and
12+
result = getOuterDisjunction(f)
13+
or
14+
result = getRootDisjunction(getOuterDisjunction(f))
15+
}
16+
17+
/** Get the root disjunction for `f` if there is one, other gets `f` itself. */
18+
pragma[inline]
19+
private AstNode tryGetRootDisjunction(AstNode f) {
20+
result = getRootDisjunction(f)
21+
or
22+
not exists(getRootDisjunction(f)) and
23+
result = f
24+
}
25+
26+
AstNode getADisjunctionOperand(AstNode disjunction) {
27+
exists(Disjunction d |
28+
result = d.getAnOperand() and
29+
// skip intermediate nodes in large disjunctions
30+
disjunction = tryGetRootDisjunction(d) and
31+
not result instanceof Disjunction
32+
)
33+
or
34+
result = disjunction.(Implication).getAChild()
35+
or
36+
result = disjunction.(IfFormula).getThenPart()
37+
or
38+
result = disjunction.(IfFormula).getElsePart()
39+
or
40+
exists(Forall all |
41+
disjunction = all and
42+
exists(all.getFormula()) and
43+
exists(all.getRange()) and
44+
result = [all.getRange(), all.getFormula()]
45+
)
46+
or
47+
result = disjunction.(Set).getAnElement()
48+
}
49+
50+
/**
51+
* A node that acts as a disjunction:
52+
* - The root in a tree of `or` operators, or
53+
* - An `implies`, `if`, `forall`, or set literal.
54+
*/
55+
class DisjunctionOperator extends AstNode {
56+
DisjunctionOperator() { exists(getADisjunctionOperand(this)) }
57+
58+
AstNode getAnOperand() { result = getADisjunctionOperand(this) }
59+
}
60+
61+
/**
62+
* Gets the scope of `var`, such as the predicate or `exists` clause that binds it.
63+
*/
64+
AstNode getVarDefScope(VarDef var) {
65+
// TODO: not valid for `as` expressions
66+
result = var.getParent()
67+
}
68+
69+
/** A `VarAccess` or disjunct, representing the input to refinement of a variable. */
70+
class VarAccessOrDisjunct = AstNode;
71+
72+
/**
73+
* Walks upwards from an access to `varDef` until encountering either the scope of `varDef`
74+
* or a disjunct. When a disjunct is found, the disjunct becomes the new `access`, representing
75+
* a refinement we intend to insert there.
76+
*/
77+
private AstNode getVarScope(VarDef varDef, VarAccessOrDisjunct access) {
78+
access.(VarAccess).getDeclaration() = varDef and
79+
result = access
80+
or
81+
exists(AstNode scope | scope = getVarScope(varDef, access) |
82+
not scope = getADisjunctionOperand(_) and
83+
not scope = getVarDefScope(varDef) and
84+
result = scope.getParent()
85+
)
86+
or
87+
isRefinement(varDef, _, access) and
88+
result = tryGetRootDisjunction(access.getParent())
89+
}
90+
91+
/**
92+
* Holds if `inner` should be seen as a refinement of `outer`.
93+
*
94+
* `outer` is always a disjunct, and `inner` is either a `VarAccess` or another disjunct.
95+
*/
96+
predicate isRefinement(VarDef varDef, VarAccessOrDisjunct inner, VarAccessOrDisjunct outer) {
97+
getVarScope(varDef, inner) = outer and
98+
(
99+
outer = getADisjunctionOperand(_)
100+
or
101+
outer = getVarDefScope(varDef)
102+
)
103+
}

0 commit comments

Comments
 (0)