mirror of
https://github.com/github/codeql.git
synced 2026-02-12 05:01:06 +01:00
Cfg: Extract CFG pretty-printing code.
This commit is contained in:
@@ -1310,160 +1310,21 @@ 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;
|
||||
|
||||
class ControlFlowNode extends Node {
|
||||
Callable getEnclosingCallable() { result = this.getScope() }
|
||||
}
|
||||
|
||||
/**
|
||||
* 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() }
|
||||
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 {
|
||||
|
||||
184
shared/controlflow/codeql/controlflow/PrintGraph.qll
Normal file
184
shared/controlflow/codeql/controlflow/PrintGraph.qll
Normal file
@@ -0,0 +1,184 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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> {
|
||||
/** 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 `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