mirror of
https://github.com/github/codeql.git
synced 2026-02-11 20:51:06 +01:00
Merge pull request #21268 from aschackmull/java/view-cfg
Java: Add support for "View CFG" in VSCode.
This commit is contained in:
45
java/ql/lib/printCfg.ql
Normal file
45
java/ql/lib/printCfg.ql
Normal file
@@ -0,0 +1,45 @@
|
||||
/**
|
||||
* @name Print CFG
|
||||
* @description Produces a representation of a file's Control Flow Graph.
|
||||
* This query is used by the VS Code extension.
|
||||
* @id java/print-cfg
|
||||
* @kind graph
|
||||
* @tags ide-contextual-queries/print-cfg
|
||||
*/
|
||||
|
||||
import java
|
||||
|
||||
external string selectedSourceFile();
|
||||
|
||||
private predicate selectedSourceFileAlias = selectedSourceFile/0;
|
||||
|
||||
external int selectedSourceLine();
|
||||
|
||||
private predicate selectedSourceLineAlias = selectedSourceLine/0;
|
||||
|
||||
external int selectedSourceColumn();
|
||||
|
||||
private predicate selectedSourceColumnAlias = selectedSourceColumn/0;
|
||||
|
||||
module ViewCfgQueryInput implements ViewCfgQueryInputSig<File> {
|
||||
predicate selectedSourceFile = selectedSourceFileAlias/0;
|
||||
|
||||
predicate selectedSourceLine = selectedSourceLineAlias/0;
|
||||
|
||||
predicate selectedSourceColumn = selectedSourceColumnAlias/0;
|
||||
|
||||
predicate cfgScopeSpan(
|
||||
Callable callable, File file, int startLine, int startColumn, int endLine, int endColumn
|
||||
) {
|
||||
file = callable.getFile() and
|
||||
callable.getLocation().getStartLine() = startLine and
|
||||
callable.getLocation().getStartColumn() = startColumn and
|
||||
exists(Location loc |
|
||||
loc.getEndLine() = endLine and
|
||||
loc.getEndColumn() = endColumn and
|
||||
loc = callable.getBody().getLocation()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
import ViewCfgQuery<File, ViewCfgQueryInput>
|
||||
@@ -1775,3 +1775,17 @@ class ConditionNode extends ControlFlow::Node {
|
||||
/** Gets the condition of this `ConditionNode`. */
|
||||
ExprParent getCondition() { result = this.asExpr() or result = this.asStmt() }
|
||||
}
|
||||
|
||||
private import codeql.controlflow.PrintGraph as PrintGraph
|
||||
|
||||
private module PrintGraphInput implements PrintGraph::InputSig<Location> {
|
||||
private import java as J
|
||||
|
||||
class Callable = J::Callable;
|
||||
|
||||
class ControlFlowNode = J::ControlFlowNode;
|
||||
|
||||
ControlFlowNode getASuccessor(ControlFlowNode n, SuccessorType t) { result = n.getASuccessor(t) }
|
||||
}
|
||||
|
||||
import PrintGraph::PrintGraph<Location, PrintGraphInput>
|
||||
|
||||
@@ -1122,6 +1122,9 @@ module MakeWithSplitting<
|
||||
/** Gets the scope of this node. */
|
||||
CfgScope getScope() { result = getNodeCfgScope(this) }
|
||||
|
||||
/** Gets the enclosing callable of this node. */
|
||||
CfgScope getEnclosingCallable() { result = this.getScope() }
|
||||
|
||||
/** Gets a successor node of a given type, if any. */
|
||||
Node getASuccessor(SuccessorType t) { result = getASuccessor(this, t) }
|
||||
|
||||
@@ -1310,160 +1313,19 @@ module MakeWithSplitting<
|
||||
}
|
||||
}
|
||||
|
||||
/** A node to be included in the output of `TestOutput`. */
|
||||
signature class RelevantNodeSig extends Node;
|
||||
private import PrintGraph as Pp
|
||||
|
||||
/**
|
||||
* Import this module into a `.ql` file to output a CFG. The
|
||||
* graph is restricted to nodes from `RelevantNode`.
|
||||
*/
|
||||
module TestOutput<RelevantNodeSig RelevantNode> {
|
||||
/** Holds if `pred -> succ` is an edge in the CFG. */
|
||||
query predicate edges(RelevantNode pred, RelevantNode succ, string label) {
|
||||
label =
|
||||
strictconcat(SuccessorType t, string s |
|
||||
succ = getASuccessor(pred, t) and
|
||||
if t instanceof DirectSuccessor then s = "" else s = t.toString()
|
||||
|
|
||||
s, ", " order by s
|
||||
)
|
||||
}
|
||||
private module PrintGraphInput implements Pp::InputSig<Location> {
|
||||
class Callable = CfgScope;
|
||||
|
||||
/**
|
||||
* Provides logic for representing a CFG as a [Mermaid diagram](https://mermaid.js.org/).
|
||||
*/
|
||||
module Mermaid {
|
||||
private string nodeId(RelevantNode n) {
|
||||
result =
|
||||
any(int i |
|
||||
n =
|
||||
rank[i](RelevantNode p, string filePath, int startLine, int startColumn, int endLine,
|
||||
int endColumn |
|
||||
p.getLocation()
|
||||
.hasLocationInfo(filePath, startLine, startColumn, endLine, endColumn)
|
||||
|
|
||||
p order by filePath, startLine, startColumn, endLine, endColumn, p.toString()
|
||||
)
|
||||
).toString()
|
||||
}
|
||||
class ControlFlowNode = Node;
|
||||
|
||||
private string nodes() {
|
||||
result =
|
||||
concat(RelevantNode n, string id, string text |
|
||||
id = nodeId(n) and
|
||||
text = n.toString()
|
||||
|
|
||||
id + "[\"" + text + "\"]", "\n" order by id
|
||||
)
|
||||
}
|
||||
|
||||
private string edge(RelevantNode pred, RelevantNode succ) {
|
||||
edges(pred, succ, _) and
|
||||
exists(string label |
|
||||
edges(pred, succ, label) and
|
||||
if label = ""
|
||||
then result = nodeId(pred) + " --> " + nodeId(succ)
|
||||
else result = nodeId(pred) + " -- " + label + " --> " + nodeId(succ)
|
||||
)
|
||||
}
|
||||
|
||||
private string edges() {
|
||||
result =
|
||||
concat(RelevantNode pred, RelevantNode succ, string edge, string filePath, int startLine,
|
||||
int startColumn, int endLine, int endColumn |
|
||||
edge = edge(pred, succ) and
|
||||
pred.getLocation().hasLocationInfo(filePath, startLine, startColumn, endLine, endColumn)
|
||||
|
|
||||
edge, "\n"
|
||||
order by
|
||||
filePath, startLine, startColumn, endLine, endColumn, pred.toString()
|
||||
)
|
||||
}
|
||||
|
||||
/** Holds if the Mermaid representation is `s`. */
|
||||
query predicate mermaid(string s) { s = "flowchart TD\n" + nodes() + "\n\n" + edges() }
|
||||
ControlFlowNode getASuccessor(ControlFlowNode n, SuccessorType t) {
|
||||
result = n.getASuccessor(t)
|
||||
}
|
||||
}
|
||||
|
||||
/** Provides the input to `ViewCfgQuery`. */
|
||||
signature module ViewCfgQueryInputSig<FileSig File> {
|
||||
/** The source file selected in the IDE. Should be an `external` predicate. */
|
||||
string selectedSourceFile();
|
||||
|
||||
/** The source line selected in the IDE. Should be an `external` predicate. */
|
||||
int selectedSourceLine();
|
||||
|
||||
/** The source column selected in the IDE. Should be an `external` predicate. */
|
||||
int selectedSourceColumn();
|
||||
|
||||
/**
|
||||
* Holds if CFG scope `scope` spans column `startColumn` of line `startLine` to
|
||||
* column `endColumn` of line `endLine` in `file`.
|
||||
*/
|
||||
predicate cfgScopeSpan(
|
||||
CfgScope scope, File file, int startLine, int startColumn, int endLine, int endColumn
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides an implementation for a `View CFG` query.
|
||||
*
|
||||
* Import this module into a `.ql` that looks like
|
||||
*
|
||||
* ```ql
|
||||
* @name Print CFG
|
||||
* @description Produces a representation of a file's Control Flow Graph.
|
||||
* This query is used by the VS Code extension.
|
||||
* @id <lang>/print-cfg
|
||||
* @kind graph
|
||||
* @tags ide-contextual-queries/print-cfg
|
||||
* ```
|
||||
*/
|
||||
module ViewCfgQuery<FileSig File, ViewCfgQueryInputSig<File> ViewCfgQueryInput> {
|
||||
private import ViewCfgQueryInput
|
||||
|
||||
bindingset[file, line, column]
|
||||
private CfgScope smallestEnclosingScope(File file, int line, int column) {
|
||||
result =
|
||||
min(CfgScope scope, int startLine, int startColumn, int endLine, int endColumn |
|
||||
cfgScopeSpan(scope, file, startLine, startColumn, endLine, endColumn) and
|
||||
(
|
||||
startLine < line
|
||||
or
|
||||
startLine = line and startColumn <= column
|
||||
) and
|
||||
(
|
||||
endLine > line
|
||||
or
|
||||
endLine = line and endColumn >= column
|
||||
)
|
||||
|
|
||||
scope order by startLine desc, startColumn desc, endLine, endColumn
|
||||
)
|
||||
}
|
||||
|
||||
private import IdeContextual<File>
|
||||
|
||||
private class RelevantNode extends Node {
|
||||
RelevantNode() {
|
||||
this.getScope() =
|
||||
smallestEnclosingScope(getFileBySourceArchiveName(selectedSourceFile()),
|
||||
selectedSourceLine(), selectedSourceColumn())
|
||||
}
|
||||
|
||||
string getOrderDisambiguation() { result = "" }
|
||||
}
|
||||
|
||||
private module Output = TestOutput<RelevantNode>;
|
||||
|
||||
import Output::Mermaid
|
||||
|
||||
/** Holds if `pred` -> `succ` is an edge in the CFG. */
|
||||
query predicate edges(RelevantNode pred, RelevantNode succ, string attr, string val) {
|
||||
attr = "semmle.label" and
|
||||
Output::edges(pred, succ, val)
|
||||
}
|
||||
}
|
||||
import Pp::PrintGraph<Location, PrintGraphInput>
|
||||
|
||||
/** Provides a set of consistency queries. */
|
||||
module Consistency {
|
||||
|
||||
187
shared/controlflow/codeql/controlflow/PrintGraph.qll
Normal file
187
shared/controlflow/codeql/controlflow/PrintGraph.qll
Normal file
@@ -0,0 +1,187 @@
|
||||
/**
|
||||
* Provides modules for printing control flow graphs in VSCode via the "View
|
||||
* CFG" query. Also provides modules for printing control flow graphs in tests
|
||||
* and as Mermaid diagrams.
|
||||
*/
|
||||
overlay[local?]
|
||||
module;
|
||||
|
||||
private import codeql.util.FileSystem
|
||||
private import codeql.util.Location
|
||||
private import SuccessorType
|
||||
|
||||
signature module InputSig<LocationSig Location> {
|
||||
class Callable;
|
||||
|
||||
class ControlFlowNode {
|
||||
Callable getEnclosingCallable();
|
||||
|
||||
Location getLocation();
|
||||
|
||||
string toString();
|
||||
}
|
||||
|
||||
ControlFlowNode getASuccessor(ControlFlowNode n, SuccessorType t);
|
||||
}
|
||||
|
||||
/** Provides modules for printing control flow graphs. */
|
||||
module PrintGraph<LocationSig Location, InputSig<Location> Input> {
|
||||
private import Input
|
||||
|
||||
/** A node to be included in the output of `TestOutput`. */
|
||||
signature class RelevantNodeSig extends ControlFlowNode;
|
||||
|
||||
/**
|
||||
* Import this module into a `.ql` file to output a CFG. The
|
||||
* graph is restricted to nodes from `RelevantNode`.
|
||||
*/
|
||||
module TestOutput<RelevantNodeSig RelevantNode> {
|
||||
/** Holds if `pred -> succ` is an edge in the CFG. */
|
||||
query predicate edges(RelevantNode pred, RelevantNode succ, string label) {
|
||||
label =
|
||||
strictconcat(SuccessorType t, string s |
|
||||
succ = getASuccessor(pred, t) and
|
||||
if t instanceof DirectSuccessor then s = "" else s = t.toString()
|
||||
|
|
||||
s, ", " order by s
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides logic for representing a CFG as a [Mermaid diagram](https://mermaid.js.org/).
|
||||
*/
|
||||
module Mermaid {
|
||||
private string nodeId(RelevantNode n) {
|
||||
result =
|
||||
any(int i |
|
||||
n =
|
||||
rank[i](RelevantNode p, string filePath, int startLine, int startColumn, int endLine,
|
||||
int endColumn |
|
||||
p.getLocation()
|
||||
.hasLocationInfo(filePath, startLine, startColumn, endLine, endColumn)
|
||||
|
|
||||
p order by filePath, startLine, startColumn, endLine, endColumn, p.toString()
|
||||
)
|
||||
).toString()
|
||||
}
|
||||
|
||||
private string nodes() {
|
||||
result =
|
||||
concat(RelevantNode n, string id, string text |
|
||||
id = nodeId(n) and
|
||||
text = n.toString()
|
||||
|
|
||||
id + "[\"" + text + "\"]", "\n" order by id
|
||||
)
|
||||
}
|
||||
|
||||
private string edge(RelevantNode pred, RelevantNode succ) {
|
||||
edges(pred, succ, _) and
|
||||
exists(string label |
|
||||
edges(pred, succ, label) and
|
||||
if label = ""
|
||||
then result = nodeId(pred) + " --> " + nodeId(succ)
|
||||
else result = nodeId(pred) + " -- " + label + " --> " + nodeId(succ)
|
||||
)
|
||||
}
|
||||
|
||||
private string edges() {
|
||||
result =
|
||||
concat(RelevantNode pred, RelevantNode succ, string edge, string filePath, int startLine,
|
||||
int startColumn, int endLine, int endColumn |
|
||||
edge = edge(pred, succ) and
|
||||
pred.getLocation().hasLocationInfo(filePath, startLine, startColumn, endLine, endColumn)
|
||||
|
|
||||
edge, "\n"
|
||||
order by
|
||||
filePath, startLine, startColumn, endLine, endColumn, pred.toString()
|
||||
)
|
||||
}
|
||||
|
||||
/** Holds if the Mermaid representation is `s`. */
|
||||
query predicate mermaid(string s) { s = "flowchart TD\n" + nodes() + "\n\n" + edges() }
|
||||
}
|
||||
}
|
||||
|
||||
/** Provides the input to `ViewCfgQuery`. */
|
||||
signature module ViewCfgQueryInputSig<FileSig File> {
|
||||
/** Gets the source file selected in the IDE. Should be an `external` predicate. */
|
||||
string selectedSourceFile();
|
||||
|
||||
/** Gets the source line selected in the IDE. Should be an `external` predicate. */
|
||||
int selectedSourceLine();
|
||||
|
||||
/** Gets the source column selected in the IDE. Should be an `external` predicate. */
|
||||
int selectedSourceColumn();
|
||||
|
||||
/**
|
||||
* Holds if `callable` spans column `startColumn` of line `startLine` to
|
||||
* column `endColumn` of line `endLine` in `file`.
|
||||
*/
|
||||
predicate cfgScopeSpan(
|
||||
Callable callable, File file, int startLine, int startColumn, int endLine, int endColumn
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides an implementation for a `View CFG` query.
|
||||
*
|
||||
* Import this module into a `.ql` that looks like
|
||||
*
|
||||
* ```ql
|
||||
* @name Print CFG
|
||||
* @description Produces a representation of a file's Control Flow Graph.
|
||||
* This query is used by the VS Code extension.
|
||||
* @id <lang>/print-cfg
|
||||
* @kind graph
|
||||
* @tags ide-contextual-queries/print-cfg
|
||||
* ```
|
||||
*/
|
||||
module ViewCfgQuery<FileSig File, ViewCfgQueryInputSig<File> ViewCfgQueryInput> {
|
||||
private import ViewCfgQueryInput
|
||||
|
||||
bindingset[file, line, column]
|
||||
private Callable smallestEnclosingScope(File file, int line, int column) {
|
||||
result =
|
||||
min(Callable callable, int startLine, int startColumn, int endLine, int endColumn |
|
||||
cfgScopeSpan(callable, file, startLine, startColumn, endLine, endColumn) and
|
||||
(
|
||||
startLine < line
|
||||
or
|
||||
startLine = line and startColumn <= column
|
||||
) and
|
||||
(
|
||||
endLine > line
|
||||
or
|
||||
endLine = line and endColumn >= column
|
||||
)
|
||||
|
|
||||
callable order by startLine desc, startColumn desc, endLine, endColumn
|
||||
)
|
||||
}
|
||||
|
||||
private import IdeContextual<File>
|
||||
|
||||
final private class FinalControlFlowNode = ControlFlowNode;
|
||||
|
||||
private class RelevantNode extends FinalControlFlowNode {
|
||||
RelevantNode() {
|
||||
this.getEnclosingCallable() =
|
||||
smallestEnclosingScope(getFileBySourceArchiveName(selectedSourceFile()),
|
||||
selectedSourceLine(), selectedSourceColumn())
|
||||
}
|
||||
|
||||
string getOrderDisambiguation() { result = "" }
|
||||
}
|
||||
|
||||
private module Output = TestOutput<RelevantNode>;
|
||||
|
||||
import Output::Mermaid
|
||||
|
||||
/** Holds if `pred` -> `succ` is an edge in the CFG. */
|
||||
query predicate edges(RelevantNode pred, RelevantNode succ, string attr, string val) {
|
||||
attr = "semmle.label" and
|
||||
Output::edges(pred, succ, val)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user