Skip to content

Commit 71cf2f1

Browse files
authored
Merge pull request #22479 from aschackmull/unified/cfg
Unified: Add control flow graph.
2 parents e7181b3 + f452bd4 commit 71cf2f1

7 files changed

Lines changed: 335 additions & 1 deletion

File tree

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
import unified
2+
import ControlFlow::Consistency
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
name: codeql/unified-consistency-queries
2+
groups: [unified, test, consistency-queries]
3+
dependencies:
4+
codeql/unified-all: ${workspace}
5+
warnOnImplicitThis: true
Lines changed: 277 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,277 @@
1+
/**
2+
* Provides classes representing the control flow graph within callables.
3+
*/
4+
overlay[local?]
5+
module;
6+
7+
private import unified
8+
private import codeql.controlflow.ControlFlowGraph
9+
private import codeql.controlflow.SuccessorType
10+
11+
private module Cfg0 = Make0<Location, Ast>;
12+
13+
private module Cfg1 = Make1<Input>;
14+
15+
private module Cfg2 = Make2<Input>;
16+
17+
private import Cfg0
18+
private import Cfg1
19+
private import Cfg2
20+
import Public
21+
22+
/** Provides an implementation of the AST signature for Unified. */
23+
private module Ast implements AstSig<Location> {
24+
private import unified as U
25+
26+
class AstNode = U::AstNode;
27+
28+
private predicate skipControlFlow(AstNode e) { e instanceof Modifier or e instanceof Identifier }
29+
30+
AstNode getChild(AstNode n, int index) {
31+
result.getParent() = n and
32+
result.getParentIndex() = index and
33+
not n instanceof Callable and
34+
not skipControlFlow(n) and
35+
not skipControlFlow(result)
36+
}
37+
38+
Callable getEnclosingCallable(AstNode node) {
39+
exists(AstNode parent | parent = node.getParent() |
40+
result = parent
41+
or
42+
not parent instanceof Callable and
43+
result = getEnclosingCallable(parent)
44+
)
45+
}
46+
47+
class Callable = U::Callable;
48+
49+
AstNode callableGetBody(Callable c) {
50+
result = c.(AccessorDeclaration).getBody() or
51+
result = c.(ConstructorDeclaration).getBody() or
52+
result = c.(DestructorDeclaration).getBody() or
53+
result = c.(FunctionDeclaration).getBody() or
54+
result = c.(FunctionExpr).getBody() or
55+
result = c.(InitializerDeclaration).getBody() or
56+
result = c.(TopLevel).getBody()
57+
}
58+
59+
class Parameter extends U::Parameter {
60+
Expr getDefaultValue() { result = super.getDefault() }
61+
62+
AstNode getPattern() { result = super.getPattern() }
63+
}
64+
65+
Parameter callableGetParameter(Callable c, int index) {
66+
result = c.(AccessorDeclaration).getParameter(index) or
67+
result = c.(ConstructorDeclaration).getParameter(index) or
68+
result = c.(FunctionDeclaration).getParameter(index) or
69+
result = c.(FunctionExpr).getParameter(index)
70+
}
71+
72+
class Stmt = U::Stmt;
73+
74+
class Expr = U::Expr;
75+
76+
class BlockStmt = U::Block;
77+
78+
class ExprStmt extends Stmt {
79+
ExprStmt() { none() }
80+
81+
Expr getExpr() { none() }
82+
}
83+
84+
class IfStmt extends Stmt {
85+
IfStmt() { none() }
86+
87+
Expr getCondition() { none() }
88+
89+
Stmt getThen() { none() }
90+
91+
Stmt getElse() { none() }
92+
}
93+
94+
abstract class LoopStmt extends Stmt {
95+
Stmt getBody() { none() }
96+
}
97+
98+
class WhileStmt extends LoopStmt instanceof U::WhileStmt {
99+
override Stmt getBody() { result = U::WhileStmt.super.getBody() }
100+
101+
Expr getCondition() { result = super.getCondition() }
102+
}
103+
104+
class DoStmt extends LoopStmt instanceof U::DoWhileStmt {
105+
override Stmt getBody() { result = U::DoWhileStmt.super.getBody() }
106+
107+
Expr getCondition() { result = super.getCondition() }
108+
}
109+
110+
class UntilStmt extends LoopStmt {
111+
UntilStmt() { none() }
112+
113+
Expr getCondition() { none() }
114+
}
115+
116+
class ForStmt extends LoopStmt {
117+
ForStmt() { none() }
118+
119+
AstNode getInit(int index) { none() }
120+
121+
Expr getCondition() { none() }
122+
123+
AstNode getUpdate(int index) { none() }
124+
}
125+
126+
class ForeachStmt extends LoopStmt instanceof U::ForEachStmt {
127+
override Stmt getBody() { result = U::ForEachStmt.super.getBody() }
128+
129+
// TODO support foreach guard
130+
//
131+
// TODO: Expr != Pattern
132+
Expr getVariable() { result = super.getPattern() }
133+
134+
Expr getCollection() { result = super.getIterable() }
135+
}
136+
137+
class BreakStmt = U::BreakExpr;
138+
139+
class ContinueStmt = U::ContinueExpr;
140+
141+
class GotoStmt extends Stmt {
142+
GotoStmt() { none() }
143+
}
144+
145+
class ReturnStmt extends U::ReturnExpr {
146+
Expr getExpr() { result = super.getValue() }
147+
}
148+
149+
class Throw extends U::ThrowExpr {
150+
Expr getExpr() { result = super.getValue() }
151+
}
152+
153+
class TryStmt extends U::TryExpr {
154+
AstNode getBody(int index) { index = 0 and result = super.getBody() }
155+
156+
CatchClause getCatch(int index) { result = super.getCatchClause(index) }
157+
158+
Stmt getFinally() { none() }
159+
}
160+
161+
class CatchClause extends U::CatchClause {
162+
AstNode getPattern() { result = super.getPattern() }
163+
164+
AstNode getVariable() { none() }
165+
166+
Expr getCondition() { none() }
167+
168+
Stmt getBody() { result = super.getBody() }
169+
}
170+
171+
class Switch extends U::SwitchExpr {
172+
Expr getExpr() { result = super.getValue() }
173+
174+
Case getCase(int index) { result = super.getCase(index) }
175+
176+
Stmt getStmt(int index) { none() }
177+
}
178+
179+
class Case extends U::SwitchCase {
180+
AstNode getPattern(int index) { result = super.getPattern() and index = 0 }
181+
182+
Expr getGuard() { none() }
183+
184+
AstNode getBody() { result = super.getBody() }
185+
}
186+
187+
class DefaultCase extends Case {
188+
DefaultCase() { not exists(super.getPattern()) }
189+
}
190+
191+
class ConditionalExpr = U::IfExpr;
192+
193+
// TODO: sort out the relationship between BinaryExpr and Assignment
194+
class BinaryExpr extends U::BinaryExpr {
195+
Expr getLeftOperand() { result = super.getLeft() }
196+
197+
Expr getRightOperand() { result = super.getRight() }
198+
}
199+
200+
class LogicalAndExpr extends BinaryExpr, U::LogicalAndExpr { }
201+
202+
class LogicalOrExpr extends BinaryExpr, U::LogicalOrExpr { }
203+
204+
class NullCoalescingExpr extends BinaryExpr, U::NullCoalescingExpr { }
205+
206+
class UnaryExpr = U::UnaryExpr;
207+
208+
class LogicalNotExpr = U::LogicalNotExpr;
209+
210+
// TODO
211+
class Assignment extends BinaryExpr {
212+
Assignment() { none() }
213+
}
214+
215+
class AssignExpr extends Assignment { }
216+
217+
class CompoundAssignment extends Assignment { }
218+
219+
class AssignLogicalAndExpr extends CompoundAssignment { }
220+
221+
class AssignLogicalOrExpr extends CompoundAssignment { }
222+
223+
class AssignNullCoalescingExpr extends CompoundAssignment { }
224+
225+
class BooleanLiteral extends U::BooleanLiteral {
226+
boolean getValue() { result.toString() = super.getValue() }
227+
}
228+
229+
class PatternMatchExpr extends U::PatternGuardExpr {
230+
Expr getExpr() { result = super.getValue() }
231+
232+
AstNode getPattern() { result = super.getPattern() }
233+
}
234+
}
235+
236+
private module Input implements InputSig1, InputSig2 {
237+
private import codeql.util.Void
238+
239+
predicate cfgCachedStageRef() { CfgCachedStage::ref() }
240+
241+
class Label extends string {
242+
Label() {
243+
any(LabeledStmt l).getLabel().getValue() = this or
244+
any(BreakExpr b).getLabel().getValue() = this or
245+
any(ContinueExpr c).getLabel().getValue() = this
246+
}
247+
248+
string toString() { result = this }
249+
}
250+
251+
private Label getLabelOfStmt(Stmt s) {
252+
exists(LabeledStmt l | s = l.getStmt() |
253+
result = l.getLabel().getValue() or
254+
result = getLabelOfStmt(l)
255+
)
256+
}
257+
258+
predicate hasLabel(Ast::AstNode n, Label l) {
259+
l = getLabelOfStmt(n)
260+
or
261+
l = n.(BreakExpr).getLabel().getValue()
262+
or
263+
l = n.(ContinueExpr).getLabel().getValue()
264+
}
265+
266+
class CallableContext = Void;
267+
268+
predicate beginAbruptCompletion(
269+
AstNode ast, PreControlFlowNode n, AbruptCompletion c, boolean always
270+
) {
271+
none()
272+
}
273+
274+
predicate endAbruptCompletion(AstNode ast, PreControlFlowNode n, AbruptCompletion c) { none() }
275+
276+
predicate step(PreControlFlowNode n1, PreControlFlowNode n2) { none() }
277+
}

