Skip to content

Commit bced7bb

Browse files
committed
unified: Add local name binding and tests
As documented in the test, 'guard if' statements are still not properly supported.
1 parent 970e991 commit bced7bb

6 files changed

Lines changed: 606 additions & 0 deletions

File tree

Lines changed: 244 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,244 @@
1+
/**
2+
* Provides classes for reasoning about lexically scoped variables and references to these.
3+
*/
4+
5+
private import unified
6+
private import unified as U
7+
private import codeql.namebinding.LocalNameBinding
8+
9+
private module LocalNameBindingInput implements LocalNameBindingInputSig<Location> {
10+
class AstNode = U::AstNode;
11+
12+
private class LogicalAndRoot extends LogicalAndExpr {
13+
LogicalAndRoot() { not this = any(LogicalAndExpr e).getAnOperand() }
14+
15+
private Expr getDescendent(string path) {
16+
path = "" and result = this
17+
or
18+
exists(LogicalAndExpr mid, string midPath | mid = this.getDescendent(midPath) |
19+
result = mid.getLeft() and path = midPath + "A"
20+
or
21+
result = mid.getRight() and path = midPath + "B"
22+
)
23+
}
24+
25+
Expr getNthLeaf(int n) {
26+
result =
27+
rank[n](Expr e, string path |
28+
e = this.getDescendent(path) and not e instanceof LogicalAndExpr
29+
|
30+
e order by path
31+
)
32+
}
33+
34+
Expr getLastLeaf() { result = max(int n | | this.getNthLeaf(n) order by n) }
35+
}
36+
37+
private AstNode getChild1(AstNode n, int index) {
38+
result = n.(Block).getStmt(index)
39+
or
40+
result = n.(LogicalAndRoot).getNthLeaf(index)
41+
or
42+
exists(PatternGuardExpr guard | n = guard |
43+
index = 0 and result = guard.getPattern()
44+
or
45+
index = 1 and result = guard.getValue()
46+
)
47+
or
48+
exists(IfExpr expr | n = expr |
49+
index = 0 and result = expr.getCondition()
50+
or
51+
index = 1 and result = expr.getThen()
52+
or
53+
index = 2 and result = expr.getElse()
54+
)
55+
or
56+
exists(VariableDeclaration decl | n = decl |
57+
index = 0 and result = decl.getPattern()
58+
or
59+
index = 1 and result = decl.getType()
60+
or
61+
index = 2 and result = decl.getValue()
62+
)
63+
}
64+
65+
AstNode getChild(AstNode n, int index) {
66+
result = getChild1(n, index)
67+
or
68+
not exists(getChild1(n, _)) and
69+
not n instanceof LogicalAndExpr and // also ignore intermediate nodes within a 'logical and' tree
70+
index = 0 and
71+
result = n.getAFieldOrChild()
72+
}
73+
74+
abstract class Conditional extends AstNode {
75+
/** Gets the condition of this conditional. */
76+
abstract AstNode getCondition();
77+
78+
/** Gets the then-branch of this conditional. */
79+
abstract AstNode getThen();
80+
81+
/** Gets the else-branch of this conditional. */
82+
abstract AstNode getElse();
83+
}
84+
85+
private class IfExprConditional extends Conditional instanceof IfExpr {
86+
override AstNode getCondition() { result = IfExpr.super.getCondition() }
87+
88+
override AstNode getThen() { result = IfExpr.super.getThen() }
89+
90+
override AstNode getElse() { result = IfExpr.super.getElse() }
91+
}
92+
93+
abstract class SiblingShadowingDecl extends AstNode {
94+
/** Gets the left-hand side of this declaration. */
95+
abstract AstNode getLhs();
96+
97+
/**
98+
* Gets the right-hand side of this declaration.
99+
*
100+
* Any local declared in the left-hand side of this declaration is _not_ in scope
101+
* in the right-hand side.
102+
*/
103+
abstract AstNode getRhs();
104+
105+
/**
106+
* Gets the else-branch of this declaration, if any.
107+
*
108+
* Any local declared in the left-hand side of this declaration is _not_ in scope
109+
* in the else-branch.
110+
*/
111+
abstract AstNode getElse();
112+
}
113+
114+
private class LocalVariableDeclarationSiblingShadowingDecl extends SiblingShadowingDecl instanceof LocalVariableDeclaration
115+
{
116+
override AstNode getLhs() { result = LocalVariableDeclaration.super.getPattern() }
117+
118+
override AstNode getRhs() { result = LocalVariableDeclaration.super.getValue() }
119+
120+
override AstNode getElse() { none() }
121+
}
122+
123+
private class PatternGuardExprSiblingShadowingDecl extends SiblingShadowingDecl instanceof PatternGuardExpr
124+
{
125+
override AstNode getLhs() { result = PatternGuardExpr.super.getPattern() }
126+
127+
override AstNode getRhs() { result = PatternGuardExpr.super.getValue() }
128+
129+
override AstNode getElse() { none() }
130+
}
131+
132+
private class GuardIfStmtSiblingShadowingDecl extends SiblingShadowingDecl instanceof GuardIfStmt {
133+
override AstNode getLhs() { result = GuardIfStmt.super.getCondition() }
134+
135+
override AstNode getRhs() { none() }
136+
137+
override AstNode getElse() { result = GuardIfStmt.super.getElse() }
138+
}
139+
140+
private predicate bindingContext(AstNode pattern, AstNode scope) {
141+
exists(LocalVariableDeclaration decl |
142+
scope = decl and // LocalVariableDeclaration is a ShadowingSiblingDecl, it must use itself as the scope
143+
pattern = decl.getPattern()
144+
)
145+
or
146+
exists(LocalFunctionDeclaration func |
147+
scope = func.getDeclaringBlock() and
148+
pattern = func.getName()
149+
)
150+
or
151+
exists(Parameter param |
152+
scope = param.getParent() and // TODO: add SourceCallable and use .getParameter() instead
153+
pattern = param.getPattern()
154+
)
155+
or
156+
exists(CatchClause catch |
157+
scope = catch and // ensure both 'body' and 'guard' clause are in scope
158+
pattern = catch.getPattern()
159+
)
160+
or
161+
exists(SwitchCase case |
162+
scope = case and // ensure both 'body' and 'guard' clause are in scope (TODO: merge CatchClause and SwitchCase?)
163+
pattern = case.getPattern()
164+
)
165+
or
166+
exists(ForEachStmt stmt |
167+
scope = stmt and // ensure both 'body' and 'guard' are in scope
168+
pattern = stmt.getPattern()
169+
)
170+
or
171+
exists(TuplePattern pat |
172+
bindingContext(pat, scope) and
173+
pattern = pat.getElement(_).getPattern()
174+
)
175+
or
176+
exists(ConstructorPattern pat |
177+
bindingContext(pat, scope) and
178+
pattern = pat.getElement(_).getPattern()
179+
)
180+
or
181+
exists(OrPattern pat |
182+
bindingContext(pat, scope) and
183+
pattern = pat.getPattern(_)
184+
)
185+
or
186+
exists(PatternGuardExpr expr |
187+
pattern = expr.getPattern() and
188+
scope = expr
189+
)
190+
}
191+
192+
predicate declInScope(AstNode definingNode, string name, AstNode scope) {
193+
bindingContext(definingNode, scope) and
194+
(
195+
definingNode.(NamePattern).getIdentifier().getValue() = name
196+
or
197+
definingNode.(Identifier).getValue() = name
198+
)
199+
}
200+
201+
predicate implicitDeclInScope(string name, AstNode scope) {
202+
none()
203+
// TODO: self
204+
}
205+
206+
predicate accessCand(AstNode n, string name) {
207+
n.(NameExpr).getIdentifier().getValue() = name
208+
or
209+
n.(NamePattern).getIdentifier().getValue() = name
210+
or
211+
n = any(LocalFunctionDeclaration f).getName() and
212+
n.(Identifier).getValue() = name
213+
}
214+
215+
predicate lookupStartsAt(AstNode n, AstNode scope) { none() }
216+
}
217+
218+
module LocalNameBindingOutput = LocalNameBinding<Location, LocalNameBindingInput>;
219+
220+
module Public {
221+
/**
222+
* A local variable.
223+
*/
224+
class Variable extends LocalNameBindingOutput::Local {
225+
VariableAccess getAnAccess() { result.getVariable() = this }
226+
}
227+
228+
/**
229+
* An AST node that is a reference to a local variable.
230+
*/
231+
class VariableAccess extends AstNode instanceof LocalNameBindingOutput::LocalAccess {
232+
Variable getVariable() { result = super.getLocal() }
233+
234+
Identifier getIdentifier() {
235+
result = this.(NameExpr).getIdentifier()
236+
or
237+
result = this.(NamePattern).getIdentifier()
238+
or
239+
result = this
240+
}
241+
242+
string getName() { result = this.getIdentifier().getValue() }
243+
}
244+
}

unified/ql/lib/qlpack.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,6 @@ library: true
77
upgrades: upgrades
88
dependencies:
99
codeql/util: ${workspace}
10+
codeql/namebinding: ${workspace}
1011
warnOnImplicitThis: true
1112
compileForOverlayEval: true

unified/ql/lib/unified.qll

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,4 @@ import codeql.Locations
66
import codeql.files.FileSystem
77
import codeql.unified.Ast::Unified
88
import codeql.unified.internal.AstExtra::Public
9+
import codeql.unified.internal.Variables::Public

0 commit comments

Comments
 (0)