Skip to content

Commit b0efffd

Browse files
committed
Rust: AST support for variables
1 parent a282efc commit b0efffd

File tree

11 files changed

+973
-108
lines changed

11 files changed

+973
-108
lines changed
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
/**
2+
* This module provides classes related to variables.
3+
*/
4+
5+
private import internal.VariableImpl
6+
7+
final class Variable = Impl::Variable;
8+
9+
final class VariableAccess = Impl::VariableAccess;
Lines changed: 340 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,340 @@
1+
private import rust
2+
private import codeql.rust.elements.internal.generated.ParentChild
3+
private import codeql.rust.elements.internal.PathExprImpl::Impl as PathExprImpl
4+
5+
module Impl {
6+
/**
7+
* A variable scope. Either a block `{ ... }`, the guard/rhs
8+
* of a match arm, or the body of a closure.
9+
*/
10+
abstract class VariableScope extends AstNode { }
11+
12+
class BlockExprScope extends VariableScope, BlockExpr { }
13+
14+
abstract class MatchArmScope extends VariableScope {
15+
MatchArm arm;
16+
17+
bindingset[arm]
18+
MatchArmScope() { exists(arm) }
19+
20+
Pat getPat() { result = arm.getPat() }
21+
}
22+
23+
class MatchArmExprScope extends MatchArmScope {
24+
MatchArmExprScope() { this = arm.getExpr() }
25+
}
26+
27+
class MatchArmGuardScope extends MatchArmScope {
28+
MatchArmGuardScope() { this = arm.getGuard() }
29+
}
30+
31+
class ClosureBodyScope extends VariableScope {
32+
ClosureBodyScope() { this = any(ClosureExpr ce).getBody() }
33+
}
34+
35+
private Pat getImmediatePatParent(AstNode n) {
36+
result = getImmediateParent(n)
37+
or
38+
result.(RecordPat).getRecordPatFieldList().getAField().getPat() = n
39+
}
40+
41+
private Pat getAPatAncestor(Pat p) {
42+
(p instanceof IdentPat or p instanceof OrPat) and
43+
exists(Pat p0 | result = getImmediatePatParent(p0) |
44+
p0 = p
45+
or
46+
p0 = getAPatAncestor(p) and
47+
not p0 instanceof OrPat
48+
)
49+
}
50+
51+
/** Gets the immediately enclosing `|` pattern of `p`, if any */
52+
private OrPat getEnclosingOrPat(Pat p) { result = getAPatAncestor(p) }
53+
54+
/** Gets the outermost enclosing `|` pattern parent of `p`, if any. */
55+
private OrPat getOutermostEnclosingOrPat(IdentPat p) {
56+
result = getEnclosingOrPat+(p) and
57+
not exists(getEnclosingOrPat(result))
58+
}
59+
60+
/**
61+
* Holds if `p` declares a variable named `name` at `definingNode`. Normally,
62+
* `definingNode = p`, except in cases like
63+
*
64+
* ```rust
65+
* match either {
66+
* Either::Left(x) | Either::Right(x) => println!(x),
67+
* }
68+
* ```
69+
*
70+
* where `definingNode` is the entire `Either::Left(x) | Either::Right(x)`
71+
* pattern.
72+
*/
73+
private predicate variableDecl(AstNode definingNode, IdentPat p, string name) {
74+
(
75+
definingNode = getOutermostEnclosingOrPat(p)
76+
or
77+
not exists(getOutermostEnclosingOrPat(p)) and
78+
definingNode = p.getName()
79+
) and
80+
name = p.getName().getText()
81+
}
82+
83+
/** A variable. */
84+
class Variable extends MkVariable {
85+
private AstNode definingNode;
86+
private string name;
87+
88+
Variable() { this = MkVariable(definingNode, name) }
89+
90+
/** Gets the name of this variable. */
91+
string getName() { result = name }
92+
93+
/** Gets the location of this variable. */
94+
Location getLocation() { result = definingNode.getLocation() }
95+
96+
/** Gets a textual representation of this variable. */
97+
string toString() { result = this.getName() }
98+
99+
/** Gets an access to this variable. */
100+
VariableAccess getAnAccess() { result.getVariable() = this }
101+
}
102+
103+
/** A path expression that may access a local variable. */
104+
private class VariableAccessCand extends PathExpr {
105+
string name_;
106+
107+
VariableAccessCand() {
108+
exists(Path p, PathSegment ps |
109+
p = this.getPath() and
110+
not p.hasQualifier() and
111+
ps = p.getPart() and
112+
not ps.hasGenericArgList() and
113+
not ps.hasParamList() and
114+
not ps.hasPathType() and
115+
not ps.hasReturnTypeSyntax() and
116+
name_ = ps.getNameRef().getText()
117+
)
118+
}
119+
120+
string getName() { result = name_ }
121+
}
122+
123+
private AstNode getAnAncestorInVariableScope(AstNode n) {
124+
(
125+
n instanceof Pat or
126+
n instanceof VariableAccessCand or
127+
n instanceof LetStmt or
128+
n instanceof VariableScope
129+
) and
130+
exists(AstNode n0 | result = getImmediateParent(n0) |
131+
n0 = n
132+
or
133+
n0 = getAnAncestorInVariableScope(n) and
134+
not n0 instanceof VariableScope
135+
)
136+
}
137+
138+
/** Gets the immediately enclosing variable scope of `n`. */
139+
private VariableScope getEnclosingScope(AstNode n) { result = getAnAncestorInVariableScope(n) }
140+
141+
private Pat getAVariablePatAncestor(Variable v) {
142+
exists(AstNode definingNode, string name |
143+
v = MkVariable(definingNode, name) and
144+
variableDecl(definingNode, result, name)
145+
)
146+
or
147+
exists(Pat mid |
148+
mid = getAVariablePatAncestor(v) and
149+
result = getImmediatePatParent(mid)
150+
)
151+
}
152+
153+
/**
154+
* Holds if `v` is named `name` and is declared inside variable scope
155+
* `scope`, and `v` is bound starting from `(line, column)`.
156+
*/
157+
private predicate variableDeclInScope(
158+
Variable v, VariableScope scope, string name, int line, int column
159+
) {
160+
name = v.getName() and
161+
exists(Pat pat | pat = getAVariablePatAncestor(v) |
162+
scope =
163+
any(MatchArmScope arm |
164+
arm.getPat() = pat and
165+
arm.getLocation().hasLocationInfo(_, line, column, _, _)
166+
)
167+
or
168+
exists(Function f |
169+
f.getParamList().getAParam().getPat() = pat and
170+
scope = f.getBody() and
171+
scope.getLocation().hasLocationInfo(_, line, column, _, _)
172+
)
173+
or
174+
exists(LetStmt let |
175+
let.getPat() = pat and
176+
scope = getEnclosingScope(let) and
177+
// for `let` statements, variables are bound _after_ the statement, i.e.
178+
// not in the RHS
179+
let.getLocation().hasLocationInfo(_, _, _, line, column)
180+
)
181+
or
182+
exists(IfExpr ie, LetExpr let |
183+
let.getPat() = pat and
184+
ie.getCondition() = let and
185+
scope = ie.getThen() and
186+
scope.getLocation().hasLocationInfo(_, line, column, _, _)
187+
)
188+
or
189+
exists(ForExpr fe |
190+
fe.getPat() = pat and
191+
scope = fe.getLoopBody() and
192+
scope.getLocation().hasLocationInfo(_, line, column, _, _)
193+
)
194+
or
195+
exists(ClosureExpr ce |
196+
ce.getParamList().getAParam().getPat() = pat and
197+
scope = ce.getBody() and
198+
scope.getLocation().hasLocationInfo(_, line, column, _, _)
199+
)
200+
)
201+
}
202+
203+
/**
204+
* Holds if `cand` may access a variable named `name` at
205+
* `(startline, startcolumn, endline, endcolumn)` in the variable scope
206+
* `scope`.
207+
*
208+
* `nestLevel` is the number of nested scopes that need to be traversed
209+
* to reach `scope` from `cand`.
210+
*/
211+
private predicate variableAccessCandInScope(
212+
VariableAccessCand cand, VariableScope scope, string name, int nestLevel, int startline,
213+
int startcolumn, int endline, int endcolumn
214+
) {
215+
name = cand.getName() and
216+
scope = [cand.(VariableScope), getEnclosingScope(cand)] and
217+
cand.getLocation().hasLocationInfo(_, startline, startcolumn, endline, endcolumn) and
218+
nestLevel = 0
219+
or
220+
exists(VariableScope inner |
221+
variableAccessCandInScope(cand, inner, name, nestLevel - 1, _, _, _, _) and
222+
scope = getEnclosingScope(inner) and
223+
// Use the location of the inner scope as the location of the access, instead of the
224+
// actual access location. This allows us to collapse multiple accesses in inner
225+
// scopes to a single entity
226+
scope.getLocation().hasLocationInfo(_, startline, startcolumn, endline, endcolumn)
227+
)
228+
}
229+
230+
private newtype TVariableOrAccessCand =
231+
TVariableOrAccessCandVariable(Variable v) or
232+
TVariableOrAccessCandVariableAccessCand(VariableAccessCand va)
233+
234+
private class VariableOrAccessCand extends TVariableOrAccessCand {
235+
Variable asVariable() { this = TVariableOrAccessCandVariable(result) }
236+
237+
VariableAccessCand asVariableAccessCand() {
238+
this = TVariableOrAccessCandVariableAccessCand(result)
239+
}
240+
241+
string toString() {
242+
result = this.asVariable().toString() or result = this.asVariableAccessCand().toString()
243+
}
244+
245+
Location getLocation() {
246+
result = this.asVariable().getLocation() or result = this.asVariableAccessCand().getLocation()
247+
}
248+
249+
pragma[nomagic]
250+
predicate rankBy(
251+
string name, VariableScope scope, int startline, int startcolumn, int endline, int endcolumn
252+
) {
253+
variableDeclInScope(this.asVariable(), scope, name, startline, startcolumn) and
254+
endline = -1 and
255+
endcolumn = -1
256+
or
257+
variableAccessCandInScope(this.asVariableAccessCand(), scope, name, _, startline, startcolumn,
258+
endline, endcolumn)
259+
}
260+
}
261+
262+
/**
263+
* Gets the rank of `v` amongst all other declarations or access candidates
264+
* to a variable named `name` in the variable scope `scope`.
265+
*/
266+
private int rankVariableOrAccess(VariableScope scope, string name, VariableOrAccessCand v) {
267+
v =
268+
rank[result + 1](VariableOrAccessCand v0, int startline, int startcolumn, int endline,
269+
int endcolumn |
270+
v0.rankBy(name, scope, startline, startcolumn, endline, endcolumn)
271+
|
272+
v0 order by startline, startcolumn, endline, endcolumn
273+
)
274+
}
275+
276+
/**
277+
* Holds if `v` can reach rank `rnk` in the variable scope `scope`. This is needed to
278+
* take shadowing into account, for example in
279+
*
280+
* ```rust
281+
* let x = 0; // rank 0
282+
* use(x); // rank 1
283+
* let x = ""; // rank 2
284+
* use(x); // rank 3
285+
* ```
286+
*
287+
* the declaration at rank 0 can only reach the access at rank 1, while the declaration
288+
* at rank 2 can only reach the access at rank 3.
289+
*/
290+
private predicate variableReachesRank(VariableScope scope, string name, Variable v, int rnk) {
291+
rnk = rankVariableOrAccess(scope, name, TVariableOrAccessCandVariable(v))
292+
or
293+
variableReachesRank(scope, name, v, rnk - 1) and
294+
rnk = rankVariableOrAccess(scope, name, TVariableOrAccessCandVariableAccessCand(_))
295+
}
296+
297+
private predicate variableReachesCand(
298+
VariableScope scope, string name, Variable v, VariableAccessCand cand, int nestLevel
299+
) {
300+
exists(int rnk |
301+
variableReachesRank(scope, name, v, rnk) and
302+
rnk = rankVariableOrAccess(scope, name, TVariableOrAccessCandVariableAccessCand(cand)) and
303+
variableAccessCandInScope(cand, scope, name, nestLevel, _, _, _, _)
304+
)
305+
}
306+
307+
/** A variable access. */
308+
class VariableAccess extends PathExprImpl::PathExpr instanceof VariableAccessCand {
309+
private string name;
310+
private Variable v;
311+
312+
VariableAccess() { variableAccess(_, name, v, this) }
313+
314+
/** Gets the variable being accessed. */
315+
Variable getVariable() { result = v }
316+
317+
override string toString() { result = name }
318+
319+
override string getAPrimaryQlClass() { result = "VariableAccess" }
320+
}
321+
322+
cached
323+
private module Cached {
324+
cached
325+
newtype TVariable =
326+
MkVariable(AstNode definingNode, string name) { variableDecl(definingNode, _, name) }
327+
328+
cached
329+
predicate variableAccess(VariableScope scope, string name, Variable v, VariableAccessCand cand) {
330+
v =
331+
min(Variable v0, int nestLevel |
332+
variableReachesCand(scope, name, v0, cand, nestLevel)
333+
|
334+
v0 order by nestLevel
335+
)
336+
}
337+
}
338+
339+
private import Cached
340+
}

