Skip to content

Commit e4daeec

Browse files
authored
Merge pull request #21268 from aschackmull/java/view-cfg
Java: Add support for "View CFG" in VSCode.
2 parents 544931f + 81977f1 commit e4daeec

File tree

4 files changed

+256
-148
lines changed

4 files changed

+256
-148
lines changed

java/ql/lib/printCfg.ql

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
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 java/print-cfg
6+
* @kind graph
7+
* @tags ide-contextual-queries/print-cfg
8+
*/
9+
10+
import java
11+
12+
external string selectedSourceFile();
13+
14+
private predicate selectedSourceFileAlias = selectedSourceFile/0;
15+
16+
external int selectedSourceLine();
17+
18+
private predicate selectedSourceLineAlias = selectedSourceLine/0;
19+
20+
external int selectedSourceColumn();
21+
22+
private predicate selectedSourceColumnAlias = selectedSourceColumn/0;
23+
24+
module ViewCfgQueryInput implements ViewCfgQueryInputSig<File> {
25+
predicate selectedSourceFile = selectedSourceFileAlias/0;
26+
27+
predicate selectedSourceLine = selectedSourceLineAlias/0;
28+
29+
predicate selectedSourceColumn = selectedSourceColumnAlias/0;
30+
31+
predicate cfgScopeSpan(
32+
Callable callable, File file, int startLine, int startColumn, int endLine, int endColumn
33+
) {
34+
file = callable.getFile() and
35+
callable.getLocation().getStartLine() = startLine and
36+
callable.getLocation().getStartColumn() = startColumn and
37+
exists(Location loc |
38+
loc.getEndLine() = endLine and
39+
loc.getEndColumn() = endColumn and
40+
loc = callable.getBody().getLocation()
41+
)
42+
}
43+
}
44+
45+
import ViewCfgQuery<File, ViewCfgQueryInput>

java/ql/lib/semmle/code/java/ControlFlowGraph.qll

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1775,3 +1775,17 @@ class ConditionNode extends ControlFlow::Node {
17751775
/** Gets the condition of this `ConditionNode`. */
17761776
ExprParent getCondition() { result = this.asExpr() or result = this.asStmt() }
17771777
}
1778+
1779+
private import codeql.controlflow.PrintGraph as PrintGraph
1780+
1781+
private module PrintGraphInput implements PrintGraph::InputSig<Location> {
1782+
private import java as J
1783+
1784+
class Callable = J::Callable;
1785+
1786+
class ControlFlowNode = J::ControlFlowNode;
1787+
1788+
ControlFlowNode getASuccessor(ControlFlowNode n, SuccessorType t) { result = n.getASuccessor(t) }
1789+
}
1790+
1791+
import PrintGraph::PrintGraph<Location, PrintGraphInput>

shared/controlflow/codeql/controlflow/Cfg.qll

Lines changed: 10 additions & 148 deletions
Original file line numberDiff line numberDiff line change
@@ -1122,6 +1122,9 @@ module MakeWithSplitting<
11221122
/** Gets the scope of this node. */
11231123
CfgScope getScope() { result = getNodeCfgScope(this) }
11241124

1125+
/** Gets the enclosing callable of this node. */
1126+
CfgScope getEnclosingCallable() { result = this.getScope() }
1127+
11251128
/** Gets a successor node of a given type, if any. */
11261129
Node getASuccessor(SuccessorType t) { result = getASuccessor(this, t) }
11271130

@@ -1310,160 +1313,19 @@ module MakeWithSplitting<
13101313
}
13111314
}
13121315