unified/ql/lib/codeql/unified/internal/FacadeAst.qll

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,14 @@ module Unified {
3939
}
4040
}
4141

42+
/** A block statement. */
43+
class Block extends G::Block {
44+
/** Gets the last statement in this block. */
45+
Stmt getLastStmt() {
46+
exists(int i | result = this.getStmt(i) and not exists(this.getStmt(i + 1)))
47+
}
48+
}
49+
4250
/** An expression */
4351
class Expr extends G::Expr {
4452
/** Gets the string value of this expression, if it is a known string constant. */
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/**
2+
* @name Print CFG
3+
* @description Produces a representation of a file's Control Flow Graph.
4+
* This query is used by the VS Code extension.
5+
* @id unified/print-cfg
6+
* @kind graph
7+
* @tags ide-contextual-queries/print-cfg
8+
*/
9+
10+
private import unified
11+
private import codeql.Locations
12+
13+
external string selectedSourceFile();
14+
15+
private predicate selectedSourceFileAlias = selectedSourceFile/0;
16+
17+
external int selectedSourceLine();
18+
19+
private predicate selectedSourceLineAlias = selectedSourceLine/0;
20+
21+
external int selectedSourceColumn();
22+
23+
private predicate selectedSourceColumnAlias = selectedSourceColumn/0;
24+
25+
module ViewCfgQueryInput implements ControlFlow::ViewCfgQueryInputSig<File> {
26+
predicate selectedSourceFile = selectedSourceFileAlias/0;
27+
28+
predicate selectedSourceLine = selectedSourceLineAlias/0;
29+
30+
predicate selectedSourceColumn = selectedSourceColumnAlias/0;
31+
32+
predicate cfgScopeSpan(
33+
Callable scope, File file, int startLine, int startColumn, int endLine, int endColumn
34+
) {
35+
file = scope.getFile() and
36+
scope.getLocation().hasLocationInfo(_, startLine, startColumn, endLine, endColumn)
37+
}
38+
}
39+
40+
import ControlFlow::ViewCfgQuery<File, ViewCfgQueryInput>

unified/ql/lib/qlpack.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ extractor: unified
66
library: true
77
upgrades: upgrades
88
dependencies:
9-
codeql/util: ${workspace}
9+
codeql/controlflow: ${workspace}
1010
codeql/namebinding: ${workspace}
11+
codeql/util: ${workspace}
1112
warnOnImplicitThis: true
1213
compileForOverlayEval: true

unified/ql/lib/unified.qll

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

0 commit comments

Comments
 (0)