@syntest/analysis-javascript
Version:
SynTest CFG JavaScript is a library for generating control flow graphs for the JavaScript language
739 lines • 34.8 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ControlFlowGraphVisitor = void 0;
const ast_visitor_javascript_1 = require("@syntest/ast-visitor-javascript");
const cfg_1 = require("@syntest/cfg");
const logging_1 = require("@syntest/logging");
class ControlFlowGraphVisitor extends ast_visitor_javascript_1.AbstractSyntaxTreeVisitor {
get cfg() {
if (!this._nodes.has("ENTRY")) {
throw new Error("No entry node found");
}
if (!this._nodes.has("SUCCESS_EXIT")) {
throw new Error("No success exit node found");
}
if (!this._nodes.has("ERROR_EXIT")) {
throw new Error("No error exit node found");
}
if (this._nodesList.length !== this._nodes.size) {
throw new Error("Number of nodes dont match");
}
const entryNode = this._nodes.get("ENTRY");
const successExitNode = this._nodes.get("SUCCESS_EXIT");
const errorExitNode = this._nodes.get("ERROR_EXIT");
if (this._currentParents[0] === "ENTRY") {
// nothing added so we add
}
// connect last nodes to success exit
this._connectToParents(successExitNode);
// connect all return nodes to success exit
for (const returnNode of this._returnNodes) {
this._edges.push(this._createEdge(this._nodes.get(returnNode), successExitNode, cfg_1.EdgeType.NORMAL));
}
// connect all throw nodes to error exit
for (const throwNode of this._throwNodes) {
this._edges.push(this._createEdge(this._nodes.get(throwNode), errorExitNode, cfg_1.EdgeType.EXCEPTION));
}
if (this._regularBreakNodesStack.length > 0) {
ControlFlowGraphVisitor.LOGGER.warn(`Found ${this._regularBreakNodesStack.length} break node stacks that are not connected to a loop`);
}
if (this._regularContinueNodesStack.length > 0) {
ControlFlowGraphVisitor.LOGGER.warn(`Found ${this._regularContinueNodesStack.length} continue node stacks that are not connected to a loop`);
}
if (this._labeledBreakNodes.size > 0) {
ControlFlowGraphVisitor.LOGGER.warn(`Found ${this._labeledBreakNodes.size} break node labels that are not connected to a label exit`);
}
if (this._labeledContinueNodes.size > 0) {
ControlFlowGraphVisitor.LOGGER.warn(`Found ${this._labeledContinueNodes.size} continue node labels that are not connected to a label exit`);
}
return {
graph: new cfg_1.ControlFlowGraph(entryNode, successExitNode, errorExitNode, this._nodes, this._edges),
functions: this._functions.map((function_, index) => {
if (this._functions.filter((f) => f.name === function_.name).length > 1) {
function_.name = `${function_.name} (${index})`;
}
return function_;
}),
};
}
constructor(filePath, syntaxForgiving) {
super(filePath, syntaxForgiving);
this.Block = (path) => {
ControlFlowGraphVisitor.LOGGER.debug(`Entering block at ${this._getNodeId(path)}`);
// we need to repeat this from the baseclass because we cannot use super.Program
if (path.isProgram() && this._scopeIdOffset === undefined) {
this._scopeIdOffset = this._getUidFromScope(path.scope);
this._thisScopeStack.push(this._getUidFromScope(path.scope));
this._thisScopeStackNames.push("global");
}
for (const statement of path.get("body")) {
statement.visit();
}
path.skip();
};
// functions
this.Function = (path) => {
ControlFlowGraphVisitor.LOGGER.debug(`Entering function at ${this._getNodeId(path)}`);
if (!this._nodes.has(this._getNodeId(path))) {
const node = this._createNode(path);
this._connectToParents(node);
this._currentParents = [node.id];
}
const subVisitor = new ControlFlowGraphVisitor(this.filePath, this.syntaxForgiving);
path.traverse(subVisitor);
if (!subVisitor._nodes.has("ENTRY")) {
throw new Error("Should not be possible");
}
const name = path.has("id")
? path.get("id").node.name
: path.has("key")
? path.get("key").node.name
: "anonymous";
const cfp = subVisitor.cfg;
this._functions.push({
id: this._getNodeId(path),
name: name,
graph: cfp.graph,
},
// sub functions within this function
...cfp.functions);
path.skip();
};
// actual control flow graph related nodes
this.Statement = (path) => {
ControlFlowGraphVisitor.LOGGER.debug(`Entering statement: ${path.type}\tline: ${path.node.loc.start.line}\tcolumn: ${path.node.loc.start.column}`);
if (this._nodes.has(this._getNodeId(path))) {
throw new Error(`Id already used id: ${this._getNodeId(path)}`);
}
else {
const node = this._createNode(path);
this._connectToParents(node);
this._currentParents = [node.id];
}
};
this.Expression = (path) => {
ControlFlowGraphVisitor.LOGGER.debug(`Entering Expression at ${this._getNodeId(path)}`);
if (this._nodes.has(this._getNodeId(path))) {
// just ignore
}
else if (!path.isLiteral() && !path.isIdentifier()) {
const node = this._createNode(path);
this._connectToParents(node);
this._currentParents = [node.id];
}
};
this.Conditional = (path) => {
ControlFlowGraphVisitor.LOGGER.debug(`Entering IfStatement at ${this._getNodeId(path)}`);
const branchNode = this._createNode(path);
this._connectToParents(branchNode);
this._currentParents = [branchNode.id];
const testNode = this._createNode(path.get("test"));
this._connectToParents(testNode);
this._currentParents = [testNode.id];
// consequent
this._edgeType = cfg_1.EdgeType.CONDITIONAL_TRUE;
let sizeBefore = this._nodesList.length;
path.get("consequent").visit();
// there either is no consequent or it is empty
if (sizeBefore === this._nodesList.length) {
const consequent = this._createNode(path.get("consequent"));
this._connectToParents(consequent);
this._currentParents = [consequent.id];
}
const consequentNodes = this._currentParents;
// alternate
this._currentParents = [testNode.id];
this._edgeType = cfg_1.EdgeType.CONDITIONAL_FALSE;
sizeBefore = this._nodesList.length;
if (path.has("alternate")) {
path.get("alternate").visit();
}
// there either is no alternate or it is empty
if (sizeBefore === this._nodesList.length) {
if (path.has("alternate")) {
const alternate = this._createNode(path.get("alternate"));
this._connectToParents(alternate);
this._currentParents = [alternate.id];
}
else {
const alternate = this._createPlaceholderNode(path);
this._connectToParents(alternate);
this._currentParents = [alternate.id];
}
}
const alternateNodes = this._currentParents;
this._currentParents = [...alternateNodes, ...consequentNodes];
path.skip();
};
// labels
this.LabeledStatement = (path) => {
ControlFlowGraphVisitor.LOGGER.debug(`Entering LabeledStatement at ${this._getNodeId(path)}`);
const label = path.get("label").node.name;
this._labeledBreakNodes.set(label, new Set());
this._labeledContinueNodes.set(label, new Set());
const labelNode = this._createNode(path);
this._connectToParents(labelNode);
// body
this._currentParents = [labelNode.id];
const beforeSize = this._nodes.size;
path.get("body").visit();
// check if something was created
if (beforeSize === this._nodes.size) {
// empty body
// create placeholder node
const placeholderNode = this._createPlaceholderNode(path.get("body"));
this._connectToParents(placeholderNode);
this._currentParents = [placeholderNode.id];
}
// exit
const labelExit = this._createPlaceholderNode(path, true);
// connect all break nodes to exit
this._currentParents.push(...this._labeledBreakNodes.get(label));
this._connectToParents(labelExit);
this._currentParents = [labelExit.id];
// connect all continue nodes to label entry
for (const continueNode of this._labeledContinueNodes.get(label)) {
this._edges.push(this._createEdge(this._nodes.get(continueNode), labelNode, cfg_1.EdgeType.BACK_EDGE));
}
// remove labeled break/continues
this._labeledBreakNodes.delete(label);
this._labeledContinueNodes.delete(label);
path.skip();
};
// loops
this.DoWhileStatement = (path) => {
ControlFlowGraphVisitor.LOGGER.debug(`Entering DoWhileStatement at ${this._getNodeId(path)}`);
const doWhileNode = this._createNode(path);
this._connectToParents(doWhileNode);
this._currentParents = [doWhileNode.id];
this._regularBreakNodesStack.push(new Set());
this._regularContinueNodesStack.push(new Set());
const size = this._nodesList.length;
// body
path.get("body").visit();
let firstBodyNode = this._nodesList[size];
if (firstBodyNode === undefined) {
// empty body
// create placeholder node
const placeholderNode = this._createPlaceholderNode(path.get("body"));
this._connectToParents(placeholderNode);
this._currentParents = [placeholderNode.id];
firstBodyNode = placeholderNode;
}
// loop
const loopNode = this._createNode(path.get("test"));
this._connectToParents(loopNode);
// consequent
this._currentParents = [loopNode.id];
this._edgeType = cfg_1.EdgeType.CONDITIONAL_TRUE;
const consequent = this._createPlaceholderNode(path.get("test")); // bit of a hack to use the test
this._connectToParents(consequent);
// the back edge
this._edges.push(this._createEdge(consequent, firstBodyNode, cfg_1.EdgeType.BACK_EDGE));
// false
this._currentParents = [loopNode.id];
this._edgeType = cfg_1.EdgeType.CONDITIONAL_FALSE;
const alternate = this._createPlaceholderNode(path);
this._connectToParents(alternate);
// exit
this._currentParents = [alternate.id];
const loopExit = this._createPlaceholderNode(path, true);
// connect all break nodes to loop exit
this._currentParents.push(...this._regularBreakNodesStack.pop());
this._connectToParents(loopExit);
this._currentParents = [loopExit.id];
// connect all continue nodes to test
for (const continueNode of this._regularContinueNodesStack.pop()) {
this._edges.push(this._createEdge(this._nodes.get(continueNode), loopNode, cfg_1.EdgeType.BACK_EDGE));
}
path.skip();
};
this.WhileStatement = (path) => {
ControlFlowGraphVisitor.LOGGER.debug(`Entering WhileStatement at ${this._getNodeId(path)}`);
const whileNode = this._createNode(path);
this._connectToParents(whileNode);
this._currentParents = [whileNode.id];
this._regularBreakNodesStack.push(new Set());
this._regularContinueNodesStack.push(new Set());
// loop
const loopNode = this._createNode(path.get("test"));
this._connectToParents(loopNode);
// true body
this._currentParents = [loopNode.id];
this._edgeType = cfg_1.EdgeType.CONDITIONAL_TRUE;
const beforeSize = this._nodes.size;
path.get("body").visit();
// check if something was created
if (beforeSize === this._nodes.size) {
// empty body
// create placeholder node
const placeholderNode = this._createPlaceholderNode(path.get("body"));
this._connectToParents(placeholderNode);
this._currentParents = [placeholderNode.id];
}
// the back edge
this._edgeType = cfg_1.EdgeType.BACK_EDGE;
this._connectToParents(loopNode); // TODO should be label back edge too
// false
this._currentParents = [loopNode.id];
this._edgeType = cfg_1.EdgeType.CONDITIONAL_FALSE;
const alternate = this._createPlaceholderNode(path);
this._connectToParents(alternate); // TODO should be label back edge too
// exit
this._currentParents = [alternate.id];
const loopExit = this._createPlaceholderNode(path, true);
// connect all break nodes to loop exit
this._currentParents.push(...this._regularBreakNodesStack.pop());
this._connectToParents(loopExit);
this._currentParents = [loopExit.id];
// connect all continue nodes to test entry
for (const continueNode of this._regularContinueNodesStack.pop()) {
this._edges.push(this._createEdge(this._nodes.get(continueNode), loopNode, cfg_1.EdgeType.BACK_EDGE));
}
path.skip();
};
this.ForStatement = (path) => {
ControlFlowGraphVisitor.LOGGER.debug(`Entering ForStatement at ${this._getNodeId(path)}`);
const forNode = this._createNode(path);
this._connectToParents(forNode);
this._currentParents = [forNode.id];
this._regularBreakNodesStack.push(new Set());
this._regularContinueNodesStack.push(new Set());
// init
if (path.has("init")) {
const init = path.get("init");
// stupid hack because the variable declaration of an init is not registered correctly?
if (init.isVariableDeclaration()) {
const node = this._createNode(init.get("declarations")[0].get("init"));
this._connectToParents(node);
this._currentParents = [node.id];
}
else {
init.visit();
}
}
// test
let testNode;
if (path.has("test")) {
testNode = this._createNode(path.get("test"));
this._connectToParents(testNode);
this._currentParents = [testNode.id];
// true
this._edgeType = cfg_1.EdgeType.CONDITIONAL_TRUE;
}
// body
let beforeSize = this._nodes.size;
path.get("body").visit();
// check if something was created
if (beforeSize === this._nodes.size) {
// empty body
// create placeholder node
const placeholderNode = this._createPlaceholderNode(path.get("body"));
this._connectToParents(placeholderNode);
this._currentParents = [placeholderNode.id];
}
// update
if (path.has("update")) {
beforeSize = this._nodesList.length;
path.get("update").visit();
if (beforeSize === this._nodesList.length) {
throw new Error(`No node was added for the update part of the for loop,`);
}
}
// connect to test
if (path.has("test")) {
this._edgeType = cfg_1.EdgeType.BACK_EDGE;
this._connectToParents(testNode);
// false
this._currentParents = [testNode.id];
this._edgeType = cfg_1.EdgeType.CONDITIONAL_FALSE;
const alternate = this._createPlaceholderNode(path);
this._connectToParents(alternate);
this._currentParents = [alternate.id];
}
else {
this._currentParents = [];
}
// exit
const loopExit = this._createPlaceholderNode(path, true);
// connect all break nodes to loop exit
this._currentParents.push(...this._regularBreakNodesStack.pop());
this._connectToParents(loopExit);
this._currentParents = [loopExit.id];
// connect all continue nodes to test
for (const continueNode of this._regularContinueNodesStack.pop()) {
this._edges.push(this._createEdge(this._nodes.get(continueNode), testNode, cfg_1.EdgeType.BACK_EDGE));
}
path.skip();
};
this.ForInStatement = (path) => {
ControlFlowGraphVisitor.LOGGER.debug(`Entering ForInStatement at ${this._getNodeId(path)}`);
const forInNode = this._createNode(path);
this._connectToParents(forInNode);
this._currentParents = [forInNode.id];
this._regularBreakNodesStack.push(new Set());
this._regularContinueNodesStack.push(new Set());
if (!path.has("left")) {
// unsupported
throw new Error(`ForInStatement left not implemented at ${this._getNodeId(path)}`);
}
if (!path.has("right")) {
// unsupported
throw new Error(`ForInStatement right not implemented at ${this._getNodeId(path)}`);
}
// left
path.get("left").visit();
// test does not exist so we create placeholder?
const testNode = this._createPlaceholderNode(path.get("left")); // stupid hack but we cannot have the placeholder twice
this._connectToParents(testNode);
this._currentParents = [testNode.id];
// true
// body
this._edgeType = cfg_1.EdgeType.CONDITIONAL_TRUE;
const beforeSize = this._nodes.size;
path.get("body").visit();
// check if something was created
if (beforeSize === this._nodes.size) {
// empty body
// create placeholder node
const placeholderNode = this._createPlaceholderNode(path.get("body"));
this._connectToParents(placeholderNode);
this._currentParents = [placeholderNode.id];
}
// connect to test
this._edgeType = cfg_1.EdgeType.BACK_EDGE;
this._connectToParents(testNode);
// false
this._currentParents = [testNode.id];
this._edgeType = cfg_1.EdgeType.CONDITIONAL_FALSE;
const alternate = this._createPlaceholderNode(path);
this._connectToParents(alternate);
// exit
this._currentParents = [alternate.id];
const loopExit = this._createPlaceholderNode(path, true);
// connect all break nodes to loop exit
this._currentParents.push(...this._regularBreakNodesStack.pop());
this._connectToParents(loopExit);
this._currentParents = [loopExit.id];
// connect all continue nodes to test
for (const continueNode of this._regularContinueNodesStack.pop()) {
this._edges.push(this._createEdge(this._nodes.get(continueNode), testNode, cfg_1.EdgeType.BACK_EDGE));
}
path.skip();
};
this.ForOfStatement = (path) => {
ControlFlowGraphVisitor.LOGGER.debug(`Entering ForOfStatement at ${this._getNodeId(path)}`);
const forOfNode = this._createNode(path);
this._connectToParents(forOfNode);
this._currentParents = [forOfNode.id];
this._regularBreakNodesStack.push(new Set());
this._regularContinueNodesStack.push(new Set());
if (!path.has("left")) {
// unsupported
throw new Error(`ForOfStatement left not implemented at ${this._getNodeId(path)}`);
}
if (!path.has("right")) {
// unsupported
throw new Error(`ForOfStatement right not implemented at ${this._getNodeId(path)}`);
}
// left
path.get("left").visit();
// test does not exist so we create placeholder?
const testNode = this._createPlaceholderNode(path.get("left")); // stupid hack but we cannot have the placeholder twice
this._connectToParents(testNode);
this._currentParents = [testNode.id];
// true
// body
this._edgeType = cfg_1.EdgeType.CONDITIONAL_TRUE;
const beforeSize = this._nodes.size;
path.get("body").visit();
// check if something was created
if (beforeSize === this._nodes.size) {
// empty body
// create placeholder node
const placeholderNode = this._createPlaceholderNode(path.get("body"));
this._connectToParents(placeholderNode);
this._currentParents = [placeholderNode.id];
}
// connect to test
this._edgeType = cfg_1.EdgeType.BACK_EDGE;
this._connectToParents(testNode);
// false
this._currentParents = [testNode.id];
this._edgeType = cfg_1.EdgeType.CONDITIONAL_FALSE;
const alternate = this._createPlaceholderNode(path);
this._connectToParents(alternate);
// exit
this._currentParents = [alternate.id];
const loopExit = this._createPlaceholderNode(path, true);
// connect all break nodes to loop exit
this._currentParents.push(...this._regularBreakNodesStack.pop());
this._connectToParents(loopExit);
this._currentParents = [loopExit.id];
// connect all continue nodes to loop entry
for (const continueNode of this._regularContinueNodesStack.pop()) {
this._edges.push(this._createEdge(this._nodes.get(continueNode), testNode, cfg_1.EdgeType.BACK_EDGE));
}
path.skip();
};
this.SwitchStatement = (path) => {
ControlFlowGraphVisitor.LOGGER.debug(`Entering SwitchStatement at ${this._getNodeId(path)}`);
this._regularBreakNodesStack.push(new Set());
const switchNode = this._createNode(path);
this._connectToParents(switchNode);
this._currentParents = [switchNode.id];
const testNode = this._createNode(path.get("discriminant"));
this._connectToParents(testNode);
this._currentParents = [testNode.id];
let fallThrough = [];
for (const caseNode of path.get("cases")) {
if (caseNode.has("test")) {
// test
const caseTestNode = this._createNode(caseNode.get("test"));
this._connectToParents(caseTestNode);
this._currentParents = [caseTestNode.id];
// consequent
this._edgeType = cfg_1.EdgeType.CONDITIONAL_TRUE;
const consequentNode = this._createNode(caseNode);
this._connectToParents(consequentNode);
this._currentParents = [consequentNode.id, ...fallThrough];
if (caseNode.get("consequent").length > 0) {
for (const consequentNode of caseNode.get("consequent")) {
consequentNode.visit();
}
}
const trueParents = this._currentParents; // if there is a break these should be empty
fallThrough = [...trueParents]; // fall through
// alternate
// placeholder
this._edgeType = cfg_1.EdgeType.CONDITIONAL_FALSE;
this._currentParents = [caseTestNode.id];
const alternateNode = this._createPlaceholderNode(caseNode);
this._connectToParents(alternateNode);
this._currentParents = [alternateNode.id]; // normal
}
else {
// default
if (caseNode.get("consequent").length === 0) {
// empty body
// create placeholder node
const placeholderNode = this._createPlaceholderNode(caseNode);
this._connectToParents(placeholderNode);
this._currentParents = [placeholderNode.id];
}
else {
for (const consequentNode of caseNode.get("consequent")) {
consequentNode.visit();
}
}
}
}
// exit
const switchExit = this._createPlaceholderNode(path, true);
this._currentParents.push(
// connect fall through nodes to switch exit
...fallThrough,
// connect all break nodes to switch exit
...this._regularBreakNodesStack.pop());
this._connectToParents(switchExit);
this._currentParents = [switchExit.id];
path.skip();
};
// terminating statements
// these statements are the end of a path
// so they don't have any children
// which is why we empty the parents list instead of adding the node to it
this.BreakStatement = (path) => {
ControlFlowGraphVisitor.LOGGER.debug(`Entering BreakStatement at ${this._getNodeId(path)}`);
const node = this._createNode(path);
this._connectToParents(node);
if (path.has("label")) {
// labeled break node
const label = path.get("label").node.name;
if (!this._labeledBreakNodes.has(label)) {
throw new Error(`Label ${label} does not exist for break node at ${this._getNodeId(path)}`);
}
this._labeledBreakNodes.get(label).add(node.id);
}
else {
// regular break node
this._getBreakNodes().add(node.id);
}
this._currentParents = [];
path.skip();
};
this.ContinueStatement = (path) => {
ControlFlowGraphVisitor.LOGGER.debug(`Entering ContinueStatement at ${this._getNodeId(path)}`);
const node = this._createNode(path);
this._connectToParents(node);
if (path.has("label")) {
// labeled continue node
const label = path.get("label").node.name;
if (!this._labeledContinueNodes.has(label)) {
throw new Error(`Label ${label} does not exist for continue node at ${this._getNodeId(path)}`);
}
this._labeledContinueNodes.get(label).add(node.id);
}
else {
// regular continue node
this._getContinueNodes().add(node.id);
}
this._currentParents = [];
path.skip();
};
this.ReturnStatement = (path) => {
ControlFlowGraphVisitor.LOGGER.debug(`Entering ReturnStatement at ${this._getNodeId(path)}`);
const node = this._createNode(path);
this._connectToParents(node);
this._currentParents = [node.id];
if (path.has("argument")) {
path.get("argument").visit();
}
for (const nodeId of this._currentParents) {
this._returnNodes.add(nodeId);
}
this._currentParents = [];
path.skip();
};
this.ThrowStatement = (path) => {
ControlFlowGraphVisitor.LOGGER.debug(`Entering ThrowStatement at ${this._getNodeId(path)}`);
const node = this._createNode(path);
this._connectToParents(node);
this._throwNodes.add(node.id);
this._currentParents = [];
path.skip();
};
ControlFlowGraphVisitor.LOGGER = (0, logging_1.getLogger)("ControlFlowGraphVisitor");
this._nodesList = [];
this._nodes = new Map();
this._edges = [];
this._labeledBreakNodes = new Map();
this._labeledContinueNodes = new Map();
this._regularBreakNodesStack = [];
this._regularContinueNodesStack = [];
this._returnNodes = new Set();
this._throwNodes = new Set();
this._functions = [];
this._currentParents = [];
this._edgeType = cfg_1.EdgeType.NORMAL;
const entry = new cfg_1.Node("ENTRY", cfg_1.NodeType.ENTRY, "ENTRY", [], {});
const successExit = new cfg_1.Node("SUCCESS_EXIT", cfg_1.NodeType.EXIT, "EXIT", [], {});
const errorExit = new cfg_1.Node("ERROR_EXIT", cfg_1.NodeType.EXIT, "EXIT", [], {});
this._nodes.set(entry.id, entry);
this._nodes.set(successExit.id, successExit);
this._nodes.set(errorExit.id, errorExit);
this._nodesList.push(entry, successExit, errorExit);
this._currentParents = [entry.id];
}
_getBreakNodes() {
if (this._regularBreakNodesStack.length === 0) {
throw new Error("No break nodes found");
}
return this._regularBreakNodesStack[this._regularBreakNodesStack.length - 1];
}
_getContinueNodes() {
if (this._regularContinueNodesStack.length === 0) {
throw new Error("No continue nodes found");
}
return this._regularContinueNodesStack[this._regularContinueNodesStack.length - 1];
}
_getLocation(path) {
return {
start: {
line: path.node.loc.start.line,
column: path.node.loc.start.column,
index: path.node.loc.start.index,
},
end: {
line: path.node.loc.end.line,
column: path.node.loc.end.column,
index: path.node.loc.end.index,
},
};
}
_createNode(path) {
const id = `${this._getNodeId(path)}`;
const node = new cfg_1.Node(id, cfg_1.NodeType.NORMAL, path.node.type, [
{
id: id,
location: this._getLocation(path),
statementAsText: path.toString(),
},
], {}, path.node.type);
if (this._nodes.has(id)) {
throw new Error(`Node already registered ${id}`);
}
this._nodes.set(id, node);
this._nodesList.push(node);
return node;
}
_getPlaceholderNodeId(path) {
if (path.node.loc === undefined) {
throw new Error(`Node ${path.type} in file '${this._filePath}' does not have a location`);
}
const startLine = path.node.loc.start.line;
const startColumn = path.node.loc.start
.column;
const startIndex = path.node.loc.start
.index;
const endLine = path.node.loc.end.line;
const endColumn = path.node.loc.end.column;
const endIndex = path.node.loc.end.index;
return `${this._filePath}:${startLine}:${startColumn}:::${endLine}:${endColumn}:::${startIndex}:${endIndex}`;
}
/**
* Create a placeholder node for a node that is not in the AST, but is used in the CFG.
* Uses the end location of the parent node as the start and end location of the placeholder node.
* @param path
* @returns
*/
_createPlaceholderNode(path, double = false) {
let id = `placeholder:::${this._getPlaceholderNodeId(path)}`;
if (double) {
id = "placeholder:::" + id;
}
const location = this._getLocation(path);
const node = new cfg_1.Node(id, cfg_1.NodeType.NORMAL, path.node.type, [
{
id: id,
location: {
start: {
line: location.start.line,
column: location.start.column,
index: location.start.index,
},
end: {
line: location.end.line,
column: location.end.column,
index: location.end.index,
},
},
statementAsText: path.toString(),
},
], {}, path.node.type);
if (this._nodes.has(id)) {
throw new Error(`Node already registered ${id}`);
}
this._nodes.set(id, node);
this._nodesList.push(node);
return node;
}
_createEdge(source, target, edgeType, label = "") {
return new cfg_1.Edge(`${source.id}->${target.id}`, edgeType, label, source.id, target.id, "description");
}
/**
* Connects the current parents to the given node
* It uses the current edge type and resets it back to normal afterwards
*
* @param node
*/
_connectToParents(node) {
// it is actually possible that there are no parents
for (const parent of this._currentParents) {
this._edges.push(this._createEdge(this._nodes.get(parent), node, this._edgeType));
this._edgeType = cfg_1.EdgeType.NORMAL;
}
}
}
exports.ControlFlowGraphVisitor = ControlFlowGraphVisitor;
//# sourceMappingURL=ControlFlowGraphVisitor.js.map