1313-
/** A node to be included in the output of `TestOutput`. */
1314-
signature class RelevantNodeSig extends Node;
1315-
1316-
/**
1317-
* Import this module into a `.ql` file to output a CFG. The
1318-
* graph is restricted to nodes from `RelevantNode`.
1319-
*/
1320-
module TestOutput<RelevantNodeSig RelevantNode> {
1321-
/** Holds if `pred -> succ` is an edge in the CFG. */
1322-
query predicate edges(RelevantNode pred, RelevantNode succ, string label) {
1323-
label =
1324-
strictconcat(SuccessorType t, string s |
1325-
succ = getASuccessor(pred, t) and
1326-
if t instanceof DirectSuccessor then s = "" else s = t.toString()
1327-
|
1328-
s, ", " order by s
1329-
)
1330-
}
1331-
1332-
/**
1333-
* Provides logic for representing a CFG as a [Mermaid diagram](https://mermaid.js.org/).
1334-
*/
1335-
module Mermaid {
1336-
private string nodeId(RelevantNode n) {
1337-
result =
1338-
any(int i |
1339-
n =
1340-
rank[i](RelevantNode p, string filePath, int startLine, int startColumn, int endLine,
1341-
int endColumn |
1342-
p.getLocation()
1343-
.hasLocationInfo(filePath, startLine, startColumn, endLine, endColumn)
1344-
|
1345-
p order by filePath, startLine, startColumn, endLine, endColumn, p.toString()
1346-
)
1347-
).toString()
1348-
}
1349-
1350-
private string nodes() {
1351-
result =
1352-
concat(RelevantNode n, string id, string text |
1353-
id = nodeId(n) and
1354-
text = n.toString()
1355-
|
1356-
id + "[\"" + text + "\"]", "\n" order by id
1357-
)
1358-
}
1316+
private import PrintGraph as Pp
13591317

1360-
private string edge(RelevantNode pred, RelevantNode succ) {
1361-
edges(pred, succ, _) and
1362-
exists(string label |
1363-
edges(pred, succ, label) and
1364-
if label = ""
1365-
then result = nodeId(pred) + " --> " + nodeId(succ)
1366-
else result = nodeId(pred) + " -- " + label + " --> " + nodeId(succ)
1367-
)
1368-
}
1318+
private module PrintGraphInput implements Pp::InputSig<Location> {
1319+
class Callable = CfgScope;
13691320

1370-
private string edges() {
1371-
result =
1372-
concat(RelevantNode pred, RelevantNode succ, string edge, string filePath, int startLine,
1373-
int startColumn, int endLine, int endColumn |
1374-
edge = edge(pred, succ) and
1375-
pred.getLocation().hasLocationInfo(filePath, startLine, startColumn, endLine, endColumn)
1376-
|
1377-
edge, "\n"
1378-
order by
1379-
filePath, startLine, startColumn, endLine, endColumn, pred.toString()
1380-
)
1381-
}
1321+
class ControlFlowNode = Node;
13821322

1383-
/** Holds if the Mermaid representation is `s`. */
1384-
query predicate mermaid(string s) { s = "flowchart TD\n" + nodes() + "\n\n" + edges() }
1323+
ControlFlowNode getASuccessor(ControlFlowNode n, SuccessorType t) {
1324+
result = n.getASuccessor(t)
13851325
}
13861326
}
13871327

1388-
/** Provides the input to `ViewCfgQuery`. */
1389-
signature module ViewCfgQueryInputSig<FileSig File> {
1390-
/** The source file selected in the IDE. Should be an `external` predicate. */
1391-
string selectedSourceFile();
1392-
1393-
/** The source line selected in the IDE. Should be an `external` predicate. */
1394-
int selectedSourceLine();
1395-
1396-
/** The source column selected in the IDE. Should be an `external` predicate. */
1397-
int selectedSourceColumn();
1398-
1399-
/**
1400-
* Holds if CFG scope `scope` spans column `startColumn` of line `startLine` to
1401-
* column `endColumn` of line `endLine` in `file`.
1402-
*/
1403-
predicate cfgScopeSpan(
1404-
CfgScope scope, File file, int startLine, int startColumn, int endLine, int endColumn
1405-
);
1406-
}
1407-
1408-
/**
1409-
* Provides an implementation for a `View CFG` query.
1410-
*
1411-
* Import this module into a `.ql` that looks like
1412-
*
1413-
* ```ql
1414-
* @name Print CFG
1415-
* @description Produces a representation of a file's Control Flow Graph.
1416-
* This query is used by the VS Code extension.
1417-
* @id <lang>/print-cfg
1418-
* @kind graph
1419-
* @tags ide-contextual-queries/print-cfg
1420-
* ```
1421-
*/
1422-
module ViewCfgQuery<FileSig File, ViewCfgQueryInputSig<File> ViewCfgQueryInput> {
1423-
private import ViewCfgQueryInput
1424-
1425-
bindingset[file, line, column]
1426-
private CfgScope smallestEnclosingScope(File file, int line, int column) {
1427-
result =
1428-
min(CfgScope scope, int startLine, int startColumn, int endLine, int endColumn |
1429-
cfgScopeSpan(scope, file, startLine, startColumn, endLine, endColumn) and
1430-
(
1431-
startLine < line
1432-
or
1433-
startLine = line and startColumn <= column
1434-
) and
1435-
(
1436-
endLine > line
1437-
or
1438-
endLine = line and endColumn >= column
1439-
)
1440-
|
1441-
scope order by startLine desc, startColumn desc, endLine, endColumn
1442-
)
1443-
}
1444-
1445-
private import IdeContextual<File>
1446-
1447-
private class RelevantNode extends Node {
1448-
RelevantNode() {
1449-
this.getScope() =
1450-
smallestEnclosingScope(getFileBySourceArchiveName(selectedSourceFile()),
1451-
selectedSourceLine(), selectedSourceColumn())
1452-
}
1453-
1454-
string getOrderDisambiguation() { result = "" }
1455-
}
1456-
1457-
private module Output = TestOutput<RelevantNode>;
1458-
1459-
import Output::Mermaid
1460-
1461-
/** Holds if `pred` -> `succ` is an edge in the CFG. */
1462-
query predicate edges(RelevantNode pred, RelevantNode succ, string attr, string val) {
1463-
attr = "semmle.label" and
1464-
Output::edges(pred, succ, val)
1465-
}
1466-
}
1328+
import Pp::PrintGraph<Location, PrintGraphInput>
14671329

14681330
/** Provides a set of consistency queries. */
14691331
module Consistency {

0 commit comments

Comments
 (0)