@eagleoutice/flowr
Version:
Static Dataflow Analyzer and Program Slicer for the R Programming Language
291 lines • 10.9 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ControlFlowGraph = exports.CfgVertexType = void 0;
exports.edgeTypeToString = edgeTypeToString;
exports.equalVertex = equalVertex;
exports.emptyControlFlowInformation = emptyControlFlowInformation;
const assert_1 = require("../util/assert");
var CfgVertexType;
(function (CfgVertexType) {
/** Marks a break point in a construct (e.g., between the name and the value of an argument, or the formals and the body of a function) */
CfgVertexType["MidMarker"] = "mid";
/** The explicit exit-nodes to ensure the hammock property */
CfgVertexType["EndMarker"] = "end";
/** something like an if, assignment, ... even though in the classical sense of R they are still expressions */
CfgVertexType["Statement"] = "stm";
/** something like an addition, ... */
CfgVertexType["Expression"] = "expr";
/** a (as far as R allows this) 'basic' block */
CfgVertexType["Block"] = "blk";
})(CfgVertexType || (exports.CfgVertexType = CfgVertexType = {}));
function edgeTypeToString(type) {
switch (type) {
case 0 /* CfgEdgeType.Fd */:
return 'FD';
case 1 /* CfgEdgeType.Cd */:
return 'CD';
default:
throw new Error(`Unknown edge type ${JSON.stringify(type)}`);
}
}
function equalVertex(a, b) {
if (a.type !== b.type || a.id !== b.id) {
return false;
}
else if (a.type === CfgVertexType.Block && b.type === CfgVertexType.Block) {
return a.elems.length === b.elems.length && a.elems.every((e, i) => e.id === b.elems[i].id);
}
else if (a.type === CfgVertexType.MidMarker && b.type === CfgVertexType.MidMarker) {
return a.kind === b.kind && a.root === b.root;
}
else if (a.type === CfgVertexType.EndMarker && b.type === CfgVertexType.EndMarker) {
return a.root === b.root;
}
return true;
}
/**
* This class represents the control flow graph of an R program.
* The control flow may be hierarchical when confronted with function definitions (see {@link CfgSimpleVertex} and {@link CFG#rootVertexIds|rootVertexIds()}).
*
* There are two very simple visitors to traverse a CFG:
* - {@link visitCfgInOrder} visits the graph in the order of the vertices
* - {@link visitCfgInReverseOrder} visits the graph in reverse order
*
* If you want to prohibit modification, please refer to the {@link ReadOnlyControlFlowGraph} interface.
*/
class ControlFlowGraph {
rootVertices = new Set();
/** Nesting-Independent vertex information, mapping the id to the vertex */
vertexInformation = new Map();
/** the basic block children map contains a mapping of ids to all vertices that are nested in basic blocks, mapping them to the Id of the block they appear in */
bbChildren = new Map();
/** basic block agnostic edges */
edgeInformation = new Map();
/** used as an optimization to avoid unnecessary lookups */
_mayHaveBasicBlocks = false;
/**
* Add a new vertex to the control flow graph.
*
* @see {@link ControlFlowGraph#addEdge|addEdge()} - to add an edge
*/
addVertex(vertex, rootVertex = true) {
(0, assert_1.guard)(!this.vertexInformation.has(vertex.id), `Node with id ${vertex.id} already exists`);
if (vertex.type === CfgVertexType.Block) {
this._mayHaveBasicBlocks = true;
if (vertex.elems.some(e => this.bbChildren.has(e.id) || this.rootVertices.has(e.id))) {
throw new Error(`Vertex ${vertex.id} contains vertices that are already part of the graph`);
}
for (const elem of vertex.elems) {
this.bbChildren.set(elem.id, vertex.id);
}
}
this.vertexInformation.set(vertex.id, vertex);
if (rootVertex) {
this.rootVertices.add(vertex.id);
}
return this;
}
/**
* Add a new edge to the control flow graph.
*
* @see {@link ControlFlowGraph#addVertex|addVertex()} - to add vertices
* @see {@link ControlFlowGraph#addEdges|addEdges()} - to add multiple edges at once
*/
addEdge(from, to, edge) {
const edgesFrom = this.edgeInformation.get(from) ?? new Map();
if (!this.edgeInformation.has(from)) {
this.edgeInformation.set(from, edgesFrom);
}
edgesFrom.set(to, edge);
return this;
}
/**
* Add multiple edges from a given source vertex to the control flow graph.
*/
addEdges(from, to) {
const edgesFrom = this.edgeInformation.get(from) ?? new Map();
if (!this.edgeInformation.has(from)) {
this.edgeInformation.set(from, edgesFrom);
}
for (const [toId, edge] of to) {
edgesFrom.set(toId, edge);
}
return this;
}
outgoingEdges(node) {
return this.edgeInformation.get(node);
}
ingoingEdges(id) {
const edges = new Map();
for (const [source, outgoing] of this.edgeInformation.entries()) {
if (outgoing.has(id)) {
edges.set(source, outgoing.get(id));
}
}
return edges;
}
rootIds() {
return this.rootVertices;
}
vertices(includeBasicBlockElements = true) {
if (includeBasicBlockElements) {
const all = new Map(this.vertexInformation);
for (const [id, block] of this.bbChildren.entries()) {
const blockVertex = all.get(block);
if (blockVertex === undefined || blockVertex.type !== CfgVertexType.Block) {
continue;
}
const elem = blockVertex.elems.find(e => e.id === id);
if (elem !== undefined) {
all.set(id, elem);
}
}
return all;
}
else {
return this.vertexInformation;
}
}
getBasicBlock(elemId) {
const block = this.bbChildren.get(elemId);
if (block === undefined) {
return undefined;
}
const blockVertex = this.vertexInformation.get(block);
if (blockVertex === undefined || blockVertex.type !== CfgVertexType.Block) {
return undefined;
}
return blockVertex;
}
edges() {
return this.edgeInformation;
}
/**
* Retrieve a vertex by its id.
*/
getVertex(id, includeBlocks = true) {
const res = this.vertexInformation.get(id);
if (res || !includeBlocks) {
return res;
}
const block = this.bbChildren.get(id);
if (block === undefined) {
return undefined;
}
const blockVertex = this.vertexInformation.get(block);
if (blockVertex === undefined || blockVertex.type !== CfgVertexType.Block) {
return undefined;
}
blockVertex.elems.find(e => e.id === id);
}
hasVertex(id, includeBlocks = true) {
return this.vertexInformation.has(id) || (this._mayHaveBasicBlocks && includeBlocks && this.bbChildren.has(id));
}
mayHaveBasicBlocks() {
return this._mayHaveBasicBlocks;
}
/**
* This removes the vertex and all edges to and from it.
* @param id - the id of the vertex to remove
*
* @see {@link ControlFlowGraph#addVertex|addVertex()} - to add a vertex
* @see {@link ControlFlowGraph#removeEdge|removeEdge()} - to remove a specific edge
*/
removeVertex(id) {
this.vertexInformation.delete(id);
this.edgeInformation.delete(id);
this.bbChildren.delete(id);
// remove all bbChildren with id as target
for (const [a, b] of this.bbChildren.entries()) {
if (b === id) {
this.bbChildren.delete(a);
}
}
for (const edges of this.edgeInformation.values()) {
edges.delete(id);
}
this.rootVertices.delete(id);
return this;
}
/**
* Removes a all direct edges between `from` and `to` from the control flow graph.
*
* @see {@link ControlFlowGraph#addEdge|addEdge()} - to add an edge
* @see {@link ControlFlowGraph#removeVertex|removeVertex()} - to remove a vertex and all its edges
*/
removeEdge(from, to) {
const edges = this.edgeInformation.get(from);
if (edges) {
edges.delete(to);
if (edges.size === 0) {
this.edgeInformation.delete(from);
}
}
return this;
}
/** merges b into a */
mergeTwoBasicBlocks(a, b) {
const aVertex = this.getVertex(a);
const bVertex = this.getVertex(b);
if (!aVertex || !bVertex || aVertex.type !== CfgVertexType.Block || bVertex.type !== CfgVertexType.Block) {
return this;
}
const bElems = bVertex.elems;
aVertex.elems = aVertex.elems.concat(bElems);
// update cache
for (const elem of bElems) {
this.bbChildren.set(elem.id, a);
}
// drop all edges from a to b
this.removeEdge(a, b);
const bOutgoing = this.outgoingEdges(b);
this.removeVertex(b);
// reroute all edge from b to a
for (const [to, edge] of bOutgoing ?? []) {
this.addEdge(a, to, edge);
}
return this;
}
/**
* Merge another control flow graph into this one.
* @param other - the other control flow graph to merge into this one
* @param forceNested - should the other graph be assumed to be fully nested (e.g., within a function definition).
*
* This is the pendant of {@link DataflowGraph#mergeWith|mergeWith()} on a {@link DataflowGraph}.
*/
mergeWith(other, forceNested = false) {
this._mayHaveBasicBlocks ||= other._mayHaveBasicBlocks;
const roots = other.rootVertices;
if (this._mayHaveBasicBlocks) {
for (const [id, node] of other.vertexInformation) {
this.addVertex(node, forceNested ? false : roots.has(id));
}
}
else {
for (const [id, node] of other.vertexInformation) {
this.vertexInformation.set(id, node);
}
if (!forceNested) {
for (const root of roots) {
this.rootVertices.add(root);
}
}
}
for (const [from, edges] of other.edgeInformation) {
this.addEdges(from, edges);
}
return this;
}
}
exports.ControlFlowGraph = ControlFlowGraph;
function emptyControlFlowInformation() {
return {
returns: [],
breaks: [],
nexts: [],
entryPoints: [],
exitPoints: [],
graph: new ControlFlowGraph()
};
}
//# sourceMappingURL=control-flow-graph.js.map