rust/ql/lib/rust.qll

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@ import codeql.rust.elements
44
import codeql.Locations
55
import codeql.files.FileSystem
66
import codeql.rust.elements.LogicalOperation
7+
import codeql.rust.elements.Variable
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
| gen_let_expr.rs:5:8:5:31 | LetExpr | gen_let_expr.rs:5:22:5:31 | PathExpr |
1+
| gen_let_expr.rs:5:8:5:31 | LetExpr | gen_let_expr.rs:5:22:5:31 | maybe_some |
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
| gen_match_arm.rs:6:9:6:29 | MatchArm | gen_match_arm.rs:6:28:6:28 | PathExpr |
1+
| gen_match_arm.rs:6:9:6:29 | MatchArm | gen_match_arm.rs:6:28:6:28 | y |
22
| gen_match_arm.rs:7:9:7:26 | MatchArm | gen_match_arm.rs:7:25:7:25 | 0 |
33
| gen_match_arm.rs:10:9:10:35 | MatchArm | gen_match_arm.rs:10:30:10:34 | ... / ... |
44
| gen_match_arm.rs:11:9:11:15 | MatchArm | gen_match_arm.rs:11:14:11:14 | 0 |
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
| gen_match_expr.rs:5:5:8:5 | MatchExpr | gen_match_expr.rs:5:11:5:11 | PathExpr |
2-
| gen_match_expr.rs:9:5:12:5 | MatchExpr | gen_match_expr.rs:9:11:9:11 | PathExpr |
1+
| gen_match_expr.rs:5:5:8:5 | MatchExpr | gen_match_expr.rs:5:11:5:11 | x |
2+
| gen_match_expr.rs:9:5:12:5 | MatchExpr | gen_match_expr.rs:9:11:9:11 | x |

0 commit comments

Comments
 (0)